Good notes posted to Ruby on Rails
RSS feedImportant note
It has been said that “it can be compared to, but isn’t the same thing as”:
class Bar class << self attr_accessor :greeting end end
Which is true. However, they are “inherited” isn’t exactly the case. Rather, cattr_accessor uses class variables.
The problem with class variables in Ruby, is that a class variable is the same object across all subclasses of a class. Consider the following example of what happens with cattr_accessor:
class A @@foo = 'foo' def self.foo @@foo end end p A.foo # => "foo" class B < A end p B.foo # => "foo" class B @@foo = 'bar' end p B.foo # => "bar"
So far so good you might think. However, something you might not have expected is that the variable has now also changed in class A:
p A.foo # => "bar"
This is in my opinion almost never what you’d want. More probable is that you’d want the individual class instance to have an accessor. (Remember classes are objects in Ruby). I do the following in regular Ruby:
class A class << self attr_accessor :foo end self.foo = 'foo' end p A.foo # => "foo" class B < A end p B.foo # => nil class B self.foo = 'bar' end p B.foo # => "bar" p A.foo # => "foo"
As you can see, this returns nil when a value hasn’t explicitly been set yet on the new class instance. If you’d like to have inheritance without messing with the superclasses variables, have a look at ActiveSupport’s class_inheritable_accessor, which does the same as I just explained, but creates a clone of the object and assigns it to the subclass whenever a class is inherited.
What I’d normally do in Ruby to fix the issue of it returning nil is to create the accessor manually and have it set the instance variable to the default if it’s nil:
class A class << self def foo @foo ||= 'foo' end end end class B < A end p B.foo # => nil
So to recap:
-
cattr_accessor uses class variables (@@foo), in which case the object is shared across all subclasses of a class. Use it mainly for static data, in which case you’d probably best use a constant.
-
class_inheritable_accessor (or what I showed) uses instance variables (@foo) at the Class instance level. These variables are not shared across all subclasses.
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
cattr_accessor_with_default
Class attribute assessors are neat if you want to set up modifiable constant-like varibles. This is how you’d normally set it up:
module MyPlugin class Conf @@awesome_level = 'huge' cattr_accessor :awesome_level end end
Then you can call and modify it like this:
>> MyPlugin::Conf.awesome_level => 'huge' >> MyPlugin::Conf.awesome_level = 'massive' >> MyPlugin::Conf.awesome_level => 'massive'
If you have a pile of those accessors I’d do something like this (there might be a better way, but it works):
module MyPlugin class Conf def self.cattr_accessor_with_default(name, value = nil) cattr_accessor name self.send("#{name}=", value) if value end cattr_accessor_with_default :awesome_level, 'huge' cattr_accessor_with_default :speed_level, 'insane' cattr_accessor_with_default :indifferent_level cattr_accessor_with_default :craziness_level, 'nuts' end end
This way you declare accessor and it’s optional default value on the same line
Keeping the flash object on multiple redirects
If your controllers are redirecting more than once, the flash contents will be lost. To avoid it, execute flash.keep before each redirection.
Check ActionController::Flash::FlashHash for more handy methods (discard, now, …)
Have the check_box checked by default
To have the check box checked by default, pass either :checked => true or :checked => 'checked' in the options. See ActionView::Helpers::InstanceTag#to_check_box_tag for details.
This is ON by default in :has_many
When defining a has_many relationship this behaviour is on by default. See has_many documentation, look for the :validate flag.
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.
ActiveRecord::RecordNotSaved can be triggered by accidental false return values in callbacks
You may have this exception raised if any of the defined callbacks such as ActiveRecord::Base#before_save or ActiveRecord::Base#before_create return false.
This can happen accidentally. For example:
class MyModel < ActiveRecord::Base before_save :assign_default_foo protected def assign_default_foo self.foo = false end end
Since assign_default_foo leaves a false value on the stack, the model will not be saved. A way around this is to simply leave nil or an empty return instead:
class MyModel < ActiveRecord::Base before_save :assign_default_foo protected def assign_default_foo self.foo = false nil end end
Writing and reading a cookie in the same request.
As of 0349278f3da9f7f532330cf295eed35ede3bae66 cookie updates will persist in the current request.
Tip: Define from_param(...) as Opposite
Often when defining a to_param method, it’s handy to introduce an opposite method for decoding them. For example:
class User < ActiveRecord::Base def self.from_param(param) find_by_name!(param) end def to_param name end end
While you can just as easily redefine the find() method, this may be confusing since the expectation is that find() works with numerical IDs, or whatever the key column is defined as.
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
Set :use_route to nil to let Rails pick the best route
Imagine the following case. You have two landing pages, one generic one, and an account specific one. The urls are as follows:
map.landing 'landing', :controller => 'landing', :action => 'index' map.account_landing 'accounts/:account_id/landing', :controller => 'landing', :action => 'index'
Now imagine you want a path to the landing page, using the most specific route possible. If you have an account_id, use it, if not, skip it.
You could do
url_for(:controller => 'landing', :action => 'index', :account_id => current_account)
If current_account is set you’ll get “/accounts/:account_id/landing” if not, you’ll get “/landing”. However, that just looks ugly.
Enter :use_route => nil.
landing_path(:account_id => nil) # => '/landing' landing_path(:account_id => 1) # => '/landing?account_id=1' landing_path(:account_id => nil, :use_route => nil) # => '/landing' landing_path(:account_id => 1, :use_route => nil) # => '/accounts/1/landing'
Setting :use_route to nil, is equivalent to the earlier #url_for example.
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.
Do not forget to add indexes
Don’t forget to add indexes to HATM table:
add_index :developers_projects, [:developer_id, :project_id]
strip_tags method not functioning in controllers, models, or libs
It comes up with an error about white_list_sanitizer undefined in the class you’re using it in. To get around this, use:
ActionController::Base.helpers.strip_tags('string')
To shorten this, add something like this in an initializer:
class String def strip_tags ActionController::Base.helpers.strip_tags(self) end end
then call it with:
'string'.strip_tags
sanitize method not functioning in controllers, models, or libs
It comes up with an error about white_list_sanitizer undefined in the class you’re using it in. To get around this, use:
ActionController::Base.helpers.sanitize('string')
To shorten this, add something like this in an initializer:
class String def sanitize ActionController::Base.helpers.sanitize(self) end end
then call it with:
'string'.sanitize
Corrected link to ActiveRecord's "new_record?" method
rafaelrosafu, the correct tink to ActiveRecord’s “new_record?” method is:
"http://apidock.com/rails/ActiveRecord/Base/new_record%3F"
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]}
Take care when writing regex
When you want to validate a field for a continuous string you’d probably write something like this (if it’s really early in the morning and you didn’t have your coffee yet):
validates_format_of :something => /\w/
At the first sight it looks like it’s working because something = “blahblahblah” is valid. However, so is this: something = “blah meh 55”. It’s just that your regex matched a substring of the value and not the whole thing. The proper regex you’re looking for is actually:
validates_format_of :something => /^\w$/
The docs are in AR::Base
The docs you’re looking for are in ActiveRecord::Base
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.
Override fieldWithErrors markup in Rails > v2
The code posted by @hosiawak will still work in recent versions of Rails, but maybe a more current, idiomatic way to do it is to stick this inside the Rails::Initializer block in environment.rb (obviously you’ll also need to restart your server to pick up the config change):
config.action_view.field_error_proc = Proc.new {|html_tag, instance| %(<span class="fieldWithErrors">#{html_tag}</span>)}
Usage
Here’s how to use it, just so it’s perfectly clear:
skip_before_filter :method_to_skip, :only => [:method_name]


