Notes posted to Ruby on Rails
RSS feed

Nested Model Forms
For a good example of nested model forms check out the rails blog.

Expression
You can put some expression too. For example for I18n (using haml on view):
# some_locale.yml
links: contacts: "My contacts"
# index.html.haml
= link_to "#{t "links.contacts"}", :action => 'contacts'


Post =form post, NOT the http POST method
Post =forum post, NOT the http POST method
Note: any “Post” on this page has nothing to do with http methods. When I just looked at the collection_select code I was thrown off.

link_to_remote renders javascript as text
If you want to render an RJS file with link to remote you shouldn’t specify the :update function. Using this:
link_to_remote('add',:update=> 'someId', :url => {:controller => 'my_controller', :action => 'new'} ) # assume having 'my_controller/new.rjs'
will return something like
try { Element.update("someElement\n"); } catch (e) { alert('RJS error:\n\n' + e.toString()); alert('Element.update(\"someId\", \"item\\n\");'); throw e }
You should be specifying the id you are updating in the RJS file, not in link_to_remote. This will render correctly:
link_to_remote('add',:url => {:controller => 'my_controller', :action => 'new'} ) # assume having 'my_controller/new.rjs'
Only use :update when rendering html.

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.

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

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


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

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.

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:
controller.request.format.html? controller.request.format.json?

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…


Memoize will not cache singleton methods
The following does not work:
class PersonType < ActiveRecord::Base extend ActiveSupport::Memoizable class << self def mister find_by_name('Mister') end memoize :mister end
I guess one could extend the superclass, Class, with Memoizable support, but that seems Evil.

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


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.

Specify controller
If needed, you can also specify a controller.
redirect_to :controller => 'post', :action => 'index'

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"}]}

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"

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.

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.

read_attribute?
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?

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

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

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