Or, 7 Patterns to Refactor Fat ActiveRecord Models
http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/
Objects whose equality is dependent on their value rather than an identity
class Rating
include Comparable
def self.from_cost(cost)
if cost <= 2
new("A")
elsif cost <= 4
new("B")
elsif cost <= 8
new("C")
elsif cost <= 16
new("D")
else
new("F")
end
end
def initialize(letter)
@letter = letter
end
def better_than?(other)
self > other
end
def <=>(other)
other.to_s <=> to_s
end
def hash
@letter.hash
end
def eql?(other)
to_s == other.to_s
end
def to_s
@letter.to_s
end
end
Translation layer between forms and the models that are accessed.
class Signup
include Virtus
extend ActiveModel::Naming
include ActiveModel::Conversion
include ActiveModel::Validations
attr_reader :user
attr_reader :company
An object that encapsulates one or more discrete business level operations.
class UserAuthenticator
def initialize(user)
@user = user
end
def authenticate(unencrypted_password)
return false unless @user
if BCrypt::Password.new(@user.password_digest) == unencrypted_password
@user
else
false
end
end
end
If logic is needed purely for display purposes, it does not belong in the model.
class DonutChart
def initialize(snapshot)
@snapshot = snapshot
end
def cache_key
@snapshot.id.to_s
end
def data
# pull data from @snapshot and turn it into a JSON structure
end
end
Each query object is responsible for returning a result set based on business rules.
class AbandonedTrialQuery
def initialize(relation = Account.scoped)
@relation = relation
end
def find_each(&block)
@relation.
where(plan: nil, invites_count: 0).
find_each(&block)
end
end
AbandonedTrialQuery.new.find_each do |account|
account.send_offer_for_support
end
Let you layer on functionality to existing operations.
class FacebookCommentNotifier
def initialize(comment)
@comment = comment
end
def save
@comment.save && post_to_wall
end
def post_to_wall
Facebook.post(title: @comment.title, user: @comment.author)
end
end
class CommentsController < ApplicationController
def create
@comment = FacebookCommentNotifier.new(Comment.new(params[:comment]))
if @comment.save
redirect_to blog_path, notice: "Your comment was posted."
else
render "new"
end
end
end
Policy Objects encapsulate one business rule
class ActiveUserPolicy
def initialize(user)
@user = user
end
def active?
@user.email_confirmed? &&
@user.last_login_at > 14.days.ago
end
end