Skip to content

Instantly share code, notes, and snippets.

Created December 30, 2012 12:35
Show Gist options
  • Save anonymous/4412635 to your computer and use it in GitHub Desktop.
Save anonymous/4412635 to your computer and use it in GitHub Desktop.
# Extract Service Object

Extract Service Object

http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/

適合場景

  • 複雜的 action
  • action 裡面牽涉到多個 model ( 如 e-commerce 的結帳,牽扯到訂單, 使用者, 明細的修改 )
  • action 裡面牽扯到外部 service ( 例如 po 到 twitter 上)
  • action 的不是這個 model 核心業務的一部分(如清除舊資料...)
  • 有很多方式可以執行這樣的 action (例如 驗證帳號密碼). 這是四人幫裡的 Strategy Patten

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

My Example

第一個實例: 通知留言相關人士

before

app/controllers/comments_controller.rb

  def create
    @comment      = @resource.comments.new(params[:comment])
    @comment.user = current_user

    if @comment.save
       @comment.notify_commneters
      redirect_to(@resource, :notice => "已送出留言")
    else
      render :new
    end
  end

app/models/comment.rb

class Comment

  def notify_commneters
    teacher = resource.teacher
    commenter = user

    comment_related_users = []
    comment_related_users << resource.comment_users
    comment_related_users << teacher
    comment_related_users.uniq!
    comment_related_users.delete(commenter)

    comment_related_users.each do |related_user|
      CommentMailer.delay.comment_course_email(related_user, comment)
    end
  end
end

after

app/controllers/comments_controller.rb

  def create
    @comment      = @resource.comments.new(params[:comment])
    @comment.user = current_user

    if @comment.save
      CommentNotifer.notify_related_users(@comment)
      redirect_to(@resource, :notice => "已送出留言")
    else
      render :new
    end
  end

app/services/comment_notifier.rb

class CommentNotifier

  def self.notify_related_users(comment)
    resource = comment.resource
    teacher = resource.teacher
    commenter = comment.user

    comment_related_users = []
    comment_related_users << resource.comment_users
    comment_related_users << teacher
    comment_related_users.uniq!
    comment_related_users.delete(commenter)

    
    comment_related_users.each do |related_user|
      CommentMailer.delay.comment_course_email(related_user, self)
    end
  end
end

第二個實例 : 報名付款

before

app/controllers/registrations_controller.rb

if payment.success?
  current_user.pay_registration_with_paypal(amount, registration_id)
end

app/models/user.rb

def pay_registration_with_paypal(amount, registration_id)
  registration = Registrant.find(registration_id)
  registration.pay!(amount, "paypal")
end

app/models/registrant.rb

  def pay!(amount, payment_type)
    r              = self
    r.paid_amount  = amount
    r.payment_type = payment_type
    r.paid_at      = Time.now
    r.save
    r.generate_invoice(amount, payment_type)
    r.notify_teacher_payment_complete
    r.notify_student_payment_complete
  end

  def notify_teacher_payment_complete
    @student = user
    @teacher = course.user
    @course  = course
    @amount  = paid_amount
    @invoice = invoice
    PaymentMailer.delay.notify_teacher_payment_complete(@student, @teacher, @course, @amount, @invoice)
  end

  def notify_student_payment_complete
    @student = user
    @teacher = course.user
    @course  = course
    @amount  = paid_amount
    @invoice = invoice
    PaymentMailer.delay.notify_student_payment_complete(@student, @teacher, @course, @amount, @invoice)
  end


  def generate_invoice(amount, payment_type)
    r                    = self
    invoice              = Invoice.new
    invoice.resource     = r
    invoice.amount       = amount
    invoice.payment_type = payment_type
    invoice.paid_at      = Time.now
    invoice.user         = r.user
    invoice.save
  end

after

app/controllers/registrations_controller.rb

if payment.success?
    PayRegistrant.new(registration_id, amount, "paypal" ).pay!
end

app/services/pay_registrant.rb

class PayRegistrant

  def initialize(registrant_id, amount, payment_type)
    @registrant = Registrant.find(registrant_id)
    @amount = amount
    @payment_type = payment_type
  end

  def pay!
    update_registrant_paid_at
    generate_invoice_for_registrant
    notify_teacher_payment_complete
    notify_student_payment_complete
  end

  def update_registrant_paid_at
    @registrant.paid_amount  = @amount
    @registrant.payment_type = @payment_type
    @registrant.paid_at      = Time.now
    @registrant.save
  end

  def generate_invoice_for_registrant
    invoice = @registrant.build_invoice
    invoice.amount       = @amount
    invoice.payment_type = @payment_type
    invoice.paid_at      = Time.now
    invoice.user         = @registrant.user
    invoice.save
  end

  def notify_teacher_payment_complete
    PaymentMailer.delay.notify_teacher_payment_complete(@registrant)
  end

  def notify_student_payment_complete
    PaymentMailer.delay.notify_student_payment_complete(@registrant)
  end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment