Flowdock

Notes posted to Ruby on Rails

RSS feed
August 14, 2008
6 thanks

Optional classes

This piece of syntax saves me allot of time. Note the if statement.

Code example

content_tag(:div, "Hello World", :class => ("active" if i_am_an_active_item?))
August 13, 2008 - (v2.1.0)
2 thanks

end_year

date_select supports end_year, too.

date_select("user", "birthday", :start_year => 1940, :end_year => Date.current.year - 13)
August 13, 2008
7 thanks

Update element after remote call

Not mentioned in the documentation, you can add :update option to the remote_form_for and pass the id of element you’d like to update after ajax action as you do with link_to_remote, for example:

<% remote_form_for "comment", :update => "form" } do |f| %>
  # your form here
<% end %>

Or

<% remote_form_for "comment", :update => {:success => "form", :failure => "errors"} do |f| %>
  # your form here
<% end %>
August 13, 2008
7 thanks

Re: Helper method taking a block

The same using the ActionView::Helpers::TagHelper#content_tag and ActionView::Helpers::CaptureHelper#capture methods:

def render_tree(collection, &block)
  concat(
    content_tag(:ul,
      collection.collect { |item|
        content_tag(:li, capture(item, &block))
      }.join("\n")
    ),
    block.binding
  )
end

The benefit is that it’s easier to improve with html attributes (just add a hash of options to the content_tag call) and it makes just one call to concat (which probably makes it faster).

August 13, 2008 - (v1.2.6 - v2.1.0)
3 thanks

Insertion/Deletion callbacks

All ActiveRecord associations except for has_many :through support callbacks for pre- and post-insertion/deletion via the following, self-documenting parameters:

Adding to an Association

:before_add

:after_add

Removing from an Association

:before_remove

:after_remove

The flexibility that these callbacks offer is quite handy, but I’ll demonstrate with a silly example: logging each insertion and deletion.

class Ship < ActiveRecord::Base

  has_many :pirates, 
    :after_add => :say_hello,
    :before_remove => :say_goodbye

  private

  def say_hello(pirate)
    STDOUT.write("hello #{pirate.name} ")
  end

  def say_goodbye(pirate)
    STDOUT.write("goodbye #{pirate.name} ")
  end

end

Now, we’ll see confirmation when we add/remove Pirates in the console from our ship (and yes, this must be the ghetto):

>> jolly_roger = PirateShip.new

=> #<Ship id: nil, created_at: nil, updated_at: nil>
>> jolly_roger.pirates << Pirate.create(:name => 'Black Bart')
hello Black Bart => [#<Pirate id: 1, name: "Black Bart", created_at: "2008-07-29 14:41:13", updated_at: "2008-08-11 11:51:25">
>> jolly_roger.pirates.first.delete
goodbye Black Bart => []
August 12, 2008
6 thanks

Helper method taking a block

Following the similar egzample by autonomous, here’s a simpler version when you just need to write a flexible helper method that takes a block.

For example, suppose you have a method that renders a tree:

def render_tree(ary, &block)
  concat("<ul>", block.binding)
  for elem in ary
    concat("<li>", block.binding)
    yield elem
    concat("</li>", block.binding)
  end
  concat("</ul>", block.binding)
end

You can use it in your view, eg:

<% render_tree(@objects) do |elem| -%>
  <%= elem.title -%>
  <%= link_to 'delete', elem -%>
<% end -%>

that would return for egzample:

<ul>
  <li>
    Test title
    <a href="delete">/elems/1</a>
  </li>
</ul>

Testing concat

To test such helper methods, use the following pattern (a utility method added to your Rspec/unit test suite:

def render_for(root, options = {})
  _erbout = ''
  render_tree(root, options) do |node|
    _erbout.concat(node.title)
  end
  _erbout
end

and test like this (RSpec example):

it "should return abc" do
  render_for(object).should == 'abc'
end
August 12, 2008
1 thank

Overview of all routes

To see all defined routes type in your console:

rake routes

This produces (eg.):

reorder_toolbox_items   PUT   /toolbox_items reord {:controller=>"toolbox_items", :action=>"reorder"}
channels   GET  /channels {:controller=>"channels", :action=>"index"}
...
etc.
August 12, 2008
1 thank

Overview of all routes

To see all defined routes type in your console:

rake routes

This produces (eg.):

reorder_toolbox_items   PUT   /toolbox_items reord {:controller=>"toolbox_items", :action=>"reorder"}
channels   GET  /channels {:controller=>"channels", :action=>"index"}
...
etc.
August 12, 2008 - (>= v2.1.0)
4 thanks

polymorphic_url and namespaces and nested resources

You can use polymorphic_url with namespaces and nested resources using array as parameter:

polymorphic_url([:admin, @post])

will return:

admin_post_url(@post)
August 12, 2008
12 thanks

Overriding the default div class="fieldWithErrors"

By default fields that are invalid are wrapped in:

<div class="fieldWithErrors">
  <input type="text" name="blah">
</div>

To override and wrap in spans instead of divs place the following in your environment.rb:

ActionView::Base.field_error_proc = Proc.new { |html_tag, instance| "<span class=\"fieldWithErrors\">#{html_tag}</span>" }

or to not use wrapping at all:

ActionView::Base.field_error_proc = Proc.new { |html_tag, instance| "#{html_tag}" }
August 12, 2008
9 thanks

Overriding the default div class="fieldWithErrors"

By default fields that are invalid are wrapped in:

<div class="fieldWithErrors">
  <input type="text" name="blah">
</div>

To override and wrap in spans instead of divs place the following in your environment.rb:

ActionView::Base.field_error_proc = Proc.new { |html_tag, instance| "<span class=\"fieldWithErrors\">#{html_tag}</span>" }

or to not use wrapping at all:

ActionView::Base.field_error_proc = Proc.new { |html_tag, instance| "#{html_tag}" }
August 11, 2008
10 thanks

RE: Using validates_format_of to validate URIs

Further to Olly’s note below, you can also specify the protocol to further limit the valid uri’s, else things like ‘ftp ://someurl.com’ (there’s only a space in there to get it to display on here) would be valid.

validates_format_of :uri, :with => URI.regexp(['http'])
August 11, 2008
8 thanks

Validations

update_attribute will not perform validations checks when the Validation module is included.

If you want to perform validations when updating, use update_attributes instead.

August 11, 2008 - (>= v2.1.0)
2 thanks

Examples

@articles = cache(‘articles’) do

Articles.latest 

end

Or:

@articles = cache([‘articles’, user.id], :expires_in => 15.minutes)

# Advanced Rails Recipies says: "expires_in option works only with memcached store"
Articles.latest

end

Also if you’d like to have short ‘cache’ call in your model you can have it - add the following method to the model:

def cache(key, options = {})

ActionController::Base.cache_store.fetch(key, options) { yield }

end

August 10, 2008
4 thanks

converts a Time object to a string in rfc822 / rfc2822 format

>> time = p.updated_at

> Fri Jun 20 14:05:30 +0100 2008

>> time.class

> Time

>> time.httpdate

> “Fri, 20 Jun 2008 13:05:30 GMT”

.

August 10, 2008 - (>= v2.0.0)
10 thanks

New way of calling partials on collections

You can directly call a partial on a collection of objects like this:

<%= render :partial => @users %>

This will call the partial _user.html.erb and populate a local variable ‘user’ within the partial. Then you can display the user partial in this way:

_user.html.erb:

Name: <%= user.name %> <br />
Email: <%= user.email %> <br />

The above render statement is equivalent to this code:

<% for user in @users %>
  <%= render :partial => 'user', :locals => { :user => user } %>
<% end %>
August 8, 2008
0 thanks

finder_sql

If you are using the finder_sql option, it is important to use single quotes if need to interpolate variables, such as the id of the record. Otherwise you will get the object_id of the class.

August 8, 2008
18 thanks

Using validates_format_of to validate URIs

You can use the validates_format_of with the regular expression in the URI library ensure valid URIs.

Example

require 'uri'

class Feed < ActiveRecord::Base
  validates_format_of :uri, :with => URI.regexp
end
August 8, 2008
2 thanks

X-Sendfile equivalent for Nginx

Nginx supports a similar http header to X-Sendfile called X-Accel-Redirect.

Set the X_SENDFILE_HEADER constant somewhere (eg in your environment.rb) file:

ActionController::Streaming::X_SENDFILE_HEADER = 'X-Accel-Redirect'  

Then you can use x_sendfile => true as usual.

More here: http://wiki.codemongers.com/NginxXSendfile

August 8, 2008
7 thanks

Avoiding duplicate results when you do a join

When you use the :joins option you can get multiple instances of the same item. For example, say you want every User who owns one or more Lists. With the code below, if a user owns 5 lists, the User will show up five times in the results:

users = User.find(:all, 
  :conditions => ['users.id = lists.user_id'], 
  :joins => [:lists], 
  :order => 'users.username')

You can cause each found user to appear only once by using the :select option with “DISTINCT”:

users = User.find(:all, 
  :conditions => ['users.id = lists.user_id'], 
  :joins => [:lists], 
  :select => 'DISTINCT users.*'
  :order => 'users.username')
August 7, 2008
12 thanks

Function to Determine Layout

Sometimes its nice to have different layouts choosen automagicly:

class ApplicationController < ActionController::Base
 layout :determine_layout
 def determine_layout
   if is_admin?
     "admin"
   else
     "application"
   end
...
end
August 7, 2008 - (>= v2.1.0)
6 thanks

around_filter - working example

More detailed, working example of usage:

class HomeController < ApplicationController   
  around_filter :action1, :action2

  around_filter do |controller, action|
    logger.info "code block before action"
    action.call
    logger.info "code block after action"
  end

  def index
    logger.info "ACTION"
  end       

  private # filters should not be available for external URL

  def action1            
    logger.info "ACTION1 before yield"
    yield "ACTION1" 
    logger.info "ACTION1 after yield"
  end

  def action2            
    logger.info "ACTION2 before yield"
    yield "ACTION2" 
    logger.info "ACTION2 after yield"
  end       
end

Results (in log file):

ACTION1 before yield
ACTION2 before yield
code block before action
ACTION
Rendering home/index
code block after action
ACTION2 after yield
ACTION1 after yield 
August 6, 2008
1 thank

not loaded if any gems missing at startup

similar to http://rails-doc.org/rails/Rails/Initializer/load_application_initializers#95-initializers

observers and custom initializers aren’t loaded if any gems are missing -

current behaviour isn’t great - as it’s not intuitive - and you get no warning.

here’s a lighthouse ticket i created a while ago about this.

http://rails.lighthouseapp.com/projects/8994/tickets/618-initializer-doesnt-load-observers-initializers-if-unable-to-load-a-gem#ticket-618-1

August 6, 2008 - (v2.1.0)
1 thank

RE: Using counters with collections

Note that as of Rails 2.1, it’s currently not possible to override the internal counter variable you get when using collections via passing it in through :locals.

This is a useful feature when you have a collection of items rendered but then wish to add another one - most likely via an AJAX request.

I’ve been informed it’s back in Edge, so hopefully it’ll re-appear again in Rails 2.2.

August 6, 2008 - (v2.0.0 - v2.1.0)
1 thank

RE: Cross browser issues

In response to subblue’s note below, you should bear in mind that there may be circumstances where you want an AJAX request to enter the format.html block and not format.js.

When you’re returning HTML content, for example.

By using jQuery’s .ajaxSetup method in such an indiscriminate way (applying it by default to all ajax requests), you make it harder to keep track of what’s returning what.

A better alternative is to use it as a stored function;

function set_content_type_to_javascript(xhr) {
 xhr.setRequestHeader("Accept", "text/javascript");
}

and call this from within your .ajax requests when required;

$.ajax({
 type: "GET",
 url: "/some/url",
 dataType: "script",
 beforeSend: function(xhr) { set_content_type_to_javascript(xhr) },etc…
});
August 5, 2008
17 thanks

Multipart form

Don’t forget to add :multipart => true if you have file upload in your form.

<% form_for "user", :html => { :multipart => true } do |f| %>
August 4, 2008
7 thanks

Example: find by associated table

Say you have tables “authors” and “books” and they have a one-to-many association.

You want authors who have written books with “cooking” in the title…

cookbook_authors = Author.find(:all, 
  :conditions => ['books.title LIKE ?', '%cooking%'], 
  :joins => [:books], 
  :order => 'authors.last_name' )

For many-to-many associations, it’s a similar pattern. Say you have tables “people” and “organizations” with a many-to-many association through the join table “organization_memberships”.

Ski Club members whose first name starts with “a”…

ski_club_members = Person.find(:all, 
  :conditions => ['first_name LIKE ? AND organizations.name = ?', 
    'a%', 'Ski Club'], 
  :joins => [:organizations], 
  :order => 'people.last_name' )
August 4, 2008
0 thanks

How to handle the MultiparameterAssignmentErrors exception

If you expect to get this error, do this in your controller:

def create
  @event = Event.new
  @event.attributes = params[:event]
  @event.save!
rescue ActiveRecord::MultiparameterAssignmentErrors => e
  e.errors.each do |error|
    @event.errors.add(error.attribute, ActiveRecord::Errors.default_error_messages[:invalid])
  end
rescue ActiveRecord::RecordInvalid
  # event is invalid
ensure
  if @event.errors.empty? and not @event.new_record?
    redirect_to event_path(@event)
  else
    render :action => :new
  end
end

If attribute assignment gives the MultiparameterAssignmentErrors exception we handle it by adding ‘invalid’ errors to the attributes involved. We also rescue the RecordInvalid exception and handle all the redirecting/rendering in the ensure block.

August 4, 2008
3 thanks

Detailed messages for a nested model

Detailed messages for a nested model

<%@address = @order.address%>
<%=error_messages_for :address%>
August 4, 2008 - (>= v2.1.0)
1 thank

You can turn off dirty objects

If you expierence problems with dirty objects you can turn it off:

ActiveRecord::Base.partial_updates = false