Good notes posted to Ruby on Rails
RSS feedautomatically generate scopes for model states
or better known as “throw on some more tasty meta-programming” :). Given an example of a model which has a state (String) which must from a set of defined values, e.g. pending, approved, denied.
class User < ActiveRecord::Base STATES = [ 'pending', 'approved', 'denied' ] validates_inclusion_of :state, :in => STATES # Define a named scope for each state in STATES STATES.each { |s| named_scope s, :conditions => { :state => s } } end
This automatically defines a named_scope for each of the model states without having to define a named_scope manually for each state (nice and DRY).
options_for_select further example (using a collection and with a default value)
In this example, we are editing a collection of region records, each with its own select list of countries. (Region belongs_to :country.) If the region doesn’t have a country associated, then we want a default message of “unassigned”. Of course, if the region does have a country associated then we want that country displayed:
<% name = "region[" + region.id.to_s + "][country_id]" %> <% id = "region_" + region.id.to_s %> <%= select_tag(id, options_for_select([["unassigned" , "0" ]] + Country.to_dropdown, region.country_id),
{:name => name} ) %> This give us:
<select id="region_3" name="region[3][country_id]"> <option value="0">unassigned</option> <option selected="selected" value="12">England</option> </select>
NB: we’re using the handy acts_as_dropdown plugin (http://delynnberry.com/projects/acts-as-dropdown/) but we could just as easily prepare the select list with map / collect as above.
:only, :except and passing in multiple parameters
To specify that the filter should be applied to or excluded from given controller actions, use the :only and :except parameters. To pass in multiple controller actions use an array:
before_filter :authorize, :except => [:index, :show] before_filter :authorize, :only => :delete
Keep your controllers clear
When you use redirect_to or render with flash[:notice] or flash[:error], you can define some helper methods in your ApplicationController (or somewhere you want):
class ApplicationController < ActionController::Base protected %w(notice error).each do |message| class_eval <<-END_EVAL def redirect_#{message}(url, message) flash[:#{message}] = message redirect_to url end def render_#{message}(action, message) flash[:#{message}] = message render :action => action end END_EVAL end end
Now you have four methods - redirect_notice, redirect_error, render_notice and render_error.
Custom annotation types
For group work you may need something more than FIXME, OPTIMIZE and TODO. Just create new rake file and place it to lib/tasks:
require 'source_annotation_extractor' task :notes do SourceAnnotationExtractor.enumerate "WTF|OMG", :tag => true end namespace :notes do desc "Enumerate all WTF annotations" task :wtf do SourceAnnotationExtractor.enumerate "WTF" end desc "Enumerate all OMG annotations" task :omg do SourceAnnotationExtractor.enumerate "OMG" end end
or create an array of new types and generate tasks dynamicaly.
Pass id collections with check box tags
It can be useful to pass a collection of ids to a controller, especially in the case of a has_many relationship, for example:
User has_many Roles
In your view you could have something like:
<ul> <% @roles.each do |role| %> <li> <%= check_box_tag 'role_ids[]', role.id -%> <%= h role.name -%> </li> <% end %> </ul>
Note the square brackets after role_ids - this is important for passing a collection through to the controller.
If you place this in a form and submit it, you can expect to see a param passed into the controller that looks like:
"role_ids"=>["1", "2", "3"]
Easy and effective admin authentication
Great for use within an AdminController (in which all other administrative controllers inherit from AdminController).
class AdminController < ApplicationController before_filter :authenticate def authenticate authenticate_or_request_with_http_basic('Administration') do |username, password| username == 'admin' && password == 'password' end end end
perform update_all scoped within a has_many collection
For example: having two models, User and Message (user has_many messages, each message has a boolean flag called ‘read’). You want to mark all messages as read for a particular user.
Mark all messages as read for a particular user
Message.update_all({:read => true}, {:id => user.messages})
Loading fixtures in migrations
This helper is wrapper around Fixtures#create_fixtures and just load fixtures from specified directory (db/migrate/data by default):
class ActiveRecord::Migration def self.load_data(filename, dir = 'db/migrate/data') Fixtures.create_fixtures(File.join(RAILS_ROOT, dir), filename) end end
It is usefull for tables with data like country list:
class CreateCountries < ActiveRecord::Migration def self.up create_table :countries do |t| t.string :name, :code, :null => false t.timestamps end load_data :countries end def self.down drop_table :countries end end
:conditions examples
:conditions => {:login => login, :password => password}
:conditions => [‘subject LIKE :foo OR body LIKE :foo’, {:foo => ‘woah’}]
(from the book “The Rails Way”)
Migration helpers
You can add your own migration helpers as references:
Code example
class ActiveRecord::ConnectionsAdapters::TableDefinition def counter_caches(*args) args.each { |col| column("#{col}_count", :integer, :default => 0) } end end class CreateUsers < ActiveRecord::Migration def self.up create_table :users do |t| t.string :first_name, :last_name, :email t.counter_caches :photos, :messages t.timestamps end end def self.down drop_table :users end end
select_tag with options_for_select example
An example of using options_for_select with select_tag
select_tag 'user_id', options_for_select(@users.collect{ |u| [u.name, u.id] })
This would generate something like:
<select id="user_id" name="user_id"> <option value="1">Brad</option> <option value="2">Angie</option> <option value="3">Jenny</option> </select>
Nested resources in form_for
If you like doing things RESTfully and have a model relationship like:
Post_ has many Comments_
Then you can construct a form_for within your view to mirror this relationship when creating comments:
form_for [@post, @comment] do |f| ... end
You also need to make sure your routes reflect this relationship:
map.resources :post, :has_many => [:comments]
Demo: select onchange invoke an ajax
select(“order”, “customer_id”, o.customer.collect {|c| [ c.label, c.id ] },
{:include_blank => true, :selected => o.customer_id }, :onchange => remote_function(:update => "message_id", :method => "put", :with => "'item=' + value", :url => { :controller => :orders, :action => :set_customer_id, :id => order.id}))
Documentation
Allows for reverse merging where its the keys in the calling hash that wins over those in the other_hash. This is particularly useful for initializing an incoming option hash with default values:
def setup(options = {}) options.reverse_merge! :size => 25, :velocity => 10 end
The default :size and :velocity is only set if the options passed in doesn‘t already have those keys set.
Rails 2.1 migrations
Things to take note of are the lack of ‘column spam’, which didn’t convey much semantic meaning. Also the combination of multiple fields per line with the same type.
references is also a nice helper to convey relationship information (t.references :role is equivilant to t.integer :role_id). references also takes another parameters, see the method for more details.
code
class CreateUsers < ActiveRecord::Migration def self.up create_table :users do |t| t.string :first_name, :last_name, :email t.text :address t.date :date_of_birth t.references :role t.timestamps end add_index :users, :email end def self.down drop_table :users end end
Better slug generation (essentially a to_param replacement)
Monkey Patching String
class String def slugify returning self.downcase.gsub(/'/, '').gsub(/[^a-z0-9]+/, '-') do |slug| slug.chop! if slug.last == '-' end end end
In a model, or wherever
def to_param # Don't need the id here if we're looking up the model by the stored slug. "#{id} #{title}".slugify end
Is it really deprecated?
I think the deprecation notice is in the wrong place, it is actually the instance method ActiveRecord::Validations#validate that has been deprecated.
The same applies to ActiveRecord::Validations#validate_on_create and ActiveRecord::Validations#validate_on_update: they have both been deprecated in favour of the class methods validate_on_create and validate_on_update.
helper method to partial
concat can be useful for rendering a block to a partial from a helper:
def block_to_partial(partial_name, options = {}, &block) options.merge!(:body => capture(&block)) concat(render(:partial => partial_name, :locals => options), block.binding) end
This would be particularly useful if you had some partial to help you out with rounded corners, for example. So, in your helper:
def rounded_corners &block block_to_partial("shared/rounded_corners", {}, &block) end
In your view you could have something like:
<% rounded_corners do -%> This text is surrounded by rounded corners <% end -%>
You would have to create some partial in
app/views/shared/rounded_corners.html.erb
And it would look something like:
<div class='c1'> <div class=c2> . . . <%= body -%> </div> </div>
Generate an observer
Generating an observer from the command line follows the usual pattern:
script/generate observer audit
This will create a model called:
app/models/audit_observer.rb
Where you declare before_destroy matters
Beware that where you declare a before_destroy callback matters if you have any “acts_as” type declarations, that also declare before_destroy callbacks. A good example is the acts_as_nested_set. If you specify acts_as_nested_set prior to your before_destroy, then acts_as_nested_set will go through and destroy all children first, then your callback will run (at which point children will be empty). Thus, if your before_destroy callback needs to examine the children, it will not work properly. Putting your before_destroy callback ahead of acts_as_nested_set or anything else that declares a before_destroy is key in such cases.
Required Reading
The details for using layout (such as possible values for the conditions hash) can be found in ActionController::Layout::ClassMethods.
Convert strings to Dates
Uses the undocumented Date._parse method. Some usage examples:
'06/15/2008'.to_date # => Sun, 15 Jun 2008 '20080615'.to_date # => Sun, 15 Jun 2008 '2008-06-15'.to_date # => Sun, 15 Jun 2008 'Sun, 15 Jun 2008'.to_date # => Sun, 15 Jun 2008
For more information
See Base class.
For more information
See ActiveMailer::Base.
List of statuses
You can view a full list of statuses at http://dev.rubyonrails.org/browser/trunk/actionpack/lib/action_controller/status_codes.rb.
head can be called with a symbol or a status code:
Using head with a symbol
head :length_required # 411 Length Required head :ok # 200 OK
Using head with a status code
head 404 # 404 Not Found
Using counters with collections
When you’re rendering a collection partial, the partial_name_counter variable contains the position of the current element in the collection. For example:
<%= render(:partial => 'example', :collection => %w(rails-doc is cool)) %>
Now in _example.html.erb:
<p>Element: <%= example %> (index: <%= example_counter %>)</p>
It would produce:
<p>Element: rails-doc (index: 1)</p> <p>Element: is (index: 2)</p> <p>Element: cool (index: 3)</p>
As you can see, indexing starts from 1.
Link to same URL with different format
Use params.merge as options. Ex.
<%= link_to "RSS feed", params.merge(:format => :rss), :class => "feed_link" %>