Flowdock

Notes posted to Ruby on Rails

RSS feed
April 30, 2009
1 thank

Caveat and design hints regarding :counter_cache

(From Obie Fernandez/ The Rails Way, ISBN 978-0321445612. Thanks Obie!)

This caveat:

The value of the counter cache column must be set to zero by default in the database! Otherwise the counter caching won’t work at all. It’s because the way that Rails implements the counter caching behavior is by adding a simple callback that goes directly to the database with an UPDATE command and increments the value of the counter.

And these tips:

If a significant percentage of your association collections will be empty at any given moment, you can optimize performance at the cost of some extra database storage by using counter caches liberally. The reason is that when the counter cache attribute is at zero, Rails won’t even try to query the database for the associated records!

If you’re not careful, and neglect to set a default value of 0 for the counter cache column on the database, or misspell the column name, the counter cache will still seem to work! There is a magic method on all classes with has_many associations called collection_count, just like the counter cache. It will return a correct count value if you don’t have a counter cache option set or the counter cache column value is null!

April 30, 2009
0 thanks

attachments and implicit multipart

There is a small gotcha - this caught me up for a while.

If you are using implicit multipart mime types by naming your template xxx.text.html.erb and xxx.text.plain.erb, you will need to change your template name back to the original xxx.erb.

If you use the implicit template name, your attachment will be the only thing in the body of the message - it will ignore your template.

See the “Multipart email” section of the ActionMailer.base documentation.

April 30, 2009
0 thanks

Video tutorial

If you want to get up to speed with Rails’ caching and haven’t seen it already, definitely check out this video series on Scaling Rails:

http://railslab.newrelic.com/scaling-rails

April 29, 2009 - (<= v2.3.2)
1 thank

Including instance methods to JSON output

Use :methods parameter to include ActiveRecord instance methods to JSON output. :only and :except uses DB columns only.

@events.to_json(:include => { 
                  :images => { 
                    :only => [], :methods => [:public_url] }})

In the previous example events have multiple images and only public_url instance method is included in the JSON output.

April 28, 2009
1 thank

Moved to ActiveSupport::Inflector

This isn’t gone, it’s just been moved to the ActiveSupport module namespace.

See: ActiveSupport::Inflector#pluralize

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)
5 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 27, 2009 - (>= v2.0.0)
0 thanks

has_one through belongs_to not working

code example:

class Company < ActiveRecord::Base
  has_many :route_lists
end

class RouteList < ActiveRecord::Base
  belongs_to :company
  has_many :routes
end

class Route < ActiveRecord::Base
  belongs_to :route_list
  has_one :company :through => :route_list
end

This creates an invalid SQL query, where the keys in the join between route and routelist are switched, when used as an include:

Routes.find :all, :conditions => ["companies.type = ?", "Account"], :include => :company

route_lists.route_list_id = route.id

instead of: route_lists.id = route.route_list_id

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 24, 2009
2 thanks

have your to_param begin with the object's id

If you overwrite the to_param method in your model class such that it does not begin with its id, you can be in for a nasty surprise:

Example

class User
  def to_param
    self.login
  end
  ...
end

Let’s say you have a user called “bob”, than you might think this works:

>> bob = User.find(3)
=> #<User id: 3, login: "bob", ...>
>> User.find(bob.to_param)
ActiveRecord::RecordNotFound: Couldn't find User with ID=bob

But it’s not the reason being that Rails find method looks for a beginning number (d+) and uses that to look up the record (and ignores everything that comes after the last digit). So the solution is to have your to_param return something that begins with the object’s id, like so:

Example

class User
  def to_param
    "#{self.id}-#{self.login}"
  end
  ...
end

>> bob = User.find(3)
=> #<User id: 3, login: "bob", ...>

>> User.find(bob.to_param)

> # id: 3, login: “bob”, …>

>> bob.to_param

> “3-bob”

April 23, 2009
1 thank

Using strings as association names

Beware, that using strings as association names, when giving Hash to :include will render errors:

The error occurred while evaluating nil.name

So, :include => [‘assoc1’, ‘assoc2’ ] will work, and :include => [ {‘assoc1’ => ‘assoc3’}, ‘assoc2’] won’t. Use symbols:

Proper form

:include => [ {:assoc1 => :assoc3}, ‘assoc2’]

April 23, 2009 - (v2.0.0 - v2.3.2)
0 thanks

Real HTML_ESCAPE VALUE

Real value:

HTML_ESCAPE = { '&' => '&',  '>' => '>',   '<' => '<', '"' => '"' }
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
1 thank

Merges with inherited values from super class

http://www.spacevatican.org/2008/8/19/fun-with-class-variables

“When you set a class_inheritable_array or a class_inheritable_hash you are actually concatenating (or merging) with the value inherited from the super class.”

Code example

class Base
  class_inheritable_hash :attrs
  self.attrs = {:name => 'Fred'}
end

class Derived < Base
  self.attrs = {:export => 'Pain'}
end

Derived.attrs #=> {:name => 'Fred', :export => 'Pain'}
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 20, 2009
2 thanks

Cycle with first and last

I needed a cycle that was also aware of the first and last items in the collection. This is adapted from a snippet I found while Googling:

def cycle_with_first_last(object, collection, options = { })
  addition = ""
  addition += " #{options[:first]}" if object == collection.first
  addition += " #{options[:last]}"if object == collection.last
  cycle(options[:odd], options[:even]) + addition
end

Just put that in your helpers…

April 16, 2009 - (>= v2.0.0)
2 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 15, 2009 - (>= v2.3.2)
0 thanks

Testing HTTP Digest authentication

Testing HTTP Digest authentication is a bit tricky. I wrote a post describing how to accomplish it.

http://lightyearsoftware.com/blog/2009/04/testing-http-digest-authentication-in-rails/

Note also that Digest auth is broken for REST actions using PUT or DELETE. There is an open Lighthouse ticket for this, #2490:

rails.lighthouseapp.com/projects/8994/tickets/2490-http-digest-auth-uses-wrong-request-method-for-put-delete

April 15, 2009
0 thanks

Using global $! to address exception

@noxyu3m: Your code is actually syntactically wrong. The global is called $!

Your code should have been:

def create
  @model = Model.new(params[:model)
  @model.save!
rescue
  logger.error($!.to_s)
end

Although I would prefer

def create
  @model = Model.new(params[:model)
  @model.save!
rescue ActiveRecord::RecordInvalid
  logger.error($!.to_s)
end

to only catch expected exceptions, just like the documentation proposed.

April 14, 2009 - (v2.2.1 - v2.3.2)
2 thanks
April 14, 2009
0 thanks

Re: Force initial value

An alternative to @eric_programmer’s would be to extract it entirely from the controller logic…

class Person
  def sender
    self[:sender] || 'contact@host.com'
  end
end

There are a ton of ways to do it, but making this a business logic decision will let you get the same logic from any possible implementation angle. Scripts, web service, etc.

April 12, 2009
1 thank

To find a tag with an id or class

assert_select ‘td.highlight’, { :count => 2 }

finds 2 td tags with the highlight class.

assert_select ‘div#special’

finds div with id=special

April 12, 2009
0 thanks

Takes attribute as a symbol

Attribute must be passed as a symbol:

User.toggle(:funny)

not

User.toggle(funny)
April 9, 2009
0 thanks

Deprecation warning for old-style options

You will get a warning if you don’t define your separators as a hash:

DEPRECATION WARNING: number_with_delimiter takes an option hash instead of separate delimiter and precision arguments

So while you can still use that style, it’s not without a scolding.

April 9, 2009
1 thank

Outside of app code

How do I call this from script/console?