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.

Tags: , , ,


About Claudio

Claudio Marai is a co-founder of DevInterface.

After graduating in Computer Science has contributed to develop complex web applications based on Java/J2EE and desktop applications with the. NET framework for the Ministry of Justice and ultimately for the banking ambit.

The passion for web in recent years has led him to be interested in more modern frameworks such as Ruby on Rails and Django, and to a development approach based on agile methodologies such as eXtreme Programming and SCRUM.

About DevInterface

We are an information and communication technology agency. Our mission is to provide web application development, design services and communication strategies. We specialize in building web applications with modern and efficient frameworks.

Related Post

15 Responses to “Rails Best Practices 4: Put method in the right model”

  1. [...] This post was mentioned on Twitter by William Yánez, DevInterface. DevInterface said: "Rails Best Practices 4: Put method in the right modelRails Best Practices 4: Scrivere i metodi nel model corretto" – http://bit.ly/cLv2UF [...]

  2. Jayro says:

    Great article – one small error in the sample code:

    @animals = @kind.herbivores

    you may want to add .animals. into that example.

    NoMethodError: undefined method `herbivores’ for #

  3. Claudio says:

    @Jayro: Thank you Jayro, I’ve fixed the example code.

  4. James Herdman says:

    I’m not a big fan of your usage of delegate. It isn’t obvious to me how replacing access to an associated object’s methods with an underscore make it more elegant than accessing the associated object and its method directly. It’s not only unnecessary, but it’s more work than you needed to do.

  5. Sebastian says:

    I am with James here: I don’t see the point in using delegate in this way. Maybe you can explain your motivation in more depth?

  6. +1 for James’ comment. To me, the underscore looks less elegant for some reason (especially framed by the extra code you have to add to use the feature in the first place).

  7. SteveD says:

    On delegate: leave off :prefix and you are left with:

    Which some may find more elegant.

  8. SteveD says:

    Oops, imagine the irb tags:

    @location.owner_name
    @location.owner_city
    @location.owner_street
    @location.owner_email

  9. SteveD says:

    Tags got swallowed. Each line would look something like: @location.city

  10. SteveD says:

    And as long as we’re delegating:

    class Kind :animals
    end

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

  11. SteveD says:

    Again with the tags. My example tried to show you can delegate :herbivores to :animals in the Kind class.

  12. Claudio says:

    Hi guys!
    Thank you all for suggestions.
    About delegation: yes it probably is not really a best practices rather a good possibility that ruby offers to us.
    However, I like to show an example of it because I think it is a construct that can, sometimes, be useful.

    If you’re interested in learning the subject there a good discussion here: http://haacked.com/archive/2009/07/14/law-of-demeter-dot-counting.aspx

  13. Leonardo Brito says:

    Although I like the concept of delegate, I think it should be restricted to a scope, where you can assure to include the delegating class. Otherwise you can easily spawn your DB requests.

  14. insanedreamer says:

    Thanks for the post.

    I’m also not a fan of the delegate method shown. It requires extra coding without any actual benefit. Secondly, someone else looking at your code has to understand that owner_name is indeed owner.name and not just some other attribute belonging to Location. Of course they can check the schema or the models, but I prefer code that’s self-explanatory, which @location.owner.name is.

  15. i’d love to share this posting with the readers on my site. thanks for sharing!

Leave a Reply

Insert code beetween <code lang="ruby"> and </code>

Copyright 2012 DevInterface s.n.c.

DevInterface Blog is proudly powered by WordPress