Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

肥大化したActiveRecordモデルをリファクタリングする7つの方法(翻訳)


 :
 2013/11/19: 
 2021/01/08: 

hachi8833Active Record1Rails 3Rails 4

app

 : 使使

refactor

(一)Value Object

(二)Service Object

(三)Form Object

(四)Query Object

(五)View Object

(六)Policy Object

(七)Decorator

 Active Record7


: 7 Patterns to Refactor Fat ActiveRecord Models
Posted by @brynary on Oct 17th, 2012 (Code Climate Blog)

RailsCode Climate使()1 (Single Responsibility Principle: SRP)

SRPActiveRecord12User 500

()()調



 RailsOOP?!

調RailsOOPRailsRailsActive RecordRails

 


ActiveRecordconcerns1

 app/concerns 使(=concerning)

調

 1. Value Object


Value ObjectValue ObjectRubyDateURIPathname Value RailsValue ObjectActiveRecordValue Object

Rails1Value ObjectValue Object

PhoneNumber Value ObjectEMoneyCode Climate RatingValue ObjectAFRuby String使(使)Rating使
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

ConstantSnapshotRating
class ConstantSnapshot < ActiveRecord::Base
  # …

  def rating
    @rating ||= Rating.from_cost(cost)
  end
end

ConstantSnapshot


#worse_than?#better_than?<>Ruby

#hash#eql?Rating使Code ClimateEnumberable#group_by

#to_sRating

便 (=)  Rating 

 2. Service Object


Service Object1Service Object


 (簿)

 (eOrder, CreditCard, Customer 使)

 (SNS稿)

 ()

 ()GoF (Gang of Four)Strategy


User#authenticate UserAuthenticator
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

SessionsController 
class SessionsController < ApplicationController
  def create
    user = User.where(email: params[:email]).first

    if UserAuthenticator.new(user).authenticate(params[:password])
      self.current_user = user
      redirect_to dashboard_path
    else
      flash[:alert] = "Login failed."
      render "new"
    end
  end
end

 3. Form Object


1Active RecordForm Object使Form Object使(使) accepts_nested_attributes_forCompany  User
class Signup
  include Virtus

  extend ActiveModel::Naming
  include ActiveModel::Conversion
  include ActiveModel::Validations

  attr_reader :user
  attr_reader :company

  attribute :name, String
  attribute :company_name, String
  attribute :email, String

  validates :email, presence: true
  # …その他のバリデーション …

  # フォームそのものは決して永続化しない
  def persisted?
    false
  end

  def save
    if valid?
      persist!
      true
    else
      false
    end
  end

private

  def persist!
    @company = Company.create!(name: company_name)
    @user = @company.users.create!(name: name, email: email)
  end
end

Virtus gemActive RecordForm ObjectActive Record
class SignupsController < ApplicationController
  def create
    @signup = Signup.new(params[:signup])

    if @signup.save
      redirect_to dashboard_path
    else
      render "new"
    end
  end
end

Form ObjectService Object

Form ObjectActive Record

 2021/01/08


Rails 5Active Recordattributes API使

: Rails 5Active Record attributes API | 

Rails 5.2Active Modelattributes API使

: RailsActiveModel::Attributes便 - 

 4. Query Object


ActiveRecordSQLQuery Object1Query Object1

Query Object
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

ActiveRecord::RelationRails 3Query Object使使
old_accounts = Account.where("created_at < ?", 1.month.ago)
old_abandoned_trials = AbandonedTrialQuery.new(old_accounts)

joineager loadingN + 1

 5. View Object


使UI(UI)View Object

Code ClimateRails on Code Climate使View Object
class DonutChart
  def initialize(snapshot)
    @snapshot = snapshot
  end

  def cache_key
    @snapshot.id.to_s
  end

  def data
    # @snapshotからデータを取り出してJSON構造に変換するコードを置く
  end
end

ERBHamlSlimRails使Two Step View調

 


Rails使PresenterPresenterForm ObjectJay FieldsRailsView()使View ObjectView

 6. Policy Object


Policy ObjectPolicy Object使 () 
class ActiveUserPolicy
  def initialize(user)
    @user = user
  end

  def active?
    @user.email_confirmed? &&
    @user.last_login_at > 14.days.ago
  end
end

Policy Object1email2Policy Object (Authorizer ) 

Policy ObjectService ObjectService ObjectPolicy Object使Query ObjectPolicy ObjectQuery ObjectSQL

 7. Decorator


DecoratorDecorator=便

稿Facebook稿Comment

Facebook稿Decoracor
class FacebookCommentNotifier
  def initialize(comment)
    @comment = comment
  end

  def save
    @comment.save && post_to_wall
  end

private

  def post_to_wall
    Facebook.post(title: @comment.title, user: @comment.author)
  end
end

Decorator使
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

DecoratorService ObjectService ObjectDecoratorFacebookCommentNotifier Comment

Ruby使Decorator

 


Rails使RailsActive RecordsActiveRecords

POROPlain Old Ruby Object: Ruby使OOPOOP

7

: Code ClimateOOPRails



Crazy, Heretical, and Awesome: The Way I Write Rails Apps

ActiveRecord (and Rails) Considered Harmful

Single Responsibility Principle on Rails Explained


: Steven Bristol, Piotr Solnica, Don Morrison, Jason Roelofs, Giles Bowkett, Justin Ko, Ernie Miller, Steve Klabnik, Pat Maddox, Sergey Nartimov, Nick Gauthier


Service Object


Rails: ActiveRecordattributes API


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。