Notes posted to Ruby on Rails

RSS feed
March 12, 2009 - (<= v2.1.0)
0 thanks

Another fix

Another way around this problem, with code that already employs String.first, is to change the ActiveSupport definition as follows (in environment.rb)

module ActiveSupport
  module CoreExtensions 
    module String 
      module Access
        def first(limit = 1)
          chars.to_a[0..(limit - 1)].to_s
March 12, 2009
2 thanks

Incompatible with Ruby 1.8.7

If using Rails < 2.2 with Ruby 1.8.7, calling truncate will result in the following error:

undefined method `length' for #<Enumerable::Enumerator:0xb74f952c>

The workaround (other than upgrading to Rails 2.2 or higher), is to overwrite the truncate method, by inserting the following at the end of environment.rb (or where it will be called on startup):

module ActionView
  module Helpers
    module TextHelper
      def truncate(text, length = 30, truncate_string = "...")
        if text.nil? then return end
        l = length - truncate_string.chars.to_a.size
        (text.chars.to_a.size > length ? text.chars.to_a[0...l].join + truncate_string : text).to_s
March 12, 2009
0 thanks


That didn’t render properly. Use:


instead of

March 12, 2009
1 thank

Selected parameter

batasrki’s note on “selected” parameter is only true for cases which “value_method” returns an int also.

The strictly correct requirement for it to work is:

object.value_method == selected

(“object” is the current object on the iteration over collection)

Since the params hash returns Strings, using it against a value_method with return type of int will never give a valid match (thus no auto-selection is done), i.e., “13” != 13.

When you’ll be using other types of value_method, like String, there’s no need to append “.to_i”, e.g:

options_from_collection_for_select(@posts, "slug", "title", params[:slug])

where “slug” is a String, it will work as expected (current selected post is auto-selected by default).

March 11, 2009
2 thanks

Use table_exists? in initializers

When using ActiveRecords in initializers, eg. for creating small constant data on startup, use table_exists? for those statements.

the initalizers are also called for migrations, etc, and if you are installing a new instance of the project these initializers will fail the migration to create those tables in the first place.

March 11, 2009 - (>= v2.1.0)
2 thanks

Can also be used to conditionally apply filters

For example:

# Skip login filter if the request is for CSS
before_filter :require_login, :unless => lambda { |controller| controller.request.format.css? }

Calling request.format on the controller returns a Mime::Type object, which can then be queried for mime types, other examples:

March 10, 2009 - (>= v2.1.0)
8 thanks

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…

March 10, 2009
1 thank

Image as a submit button

use image_sugmit_tag to use an image as a submit button

March 9, 2009
0 thanks

Memoize will not cache singleton methods

The following does not work:

class PersonType < ActiveRecord::Base
  extend ActiveSupport::Memoizable
  class << self
    def mister
    memoize :mister

I guess one could extend the superclass, Class, with Memoizable support, but that seems Evil.

March 6, 2009
2 thanks

Selected parameter needs an int

In order to pre-select an option, you can pass a fourth parameter. However, that parameter MUST be of integer type, so if you’re trying to set selected from the params hash, you must add to_i at the end of it.

<%= select_tag("job[state_id]", options_from_collection_for_select(State.find(:all), "id", "name", params[:state_id].to_i)) %>
March 5, 2009
1 thank


It’s not:

user = User.find_by_name('Phusion')


user = User.find_by_name('Phusion')
March 4, 2009
10 thanks

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"

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"
memoize :allowed?

allowed? # Outputs "Worked"
allowed? # No output

Now, look at the case where we have parameters:

def random(max=10)
  @random ||= rand(max)

random     # => 4
random     # => 4 -- Yay!
random(20) # => 4 -- Oops!

Better use memoize again!

def random(max=10)
memoize :random

random     # => 6
random     # => 6 -- Yay!
random(20) # => 12 -- Double-Yay!
random     # => 6 -- Head a'splode
March 4, 2009
5 thanks


This defines attr_accessors at a class level instead of instance level.

class Foo
  cattr_accessor :greeting

Foo.greeting = "Hello"

This could be compared to, but is not the same as doing this:

class Bar
  class << self
    attr_accessor :greeting

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.

March 3, 2009
0 thanks

Specify controller

If needed, you can also specify a controller.

redirect_to :controller => 'post', :action => 'index'
March 3, 2009 - (<= v2.2.1)
1 thank

Multiple associations on the same level

You can also specify multiple associations that are on the same level, like this:

konata = User.find(1)
konata.to_json(:include => [:posts, :roles])

{"id": 1, "name": "Konata Izumi", "age": 16,
 "created_at": "2006/08/01", "awesome": true,
 "posts": [{"id": 1, "author_id": 1, "title": "Welcome to the weblog"},
           {"id": 2, author_id: 1, "title": "So I was thinking"}],
 "roles": [{"id":1, "user_id":1, "name": "Admin"},
           {"id":2, "user_id":1, "name": "Moderator"}]}
March 2, 2009
2 thanks

Valid options changed in Rails v2.3.0 RC1

The valid options in Rails v2.3.0 RC1 are no longer

:connector and :skip_last_comma, but

:words_connector, :two_words_connector, and :last_word_connector:

>> %w(lorem ipsum dolor sit).to_sentence
=> "lorem, ipsum, dolor, and sit"

>> %w(lorem ipsum dolor sit).to_sentence(:words_connector => ' + ')
=> "lorem + ipsum + dolor, and sit"

>> %w(lorem ipsum).to_sentence(:two_words_connector => ' through ')
=> "lorem through ipsum"

# No effect if more than two words
>> %w(lorem ipsum dolor sit).to_sentence(:two_words_connector => ' through ')
=> "lorem, ipsum, dolor, and sit"

>> %w(lorem ipsum dolor sit).to_sentence(:last_word_connector => ' or ')
=> "lorem, ipsum, dolor or sit"
March 1, 2009
2 thanks

used for testing

for example, to use the @message instance variable in a view test,

assigns[:message] = @message

you could type

assigns[:foo] = @message

and then message would be available to the view as @foo.

February 27, 2009
6 thanks

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 )

The parentheses are important, will fail silently without them.

February 27, 2009 - (v1.2.6 - v2.2.1)
0 thanks


The source if this method and other methods indicate the reading of read_attribute (protected)

there is no documentation about the insides of this method, the only entry listed is read_attribute (private) which is deprecated since 1.2.6?

February 26, 2009 - (v1.0.0 - v2.1.0)
0 thanks

Conflicts with Ruby 1.8.7

Using this with Rails < 2.2.x and Ruby 1.8.7 will create a conflict between ActiveSupport and Ruby, generating the following error:

>> '/'.first
NoMethodError: undefined method `[]' for #<Enumerable::Enumerator:0x176b974>
  from /opt/local/lib/ruby/gems/1.8/gems/activesupport-2.0.2/lib/active_support/core_ext/string/access.rb:43:in `first'

So if using an older version of Rails with Ruby 1.8.7, use String to instead of String.first

February 24, 2009
9 thanks

Specialized versions of find with method_missing

Check ActiveRecord::Base.method_missing for documentation on the family of “magic” find methods (find_by_x, find_all_by_x, find_or_create_by_x, etc.).

February 24, 2009 - (>= v2.2.1)
5 thanks

ATM does not work in Rails 2.3 Edge

add to test/spec_helper to make it work again…

#spec_helper / test_helper
include ActionController::TestProcess
February 24, 2009
0 thanks

Validate an optional URL field

Let’s say that you have an optional URL field to one of your models and you want to validate the URL. You can accomplish this by using the URI library:

require 'uri' # Put this at the beginning of your model file

validates_each :url, :allow_blank => true do |record, field, value|
    valid = (URI.parse(value).scheme =~ /https?/)
  rescue URI::InvalidURIError
    valid = false
  record.errors.add field, "not a valid url" unless valid

If you want to add even more testing in there, just go ahead. For now, we just check that the link is to a HTTP resource, but you might have other requirements. This will allow stuff like “http://example” since “example” might be a valid intranet domain. If you want to check for a TLD in there, you can do so with a simple regexp.

For more information about the URI library, check out http://apidock.com/ruby/URI/

February 23, 2009 - (<= v2.2.1)
1 thank

You can't have many :through with habtm

Imagine the following

a has_many b
b has_and_belongs_to_many c
a has_many c :through => b

a.b works fine

b.c works fine

a.c throws an error!

has_many :through where the through association is a habtm is not supported in Rails. The error is:

ActiveRecord::HasManyThroughSourceAssociationMacroError: Invalid source reflection macro :has_and_belongs_to_many for has_many :stories, :through => :subcategories. Use :source to specify the source reflection

Specifying the source reflection still won’t help you though, because this kind of has_many :through isn’t supported at all.

February 23, 2009 - (>= v2.0.0)
6 thanks

Nested with_options

You can nest with_options blocks, and you can even use the same name for the block parameter each time. E.g.:

class Product
  with_options :dependent => :destroy do |product|
    product.with_options :class_name => 'Media' do |product|
      product.has_many :images, :conditions => {:content_type => 'image'}
      product.has_many :videos, :conditions => {:content_type => 'video'}

    product.has_many :comments
February 23, 2009
2 thanks

Check if value is included in array of valid values

If you want to check the value of an attribute using an array of valid values, the array has to be defined before the validation, so

validates_inclusion_of :name, :in => VALID_NAMES
VALID_NAMES = %w(Peter Paul Mary)

won’t work, but

VALID_NAMES = %w(Peter Paul Mary)
validates_inclusion_of :name, :in => VALID_NAMES


February 22, 2009
3 thanks

CAUTION! :frequency option description is misleading

To use event-based observer, don’t supply :frequency param at all. :frequency => 0 causes JS error.

Use this option only if time-based observer is what you need.

February 20, 2009
5 thanks

Static and dynamic attachments

You can attach static files directly:

attachment :content_type => "image/jpeg", :body => File.read("someimage.jpg")

and you can also define attachments dynamically by using a block:

attachment "text/csv" do |a|
  a.body = my_data.to_csv
February 18, 2009
4 thanks

Turn off for individual controllers/actions

To disable protection for all actions in your controller use skip_before_filter:

skip_before_filter :verify_authenticity_token

You can also pass :only and :except to disable protection for specific actions, e.g:

skip_before_filter :verify_authenticity_token, :only => :index