Good notes posted to Ruby on Rails
RSS feedSpecifying :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
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 %>
Re: Helper method taking a block
The same using the ActionView::Helpers::TagHelper#content_tag and ActionView::Helpers::CaptureHelper#capture methods:
def render_tree(collection, &block) concat( content_tag(:ul, collection.collect { |item| content_tag(:li, capture(item, &block)) }.join("\n") ), block.binding ) end
The benefit is that it’s easier to improve with html attributes (just add a hash of options to the content_tag call) and it makes just one call to concat (which probably makes it faster).
Insertion/Deletion callbacks
All ActiveRecord associations except for has_many :through support callbacks for pre- and post-insertion/deletion via the following, self-documenting parameters:
Adding to an Association
:before_add
:after_add
Removing from an Association
:before_remove
:after_remove
The flexibility that these callbacks offer is quite handy, but I’ll demonstrate with a silly example: logging each insertion and deletion.
class Ship < ActiveRecord::Base
has_many :pirates, :after_add => :say_hello, :before_remove => :say_goodbye private def say_hello(pirate) STDOUT.write("hello #{pirate.name} ") end def say_goodbye(pirate) STDOUT.write("goodbye #{pirate.name} ") end end
Now, we’ll see confirmation when we add/remove Pirates in the console from our ship (and yes, this must be the ghetto):
>> jolly_roger = PirateShip.new
=> #<Ship id: nil, created_at: nil, updated_at: nil> >> jolly_roger.pirates << Pirate.create(:name => 'Black Bart') hello Black Bart => [#<Pirate id: 1, name: "Black Bart", created_at: "2008-07-29 14:41:13", updated_at: "2008-08-11 11:51:25"> >> jolly_roger.pirates.first.delete goodbye Black Bart => []
Helper method taking a block
Following the similar egzample by autonomous, here’s a simpler version when you just need to write a flexible helper method that takes a block.
For example, suppose you have a method that renders a tree:
def render_tree(ary, &block) concat("<ul>", block.binding) for elem in ary concat("<li>", block.binding) yield elem concat("</li>", block.binding) end concat("</ul>", block.binding) end
You can use it in your view, eg:
<% render_tree(@objects) do |elem| -%> <%= elem.title -%> <%= link_to 'delete', elem -%> <% end -%>
that would return for egzample:
<ul> <li> Test title <a href="delete">/elems/1</a> </li> </ul>
Testing concat
To test such helper methods, use the following pattern (a utility method added to your Rspec/unit test suite:
def render_for(root, options = {}) _erbout = '' render_tree(root, options) do |node| _erbout.concat(node.title) end _erbout end
and test like this (RSpec example):
it "should return abc" do render_for(object).should == 'abc' end
polymorphic_url and namespaces and nested resources
You can use polymorphic_url with namespaces and nested resources using array as parameter:
polymorphic_url([:admin, @post])
will return:
admin_post_url(@post)
Overriding the default div class="fieldWithErrors"
By default fields that are invalid are wrapped in:
<div class="fieldWithErrors"> <input type="text" name="blah"> </div>
To override and wrap in spans instead of divs place the following in your environment.rb:
ActionView::Base.field_error_proc = Proc.new { |html_tag, instance| "<span class=\"fieldWithErrors\">#{html_tag}</span>" }
or to not use wrapping at all:
ActionView::Base.field_error_proc = Proc.new { |html_tag, instance| "#{html_tag}" }
Overriding the default div class="fieldWithErrors"
By default fields that are invalid are wrapped in:
<div class="fieldWithErrors"> <input type="text" name="blah"> </div>
To override and wrap in spans instead of divs place the following in your environment.rb:
ActionView::Base.field_error_proc = Proc.new { |html_tag, instance| "<span class=\"fieldWithErrors\">#{html_tag}</span>" }
or to not use wrapping at all:
ActionView::Base.field_error_proc = Proc.new { |html_tag, instance| "#{html_tag}" }
RE: Using validates_format_of to validate URIs
Further to Olly’s note below, you can also specify the protocol to further limit the valid uri’s, else things like ‘ftp ://someurl.com’ (there’s only a space in there to get it to display on here) would be valid.
validates_format_of :uri, :with => URI.regexp(['http'])
Validations
update_attribute will not perform validations checks when the Validation module is included.
If you want to perform validations when updating, use update_attributes instead.
New way of calling partials on collections
You can directly call a partial on a collection of objects like this:
<%= render :partial => @users %>
This will call the partial _user.html.erb and populate a local variable ‘user’ within the partial. Then you can display the user partial in this way:
_user.html.erb:
Name: <%= user.name %> <br /> Email: <%= user.email %> <br />
The above render statement is equivalent to this code:
<% for user in @users %> <%= render :partial => 'user', :locals => { :user => user } %> <% end %>
Using validates_format_of to validate URIs
You can use the validates_format_of with the regular expression in the URI library ensure valid URIs.
Example
require 'uri' class Feed < ActiveRecord::Base validates_format_of :uri, :with => URI.regexp end
Avoiding duplicate results when you do a join
When you use the :joins option you can get multiple instances of the same item. For example, say you want every User who owns one or more Lists. With the code below, if a user owns 5 lists, the User will show up five times in the results:
users = User.find(:all, :conditions => ['users.id = lists.user_id'], :joins => [:lists], :order => 'users.username')
You can cause each found user to appear only once by using the :select option with “DISTINCT”:
users = User.find(:all, :conditions => ['users.id = lists.user_id'], :joins => [:lists], :select => 'DISTINCT users.*' :order => 'users.username')
Function to Determine Layout
Sometimes its nice to have different layouts choosen automagicly:
class ApplicationController < ActionController::Base layout :determine_layout def determine_layout if is_admin? "admin" else "application" end ... end
around_filter - working example
More detailed, working example of usage:
class HomeController < ApplicationController around_filter :action1, :action2 around_filter do |controller, action| logger.info "code block before action" action.call logger.info "code block after action" end def index logger.info "ACTION" end private # filters should not be available for external URL def action1 logger.info "ACTION1 before yield" yield "ACTION1" logger.info "ACTION1 after yield" end def action2 logger.info "ACTION2 before yield" yield "ACTION2" logger.info "ACTION2 after yield" end end
Results (in log file):
ACTION1 before yield ACTION2 before yield code block before action ACTION Rendering home/index code block after action ACTION2 after yield ACTION1 after yield
Multipart form
Don’t forget to add :multipart => true if you have file upload in your form.
<% form_for "user", :html => { :multipart => true } do |f| %>
Example: find by associated table
Say you have tables “authors” and “books” and they have a one-to-many association.
You want authors who have written books with “cooking” in the title…
cookbook_authors = Author.find(:all, :conditions => ['books.title LIKE ?', '%cooking%'], :joins => [:books], :order => 'authors.last_name' )
For many-to-many associations, it’s a similar pattern. Say you have tables “people” and “organizations” with a many-to-many association through the join table “organization_memberships”.
Ski Club members whose first name starts with “a”…
ski_club_members = Person.find(:all, :conditions => ['first_name LIKE ? AND organizations.name = ?', 'a%', 'Ski Club'], :joins => [:organizations], :order => 'people.last_name' )
Detailed messages for a nested model
Detailed messages for a nested model
<%@address = @order.address%> <%=error_messages_for :address%>
Cross browser issues
We use jQuery as our Javascript library of choice, but have to use a work around for full cross-browser support.
In jQuery you need to set the AJAX request headers as:
$.ajaxSetup({
beforeSend: function(xhr) {xhr.setRequestHeader(“Accept”, “text/javascript”);}
});
But we found that IE and Safari sends headers like: HTTP_ACCEPT=>“text/html, /, text/javascript”, with the javascript header last so this mucks up the respond_to block as it will always enter the first block (usually format.html) and never reach your format.js block.
We have a before filter called on required actions that forces the request format to be javascript if it is an xml_http_request?
def fix_xml_http_request if request.xml_http_request? request.format = :js end end


