Recent good notes

RSS feed
November 12, 2009
3 thanks

Use 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}%"])
November 5, 2009 - (>= v2.1.0)
6 thanks

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.

October 22, 2009 - (>= v2.1.0)
3 thanks

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.

October 7, 2009 - (>= v2.3.2)
3 thanks

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.

September 14, 2009
3 thanks

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', ':') %>
August 20, 2009
3 thanks

Symbol#to_proc

@tadman - or simply defining:

 class Symbol
   def to_proc
     proc { |obj, *args| obj.send(self, *args) }
   end
 end
August 13, 2009 - (>= v2.3.2)
3 thanks

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.

August 7, 2009
4 thanks

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],
July 28, 2009
4 thanks

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
July 27, 2009 - (>= v2.2.1)
3 thanks

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.

July 23, 2009 - (v1.0.0 - v2.3.2)
4 thanks

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

%Z - Time zone name

%% - Literal ``%’’ character

July 8, 2009 - (<= v1_8_7_72)
4 thanks

Using block version in Ruby < 1.8.7

The block usage was added in 1.8.7, so to get the same functionality in an earlier version of Ruby, you need to utilize the find method.

Here is a quick example:

  match = list.find { |l| l.owner == myself }
  match_index = list.index(match)

If you do some gymnastics, you can have it on one line without extra variables:

  match_index = list.index(list.find { |l| l.owner == myself })
June 17, 2009
3 thanks

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
June 6, 2009
4 thanks

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

June 4, 2009
3 thanks

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.

May 13, 2009
3 thanks

Equivalent to Array#reject!

This method is functionally identical to Array#reject!

May 2, 2009
5 thanks

Create a Hash from two Arrays

Here is my favorite idiom for creating a Hash from an Array of keys and an Array of values:

  keys = [:a, :b]
  values = [1,2]
  h = Hash[*keys.zip(values).flatten]      # => {:b=>2, :a=>1}
April 28, 2009 - (>= v2.3.2)
3 thanks

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

April 23, 2009
4 thanks

Handy shorthand for array manipulation

You may write something like this:

  >> ['a', 'b', 'c'].collect{|letter| letter.capitalize}
  => ["A", "B", "C"]

But it looks so much nicer this way:

  >> ['a', 'b', 'c'].collect(&:capitalize)
  => ["A", "B", "C"]
April 21, 2009
3 thanks

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.

April 21, 2009
3 thanks

To throw an exception, use Kernel#raise

Other languages use the term throw for raising exceptions, but Ruby has a specific raise call for that.

April 16, 2009
4 thanks

Parameters for Hash#inject

When running inject on a Hash, the hash is first converted to an array before being passed through.

The typical Enumerable#inject approach would be to simply capture the value:

  array.inject(...) do |c, v|
  end

In the case of a Hash, v is actually a key/value pair Array. That is the key is v.first and the value is v.last, however using the pair this way is awkward and can lead to confusion.

Better to simply expand the parameters in the block definition:

  hash.inject(...) do |c, (k, v)|
  end

Where c is the traditional carry variable and k/v represent key and value respectively.

April 9, 2009
3 thanks

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".

April 9, 2009
4 thanks

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

April 8, 2009 - (>= v2.3.2)
9 thanks

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.

April 8, 2009 - (v2.3.2)
4 thanks

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.

April 6, 2009
5 thanks

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.

April 6, 2009
3 thanks

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]}
April 3, 2009
3 thanks

The docs are in AR::Base

The docs you’re looking for are in ActiveRecord::Base

April 1, 2009
3 thanks

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.