Rails best practices

Introduccion

In Rails the maintenance of an application usually is large and conmplicated because it is developed
by team with different coding style, but if apply best practices the project is easy to make change suggested by the client.

Code regular A good code is
Regidity
Fragility
Inmobility
Viscosity
Needless Complexity
Needless Repetition
Opacity
Readability
Flexibility
Effective
Maintainability
Consistency
Testability

This information was taken from http://www.slideshare.net/ihower/rails-best-practices with the permission of the author Wen-Tien Chang

Move code from Controller to Model

The best practice are

  • Use method named_scope(name, options = {}, &block) this belongs Module ActiveRecord.

Regular Code

class PostsController <ApplicationController

 def index
    @public_posts = Post.find(:all, :conditions => {state => 'public'},
                                              :limit =>10,
                                              :order => 'created_at desc')
    @draft_posts = Post.find(:all,  :conditions => {state => 'draft'},
                                             :limit =>10,
                                             :order => 'created_at desc')
  end
end

Good code

class UsersController < ApplicationController
    def index
      @published_post = Post.published
      @draft_post = Post.draft
    end
end 

class Post < ActiveRecord::Base
  named_scope (:published, :conditions => {:state =>'published'}.
                                      :limit => 10, :order => 'created_at desc')
  named_scope (:draft, :conditions => {:state => 'draft'},
                                :limit => 10, :order => 'created_at desc')
end
  • Use model association with belongs_to, has_and_belongs_to_many, has_many, has_one

Regular Code

class PostsController < ApplicationController

    def create
      @post = Post.new(params[:post])
      @post.user_id = current_user.id
      @post.save
    end

end

Good Code

class PostsController < ApplicationController
    def create
      @post = current_user.posts.build(params[:post])
      @post.save
    end

end

class User < ActiveRecord::Base
    has_many :posts
end

*Use Scope access

Regular Code

class PostsController < ApplicationController
   def edit
    @post = Post.find(params[:id])

      if @post.current_user! = current_user
        flash[:warning] = 'Access denied'
        redirect_to posts_url
      end
   end
end

Good Code

class PostsController < ApplicationController
    def edit
      //#raise RecordNotFound exception (404 error) if not found//
      @post = current_user.posts.find(params[:id])
    end
end
  • Add model virtual attribute this technique allows you to create form fields which may not directly relate to the database.

Regular Code

<% form_for @user do |f|  %>
    <%= text_filed_tag :full_name %>
<% end %>

class UsersController < ApplicationController
  def create
    @user= User.new[:user]
    @user.first_name = params[:full_name].split(' ',2).first
    @user.last_name = params [:full_name].split(' ',2).last
    @user.save
  end
end

Good Code

class User < ActiveRecord::Base

  def full_name
    [first_name, last_naame].join(' ')
  end

  def full_name=(name)
    split = name.split(' ',2)
    self.first_name = split.first
    self.last_name = split.last
  end
end

<% form_for @user do |f|  %>
    <%= text_filed :full_name %>
<% end %>

class UsersController < ApplicationController
  def create
    @user = User.create(params[:user])
  end
end
  • Use model callback, allow you to trigger logic before or after an alteration of the object state.

Regular Code

<% form_for @post do |f| %>
  <%= f.text_field :content %>
  <%= check_box_tag 'auto_tagging' %>
<% end %>

class PostController < ApplicationController
    def create
      @post = Post.new(params[:post])

        if params[:auto_tagging]=='1'
          @post.tags = AsiaSearch.generate_tags(@post.content)
        else
          @post.tags=""
        end

    @post.save
  end
end

Good Code

class Post < ActiveRecord::Base
  attr_accessor :auto_tagging
  before_save :generate_taggings

  private

  def generate_taggings
    return unless auto_tagging == '1'
    self.tags = Asia.search(self.content)
  end
end

<% form_for :note, ... do |f| %>
  <%= f.text_field :content %>
  <%= f.check_box :auto_tagging %>
<% end %>

class PostController < ApplicationController

  def create
    @post = Post.new(params[:post])
    @post.save
  end

end
  • Use Factory Method, the model that contains the logic of creation / initialization of the object and then call it from the controller.

Regular Code

class InvoiceController < ApplicationController
  def create
    @invoice = Invoice.new(params[:invoice])
    @invoice.address = current_user.address
    @invoice.phone = current_user.phone
    @invoice.vip = (@invoice.amount > 1000)

    if Time.now.day > 15
      @invoice.delivery_time = Time.now + 2.month
    else
      @invoice.delivery_time = Time.now + 1.month
    end
   @invoice.save
 end
end

Good Code

class Invoice < ActiveRecord::Base
  def self.new_by_user(params, user)
    invoice = self.new(params)
    invoice.address = user.address
    invoice.phone = user.phone
    invoice.vip = (invoice.amount > 1000)

   if Time.now.day > 15
    invoice.delivery_time = Time.now + 2.month
   else
    invoice.delivery_time = Time.now + 1.month
   end
 end
end

class InvoiceController < ApplicationController
    def create
      @invoice = Invoice.new_by_user(params[:invoice],current_user)
      @invoice.save
    end
end
  • Move model logic into the Model

Regular Code

class PostContoller < ApplicationController
  def publish
    @post = Post.find(params[:id])
    @post.update_attribute(:is_published, true)
    @post.approved_by = current_user
      if @post.create_at > Time.now -7.days
        @post.popular = 100
      else
        @post.popular = 0
      end

    redirect_to post_url(@post)
  end
end

Good Code

class Post < ActiveRecord::Base
  def publish
    self.is_published = true
    self.approved_by = current_user
    if self.create_at > Time.now-7.days
      self.popular = 100
    else
      selef.popular = 0
    end
  end
end

class PostController < ApplicationController
  def publish
    @post = Post.find(params[:id])
    @post.publish

    redirect_to post_url(@post)
  end
end
  • model.collection_model_ids (many-to-many)

Regular Code

class User < ActiveRecord::Base
    has_many :user_role_relationship
    has_many :roles; :through => :user_role_relationship
end

class UserRoleRelationShip < ActiveRecord::Base
    belong_to :user
    belong_to :role
end

class Role < ActiveRecord::Base
end

<% form_for @user do |f| %>
  <%= f.text_field :email %>
  <% for role in Role.all %>
    <%= check_box_tag 'role_id[]', role.id, @user.roles.include?(role) %>
    <%= role.name %>
  <% end %>
<% end %>

class User < ApplicationController
   def update
      @user = User.find(params[:id])
        if @user.update_attributes(params[:user])
           @user.roles.delete.all
           (params[:role_id] || []).each { |i| @user.roles << Role.find(i)}
        end
    end
end

Good Code

<% form_for @user do |f| %>
    <% for role in Role.all %>
      <%= check_box_tag 'user[roles_ids][]', role.id, @user.roles.include?(role)
      <%= role.name %>
   <% end %>
 <% hidden_field_tag 'user[role_ids][]','' %>

<% end %>

class User < ApplicationController
    def update
      @user = User.find(params[:id])
      @user.update_attributes(params[:user])

    end
end
  • Nested Model Forms (one-to-one)

Regular Code

class Product < ActiveRecord::Base
  has_one :detail
end

class Detail < ActiveRecord::Base
  belong_to :product
end

<% form_for :product do |f| %>
  <%= f.text_field :tittle %>

  <%fields_for :details do |details| %>
    <%= details.text_field :manufacturer %>
  <% end %>
<% end %>

class Product < ApplicationController
 def create
  @product = Product.new(params[:product])
  @details = Detail.new(params[:detail])

   Product.transaccion do
    @product.save!
    @details.prooduct = @product
    @details.save!
   end
  end
end

Good Code

class Product < ActiveRecord::Base
  has_one _detail
  accepts_nested_for :detail
end

<% form_for :product do |f| %>
  <%= f.text_field :title %>

  <% f.field_for :detail do |detail| %>
    <%= detail.text_field :manufacturer %>
  <% end %>
<% end %>

class Product < ApplicationController

  def create
    @product = Product.new(params[:product])
    @product.save
  end
end
  • Nested Model Forms (one-to-many)
class Project < ActiveRecord::Base
  has_many :tasks
  accepts_nested_attributes_for :tasks
end

class Task < ActiveRecord::Base
  belong_to :project
end

<% form_for @project do |f| %>
  <%= f.text_field :name %>
  <% f.fields_for :tasks do |tasks_form| %>
     <%= tasks_form.text_field :name %>
  <% end %>
<% end %>

RESTful

RESTful follows the principles of REST (Representational State Transfer) help you organize name controllers, routes and actions in standardization way.

Regular code

class EventsController < ApplicationController

  def index                  def white_member_list       
  end                        end                         

  def show                   def black_member_list      
  end                        end                         

  def create                 def deny_user               
  end                        end                        

  def update                 def edit_managers
  end                        end

  def destroy                def leave
  end                        end                         

  def feeds                  def set_user_as_manager
  end                        end

  def add_comment            def watch_list
  end                        end

  def show_comment           def add_favorite
  end                        end

  def destroy_comment        def invite
  end                        end

  def approve_comment        def set_user_as_member
  end                        end

Good code

class EventsController < ApplicationController
  def index; end
  def show; end
end

class CommentsControllers < ApplicationController
  def index; end
  def create; end
  def destroy; end
end

def FavoriteControllers < ApplicationController
  def create; end
  def destroy; end
end

class EventMembershipsControllers < ApplicationControllers
  def create; end
  def destroy; end
end
  • Overuse route customizations

Before

map.resources :posts, :member => {:comments => :get,
                                  :create_comment => :post,
                                  :update_comment => :post,
                                  :delete_comment => :post }

The solution to solve the overuse route customizations is to find another resources

After

map.resources :posts do |post|
  post.resources :comments
end
  • Suppose we has a event model…
class Event < ActiveRecord::Base
  has_many :attendee
  has_one :map
  has_many :memberships
  has_many :users, :through => :memberships
end
  • Needless deep nesting

Bad Code

map.resources :post do |post|
  post.resources :comments do |comment|
    comment.resources :favorites
  end
end

<%= link_to post_comment_favorite_path(@post, @comment, @favorite) %>

It is not necessary define rhe 3 level nested routes, using nested routes means the nested resources is belongs to the parent resources.

Good code

map.resources :post do |post|
  post.resources :comments
end

map.resources :comments do |comment|
  comment.resources :favorites
end

<%= link_to comment_favorite_path(@comment, @favorite)
  • Not use default route

Bad code

map.resources :posts, :member => {:push => :post}

map.connect ':controller/:action/:id'
map.connect ':controller/:action/:id.:format'

default route will be a security problem, because user can create, update or destroy a post by HTTP GET if you define the default route.

Good code

map.resources :posts, :member => {:push => :post}

#map.connect ':controller/:action/:id'
#map.connect ':controller/:action/:id.:format'

map.connect 'special/:action/:id', :controller => 'special'

Model

Controller

View

Documentation and Examples

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License