Good notes posted to Ruby on Rails
RSS feeddefault_scope on create
If you specify :conditions in your default_scope in form of a Hash, they will also be applied as default values for newly created objects. Example:
class Article default_scope :conditions => {:published => true} end Article.new.published? # => true
However:
class Article default_scope :conditions => 'published = 1' end Article.new.published? # => false
Easy workaround for missing :through option
Note that belongs_to does not support :through option like has_many (although IMHO it would make sense in some cases), but you can easily simulate it with delegate.
For example:
class Person < ActiveRecord::Base belongs_to :team ... end class Task < ActiveRecord::Base belongs_to :person delegate :team, :to => :person end
There is of course more ways to do it, but this seems to be the easiest to me.
Extract the aggregated scoping options
If you want to get the aggregated scoping options of a chain of named scopes use ActiveRecord::Base.current_scoped_methods
It works in the fashion of:
Shirt.red.medium.alphabetical.current_scoped_methods # ==> { :create => {}, :find => { :conditions => {:color => 'red', :size => 'medium'}, :order => 'shirts.name ASC' } }
reload equivalent for models
The reset_column_information method provides a similar function for the model itself. Most useful during migrations.
Paying attention to query parameters
Standard action caching ignores query parameters, which means you’d get the same results for a URL with and without query parameters if it was action cached. You can make it pay attention to them by using a custom cache path like so:
caches_action :my_action, :cache_path => Proc.new { |c| c.params }
Or, maybe you want some of the query parameters, but not all to factor into different versions of that action’s cache:
:cache_path => Proc.new { |c| c.params.delete_if { |k,v| k.starts_with?('utm_') } }
Beware of things like pagination if you use expires_in to expire the cache, as pages could get out of sync.
Using html text instead of default response
If you have a string containing html and want to assert_select against it, as the doc states you have to pass in an element (HTML::Node) as the first argument. You can do something like this:
doc = HTML::Document.new('<p><span>example</span></p>') assert_select doc.root, 'span'
Reverse version of camelize
Reverse version of camelize is underscore
Doesn't return nil if the object you try from isn't nil.
Note that this doesn’t prevent a NoMethodError if you attempt to call a method that doesn’t exist on a valid object.
a = Article.new a.try(:author) #=> #<Author ...> nil.try(:doesnt_exist) #=> nil a.try(:doesnt_exist) #=> NoMethodError: undefined method `doesnt_exist' for #<Article:0x106c7d5d8>
This is on Ruby 1.8.7 patchlevel 174
Version Ranges
To specify a version range, use array syntax like this:
config.gem 'paperclip', :version => ['>= 2.3.1.1', '< 3.0']
The example will, of course, match any version 2.3.1.1 or newer up until (not including) 3.0 or later.
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}%"])
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.
How FormBuilders work
What, you were expecting documentation? :)
An excellent survey of how FormBuilders work is here:
http://code.alexreisner.com/articles/form-builders-in-rails.html
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.
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.
form_tag with named route and html class
<% form_tag position_user_card_path(@user, card), :method => :put, :class => ‘position-form’ do %>
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', ':') %>
Will discard any order option
order_by(:created_at).find_each == FAIL!!!
class ActiveRecord::Base # normal find_each does not use given order but uses id asc def self.find_each_with_order(options={}) raise "offset is not yet supported" if options[:offset] page = 1 limit = options[:limit] || 1000 loop do offset = (page-1) * limit batch = find(:all, options.merge(:limit=>limit, :offset=>offset)) page += 1 batch.each{|x| yield x } break if batch.size < limit end end end
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.
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],
Documentation bug
When adding the :target option, the documentation states that you should user :href_options like so:
auto_link(post_body, :href_options => { :target => '_blank' })
However, I could only get it to work using :html instead:
auto_link(post_body, :html => { :target => '_blank' })
I’m using Rails 2.2.2, but I believe that this also happens for more recent version .
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
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.
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
%% - Literal “%” character
Options
Available options are (none of these exists by default):
* :limit - Requests a maximum column length. This is number of characters for :string and :text columns and number of bytes for :binary and :integer columns. * :default - The column‘s default value. Use nil for NULL. * :null - Allows or disallows NULL values in the column. This option could have been named :null_allowed. * :precision - Specifies the precision for a :decimal column. * :scale - Specifies the scale for a :decimal column.
Be careful with name of attribute writer
If restricting access to attributes you normally get code like
attr_accessible :foo,
When using these nested attributes you end up with code like
attr_accessible :foo, :bar_attributes
Its very easy to leave of the _attributes suffix e.g
attr_accessible :foo, :bar
which will cause you all sorts of problems
Optional local assigns
When you have a partial with optional local assigns, for instance:
<%= render :partial => 'articles/preview' %> <%= render :partial => 'articles/preview', :locals => { :show_call_out => true } %>
And you don’t want the partial to break when the local isn’t assigned, you can reference it through the local_assigns local variable instead of through the template binding:
<% if local_assigns[:show_call_out] %> <em><%= format @article.call_out %></em> <% end %>
Not really deprecated
This isn’t really deprecated, it’s just relocated to ActiveRecord::AttributeMethods#read_attribute


