Sunday, June 05, 2011

Tips from Rails Anti-Patterns

Another good Ruby book is out, Rails Anti-Patterns. The book is loaded with good tips on everything from following the Law of Demeter to cleaning up your views with the use of helper methods.

Here are some things I picked up from the book.

delegate can take a :prefix argument

The delegate method from active_support is used for delegating calls to another object without having to write out the full delegating methods. It can take a prefix option to customize the name of the delegating methods.

class Address
  @attr_accessors :street, :zip
end

class Person
  attr_reader :address
  # prefix => true, results in the model name being used as prefix
  delegate :street, :zip, :to => :address, :prefix => true
  #@person.address_street, @person.address_zip
end

class Person
  attr_reader :billing_address, :delivery_address
  # prefix => string, uses the string as prefix
  delegate :street, :zip, :to => :address, :prefix => delivery
  #@person.delivery_street, @person.delivery_zip
  delegate :street, :zip, :to => :address, :prefix => billing
  #@person.billing_street, @person.billing_zip
end

Transaction Scope

The code executed in the ActiveRecord callbacks execute in the same transaction as the actual call to save, create, update, or delete. Knowing this helps to eliminate unneccessary explicit transactions.

# Using a before filter
class Drink
  before_create :remove_ingredients_from_bar

  def remove_ingredients
    ingredients.each do |ingredient|
      Bar.remove(ingredient)
    end
  end
end

# Is better than using an explicit transaction
class Drink
  def create_drink
    transaction do
      remove_ingredients
      create
    end
  end

  def remove_ingredients
    ingredients.each do |ingredient|
      Bar.remove(ingredient)
    end
  end
end

Association Methods

It is possible to add methods directly on the activerecord associations. This is especially handy if the method uses information from both sides of the relation.

class Drink
  #has_field :minimum_drinking_age
end

class Customer
  #has_field :age

  has_many :drinks do
    def allowed
      # proxy_owner is the object defining the relation, Customer
      where(['minimum_drinking_age < ?', proxy_owner.age])
    end
  end
end

When to make a model active

If there is no user interface for adding, removing, or managing data, there is no need for an active model. A denormalized column populated by a hash or array of possible values is fine.

This is really just the application of the KISS principle, Keep It Simple Stupid, but I have never seen it as clearly described before reading this book.

Haml []

A nice feature of Haml that I didn’t know about, is the [] operator. When given an object, such as [record], [] acts as a combination of div_for and content_for, outputting a tag with the id and class attributes set appropriately.

-# This Haml
%span[@team]
<!-- Results in this HTML -->
<span class='team' id='team_1'></span>

RESTful actions

When using resources in Rails there are seven methods that are used.

index, create, show, update, delete, edit, and new. The first five naturally map to get(collection), post(collection), get(singular), put(singular), and delete(singular), but what isn’t as obvious is that.

The new and edit actions are really just different ways of representing the show action.

This is of course obvious when you think about it, but once again Chad and Tammer has written it down in plain simple English.

Rake Tasks

How should you treat your application specific Rake tasks on order to test them easily. Once again the solution is very simple.

Write the domain specific code as a class method on the appropriate model associated with the task.

Then all you have to do is call the method from the task.

task :fill_bar_with_ingredients do
  Bar.fill_with_ingredients
end

It is not always appropriate to add this functionality to the existing models. This is a clue that another model is needed in your domain.

task :fill_bar_with_ingredients do
  LiquorStore.order_ingredients
end

Database Index

Since most applications have far more reads than writes, you should add indexes to every field that appears in a WHERE clause or an ORDER clause. You should also add indexes for every combination of fields that are used that are combined with AND.

As always, don’t follow this advice blindely, if a table only has three rows then perhaps the index is overkill…

Conclusion

Apart from these tips, there are tons of other useful information, making this book a must-read if you are doing Rails development.

2 comments:

Abel said...

Thanks for your review. It's really handy :)

Anders Janmyr said...

@Abel, I'm glad you liked it.