Good notes posted to Ruby on Rails
RSS feedUse hash form of updates argument
The examples are unfortunate, because passing a string as the updates argument is an invitation to SQL injection attacks. Don’t do this!
Billing.update_all("author='#{author}'")
Use the hash form of updates instead:
Billing.update_all(:author => author)
Then the SQL adapter will quote everything safely. Even if [you think] you’re sure there’s no quoting issue, it’s better to cultivate the habit of using the hash form just in case you missed something.
Same with conditions—use the hash or array form rather than a string if there are variables involved.
BTW, to do this and give options, of course you’ll need to put the braces back in:
Billing.update_all({:author => author}, ['title like ?', "#{prefix}%"])
Named scope better than conditions
In modern versions of Rails, in most cases a named_scope is a better alternative to using :conditions on your has_many relations. Compare:
class User has_many :published_posts, :conditions => {:published => true} end user.published_posts
with:
class Post named_scope :published, :conditions => {:published => true} end class User has_many :posts end user.posts.published
It’s better because the Post's logic ("am I published?") should not be coupled within User class. This makes it easier to refactor: e.g. if you wanted to refactor the boolean :published field into a :status field with more available values, you would not have to modify User class. Having to modify User when you refactor some implementation detail of Post class is clearly a code smell.
This also applies to :order, :group, :having and similar options.
Update statement won't include all attributes with ActiveRecord::Dirty
With the addition of ActiveRecord::Dirty, the update statement will only feature changed columns, as opposed to the comment of railsmonk below.
Streaming XML with Builder
To generate larger XMLs, it’s a good idea to a) stream the XML and b) use Active Record batch finders.
Here’s one way of doing it:
def my_action @items = Enumerable::Enumerator.new( Item.some_named_scope, :find_each, :batch_size => 500) respond_to do |format| format.xml do render :text => lambda { |response, output| extend ApplicationHelper xml = Builder::XmlMarkup.new( :target => StreamingOutputWrapper.new(output), :indent => 2) eval(default_template.source, binding, default_template.path) } end end end
The Builder template does not need to be modified.
Pluralize Without Count
Helper method that returns the word without the count.
application_helper.rb
def pluralize_without_count(count, noun, text = nil) if count != 0 count == 1 ? "#{noun}#{text}" : "#{noun.pluralize}#{text}" end end
Example usage:
_form.html.erb
<%= pluralize_without_count(item.categories.count, 'Category', ':') %>
with_exclusive_scope example by Ramon broken in latest Rails
The example Ramon gave works within the model itself, i.e.
class Article def closed with_exclusive_scope { find(:all) } end end
However, from what I can see, this approach does not work within a controller. You may be wanting to use
Article.with_exclusive_scope { find(:all) } #=> "SELECT * FROM 'articles'
But it will error out about find(:all) not existing on ArticlesController. To get around this, you must now do
Article.with_exclusive_scope { Article.find(:all) } #=> "SELECT * FROM 'articles'
In otherwards, find(:all) isn’t being executed in the scope of the model, but in the controller in which its called.
Took me a minute or two to find out, so I thought I’d let others know.
Join multiple tables
It’s easy to join multiple tables too. In this case we have:
class Article belongs_to :feed end class Feed has_many :articles belongs_to :source end class Source has_many :feeds # t.bool :visible end
You can search articles and specify a condition on the sources table.
Article.find(:all, :conditions => { :feeds => { :sources => { :visible => true }}}, :joins => [:feed => :source],
Return True
As is the case with the before_validation and before_save callbacks, returning false will break the callback chain. For example, the expire_cache_id method will not run if Rails.cache.expire returns false (as it will if the key is not cached with memcache).
Returning False Example (Bad)
after_save :expire_cache_by_name after_save :expire_cache_by_id def expire_cache_by_name Rails.cache.expire("my_object:name:#{self.name}") end def expire_cache_by_id Rails.cache.expire("my_object:#{self.id}") end
Returning True Example (Good)
def expire_cache_by_name Rails.cache.expire("my_object:name:#{self.name}") return true end def expire_cache_by_id Rails.cache.expire("my_object:#{self.id}") return true end
Overriding default validation messages
Before Rails 2.2 you could globally customize the default validation error messages by changing AR::Base.default_error_messages. The messages have now been moved to i18n, so to customize them in 2.2 and up, just create a locales/ folder in your config/ folder, copy activerecord/lib/active_record/locale/en.yml (in Rails source) to config/locales/en.yml, and then change the strings inside. As szeryf indicated below, the strings of interest are activerecord.errors.messages.
Format meaning
%a - The abbreviated weekday name (``Sun’‘)
%A - The full weekday name (``Sunday’‘)
%b - The abbreviated month name (``Jan’‘)
%B - The full month name (``January’‘)
%c - The preferred local date and time representation
%d - Day of the month (01..31)
%H - Hour of the day, 24-hour clock (00..23)
%I - Hour of the day, 12-hour clock (01..12)
%j - Day of the year (001..366)
%m - Month of the year (01..12)
%M - Minute of the hour (00..59)
%p - Meridian indicator (``AM’‘ or ``PM’‘)
%S - Second of the minute (00..60)
%U - Week number of the current year, starting with the first Sunday as the first day of the first week (00..53)
%W - Week number of the current year, starting with the first Monday as the firstday of the first week (00..53)
%w - Day of the week (Sunday is 0, 0..6)
%x - Preferred representation for the date alone, no time
%X - Preferred representation for the time alone, no date
%y - Year without a century (00..99)
%Y - Year with century
%% - Literal ``%’’ character
Skipping validation
Unlike the save method, you can’t pass false to update_attributes to tell it to skip validation. Should you wish to do this (consider carefully if this is wise) update the attributes explicitly then call save and pass false:
@model_name.attributes = params[:model_name] @model_name.save false
add index with :quiet=>true option for indices that are possibly already added
# Allows you to specify indices to add in a migration that will only be created if they do not # already exist, or to remove indices only if they already exist with :quiet=>true module ActiveRecord::ConnectionAdapters::SchemaStatements
def add_index_with_quiet(table_name, column_names, options = {}) quiet = options.delete(:quiet) add_index_without_quiet table_name, column_names, options rescue raise unless quiet and $!.message =~ /^Mysql::Error: Duplicate key name/i puts "Failed to create index #{table_name} #{column_names.inspect} #{options.inspect}" end alias_method_chain :add_index, :quiet def remove_index_with_quiet(table_name, column_names, options = {}) quiet = options.delete(:quiet) raise "no options allowed for remove_index, except quiet with this hack #{__FILE__}:#{__LINE__}" unless options.empty? remove_index_without_quiet table_name, column_names rescue raise unless quiet and $!.message =~ /^Mysql::Error: Can't DROP/i puts "Failed to drop index #{table_name} #{column_names.inspect}" end alias_method_chain :remove_index, :quiet
end
A catch-all format
If you’d like to specify a respond_to only for 1 or a few formats and render something else for all other formats, eg: (action.rss returns a feed but action.html or action.js should just render 404), use format.all:
respond_to do |format| format.rss { render_rss } format.all { render_404 } end
Rails will render an empty string for all formats that don’t specify a response explicitly.
A very thorough explanation of use
Ryan Daigle has a great article about 2.3’s new nest forms which does a really good job of explaining how to use this and some of the potential gotchas. Highly recommended:
http://ryandaigle.com/articles/2009/2/1/what-s-new-in-edge-rails-nested-attributes
Format not coming out properly?
Date, Time and DateTime may have different formats defined.
If you do:
@user.created_at.to_formatted_s(:long_ordinal)
You will get (or something):
April 16th, 2009 22:03
But if you do:
@user.created_at.to_date.to_formatted_s(:long_ordinal)
You will get:
April 16th, 2009
So, be sure you know which one you are working with.
Define handlers in order of most generic to most specific
The later the definition of the rescue handler, the higher the priority:
rescue_from Exception, :with => :error_generic rescue_from Exception::ComputerOnFire, :with => :panic
Declaring the Exception catch-all handler last would have the side-effect of precluding any other handlers from running.
This is what is meant by being "searched…from bottom to top".
Method has moved to ActionController::Rescue::ClassMethods module
This method has simply moved, still works the same way in 2.3+
New location: ActiveSupport::Rescuable::ClassMethods#rescue_from
Setting child_index while using nested attributes mass assignment
When using nested attributes mass assignment sometimes you will want to add new records with javascript. You can do it with pure javascript, but if HTML is long your javascript will be long and messy and it will not be DRY as probably you already have a partial for it.
So to add a partial dynamically you can do something like that (notice string "index_to_replace_with_js"):
link_to_function
def add_object_link(name, form, object, partial, where) options = {:parent => true}.merge(options) html = render(:partial => partial, :locals => { :form => form}, :object => object) link_to_function name, %{ var new_object_id = new Date().getTime() ; var html = jQuery(#{js html}.replace(/index_to_replace_with_js/g, new_object_id)).hide(); html.appendTo(jQuery("#{where}")).slideDown('slow'); } end
js method in one of helpers (from minus mor plugin)
def js(data) if data.respond_to? :to_json data.to_json else data.inspect.to_json end end
This method will generate link adding generated partial to html.
The thing that is not mentioned in docs is how to set child_index. You must add it as an argument in hash.
Example of partial
<% form.fields_for :tasks, task, :child_index => (task.new_record? ? "index_to_replace_with_js" : nil) do |tasks_form| %> <% tasks_form.text_field :name %> <% end %>
Using add_object_link
<% form_for :project do |form| %> <div id="tasks"> <%# displaying existing tasks %> </div> <%= add_object_link("New task, form, Task.new, "task", "#tasks") %> <% end %>
Thanks to child_index after insertion it will change indexes to current time in miliseconds so added tasks will have different names and ids.
Superclass of OrderedHash
Note that in Rails 2.3, OrderedHash changed from being a subclass of Array to a subclass of Hash. This is contrary to what the documentation says above.
HTML entities in options
Unfortunately everything is escaped with ERB::Util#html_escape. Your only option is either manually construct options or compeletely overwrite this method.
Array clustering
Sometimes you don’t want to mangle sequence of an array and just want to group adjacent values. Here’s a nice method to do so (drop it in your initializers directory or something):
module Enumerable # clumps adjacent elements together # >> [2,2,2,3,3,4,2,2,1].cluster{|x| x} # => [[2, 2, 2], [3, 3], [4], [2, 2], [1]] def cluster cluster = [] each do |element| if cluster.last && yield(cluster.last.last) == yield(element) cluster.last << element else cluster << [element] end end cluster end end
Similarly you can do the clustering on more complex items. For instance you want to cluster Documents on creation date and their type:
Document.all.cluster{|document| [document.created_on, document.type]}
Ordering of format blocks is important
The order in which your format blocks appear, like:
format.html { } format.js { }
are used to infer priority in cases where the appropriate format is ambiguous.
So, how do you enable db sessions?
First, run:
rake db:sessions:create
Then, run your pending migrations. This will create the migration you need to run in order to create the sessions table.
Second, go into config/environment.rb and uncomment or put in:
config.action_controller.session_store = :active_record_store config.action_controller.session = { :session_key => '_your_session_name_here', :secret => 'SOME_CRYPTOGRAPHICALLY_SECURE_KEY' }
Third, get yourself a secure key with:
rake secret
And finally, paste your new key into the :secret above.
Use helpers in your ActionMailer views
It’s very easy to give your mailer access to helpers:
# Let your mailer user the ApplicationHelper methods class MyMailer < ActionMailer::Base helper :application end
More Information
More information can be found at ActionController::Verification::ClassMethods
User a block to extend your associations
You can use blocks to extend your associations with extra methods.
code sample
has_many :children, :dependent => :destroy do def at(time) proxy_owner.children.find_with_deleted :all, :conditions => [ "created_at <= :time AND (deleted_at > :time OR deleted_at IS NULL)", { :time => time } ] end end Model.children.each # do stuff Model.children.at( 1.week.ago ).each # do old stuff
you must use ‘proxy_owner’ to link back to your model.
Use lambda to avoid caching of generated query
If you’re using a named_scope that includes a changing variable you need to wrap it in a lambda to avoid the query being cached and thus becoming unaffected by future changes to the variable, example:
named_scope :translated, :conditions => { :locale => I18n.locale }
Will always return the same locale after the first hit even though I18n.locale might change. So do this instead:
named_scope :translated, lambda { { :conditions => { :locale => I18n.locale } } }
Ugly, but at least it’s working as we expect it…
Differences between normal or-assign operator
Differences between this method and normal memoization with ||=:
- memoize works with false/nil values
- Potential arguments are memoized
Take the following example:
def allowed? @allowed ||= begin # Big calculation puts "Worked" false end end allowed? # Outputs "Worked" allowed? # Outputs "Worked" again
Since @allowed is set to false (this is also applicable with nil), the ||= operator will move on the the next statement and will not be short-circuited.
When you use memoize you will not have this problem.
def allowed? # Big calculation puts "Worked" false end memoize :allowed? allowed? # Outputs "Worked" allowed? # No output
Now, look at the case where we have parameters:
def random(max=10) @random ||= rand(max) end random # => 4 random # => 4 -- Yay! random(20) # => 4 -- Oops!
Better use memoize again!
def random(max=10) rand(max) end memoize :random random # => 6 random # => 6 -- Yay! random(20) # => 12 -- Double-Yay! random # => 6 -- Head a'splode
Usage
This defines attr_accessors at a class level instead of instance level.
class Foo cattr_accessor :greeting end Foo.greeting = "Hello"
This could be compared to, but is not the same as doing this:
class Bar class << self attr_accessor :greeting end end Bar.greeting = "Hello"
The difference might not be apparent at first, but cattr_accessor will make the accessor inherited to the instances:
Foo.new.greeting #=> "Hello" Bar.new.greeting # NoMethodError: undefined method `greeting' for #<Bar:0x18e4d78>
This inheritance is also not copy-on-write in case you assumed that:
Foo.greeting #=> "Hello" foo1, foo2 = Foo.new, Foo.new foo1.greeting = "Hi!" Foo.greeting #=> "Hi!" foo2.greeting #=> "Hi!"
This makes it possible to share common state (queues, semaphores, etc.), configuration (max value, etc.) or temporary values through this.
Extend with an anonymous module
You can extend with an anonymous module for one-off cases that won’t be repeated:
belongs_to :container, :polymorphic => true, :extend => ( Module.new do def find_target ... end end )
The parentheses are important, will fail silently without them.

