# 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](http://en.wikipedia.org/wiki/Strategy_pattern) `UserAuthenticator` ``` ruby 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` ``` ruby 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` ``` ruby 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` ``` ruby 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` ``` ruby 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` ``` ruby 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` ``` ruby if payment.success? current_user.pay_registration_with_paypal(amount, registration_id) end ``` `app/models/user.rb` ``` ruby def pay_registration_with_paypal(amount, registration_id) registration = Registrant.find(registration_id) registration.pay!(amount, "paypal") end ``` `app/models/registrant.rb` ``` ruby 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` ``` ruby if payment.success? PayRegistrant.new(registration_id, amount, "paypal" ).pay! end ``` `app/services/pay_registrant.rb` ``` ruby 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 ```