ywen.in.coding

Everythng I do

My Coding Standard for Rails Projects (Part 1)

| Comments

The purpose of this post is to discuss what is I think good when coding a Rails project. Some of thoughts incorporate the internal communications with my collogues Miles Georgi and Jeshua Borges. To publicize my thoughts as a public blog port, I hope I could get more feedbacks and improve upon the comments

Layers

The Rails framework is a layered architecture, and when an application becomes significantly more complicated, the default layers of Rails are not enough. I would like to discuss the layers I want to see to go into a Rails application. Next I will discuss these layers one by one

Thin controllers

Everybody talks about thin controllers, yet with all the trams I have worked with, the controllers never be thin. Why not? I think the answer is that people never really know where these extra yet necessary code should be put into if not in a controller. I will try to explain how I want to avoid any extra code put into the controller. In my view, the only tasks belong to a controller are:

  • Authentications
  • Authorizations
  • Delegate the tasks to other layers
  • Setup objects for page rendering

Putting anything more than the above tasks is considered to be too much by my standard.

A typical controller create, show ad index actions may look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class UsersController
  def create
    user = Builders::User.new(params).build
    Persistence::User.new(user).persist
    respond_with Presenters::User.new(user)
  end

  def show
    user = Finders::User.find(params[:id])
    respond_with Presenters::User.new(user)
  end

  def index
    users = Finders::Users.all(params)
    respond_with Presenters::MinimalUserList.new(users)
  end
end

You may see, if several models use the same pattern as described above, we can DRY it up using some nice little DSL:

1
2
3
4
5
class UsersController
  create_action :user
  show_action :user
  index_action :user, :with_presenter => Presenters::MinimalUserList
end

Even better, the specs can be as simple as:

1
2
3
4
5
  desceribe UsersController do
    include_examples "standard create action", :model_name => :user
    include_examples "standard show action", :model_name => :user
    include_examples "standard index action", :model_name => :user, :with_presenter => Presenters::MinimalUserList
  end

You could always choose not to use these default actions generators, however, it will be more coding and probably less readable code as the result.

Views and Presenters

Let’s face it: the views will always have logic for any given complicated enough sites. There will always be ifs and elses in the view templates. We cannot avoid them.

But by using the Presenter pattern, we can greatly make view much less complicated. Let’s see an example (I use HAML):

1
2
3
- image_name = @user.confirmed? "confirmed.png" : "unconfirmed.png"
%img{:source => image_name, :alt => "confirmed status"}
...

The above example already has a condition statement. With a presenter, the view can be changed to:

1
%img{:source => @user.confirmed_status_image_name, :alt => "confirmed status"}

The confirmed_status_image_name method returns the image path will be used. Natually, such a method should not be in a business model class since it concerns the view and only the view. A presenter is the best place to put such logic. The same logic, when in a view, is hard to unit test, but when it is in its own method, it is super simple to write the tests.

A view template should have one and only one instance variable that passed from its associated controller action: a some sort of presenter instance. It should not have anything else. When a view requires some logic, this logic belongs to an instance method of that presenter class.

What about if the controller is rendering a JSON? A presenter can and should help with that. Again, JSON is just another representation of your resource, thus JSON should not be part of your business logic, it should be in a presenter. Some code snippet shows you how simple it could be:

1
2
3
4
5
6
7
8
9
10
module Presenters
  class User < ObjectForDisplay
    to_json_properties :name_with_first_last_in_front, :email_addresses, :phone, :status
    forward_methods :email_addresses, :status, :phone

    def name_with_first_last_in_front
      "#{object.first_name} #{object.last_name}"
    end
  end
end

The to_json_properties defines a baseline of what attributes will be included in the to_json returns. Each attribute is either forwarded from the business model object, or defined in the presenter class when it apprantely doesn’t belong to a business model.

Comments