Friday 21 September 2012

Push All find() Calls into Finders on the Model

Push All find() Calls into Finders on the Model

Programmers who are not familiar with the MVC design pattern to which Rails adheres, as well as those who are just unfamiliar with the general structures provided by Ruby on Rails may find themselves with code living where it simply doesn't belong.

The code problem discussed in this section can present itself in all three layers of the MVC design pattern, but it is most prevalent in its most blatant form in the view or the controller.

For example, if you wanted to create a web page that displays all the users in your web application, ordered by last name, you might be tempted to put this call to find directly in the view code as follows:

<html>
  <body>
    <% User.find(:order => "last_name").each do |user| %>
       <li><%= user.last_name %><%= user.first_name %></li>
    <% end %>
  </body>
</html>

At the very least, including the actual logic for what users will be displayed on this page is a violation of MVC. At worst, this logic can be duplicated many times throughout the application, causing very real maintainability issues.

In order to get rid of the MVC violation, you need to move the logic for which users are displayed on the page into the Users Controller. When you do this, you end up with something like the following:

class UsersController < ApplicationController
  def index
    @users = User.order("last_name")
  end
end

The following is the corresponding index view:

<html>
   <body>
      <ul> 
         <% @users.each do |user| -%>
            <li><%= user.last_name %><%= user.first_name %></li>
         <% end %>
      </ul>
   </body>
</html>

Now you don't have any logic in the presentation layer about the collection of users you're displaying; now it's just sitting in the controller.

We can also moving the direct find call down into the model itself. with this change, the view doesn't change at all. However, you end up with a controller that calls a new ordered method on the User model:

class UsersController < ApplicationController
  def index
    @users = User.ordered
  end
end

And the User model contains the call to find:

class User < ActiveRecord::Base
  def self.ordered
    order("last_name")
  end
end

The Ruby on Rails community has embraced this concept so strongly that is has been baked into the framework itself, with scope. We'll go into the use of scopes later, but for now suffice to say that they are shortcuts for defining methods on a model.

For example, the named scope for the ordered method would be written as follows:

class User < ActiveRecord::Base
  scope :ordered, order("last_name")
end



No comments:

Post a Comment