Flowdock

Recent good notes

RSS feed
June 17, 2009
3 thanks

Skipping validation

Unlike the save method, you can’t pass false to update_attributes to tell it to skip validation. Should you wish to do this (consider carefully if this is wise) update the attributes explicitly then call save and pass false:

@model_name.attributes = params[:model_name]
@model_name.save false
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
8 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
7 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
May 13, 2009
3 thanks

Equivalent to Array#reject!

This method is functionally identical to Array#reject!

May 2, 2009
8 thanks

Create a Hash from two Arrays

Here is my favorite idiom for creating a Hash from an Array of keys and an Array of values:

keys = [:a, :b]
values = [1,2]
h = Hash[*keys.zip(values).flatten]      # => {:b=>2, :a=>1}
May 2, 2009
3 thanks

Test if one array includes the elements of another

You can just use a set difference (aka minus) to see if one array includes all elements of another

not_included = [1,2,3] - (1..9).to_a
not_included      # => []

not_included = [1,2,3,'A'] - (1..9).to_a
not_included      # => ["A"]

Use intersection to test if any of the one are in the other:

shared = [1,2,3,'A'] & (1..9).to_a
shared     # => [1, 2, 3]
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)
4 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 23, 2009
5 thanks
April 23, 2009
6 thanks

Argument Ordering

Be aware that the order of arguments for this method is the opposite of File.join:

File.expand_path('foo', '/bar')   # => "/bar/foo"
File.join('foo', '/bar')          # => "foo/bar"
April 23, 2009
5 thanks

Handy shorthand for array manipulation

You may write something like this:

>> ['a', 'b', 'c'].collect{|letter| letter.capitalize}
=> ["A", "B", "C"]

But it looks so much nicer this way:

>> ['a', 'b', 'c'].collect(&:capitalize)
=> ["A", "B", "C"]
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
3 thanks

To throw an exception, use Kernel#raise

Other languages use the term throw for raising exceptions, but Ruby has a specific raise call for that.

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
12 thanks

Parameters for Hash#inject

When running inject on a Hash, the hash is first converted to an array before being passed through.

The typical Enumerable#inject approach would be to simply capture the value:

array.inject(...) do |c, v|
end

In the case of a Hash, v is actually a key/value pair Array. That is the key is v.first and the value is v.last, however using the pair this way is awkward and can lead to confusion.

Better to simply expand the parameters in the block definition:

hash.inject(...) do |c, (k, v)|
end

Where c is the traditional carry variable and k/v represent key and value respectively.

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.