Flowdock

Good notes posted to Ruby on Rails

RSS feed
July 24, 2008
3 thanks

render_collection

You can wrap render in helpers. For example, render_collection. In app/helpers/application.rb:

module ApplicationHelper
  def render_collection(name, collection)
    render :partial => "shared/#{name}", :collection => collection
  end
end

In views:

<h2>Comments</h2>
<%= render_collection :comments, @photo.comments %>
July 24, 2008
8 thanks

render template file different from your action (method) name

In some cases you have to avoid rails magic that uses template names named as your ActionMailer method.

rails magic

def daily_notification
  # ...
end
# will look for daily_notification.erb

def weekly_notification
  # ...
end
# will look for weekly_notification.erb

your case

Just give necessary value to @template instance variable.

def setup
  # ...
  @template = 'notification'
end

def daily_notification
  # ...
end
# will look for notification.erb

def weekly_notification
  # ...
end
# will look for notification.erb
July 24, 2008 - (v2.1.0)
20 thanks

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

July 23, 2008
3 thanks

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.

July 23, 2008 - (v1.2.0 - v2.1.0)
10 thanks

: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
July 23, 2008
4 thanks

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.

July 23, 2008
5 thanks

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.

July 23, 2008
11 thanks

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"]
July 23, 2008 - (v2.0.0 - v2.1.0)
9 thanks

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
July 23, 2008 - (v2.0.0 - v2.1.0)
9 thanks

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})
July 23, 2008
6 thanks

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
July 22, 2008
4 thanks

Example

In your migration:

def self.up
  add_column :accounts, :is_admin, :boolean, :default => 0
end
July 22, 2008
4 thanks

:conditions examples

:conditions => {:login => login, :password => password}

:conditions => [‘subject LIKE :foo OR body LIKE :foo’, {:foo => ‘woah’}]

(from the book “The Rails Way”)

July 22, 2008 - (>= v2.1.0)
11 thanks

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
July 22, 2008
18 thanks

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>
July 22, 2008
19 thanks

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]
July 22, 2008
6 thanks

selected

select :languages, :language, [‘en’, ‘de’, ‘fr’], {:selected=> ‘en’}

July 22, 2008
15 thanks

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}))
July 22, 2008
7 thanks

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.

July 22, 2008 - (v2.1.0)
4 thanks

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
July 22, 2008 - (v1.0.0 - v2.1.0)
4 thanks

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
July 21, 2008 - (v2.0.3 - v2.1.0)
10 thanks

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.

July 21, 2008
14 thanks

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>
July 20, 2008
10 thanks

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
July 17, 2008
7 thanks

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.

July 15, 2008
9 thanks

Required Reading

The details for using layout (such as possible values for the conditions hash) can be found in ActionController::Layout::ClassMethods.

July 13, 2008
5 thanks

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
July 8, 2008
4 thanks
July 8, 2008
3 thanks

For more information

See ActiveMailer::Base.

July 7, 2008
4 thanks

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