Good notes posted to Ruby on Rails
RSS feedUseful in migrations
The most common usage pattern for this method is probably in a migration, when just after creating a table you want to populate it with some default values, eg:
class CreateJobLevels < ActiveRecord::Migration def self.up create_table :job_levels do |t| t.integer :id t.string :name t.timestamps end JobLevel.reset_column_information %w{assistant executive manager director}.each do |type| JobLevel.create(:name => type) end end def self.down drop_table :job_levels end end
ActiveRecord::Base.include_root_in_json
From Rails 2.1 onwards, the variable
ActiveRecord::Base.include_root_in_json
affects how the JSON is generated. If this is true (default), then the JSON isn’t like the one above. Instead you’ll get:
konata = User.find(1) konata.to_json # => { "user": { "id": 1, "name": "Konata Izumi", "age": 16, "created_at": "2006/08/01", "awesome": true}}
(Note the model name is included as a root of the JSON object)
For Rails 2.1 generated projects, you’ll see this in the config/initializers/new_rails_defaults.rb file. You’ll need to set the value to false if you want the old behaviour.
ActiveRecord::Base.include_root_in_json = false
Always pass a block
I highly recommend always taking a block and passing it back up the chain if you use alias_method_chain, even if the original method does not. Otherwise you’re keeping anyone later in the chain from adding support for blocks.
http://tech.hickorywind.org/articles/2008/08/29/always-pass-a-block-when-using-alias_method_chain
Brazilian Real (R$ 1.200,95)
helper:
def number_to_currency_br(number) number_to_currency(number, :unit => "R$ ", :separator => ",", :delimiter => ".") end
Get year to show in descending order (Today to 1920 for example)
The way people think of time start and end would be 1920 to today. This made me think “but I want it show the current year first then down.” Well it’s as simple as swapping the start_year and end_year.
date_select :date, :start_year => Date.current.year, :end_year => 1920 # => 2008, 2007, 2006, 2005 ... 1920
Moved to ActiveRecord::Calculations::ClassMethods
It appears that this method has simply been moved to the module ActiveRecord::Calculations::ClassMethods. You can still use the example listed in the docs here, it will simply call the method ActiveRecord::Calculations::ClassMethods#count.
When using RESTful routes
I had issues using expire_page with RESTful controllers when expiring anything other than the index action because I was basing my expire_page calls off this example.
The solution is to use my_resource_path for example when User with id 5 is updated, you would have to do:
expire_page user_path(user) # or expire_page formatted_user_path(user, :xml)
This may apply to early versions but I have only tested on v2.1.0
Only attr_accessible attributes will be updated
If your model specified attr_accessible attributes, only those attributes will be updated.
Use attr_accessible to prevent mass assignment (by users) of attributes that should not be editable by a user. Mass assignment is used in create and update methods of your standard controller.
For a normal user account, for example, you only want login and password to be editable by a user. It should not be possible to change the status attribute through mass assignment.
class User < ActiveRecord::Base attr_accessible :login, :password end
So, doing the following will merrily return true, but will not update the status attribute.
@user.update_attributes(:status => 'active')
If you want to update the status attribute, you should assign it separately.
@user.status = 'active' save
Prototype hinted_text_field application_helper
Place this in your helper. It will show a message inside the text box and remove it when someone clicks on it. If they don’t enter a value when they leave the field it’ll replace the message. (Requires javascript :defaults).
def hinted_text_field_tag(name, value = nil, hint = "Click and enter text", options={}) value = value.nil? ? hint : value text_field_tag name, value, {:onclick => "if($(this).value == '#{hint}'){$(this).value = ''}", :onblur => "if($(this).value == ''){$(this).value = '#{hint}'}" }.update(options.stringify_keys) end # inside form_for example hinted_text_field_tag :search, params[:search], "Enter name, brand or mfg.", :size => 30 # => <input id="search" name="search" onblur="if($(this).value == ''){$(this).value = 'Enter name, brand or mfg.'}" onclick="if($(this).value == 'Enter name, brand or mfg.'){$(this).value = ''}" size="30" type="text" value="Enter name, brand or mfg." />
Customizing prompt
The :prompt option not only accepts a boolean value. It can also be given a string to define another than the standard prompt ‘Please select’. Referring to the example it could read:
collection_select(:post, :author_id, Author.find(:all), :id, :name_with_initial, {:prompt => 'Please select the author of this post'})
Delete collections with check box tags
Following autonomous’ directions works wonders on /edit but needs slight modifications when dealing with pagination on /index.
In a /index type listing page we can no longer assume that the list of ids coming back represents changes to all objects so we need to provide some context, that the list of object modifications in our params array is a list of modifications for some set of objects.
We can only assume subsets because pagination or filtering may reduce the set of objects we’re working on.
In our case we had a user management page which listed all users and showed whether they were activated or not. The following code is what we used to ensure that modifications to the first page of objects wouldn’t affect all the other pages.
index.rhtml
<% @users.each do |user| %> <%= hidden_field_tag('seen[]', user.id) -%> <%= check_box_tag 'activated[]', user.id -%> <% end %>
role_controller.rb
def index if request.post? activated_ids = params[:activated].collect {|id| id.to_i} if params[:activated] seen_ids = params[:seen].collect {|id| id.to_i} if params[:seen] if activated_ids seen_ids.each do |id| r = User.find_by_id(id) r.activated = activated_ids.include?(id) r.save end end end end
Rake tasks for gem dependencies
You can manage installation and other tasks for these dependencies with rake tasks, for example:
rake gems:install # Installs all required gems for this application rake gems:unpack # Unpacks the specified gem into vendor/gems
To get all rake tasks about gems:
rake -T gems
Disable default date
If you want a date selector that initially doesn’t have a date selected you can pass it the option :include_blank.
date_select("project", "due_date", :include_blank => true)
%w(true false) != [true, false]
Don´t confuse the right:
validates_inclusion_of :published, :in => [true, false]
with the wrong:
validates_inclusion_of :published, :in => %w(true false)
cause:
%w(true false) == ["true", "false"]
Include two level has many model example
class Issue < ActiveRecord::Base
has_many :journals end class Journal < ActiveRecord::Base belongs_to :issue has_many :details, :class_name => "JournalDetail", :dependent => :delete_all end class JournalDetail < ActiveRecord::Base belongs_to :journal end
<hr/>
issue = Issue.find(:first, :include => {:journals => :details} log record follow: SELECT * FROM `issues` LIMIT 1 SELECT `journals`.* FROM `journals` WHERE (`journals`.`journalized_id` IN (1) and `journals`.`journalized_type` = 'Issue' AND (dustbin <> 1)) SELECT `journal_details`.* FROM `journal_details` WHERE (`journal_details`.journal_id IN (1,2,876177898,935815637)) when execute follow code, then not build sql sentent: issue.journals issue.journals[0].details
Doesn't add an index
Typically you will want to have an index on foreign keys but this method doesn’t assume that. Outside of the create_table block you should follow this with add_index :
add_index :table_name, :goat_id # and, if polymorphic: add_index :table_name, :goat_type
Specifying :include no longer necessarily joins the association
Before Rails 2.1, adding an :include=>[:association] in your find method caused ActiveRecord to generate SQL using a join. Since 2.1, it MAY NOT execute as a join.
The join executes a large query and returned potentially duplicate records for a one-to-many association. After 2.1, the query is broken down and eager-loaded using an additional query per association, passing the set of id’s to load, and avoiding the duplicate rows.
The new method eliminates duplicates, but can incur more database overhead. If you are loading a very large set of records (more than a “page”), you may need to “force” the join or use find_by_sql instead.
When you specify a “table.column” syntax within a
:conditions=>["child.name=?", name]
or
:order=>'child.name'
then ActiveRecord will build the older, full query with the join because you are referencing columns from another table to build. This will cause the duplicate rows to reappear.
Whenever you reference a column from another table in a condition or order clause, ALWAYS use the table name to prefix the column, even if it not ambiguous among the tables involved. Otherwise the query will not be executed as a join and you will receive an SQL error referencing the “missing” column.
You can “force” a join by adding a reference to the other tables in your :conditions or :options parameters, even if the test or sort is irrelevant.
Iterate and join blocks
Following LacKac’s idea, we can write render_join (useful to render a collection with a small chunks of code, where a render :partial + :spacer_template would be overkill):
def render_join(collection, join_string, &block) output = collection.collect do |item| capture(item, &block) end.join(join_string) concat(output, block.binding) end
An example of use:
<% render_join(@items, '<br />') do |item| %> <p>Item title: <%= item.title %></p> <% end %>
Current Database Name
The MySQL database adapter extends this and allows you to call
ActiveRecord::Base.connection.current_database
to get the current databases name. Useful when you are actively changing the database you are connected to and sometimes need to check the current one.
http://apidock.com/rails/ActiveRecord/ConnectionAdapters/MysqlAdapter/current_database
Current Database Name
The MySQL database adapter extends this and allows you to call
ActiveRecord::Base.connection.current_database
to get the current databases name. Useful when you are actively changing the database you are connected to and sometimes need to check the current one.
http://apidock.com/rails/ActiveRecord/ConnectionAdapters/MysqlAdapter/current_database
preload_associations manually
Usually you preload associations using :include => [ ... ]. In Rails 2.1 each association is fetched with a separate query. Something like:
Post.find(:all, :include => [:tags, :user])
will produce 3 queries - each for posts, tags and users.
But sometimes you have a complex query, which uses :joins => :other_association and conditions between multiple tables, but not the ones you need to include. Then everything is mixed back in one query like in old versions of Rails.
Another case may be when it is not possible to use :include at all, for example while using find_by_sql, but you still want/need to preload associated records.
In rails 2.1 find uses preload_associations internally, when it is possible (There are no joins or conditions between tables).
So then you can preload asociations manually from within your model:
class Post < ActiveRecord::Base
has_many :tags belongs_to :user ... def self.find_complex_with_includes posts = find_by_sql(...) # or find(:all, :joins => .....) preload_associations(posts, [:tags, :user]) posts end
end
and then do
@posts = Post.find_complex_with_includes
Namespace or modules in routes
If you have grouped controllers into a module, e.g. admin then you can specify this in the routes using the namespace method:
map.namespace :admin do |admin| admin.resources :categories end
which will map the categories resource giving urls like
/admin/categories/
/admin/categories/new
It will also generate the named routes such as new_admin_category_url and admin_category_path
script/generate syntax
To add a post_id field to a comments table, run this:
script\generate migration add_post_id_to_comment post_id:integer
See that it´s not the table name(plural), but the model name(singular),<br /> and post_id:references, does not works like in create_table.
This is the generated migration:
class AddPostIdToComment < ActiveRecord::Migration def self.up add_column :comments, :post_id, :integer end def self.down remove_column :comments, :post_id end end
Using sweepers in script/runner
If you need to use some of your sweepers in a script/runner script or some rake task you can use this snipped:
require 'action_controller/test_process' sweepers = [ProductSweeper, UserSweeper] ActiveRecord::Base.observers = sweepers ActiveRecord::Base.instantiate_observers controller = ActionController::Base.new controller.request = ActionController::TestRequest.new controller.instance_eval do @url = ActionController::UrlRewriter.new(request, {}) end sweepers.each do |sweeper| sweeper.instance.controller = controller end
Your script will fire the ActiveRecord callbacks defined in that sweepers and you can use expire_cache, expire_fragment and also the routing helpers you have defined (hash_for_user_path, hash_for_product_path, etc.).
Explanation about :dependent option
It may seem that :dependent option is only used when the object that has the collection is destroyed, but it is also used every time a associated object is deleted, so if you use
object.collection.delete(associated_object)
your object will be deleted, destroyed or nullified, depending on the value of :dependent option.
With has_many :through associations this option is ignored at least in versions up to 2.1.0, so even if you set :dependent option to :destroy, your join objects will be deleted, not firing any callbacks you have set on destroy events.
If you need to act when your join model is deleted you can use a sweeper or an observer and the association callbacks like this:
# product.rb class Product has_many :categorizations has_many :categories, :through => :categorizations, :before_remove => :fire_before_remove_in_categorizations private def fire_before_remove_in_categorizations(category) categorization = self.categorizations.find_by_category_id(category.id) categorization.class.changed categorization.class.notify_observers(:before_remove, categorization) end end # categorization_sweeper.rb # do not forget to load this sweeper during initialization class CategorizationSweeper < ActionController::Caching::Sweeper observe Categorization def before_remove(categorization) # expire_cache, expire_fragment, whatever end end
One thing you should be aware of it is that you are using before_remove, so you have to be careful because your record may be not be removed (another callback raising an exception or the database not deleting the record) so you can not be sure your object will be delete. Expiring caches is safe because even if your record is not destroyed your cache will be regerated correctly.
You can not use after_remove, because at that point the join model do not exists anymore, so you can not fire its callbacks. But you have the model id and the associated model id, so if you do not need expiring caches maybe you can use this approach (expiring caches can be only done in a sweeper or in a controller, but with after_remove you are bound to your model).
Calls attribute setter for each key/value in the hash
This is a convenience to set multiple attributes at the same time. It calls the “setter” method
self.attribute=(value)
for each key in the hash. If you have overridden the setter to add functionality, it will be called.
This also allows you to create non-table attributes that affect the record. For instance, a full_name=() method could parse the string and set the first_name=() and last_name() accordingly.
with password md5 encrypted
If you are afraid to let your plain password on the code, you can do this instead:
require 'digest' class AdminController < ApplicationController before_filter :authenticate def authenticate authenticate_or_request_with_http_basic('Administration') do |username, password| md5_of_password = Digest::MD5.hexdigest(password) username == 'admin' && md5_of_password == '5ebe2294ecd0e0f08eab7690d2a6ee69' end end end
where ‘5ebe2294ecd0e0f08eab7690d2a6ee69’ is the md5 of the word ‘secret’.
You can get your own with this free webservice: <br /> http://qi64.appspot.com/md5/secret (replace ‘secret’ with your secret word).
Optional classes
This piece of syntax saves me allot of time. Note the if statement.
Code example
content_tag(:div, "Hello World", :class => ("active" if i_am_an_active_item?))
Update element after remote call
Not mentioned in the documentation, you can add :update option to the remote_form_for and pass the id of element you’d like to update after ajax action as you do with link_to_remote, for example:
<% remote_form_for "comment", :update => "form" } do |f| %> # your form here <% end %>
Or
<% remote_form_for "comment", :update => {:success => "form", :failure => "errors"} do |f| %> # your form here <% end %>