DRY your Rails Model

When you’re trying to make some controllers of your app tinier with agreement to have fatter models, you might think about scope or writing functions for that Model class. In this post, I wanna write some examples of that Active Record technique

Problem definition

The problem my rails app is about is:

    • We have models: User, Post and Tag
    • User and Post have 1-n relationship while Post and Tag have the n-n relationship
    • We gonna find Post with many constraints relating to User and Tag
    • For Post, we have state:string

Before DRY

In the Post model, we have state as and is in ['draft', 'published'] . It’s is normal to query posts by state like

published_posts = Post.where(state: 'published')
draft_posts = Post.where(state: 'draft')

How if you wanna check if a post published or not

post = Post.where(id: 1).first
is_published = post.state == 'published'

And now I wanna get posts of some authors

posts = Post.joins(:authors).where(author_id: params[:author_ids])

or posts with some tag names

posts = Post.joins(:tags).where(tags: {name: params[:tag][:names]})

With some improvements

All are good now, right? hm, it could be even better if we can give some logic to our model

Like this

class Post < ActiveRecord::Base
  STATES = ['published', 'draft']
  belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
  has_many :tags, through: :post_tags

  scope :published, where(state: 'published')
  scope :tag, lambda{|tags| joins(:tags).where(tags: {name: tags})}

  STATES.each do |state_name|
    define_method "#{state_name}?" do
      self.state == state_name
    end
  end
end

Then, we can have

posts = Post.tag(params[:tags]).published
post = Post .find(params[:id])
post.published?

Happy Coding!

Published by

Colin Dao

I am a hardworking Rubyist in Hanoi, Vietnam

Leave a comment