Enhance Rails Models

In Rails Models play a important part in our Rails App.We should keep are model pretty,not having dirty code Here i am just reminding you some basic points to refactor a model.

1. Follow law of Demeter(In rails it means use only one dot)

1
2
3
4
5
6
7
8
9
10
11
12
class Author  < ActiveRecord::Base
  has_many :books
  has_one  :address
end

class Book < ActiveRecord::Base
  belongs_to :author
end

class Address < ActiveRecord::Base
  belongs_to :author
end

So if in a view we have something like

1
2
3
4
5
    @book.author.name
    @book.author.address.street
    @book.author.address.city
    @book.author.address.state

Here we are calling an object’s related object using a third(book -> customer -> address) Luckily in Rails we have Delegate that can escape us from this situation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Author  < ActiveRecord::Base
  has_many :books
  has_one  :address
end

class Book < ActiveRecord::Base
  belongs_to :author'
  delegate :name,
           :street,
           :city,
           :state,
           :to => :author, :prefix => true, :allow_nil => true

end

class Address < ActiveRecord::Base
  belongs_to :author
  delegate :street,:city,:state, :to => :address ,:allow_nil => true
end

So if in a view we have something like

1
2
3
4
  @book.author_name
  @book.author_street
  @book.author_city
  @book.author_state

Now we have just only dot.Here :allow_nil option prevents the error call method on nil object

2. Use callback and validation.instead of writing large code in your method

1
2
3
4
5
6
7
8
9
10
class User < ActiveRecord::Base
  email_confirmation
  attr_accessor :email_confirmation

  def user_save
    if email && email_confirmation && save
     #send_email
    end
  end
end
Instead of doing that you can use a call back after_create
1
2
3
4
5
6
7
8
9
10
11
12
class User < ActiveRecord::Base
  validates :email, :confirmation => true
  validates :email_confirmation, :presence => true

  after_create :send_email

  private

  def send_email
   #send_email
  end
end
So now when you create user it validates if email and email_confirmation attribute is there will send email just after saving the user (here you don’t need to define attr_accessor :email_confirmation and don’t need to tae attr email_confirmation in db table )

3. Include Modules

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class Order < ActiveRecord::Base
  def self.find_purchased
    # ...
  end

  def self.find_waiting_for_review
     # ...
  end

  def self.find_waiting_for_sign_off
    #.....
  end

  def self.find_waiting_for_sign_off
   #...
  end

  def self.advanced_search(fields, options = {})
   #...
  end

  def self.simple_search(terms)
   #...
  end

  def to_xml
   #...
  end

  def to_json
   #...
  end

  def to_csv
    #...
  end

  def to_pdf
    #...
  end
end
Modules allow you to extract behavior into separate files.Modules also serve to group related information into labeled namespaces.Its improve readability of code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
class Order < ActiveRecord::Base
  extend OrderStateFinders
  extend OrderSearchers
  include OrderExporters
end

# lib/order_state_finders.rb
module OrderStateFinders
  def find_purchased
    #...
  end

  def find_waiting_for_review
    #...
  end

  def find_waiting_for_sign_off
    #...
  end
end

# lib/order_searchers.rb
module OrderSearchers
  def advanced_search(fields, options = {})
    # ...
  end

  def simple_search(terms)
    # ...
  end
end

# lib/order_exporters.rb
module OrderExporters
  def to_xml
    # ...
  end

  def to_json
    # ...
  end

  def to_csv
   # ...
  end

  def to_pdf
    # ...
  end
end
So in extend module’s method are class method on that calling class.include module’s methods are instance method for object of calling class

4.Use active record association Rails provides us association very nicely.

1
2
3
4
5
6
7
8
9
10
11
class User < ActiveRecord::Base
  has_many :blogs
end

class blog < ActiveRecord::Base
  belongs_to :user
end


@user = User.find(params[:id])
@blogs = Blog.where(:user_id => params[:user_id])

Instead of doing this.do

1
2
@user = User.find(params[:id])
@blogs = @user.blogs

5. Use Scope rather than writing complex finders

1
2
3
4
5
class Book < ActiveRecord::Base
  def search_books(params={})
    where('name like ? and price > ? and published_date > =?', "%#{params[:name]}%", 100,Date.today)
  end
end
Now suppose if you just need to find books based on published date you will create another method.That will duplicate your code
1
2
3
4
5
6
7
8
9
class Book < ActiveRecord::Base
  def search_books(params={})
    where('name like ? and price > ? and published_date > =?', "%#{params[:name]}%", 100,Date.today)
  end

  def search_by_published_date(date)
    where('published_date > =?',date)
  end
end
In this case you can use scope
1
2
3
4
5
class Book < ActiveRecord::Base
  scope :by_published_date,lambda{|d|{:where => ['published_date >= ?',d]}}
  scope :by_name,lambda{|n|{:where => ['name like ?',"%#{n}%"]}}
  scope :by_price,lambda{|p|{:where => ['price =?',p}}
end
Now you can use scope like
1
Book.by_published_date(Date.today).by_name('rails').by_price(100)

6. Do Metaprogramming

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Plan < ActiveRecord::Base
  ACTIVE='active'
  INACTIVE = 'inactive'
  IN_PROGRESS = 'in progress'
  def active?
    status == ACTIVE
  end

  def inactive?
    status == INACTIVE
  end

  def in_progress?
    status == IN_PROGRESS
  end
end
now reduce code with meta programming
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Plan < ActiveRecord::Base
  ACTIVE='active'
  INACTIVE = 'inactive'
  IN_PROGRESS = 'in progress'

  [ACTIVE, INACTIVE,IN_PROGRESS].each do |s|
    plan_status = <<-EOF
      def is_#{s}?
        self.status ==  '#{s}'
      end
    EOF
    class_eval plan_status, __FILE__, __LINE__
  end
end

7. Use full text search engine

if you app require search based on all the attribute on model or something like that then writing messy code is not an good idea. Better way is to use any search engine like Solr, Sphinx

Search engine indexed the data.so it gives you results based on search query against any full words in the indexed data

I have covered most of the steps here to enhance your Rails Models and keep your Rails Models clean, Here are some links to read more about these things. Reference links :

Thanks for reading this post :–)