Rails Best Practices 4: Put method in the right model

In today’s post I will show some optimization we can do for models. I’ll focus on how to put methods inside the right model and delegation to get a better code.

1. Put method in the right model

In our example, suppose we want to represent the animal world by creating a model Kind that represents types of animal and an Animal models representing animals.
For each type (quadrupedal, bipedal, bird) there are different animals that justify the has_many: animals relation defined in the following code.

1
2
3
4
5
6
7
8
9
10
11
12
13

class Kind < ActiveRecord::Base
has_many :animals

def find_herbivores
self.animal.find(:all,
:conditions => { :eat_herb => true })
end

end

class Animal < ActiveRecord::Base
belongs_to :kind
end

Next, we define a method “herbivores” within the AnimalsController that, given a type of animal return all the herbivorous contained in that type.

1
2
3
4
5
6

class AnimalsController < ApplicationController
def herbivores
@kind = Kind.find(params[ :id])
@animals = @kind.find_herbivores
end
end

The flaw in this code is to have defined the method find_herbivores inside the Kind model when in fact refers to a property of the Animal class.

Let’s see how to rewrite it in proper way, using a named_scope in the Animal model.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

class Kind < ActiveRecord::Base
has_many :animals
end

class Animal < ActiveRecord::Base
belongs_to :kind
named_scope :herbivores,
:conditions => { :eat_herb => true }
end

class AnimalsController < ApplicationController
def herbivores
@kind = Kind.find(params[ :id])
@animals = @kind.animals.herbivores
end
end

2. Delegate

In this second example, suppose we have a Location class associated with an owner and want to show owner’s data in the Location’s view.
A first simple implementation is the following:

1
2
3

class Location < ActiveRecord::Base
belongs_to :owner
end

And in the view:

1
2
3
4

<%= @location.owner.name %>
<%= @location.owner.city %>
<%= @location.owner.street %>
<%= @location.owner.email %>

This type of implementation is very common in Rails applications, but you can make it more elegant by using the construct “delegate” as follows:

1
2
3
4
5

class Location < ActiveRecord::Base
belongs_to :owner
delegate :name, :city :street, :email :to => :owner,
:prefix => true
end

Now we can rewrite the view as follows:

1
2
3
4

<%= @location.owner_name %>
<%= @location.owner_city %>
<%= @location.owner_street %>
<%= @location.owner_email %>

The result does not change, but the code is certainly more elegant.