Rails date_select fields hidden and

Another 2 hours wasted. Today I was trying to use the date_select helper, but I could not figure out why the fields kept showing up as hidden inputs instead of select boxes.

Poked around for a long long long time, before catching this tidbit

: order – Set to an array containing :day, :month and :year do customize the order in which the select fields are shown. If you leave out any of the symbols, the respective select will not be shown (like when you set :discard_xxx => true. Defaults to the order defined in the respective locale (e.g. [:year, :month, :day] in the en locale that ships with Rails).

For some god forsaken reason, the “order” on the date_select field in my app was not set, or is defaulted to something else. I have no idea where this happens, or how it happens. But adding and order param fixed my problem

f.datetime_select :start_time, :order => [:year, :month, :day]
  • :discard_day – Set to true if you don’t want to show a day select. This includes the day as a hidden field instead of showing a select field. Also note that this implicitly sets the day to be the first of the given month in order to not create invalid dates like 31 February.
  • :discard_month – Set to true if you don’t want to show a month select. This includes the month as a hidden field instead of showing a select field. Also note that this implicitly sets :discard_day to true.
  • :discard_year – Set to true if you don’t want to show a year select. This includes the year as a hidden field instead of showing a select field.
  • :order – Set to an array containing :day:month and :year to customize the order in which the select fields are shown. If you leave out any of the symbols, the respective select will not be shown (like when you set :discard_xxx => true. Defaults to the order defined in the respective locale (e.g. [:year, :month, :day] in the en locale that ships with Rails).
Posted in Uncategorized

Simple CRUD with MongoDB

When I meet a new technology, I like to experience it “just as it comes”. I’m happy at the command line and I like to type actual commands and see man pages before I use any wrappers or helper tools. So when I met MongoDB for the first time, I did exactly that. This post shows those first steps of creating a database, and inserting, reading, deleting and updating data.

Before we begin, you should install mongo. This is painless and for me, on ubuntu, sudo aptitude install mongodb did the trick.

Some Terminology

Here are some translations from the RDBMS equivalent wording:

  • “database” is still “database”
  • “table” becomes “collection”
  • “row” becomes “record”
  • try to forget the word “column”, we don’t have those

Let’s Begin

Creating a Database

You don’t really create a database with mongo, you just kind of start using it. Once you put something in there, it exists. I’m going to name my new database pets.

use pets

Adding Data

To do anything in mongo, you start your command with db which refers to the database you’re using. The different parts of the command are separated by dots. To insert data you use a command like db.[collection].save() and feed in the data to save. The format of the data is JSON-esque – I read JSON but I don’t really write it, however I found it became familiar pretty quickly. To insert some data, you can do:

> db.animals.save({'animal':'cat', 'name':'fluffy', 'type':'long-haired', 'owner':'Anna'});
> db.animals.save({'animal':'dog', 'type':'spaniel', 'name':'toffee', 'colour':'toffee', 'owner':'Ben'});
> db.animals.save({'owner':'Ben', 'animal':'cat', 'name':'ginger', 'collar':true});

Fetching Data

Did anything happen? We can check, using db.[collection].find() – this will give us everything in the collection, a bit like select * from [table] does in SQL.

> db.animals.find();
{ "_id" : ObjectId("4ebb8fd68f7aaffc5d287383"), "animal" : "cat", "name" : "fluffy", "type" : "long-haired", "owner" : "Anna" }
{ "_id" : ObjectId("4ebb90048f7aaffc5d287384"), "animal" : "dog", "type" : "spaniel", "name" : "toffee", "colour" : "toffee", "owner" : "Ben" }
{ "_id" : ObjectId("4ebb90768f7aaffc5d287385"), "owner" : "Ben", "animal" : "cat", "name" : "ginger", "collar" : true }

We definitely have data! We can also filter this down, the equivalent of adding a “where” clause, for example, let’s only see cats:

> db.animals.find({'animal':'cat'});
{ "_id" : ObjectId("4ebb8fd68f7aaffc5d287383"), "animal" : "cat", "name" : "fluffy", "type" : "long-haired", "owner" : "Anna" }
{ "_id" : ObjectId("4ebb90768f7aaffc5d287385"), "owner" : "Ben", "animal" : "cat", "name" : "ginger", "collar" : true }

You can add multiple constraints here, how about cats belonging to Ben?

> db.animals.find({'animal':'cat', 'owner':'Ben'});
{ "_id" : ObjectId("4ebb90768f7aaffc5d287385"), "owner" : "Ben", "animal" : "cat", "name" : "ginger", "collar" : true }

If any of the records don’t have the field you’re searching on, they won’t appear in the results. We’re not tied to a rigid structure of columns so you can just throw in whichever data seems useful at the time, and search on whatever is there. We can also search on whether we have the field at all, for example, animals where we know what colour they are:

> db.animals.find({colour: {$exists: true}});
{ "_id" : ObjectId("4ebb90048f7aaffc5d287384"), "animal" : "dog", "type" : "spaniel", "name" : "toffee", "colour" : "toffee", "owner" : "Ben" }

Updating Data

This confused me for a long time, as mongo does have an update() function, which you can use to update one or many records in a particular way. What I found I really wanted though was to use the save() method again, because if the record has an identifier that exists, mongo will update it, otherwise it will insert it as we saw above. So we can just grab a record and change it, then save it:

> db.animals.find({'animal':'dog'});
{ "_id" : ObjectId("4ebb90048f7aaffc5d287384"), "animal" : "dog", "type" : "spaniel", "name" : "toffee", "colour" : "toffee", "owner" : "Ben" }
db.animals.save({ "_id" : ObjectId("4ebb90048f7aaffc5d287384"), "animal" : "dog", "breed" : "spaniel", "name" : "toffee", "colour" : "toffee", "owner" : "Ben" });

I realised that calling a spaniel a “type” of dog would be better expressed as being a “breed”, so I simply changed that record and mongo updated it for me. The update() statement is better for working on sets of records – for example if we decide Ben should be using his Sunday name:

> db.animals.update({'owner':'Ben'}, {$set: {'owner':'Benjamin'}}, false, true);

There’s a lot going on here, so let’s look at the pieces step-by-step. The documentation describes the update function as:

db.collection.update( criteria, objNew, upsert, multi )

The first part, the criteria is the same as we would use for the find() method. The next argument is what we’re changing. I’m just setting one field to a given value, so I used the $set modifier (modifiers are an art in themselves, this post is rambling on already so I’ll write about those another day if you’re interested). The next argument is the upsert, which is whether to insert a new record if we didn’t find any matches – I want to update existing records, not insert anything, so I set this to false. Finally the multi flag tells mongo to update ALL the records it can find that match thecriteria, if this is false it will stop after one (lazy thing!).

Deleting Data

If you’ve come this far then I’m impressed, and deleting is the easy part so we’re almost there! Exactly like the find()and update() commands, we just supply a criteria to the remove() command. This could be either one of the fields, as we used already, or the object ID itself, like this:

> db.animals.remove({_id: ObjectId("4ebb90768f7aaffc5d287385")});

As with all things mongo, you won’t get any feedback about whether it worked, since most of the time we’re using this on systems so fast there isn’t time for niceties, but if you try to find() this record now, you won’t be able to.

MongoDB

There’s so much that’s exciting about mongo, the sheer size and speed of this data store, the support for map reduce, the sharding support … I could go on. However you still need to be able to have a quick word with your database and check what data it has, maybe tweak something, and I hope that these mongo examples will serve as a quick reference for anyone who needs them, including me of course! I like databases, APIs and command line, so working with mongo is kind of magical for me, are you working with it? I’d love to hear how others are getting on and what other tips I need to know, so leave a comment and share, please!

Posted in Uncategorized

3 招实用Asset Pipeline 加速 –by xdite

Asset Pipeline 最让人诟病的就是deploy 时花费速度过久。 在社群聚会时发现大家都对这个主题非常不熟。 所以把最近累积了的这方面技巧整理出来分享给大家。

1. Capistrano deployment speedup

使用capistrano 内建task 执行assets:precompie

capistrano内建了'deploy/assets'这个task。 只要在Capfile里面

 

Capfile

  1
 load 'deploy/assets'

 

deploy 就会自动执行assets precompile 的动作。 由原始档可以看到这个task实际执行的是

"cd /home/apps/APP_NAME/releases/20120708184757 && bundle exec rake RAILS_ENV=production RAILS_GROUPS=assets assets:precompile"

而执行的时机是

after 'deploy:update_code', 'deploy:assets:precompile'

许多开发者不知道有这一个task 可以用。 手动写task 去compile,造成了两个问题:

  1. 时机执行错误。 Compile 时机错误会造成站上出现空白css。
  2. 执行compile 机器负担太重。 如果是手写的task 通常会是load 整个production 的环境去compile。与只load assets 这个group 所吃的系统资源「有可能」差得非常多。

如果没有变更到assets 时,就不compile

请把这里面的内容贴到你的deploy.rb 档里面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# -*- encoding : utf-8 -*-
set :assets_dependencies, %w(app/assets lib/assets vendor/assets Gemfile.lock config/routes.rb)
namespace :deploy do
  namespace :assets do
    desc <<-DESC
      Run the asset precompilation rake task. You can specify the full path
      to the rake executable by setting the rake variable. You can also
      specify additional environment variables to pass to rake via the
      asset_env variable. The defaults are:
        set :rake, “rake”
        set :rails_env, “production”
        set :asset_env, “RAILS_GROUPS=assets”
        set :assets_dependencies, fetch(:assets_dependencies) + %w(config/locales/js)
    DESC
    task :precompile, :roles => :web, :except => { :no_release => true } do
      from = source.next_revision(current_revision)
      if capture(“cd #{latest_release} && #{source.local.log(from)} #{assets_dependencies.join ‘ ‘} | wc -l”).to_i > 0
        run %Q{cd #{latest_release} && #{rake} RAILS_ENV=#{rails_env} #{asset_env} assets:precompile}
      else
        logger.info “Skipping asset pre-compilation because there were no asset changes”
      end
    end
  end
end

这是在Railsconf 2012的Stack Smashing上学到的一招。

如果你的assets 档案没有变动的话,只要执行copy 上一版本的assets 就好了。 这段task 会侦测

  • app/assets
  • lib/assets
  • vendor/assets
  • Gemfile.lock
  • confir/routes.rb

是否有变动。 基本上已经含了所有可能assets 会变动的可能性。 有变动才会重新compile。

整体上会加速非常非常的多 。

2. use @import carefully

避免使用@import “compass”; 这种写法

compass是大家很爱用的SCSS framework。 大家写gradiant 或者css spriate 很常直接开下去。

但是你知道

 

  1
 @import "compass" ;

 

 

  1
 @import "compass/typography/links/link-colors" ;

 

这两种写法。

前者compile 的速度可能比后者慢到9 倍以上吗?

会这么慢的原因,是因为compass本身即是懒人包 , @import "compass";会把

  • “compass/utilities”;
  • “compass/typography”;
  • “compass/css3”;

下面的东西通通都挂起来(还跑directory recursive)。

所以自然慢到爆炸。 如果要用什么helper,请直接挂它单支的CSS 就好了,不要整包都挂上来。

全挂其慢无比是正常的。

避免使用partial

我知道partial 是SCSS 整理术的大绝招。 但是若非必要,也尽量避免一直单档一路@import 到底。

 

common.css.scss

  1
 2
 3
 @import "reset" ; @import "base" ; @import "product" ;
common.css.scss

  1
 2
 3
 //= require "reset" //= require "base" //= require "product"

 

这两个在asset pipeline 输出结果是一样的。 但后者会比前者快。

如果真的需要用到非得使用partial 的技巧(如需读变数用require 读​​不到,@import 才读得到)再使用即可,因为只要一牵涉到directory recursive compile 就会慢…

3. don’t require .scss & .coffee for no reason

避免使用require_tree

使用generator 产生controller 时,rails 会自动帮忙产生

  • product.css.scss
  • product.js.coffee

然后application.css 与application.js 会利用

 

application.css

  1
 //= require_tree

 

这种技巧来把这些档案挂上去。

但是你知道吗? 就算这些档案里面只写了这几行注解:

 

  1
 2
 3
 # Place all the behaviors and hooks related to the matching controller here. # All this logic will automatically be available in application.js. # You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/

 

而且实际执行结果也等于空输出。 compile 一支大概也要250ms。 你可以想想,多compile 10 支,就是2.5 秒了。 难怪超耗时。

可以使用.js 或.css 解决的不要用.scss 与.coffee 当结尾

 

  1
 2
 3
 4
 5
 Compiled jquery-ui-1.8.16.custom.css (0ms) (pid 19108) Compiled jquery.ui.1.8.16.ie.css (0ms) (pid 19108) Compiled jquery.js (5ms) (pid 19108) Compiled jquery_ujs.js (0ms) (pid 19108) Compiled custom.css (14ms) (pid 19108)

 

其中custom.css 的档名是custom.css.scss

这样应该知道为什么不要乱用scss 当档名了吧?

小结

为了方便大家调整,我把具体加速assets precompile 过程的步骤罗列在下面。

1. 换掉deploy.rb 的assets precompile tasks

2. 观察logs/product.log。

  1. 找出慢的assets。
  2. 拿掉直接使用import “comppass”; 的SCSS,改用功能针对性写法。
  3. 不需要使用@import 写法的改用require
  4. 拿掉require_tree,改用//=require 一行一行挂上去
  5. 删掉空的scss 与coffeescript
  6. 单纯只是CSS 的不要自作聪明帮忙加上.scss 档名。

====

如果有什么问题,欢迎各位在底下留言讨论。

也欢迎大家有空来Rails Tuesday坐坐。 我很乐意帮大家解答问题。

PS如果你是要问Rails 101书上的问题,请找小蟹。

Posted in Uncategorized

Creating nested resources in ruby on rails 3 and updating scaffolding links and redirection

In this article, I’ll walk through a basic Rails (3.2.x) setup for creating a nested resource for two models. Nested resources work well when you want to build out URL structure between two related models, and still maintain a RESTful convention. This code assumes you are running RVM to manage Ruby/Gem versions, and Git for version control.

Creating a new Rails project
$ mkdir family # create rvm gemset
$ echo “rvm use –create ruby-1.9.2@family” > family/.rvmrc
$ cd family # install rails
$ gem install rails # create new rails project
$ rails new . # version control
$ git init
$ git add .
$ git commit -am “new rails project”
Create two models (Parent & Child)
# Parent model
$ rails generate scaffold Parent name:string
$ git add .
$ git commit -am “rails generate scaffold Parent name:string”
# Child model
$ rails generate scaffold Child name:string parent_id:integer
$ git add .
$ git commit -am “rails generate scaffold Child name:string parent_id:integer”
# Create db (defaults to SQLite3)
$ rake db:migrate
# version control
$ git add db/schema.rb
$ git commit db/schema.rb -m “created database schema”
Review un-nested routes
$ rake routes
children GET /children(.:format) children#index
POST /children(.:format) children#create
new_child GET /children/new(.:format) children#new
edit_child GET /children/:id/edit(.:format) children#edit
child GET /children/:id(.:format) children#show
PUT /children/:id(.:format) children#update
DELETE /children/:id(.:format) children#destroy
parents GET /parents(.:format) parents#index
POST /parents(.:format) parents#create
new_parent GET /parents/new(.:format) parents#new
edit_parent GET /parents/:id/edit(.:format) parents#edit
parent GET /parents/:id(.:format) parents#show
PUT /parents/:id(.:format) parents#update
DELETE /parents/:id(.:format) parents#destroy
Adding model relationships
# file: app/models/parent.rb
class Parent < ActiveRecord::Base
attr_accessible :name
has_many :children
end

# file: app/models/child.rb
class Child < ActiveRecord::Base
attr_accessible :name, :parent_id
belongs_to :parent
end

# version control
$ git commit app/models -m "added relationships to models"
Nesting the routes
$ rake routes
parent_children GET /parents/:parent_id/children(.:format) children#index
POST /parents/:parent_id/children(.:format) children#create
new_parent_child GET /parents/:parent_id/children/new(.:format) children#new
edit_parent_child GET /parents/:parent_id/children/:id/edit(.:format) children#edit
parent_child GET /parents/:parent_id/children/:id(.:format) children#show
PUT /parents/:parent_id/children/:id(.:format) children#update
DELETE /parents/:parent_id/children/:id(.:format) children#destroy
parents GET /parents(.:format) parents#index
POST /parents(.:format) parents#create
new_parent GET /parents/new(.:format) parents#new
edit_parent GET /parents/:id/edit(.:format) parents#edit
parent GET /parents/:id(.:format) parents#show
PUT /parents/:id(.:format) parents#update
DELETE /parents/:id(.:format) parents#destroy
Adding test data via Rails console
$ rails c

> dad = Parent.new(:name => ‘Paul’)
=> #

> dad.save
(0.1ms) begin transaction
SQL (20.0ms) INSERT INTO “parents” (“created_at”, “name”, “updated_at”) VALUES (?, ?, ?) [["created_at", Fri, 06 Apr 2012 16:13:17 UTC +00:00], ["name", "Paul"], ["updated_at", Fri, 06 Apr 2012 16:13:17 UTC +00:00]]
(2.4ms) commit transaction
=> true

> son = dad.children.new(:name => ‘Eric’)
=> #

> daughter = dad.children.new(:name => ‘Mara’)
=> #

> exit
Adding a private controller method to load the Parent object for each method
# file: app/controllers/children_controller.rb
@@ -1,4 +1,7 @@
class ChildrenController < ApplicationController
+
+ before_filter :load_parent
+
# GET /children
# GET /children.json
def index
@@ -80,4 +83,11 @@ class ChildrenController < ApplicationController
format.json { head :no_content }
end
end
+
+ private
+
+ def load_parent
+ @parent = Parent.find(params[:parent_id])
+ end
+
end
At this point, each controller and view for the Child class model needs to be adjusted (links, redirection, form, etc)

Method: children#index

# file: app/controllers/children_controller.rb

def index
- @children = Child.all
+ @children = @parent.children.all
# file: app/views/children/index.html.erb

- <%= link_to 'Show', child %>

– <%= link_to 'Edit', edit_child_path(child) %>

– <%= link_to 'Destroy', child, confirm: 'Are you sure?', method: :delete %>

+ <%= link_to 'Show', parent_child_path(@parent, child) %>

+ <%= link_to 'Edit', edit_parent_child_path(@parent, child) %>

+ <%= link_to 'Destroy', [@parent, child], confirm: 'Are you sure?', method: :delete %>

-<%= link_to 'New Child', new_child_path %>
+<%= link_to 'New Child', new_parent_child_path(@parent) %>
Method: children#new

# file: app/controllers/children_controller.rb

def new
– @child = Child.new
+ @child = @parent.children.new
# file: app/views/children/_form.html.erb

-<%= form_for(@child) do |f| %>
+<%= form_for([@parent, @child]) do |f| %>
# file: app/views/children/new.html.erb

-<%= link_to 'Back', children_path %>
+<%= link_to 'Back', parent_children_path(@parent) %>
Method: children#create

# file: app/controllers/children_controller.rb

def create
– @child = Child.new(params[:child])
+ @child = @parent.children.new(params[:child])

respond_to do |format|
if @child.save
– format.html { redirect_to @child, notice: ‘Child was successfully created.’ }
+ format.html { redirect_to [@parent, @child], notice: ‘Child was successfully created.’ }
Method: children#show

# file: app/controllers/children_controller.rb

def show
– @child = Child.find(params[:id])
+ @child = @parent.children.find(params[:id])
# file: app/views/children/show.html.erb

-<%= link_to 'Edit', edit_child_path(@child) %> |
-<%= link_to 'Back', children_path %>
+<%= link_to 'Edit', edit_parent_child_path(@parent, @child) %> |
+<%= link_to 'Back', parent_children_path(@parent) %>
Method: children#edit

# file: app/controllers/children_controller.rb

def edit
– @child = Child.find(params[:id])
+ @child = @parent.children.find(params[:id])
# file: app/views/children/edit.html.erb

-<%= link_to 'Show', @child %> |
-<%= link_to 'Back', children_path %>
+<%= link_to 'Show', parent_child_path(@parent, @child) %> |
+<%= link_to 'Back', parent_children_path(@parent) %>
Method: children#update

# file: app/controllers/children_controller.rb

def update
– @child = Child.find(params[:id])
+ @child = @parent.children.find(params[:id])

respond_to do |format|
if @child.update_attributes(params[:child])
– format.html { redirect_to @child, notice: ‘Child was successfully updated.’ }
+ format.html { redirect_to [@parent, @child], notice: ‘Child was successfully updated.’ }
Method: children#destroy

# file: app/controllers/children_controller.rb

def destroy
– @child = Child.find(params[:id])
+ @child = @parent.children.find(params[:id])
@child.destroy

respond_to do |format|
– format.html { redirect_to children_url }
+ format.html { redirect_to parent_children_path(@parent) }
At this point, the default scaffolding’s links and redirection have been updated to work with the nested routes.

Posted in Uncategorized

ActionDispatch::RequestId < Object

Makes a unique request id available to the action_dispatch.request_id env variable (which is then accessible through ActionDispatch::Request#uuid) and sends the same id to the client via the X-Request-Id header.

The unique request id is either based off the X-Request-Id header in the request, which would typically be generated by a firewall, load balancer, or the web server, or, if this header is not available, a random uuid. If the header is accepted from the outside world, we sanitize it to a max of 255 chars and alphanumeric and dashes only.

The unique request id can be used to trace a request end-to-end and would typically end up being part of log files from multiple pieces of the stack.

http://api.rubyonrails.org/classes/ActionDispatch/RequestId.html

http://stackoverflow.com/search?q=rails+RequestId

Posted in Uncategorized