Flowdock

Good notes posted to Ruby on Rails

RSS feed
June 12, 2009
3 thanks

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

June 11, 2009
4 thanks

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, …)

June 10, 2009 - (v1.0.0 - v2.3.2)
5 thanks

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.

June 8, 2009 - (v2.2.1 - v2.3.2)
5 thanks

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.

June 6, 2009
9 thanks

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

June 4, 2009
5 thanks

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.

June 3, 2009
8 thanks

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
April 28, 2009
3 thanks

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.

April 28, 2009 - (>= v2.3.2)
6 thanks

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

April 25, 2009
4 thanks

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.

April 21, 2009
3 thanks

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.

April 21, 2009
7 thanks

Do not forget to add indexes

Don’t forget to add indexes to HATM table:

add_index :developers_projects, [:developer_id, :project_id]
April 21, 2009 - (>= v2.3.2)
5 thanks

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
April 21, 2009 - (>= v2.3.2)
3 thanks

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
April 16, 2009 - (>= v2.0.0)
3 thanks

Example

This function can be used to pass the ID of selected item, for example:

# with select or collection_select helpers:
{ :onchange => remote_function(:url => { :action => 'do_smth' }, :with => "'id=' + $('the_id').value") }

# and grab ID in controller action as usually: 
YourModel.find(params[:id])
April 16, 2009
3 thanks

Various use cases

Example

user = User.new
user.name = 'Akhil Bansal'
user.save

user =  User.new(:name => 'Akhil')
user.save

User.new do |u|
  u.name = 'Akhil'
  u.save
end
April 9, 2009
11 thanks

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”.

April 9, 2009
4 thanks

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

April 8, 2009 - (>= v2.3.2)
13 thanks

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.

April 8, 2009 - (v2.3.2)
6 thanks

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.

April 6, 2009
5 thanks

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.

April 6, 2009
6 thanks

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]}
April 6, 2009
3 thanks

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$/
April 6, 2009
3 thanks

Assets hosts

You can also setup assets hosts in enviroments:

config.action_controller.asset_host = "http://your-assets-server.com"
April 3, 2009
12 thanks

The docs are in AR::Base

The docs you’re looking for are in ActiveRecord::Base

April 1, 2009
6 thanks

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.