Flowdock

Good notes posted to Ruby on Rails

RSS feed
March 21, 2009
4 thanks

Passing optional arguments with defaults to a named_scope

An easy way to do this. (This also shows how you can use joins in a named_scope as well.)

Class User << ActiveRecord::Base
belongs_to :semester 

named_scope :year, lambda { |*year|
  if year.empty? || year.first.nil?
    { :joins => :semester, :conditions => ["year = #{CURRENT_SEMESTER}"]}
  else
    { :joins => :semester, :conditions => ["year = #{year}"]}
  end
  }

end

You can then call:

User.year     # defaults to CURRENT_SEMESTER constant
User.year()  # same as above
User.year(nil)  # same as above; useful if passing a param value that may or may not exist, ie, param[:year]
User.year(2010)
March 21, 2009
7 thanks

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
March 20, 2009 - (>= v2.0.0)
9 thanks

Use the current URL, with changes

You can use the current URL, whatever it is, with changes, as in:

# Create a link to the current page in RSS form
url_for(:overwrite_params => {:format => :rss})

This can be super-helpful because it preserves any GET params (like search parameters)

March 12, 2009
3 thanks
March 12, 2009
12 thanks

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.

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 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"
    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
March 4, 2009
5 thanks

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.

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
  end )

The parentheses are important, will fail silently without them.

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 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'}
    end

    product.has_many :comments
  end
end
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
end
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
February 17, 2009
4 thanks

Date_select with assert_valid_keys

If you are using date_select with assert_valid_keys you have to allow 3 parameters named field(1i), field(2i) and field(3i).

For example with field

date_select("post", "written_on")

You have to allow following fields:

params[:post].assert_valid_keys( 
  'written_on(1i)', 'written_on(2i)', 'written_on(3i)'
)
February 17, 2009
9 thanks

Empty elements

If you want to output an empty element (self-closed) like “br”, “img” or “input”, use the tag method instead.

February 13, 2009
3 thanks

New test syntax

You can use either one and even mix in the same test case if you want:

class Test < Test::Unit::TestCase
  # old way to define a test method (prefix with test_)
  def test_should_be_valid_without_content
    assert Comment.new.valid?
  end

  # new way to define a test
  test "should be valid without content" do
    assert Comment.new.valid?
  end
end
February 10, 2009
8 thanks

Security issue with non-HTML formats

Please note that using default to_xml or to_json methods can lead to security holes, as these method expose all attributes of your model by default, including salt, crypted_password, permissions, status or whatever you might have.

You might want to override these methods in your models, e.g.:

def to_xml
  super( :only => [ :login, :first_name, :last_name ] )
end

Or consider not using responds_to at all, if you only want to provide HTML.

February 9, 2009
5 thanks
February 7, 2009 - (>= v2.2.1)
3 thanks

Deprecated

This method is deprecated. You should use:

I18n.translate('activerecord.errors.messages')
February 3, 2009
3 thanks

Possible gotcha

Please note that exists? doesn’t hold all the conventions of find, i.e. you can’t do:

Person.exists?(:conditions => ['name LIKE ?', "%#{query}%"]) # DOESN'T WORK!
February 3, 2009
3 thanks

Nothing here

You’re probably looking for I18n::Backend::Simple.

February 3, 2009
4 thanks

This method will still rewrite all the values of the table

Even if you update only a small boolean flag on your record, update_attribute will generate an UPDATE statement that will include all the fields of the record, including huge BLOB and TEXT columns. Take this in account.

February 1, 2009 - (v2.2.1)
3 thanks

You can't use Symbols, but you can use Regexps

You can’t use Symbol (although Symbol is accepted with render :action => :new), like:

assert_template :new # WON'T WORK!

But you can use Regexp, e.g.:

assert_template /new/ # WORKS OK

Note that the String matched with your Regexp is the full path to the template relative to the view/ directory of your app, so this will not work:

assert_template /^new$/ # WON'T WORK!

However this might:

assert_template /^employees\/new.html.haml$/
January 30, 2009 - (v1.2.0 - v2.1.0)
3 thanks

Hash conditions require explicit key and value

When condition passed as hash, the behavior is different from a finder method. Finder methods, such as:

find(:all, :user=>user)

will apply the user_id = user.id convention, provided user is an association (e.g. belongs_to :user). The exists? method will not do the same. You must specify the foreign key name and value explicitly, i.e:

exists?(:user_id=>user.id)
January 27, 2009 - (>= v2.2.1)
5 thanks

Getting the object in a partial

If you need to get the object for the form inside a partial, and can’t use the instance variable, use the #object method… This is particularly useful when you’re dealing with single-table inheritance subclasses (e.g. MyOtherClass inherits from MyClass) or when you are using the same partial across different controllers.

new.html.erb

<% form_for(@my_object) do %>
  <%= render :partial => 'form' %>
  <%= submit_tag 'Create' %>
<% end %>

_form.html.erb

<% if f.object.class.is_a? MyClass %>
 <%# do something... %>
<% elsif f.object.is_a? MyOtherClass %>
  <%# do something else... %>
<% end %>
January 27, 2009
6 thanks

Use collect in nested content_tags

Remember to use #collect instead of #each in nested content_tags

arr = ['a','b','c']
content_tag :div do 
  arr.collect { |letter| content_tag(:scan, letter) 
end
#=> <div>
#      <scan>a</scan>
#      <scan>b</scan>
#      <scan>c</scan>
#   </div>

If you used #each you would get this (which is probably a mistake):

#=> <div>
#      abc
#   </div>