Rails Observer Alternatives สำหรับ 4.0


154

เมื่อผู้สังเกตการณ์ถูกลบอย่างเป็นทางการจาก Rails 4.0ฉันอยากรู้ว่านักพัฒนาคนอื่นกำลังใช้อะไรอยู่ (นอกเหนือจากการใช้อัญมณีที่ถูกแยกออกมา) ในขณะที่ผู้สังเกตการณ์ถูกทารุณกรรมและอาจกลายเป็นเรื่องไม่สะดวกได้อย่างง่ายดายในบางครั้งมีกรณีการใช้งานมากมายนอกเหนือจากการล้างแคชซึ่งเป็นประโยชน์

ยกตัวอย่างเช่นแอปพลิเคชันที่ต้องการติดตามการเปลี่ยนแปลงของโมเดล ผู้สังเกตการณ์สามารถเฝ้าดูการเปลี่ยนแปลงในโมเดล A และบันทึกการเปลี่ยนแปลงเหล่านั้นด้วย Model B ในฐานข้อมูลได้อย่างง่ายดาย หากคุณต้องการดูการเปลี่ยนแปลงในหลายรุ่นผู้สังเกตการณ์คนเดียวก็สามารถจัดการกับมันได้

ใน Rails 4 ฉันอยากรู้ว่านักพัฒนาคนอื่นกำลังใช้กลยุทธ์อะไรในตำแหน่ง Observers เพื่อสร้างฟังก์ชั่นนั้น

โดยส่วนตัวแล้วฉันโน้มตัวไปสู่การใช้งาน "ตัวควบคุมไขมัน" ซึ่งการเปลี่ยนแปลงเหล่านี้ถูกติดตามในวิธีการสร้าง / อัพเดต / ลบของตัวควบคุมแต่ละรุ่น ในขณะที่มันขยายพฤติกรรมของแต่ละคอนโทรลเลอร์เล็กน้อย แต่ช่วยในการอ่านและทำความเข้าใจเนื่องจากรหัสทั้งหมดอยู่ในที่เดียว ข้อเสียคือตอนนี้มีรหัสที่คล้ายกันมากกระจายไปทั่วตัวควบคุมหลายตัว การแยกรหัสนั้นออกเป็นวิธีผู้ช่วยเป็นตัวเลือก แต่คุณยังคงทิ้งการเรียกใช้วิธีการเหล่านั้นทิ้งไว้ทุกที่ ไม่ใช่จุดจบของโลก แต่ก็ไม่ได้อยู่ในจิตวิญญาณของ "ผู้ควบคุมผอม" เช่นกัน

ActiveRecord callbacks เป็นอีกทางเลือกที่เป็นไปได้แม้ว่าจะมีคนที่ฉันไม่ชอบเป็นการส่วนตัวเพราะมันมีสองรุ่นที่แตกต่างกันมากเกินไปในความคิดของฉัน

ดังนั้นใน Rails 4 โลกที่ไม่มีผู้สังเกตการณ์ถ้าคุณต้องสร้างบันทึกใหม่หลังจากสร้าง / อัปเดต / ทำลายบันทึกใหม่อีกชุดหนึ่งคุณจะใช้รูปแบบการออกแบบแบบใด ตัวควบคุมไขมันการเรียกกลับ ActiveRecord หรืออย่างอื่นอย่างสิ้นเชิง?

ขอบคุณ.


4
ฉันประหลาดใจจริงๆที่ไม่มีคำตอบเพิ่มเติมสำหรับคำถามนี้ ชนิดของความสับสน
courtimas

คำตอบ:


82

ดูที่ข้อกังวล

สร้างโฟลเดอร์ในไดเรกทอรีรุ่นของคุณที่เรียกว่าข้อกังวล เพิ่มโมดูลที่นั่น:

module MyConcernModule
  extend ActiveSupport::Concern

  included do
    after_save :do_something
  end

  def do_something
     ...
  end
end

ถัดไปรวมไว้ในโมเดลที่คุณต้องการเรียกใช้ after_save ใน:

class MyModel < ActiveRecord::Base
  include MyConcernModule
end

ขึ้นอยู่กับสิ่งที่คุณทำสิ่งนี้อาจทำให้คุณใกล้ชิดโดยไม่มีผู้สังเกตการณ์


20
มีปัญหากับวิธีนี้ มันไม่ได้ทำความสะอาดโมเดลของคุณ รวมถึงวิธีการคัดลอกจากโมดูลกลับไปยังชั้นเรียนของคุณ การแยกเมธอดคลาสออกไปยังโมดูลอาจจัดกลุ่มพวกมันตามความกังวล แต่คลาสนั้นยังคงเป็นป่อง
Steven Soroka

15
ชื่อคือ 'Rails Observer Alternatives for 4.0' ไม่ใช่ 'ฉันจะย่อส่วนให้เล็กที่สุดได้อย่างไร' มันเป็นเรื่องที่ความกังวลไม่ได้ทำงานสตีเวน? และไม่แนะนำว่า 'การขยายตัว' เป็นเหตุผลว่าทำไมสิ่งนี้ถึงไม่สามารถใช้แทนผู้สังเกตการณ์ได้ไม่ดีพอ คุณจะต้องมีข้อเสนอแนะที่ดีกว่านี้เพื่อช่วยชุมชนหรืออธิบายว่าทำไมความกังวลจึงไม่สามารถใช้แทนผู้สังเกตการณ์ได้ หวังว่าคุณจะระบุทั้งคู่ = D
UncleAdam

10
การขยายตัวเป็นสิ่งที่น่ากังวลเสมอ ทางเลือกที่ดีกว่าคือความฉลาดซึ่งหากนำไปใช้อย่างเหมาะสมจะช่วยให้คุณสามารถทำความสะอาดข้อกังวลได้โดยแยกพวกมันออกเป็นคลาสแยกที่ไม่ได้อยู่คู่กับโมเดลอย่างแน่นหนา สิ่งนี้ทำให้การทดสอบแบบแยกได้ง่ายขึ้นมาก
Steven Soroka

4
สร้างแบบจำลอง bloat หรือทั้ง App bloat โดยดึงใน Gem เพื่อทำสิ่งนี้ - เราสามารถปล่อยให้มันขึ้นอยู่กับความชอบส่วนบุคคล ขอบคุณสำหรับคำแนะนำเพิ่มเติม
UncleAdam

มันจะขยายเมนูอัตโนมัติของวิธีการของ IDE เท่านั้นซึ่งน่าจะดีสำหรับหลาย ๆ คน
lulalala

33

พวกเขาอยู่ในปลั๊กอินทันที

ฉันสามารถแนะนำทางเลือกอื่นซึ่งจะให้คอนโทรลเลอร์แก่คุณเช่น:

class PostsController < ApplicationController
  def create
    @post = Post.new(params[:post])

    @post.subscribe(PusherListener.new)
    @post.subscribe(ActivityListener.new)
    @post.subscribe(StatisticsListener.new)

    @post.on(:create_post_successful) { |post| redirect_to post }
    @post.on(:create_post_failed)     { |post| render :action => :new }

    @post.create
  end
end

ActiveSupport :: การแจ้งเตือนเป็นอย่างไร?
svoop

@svoop ได้ActiveSupport::Notificationsรับการมุ่งเน้นไปที่เครื่องมือวัดไม่ใช่ sub / pub ทั่วไป
กริช

@Kris - ถูกต้อง มันใช้สำหรับการใช้เครื่องมือเป็นหลัก แต่ฉันสงสัยว่าจะป้องกันไม่ให้ถูกใช้เป็นวิธีทั่วไปสำหรับ pub / sub ได้อย่างไร มันจัดเตรียมโครงสร้างพื้นฐานใช่มั้ย กล่าวอีกนัยหนึ่งอะไรคือข้อขึ้น / ลงที่ฉลาดกว่าการเปรียบเทียบActiveSupport::Notifications?
Gingerlime

ฉันไม่ได้ใช้Notificationsมากนัก แต่ฉันว่าWisperมี nicer API และคุณสมบัติเช่น 'สมาชิกระดับโลก', 'ในส่วนนำหน้า' และ 'การแมปเหตุการณ์' ซึ่งNotificationsไม่มี การเปิดตัวในอนาคตWisperจะอนุญาตให้เผยแพร่ async ผ่าน SideKiq / Resque / Celluloid นอกจากนี้ในอนาคต Rails จะเปิดตัว API สำหรับNotificationsสามารถเปลี่ยนเป็นเครื่องมือวัดที่เน้นมากขึ้น
กริช

21

ข้อเสนอแนะของฉันคือการอ่านโพสต์บล็อกของ James Golick ที่http://jamesgolick.com/2010/3/14/crazy-heretical-and-awesome-the-way-i-write-rails-apps.html (ลองดูวิธี ไม่สุภาพเสียงของชื่อ)

ย้อนกลับไปในวันนี้มันคือ "รุ่นไขมันตัวควบคุมผอม" จากนั้นแบบจำลองไขมันก็ปวดหัวอย่างมากโดยเฉพาะระหว่างการทดสอบ เมื่อเร็ว ๆ นี้แรงผลักดันสำหรับรุ่นผอม - ความคิดที่ว่าแต่ละชั้นควรจัดการกับความรับผิดชอบเดียวและงานของแบบจำลองคือการเก็บข้อมูลของคุณไว้ในฐานข้อมูล ดังนั้นตรรกะทางธุรกิจที่ซับซ้อนของฉันทั้งหมดจะจบลงที่ใด ในคลาสตรรกะทางธุรกิจ - คลาสที่แทนธุรกรรม

วิธีการนี้สามารถเปลี่ยนเป็นหล่ม (หัวเราะ) เมื่อตรรกะเริ่มซับซ้อน แนวคิดนี้มีความชัดเจน - แทนที่จะเรียกสิ่งต่าง ๆ โดยปริยายด้วยการเรียกกลับหรือผู้สังเกตการณ์ที่ยากต่อการทดสอบและตรวจแก้จุดบกพร่องทริกเกอร์สิ่งต่าง ๆ อย่างชัดเจนในชั้นเรียนที่มีตรรกะอยู่ด้านบนของแบบจำลองของคุณ


4
ฉันได้ทำอะไรแบบนี้กับโครงการในช่วงสองสามเดือนที่ผ่านมา คุณจะได้รับบริการเล็ก ๆ น้อย ๆ มากมาย แต่ความง่ายในการทดสอบและบำรุงรักษานั้นมีมากกว่าข้อเสียอย่างแน่นอน สเป็คที่กว้างขวางของฉันในระบบขนาดกลางนี้ยังคงใช้เวลาเพียง 5 วินาทีในการทำงาน :)
Luca Spiller

หรือที่รู้จักกันในชื่อ PORO (วัตถุทับทิมเก่าแก่) หรือวัตถุบริการ
Cyril Duchon-Doris

13

การใช้บันทึกการเรียกกลับที่ใช้งานอยู่เพียงแค่พลิกการพึ่งพาการแต่งงานของคุณ ตัวอย่างเช่นถ้าคุณมีmodelAและCacheObserverสังเกตmodelAราง 3 สไตล์ที่คุณสามารถลบCacheObserverกับปัญหาไม่มี ตอนนี้แทนที่จะบอกว่าAมีการเรียกตนเองCacheObserverหลังจากที่เซฟไว้ได้ซึ่งจะเป็นราง 4. คุณได้ย้ายการพึ่งพาของคุณเพียงเพื่อให้คุณได้อย่างปลอดภัยสามารถลบแต่ไม่ACacheObserver

ตอนนี้จากหอคอยงาช้างของฉันฉันชอบให้ผู้สังเกตการณ์ขึ้นอยู่กับแบบจำลองที่สังเกต ฉันสนใจพอที่จะถ่วงตัวควบคุมของฉันหรือไม่ สำหรับฉันคำตอบคือไม่

สมมุติว่าคุณใส่ความคิดลงไปว่าทำไมคุณถึงต้องการ / ต้องการผู้สังเกตการณ์และการสร้างแบบจำลองที่ขึ้นอยู่กับผู้สังเกตการณ์นั้นไม่ใช่เรื่องน่าเศร้า

ฉันยังมี (เหตุผลที่สมเหตุสมผลฉันคิดว่า) น่ารังเกียจสำหรับผู้สังเกตการณ์ประเภทใดที่ต้องพึ่งพาการกระทำของคอนโทรลเลอร์ ทันใดนั้นคุณต้องฉีดผู้สังเกตการณ์ของคุณในการกระทำการควบคุมใด ๆ (หรือรุ่นอื่น) ที่อาจปรับปรุงรูปแบบที่คุณต้องการสังเกต หากคุณสามารถรับประกันแอพของคุณจะแก้ไขอินสแตนซ์ผ่านแอคชั่นสร้าง / อัปเดตคอนโทรลเลอร์เพิ่มพลังให้คุณ แต่นั่นไม่ใช่ข้อสมมติฐานที่ฉันจะทำเกี่ยวกับแอปพลิเคชันทางรถไฟ (พิจารณารูปแบบซ้อนกัน


1
ขอบคุณสำหรับความคิดเห็น @agmin ฉันยินดีที่จะย้ายออกจากการใช้ผู้สังเกตการณ์หากมีลวดลายการออกแบบที่ดีกว่า ฉันสนใจมากที่สุดในการที่คนอื่น ๆ กำลังสร้างรหัสและการพึ่งพาเพื่อให้การทำงานที่คล้ายกัน (ไม่รวมแคช) ในกรณีของฉันฉันต้องการบันทึกการเปลี่ยนแปลงของโมเดลทุกครั้งที่มีการอัปเดตแอตทริบิวต์ ฉันเคยใช้ผู้สังเกตการณ์ทำเช่นนั้น ตอนนี้ฉันกำลังพยายามตัดสินใจระหว่างผู้ควบคุมไขมัน, การโทรกลับ AR หรืออย่างอื่นที่ฉันไม่เคยคิด ดูเหมือนว่าทั้งสองจะไม่สง่างามในขณะนี้
kennyc

13

Wisperเป็นทางออกที่ดี ความชอบส่วนบุคคลของฉันสำหรับการโทรกลับคือพวกเขาถูกไล่ออกจากรุ่น แต่เหตุการณ์จะรับฟังเฉพาะเมื่อมีคำขอมาเช่นฉันไม่ต้องการให้โทรกลับถูกไล่ออกในขณะที่ฉันตั้งค่าแบบจำลองในการทดสอบเป็นต้น แต่ฉันต้องการ ไล่ออกเมื่อใดก็ตามที่เกี่ยวข้องกับการควบคุม นี่เป็นเรื่องง่ายในการตั้งค่าด้วย Wisper เพราะคุณสามารถบอกให้ฟังเฉพาะเหตุการณ์ภายในบล็อก

class ApplicationController < ActionController::Base
  around_filter :register_event_listeners

  def register_event_listeners(&around_listener_block)
    Wisper.with_listeners(UserListener.new) do
      around_listener_block.call
    end
  end        
end

class User
  include Wisper::Publisher
  after_create{ |user| publish(:user_registered, user) }
end

class UserListener
  def user_registered(user)
    Analytics.track("user:registered", user.analytics)
  end
end


4

ทางเลือกของฉันสำหรับ Rails 3 Observers คือการติดตั้งแบบแมนนวลซึ่งใช้การโทรกลับที่กำหนดไว้ในตัวแบบ แต่จัดการเพื่อ

วัตถุของฉันสืบทอดมาจากคลาสฐานซึ่งมีไว้สำหรับการลงทะเบียนผู้สังเกตการณ์:

class Party411BaseModel

  self.abstract_class = true
  class_attribute :observers

  def self.add_observer(observer)
    observers << observer
    logger.debug("Observer #{observer.name} added to #{self.name}")
  end

  def notify_observers(obj, event_name, *args)
    observers && observers.each do |observer|
    if observer.respond_to?(event_name)
        begin
          observer.public_send(event_name, obj, *args)
        rescue Exception => e
          logger.error("Error notifying observer #{observer.name}")
          logger.error e.message
          logger.error e.backtrace.join("\n")
        end
    end
  end

end

(ได้รับในจิตวิญญาณของการแต่งมากกว่ามรดกรหัสข้างต้นสามารถวางไว้ในโมดูลและผสมในแต่ละรุ่น)

ผู้เริ่มต้นลงทะเบียนผู้สังเกตการณ์:

User.add_observer(NotificationSender)
User.add_observer(ProfilePictureCreator)

แต่ละรุ่นสามารถกำหนดเหตุการณ์ที่สามารถสังเกตได้ของตนเองนอกเหนือจากการเรียกกลับ ActiveRecord พื้นฐาน ตัวอย่างเช่นโมเดลผู้ใช้ของฉันมี 2 เหตุการณ์:

class User < Party411BaseModel

  self.observers ||= []

  after_commit :notify_observers, :on => :create

  def signed_up_via_lunchwalla
    self.account_source == ACCOUNT_SOURCES['LunchWalla']
  end

  def notify_observers
    notify_observers(self, :new_user_created)
    notify_observers(self, :new_lunchwalla_user_created) if self.signed_up_via_lunchwalla
  end
end

ผู้สังเกตการณ์ใด ๆ ที่ต้องการรับการแจ้งเตือนสำหรับเหตุการณ์เหล่านั้นเพียงแค่ต้อง (1) ลงทะเบียนกับรุ่นที่แสดงเหตุการณ์และ (2) มีวิธีการที่ชื่อตรงกับเหตุการณ์ อย่างที่คาดไว้ผู้สังเกตการณ์หลายคนสามารถลงทะเบียนสำหรับเหตุการณ์เดียวกันและ (อ้างอิงจากย่อหน้าที่ 2 ของคำถามเดิม) ผู้สังเกตการณ์สามารถดูเหตุการณ์ในหลาย ๆ รุ่น

คลาสผู้สังเกตการณ์ NotificationSender และ ProfilePictureCreator ด้านล่างกำหนดวิธีการสำหรับเหตุการณ์ที่เปิดเผยโดยรุ่นต่างๆ:

NotificationSender
  def new_user_created(user_id)
    ...
  end

  def new_invitation_created(invitation_id)
    ...
  end

  def new_event_created(event_id)
    ...
  end
end

class ProfilePictureCreator
  def new_lunchwalla_user_created(user_id)
    ...
  end

  def new_twitter_user_created(user_id)
    ...
  end
end

ข้อแม้หนึ่งคือชื่อของกิจกรรมทั้งหมดที่เปิดเผยในทุกรุ่นต้องไม่ซ้ำกัน


3

ฉันคิดว่าปัญหาของผู้สังเกตการณ์ที่ถูกปฏิเสธนั้นไม่ใช่ว่าผู้สังเกตการณ์ไม่ดีในตัวของมันเอง แต่พวกเขากำลังถูกทารุณกรรม

ฉันจะระมัดระวังไม่ให้เพิ่มตรรกะมากเกินไปในการเรียกกลับของคุณหรือเพียงแค่ย้ายรหัสไปรอบ ๆ เพื่อจำลองพฤติกรรมของผู้สังเกตการณ์เมื่อมีวิธีแก้ปัญหาเสียงสำหรับรูปแบบการสังเกตการณ์แล้ว

ถ้ามันสมเหตุสมผลที่จะใช้ผู้สังเกตการณ์ดังนั้นโดยทั้งหมดให้ใช้ผู้สังเกตการณ์ เพียงแค่เข้าใจว่าคุณจะต้องตรวจสอบให้แน่ใจว่าตรรกะผู้สังเกตการณ์ของคุณเป็นไปตามแนวทางปฏิบัติในการเข้ารหัสเสียงเช่น SOLID

อัญมณีผู้สังเกตการณ์มีอยู่ใน rubygems หากคุณต้องการเพิ่มกลับไปยังโครงการของคุณ https://github.com/rails/rails-observers

ดูกระทู้สั้น ๆ นี้ในขณะที่ยังไม่มีการอภิปรายที่ครอบคลุมฉันคิดว่าข้อโต้แย้งพื้นฐานนั้นถูกต้อง https://github.com/rails/rails-observers/issues/2


2

คุณอาจจะลองhttps://github.com/TiagoCardoso1983/association_observers ยังไม่ได้ทดสอบกับ Rails 4 (ซึ่งยังไม่ได้เปิดตัว) และต้องการความร่วมมือเพิ่มเติม แต่คุณสามารถตรวจสอบได้ว่ามันเป็นเคล็ดลับสำหรับคุณหรือไม่


2

ลองใช้ PORO แทนล่ะ

ตรรกะเบื้องหลังสิ่งนี้คือ 'การกระทำพิเศษในการบันทึก' ของคุณน่าจะเป็นตรรกะทางธุรกิจ ฉันต้องการแยกจากทั้งสองรุ่น AR (ซึ่งควรจะง่ายที่สุดเท่าที่จะเป็นไปได้) และคอนโทรลเลอร์ (ซึ่งน่ารำคาญสำหรับการทดสอบอย่างถูกต้อง)

class LoggedUpdater

  def self.save!(record)
    record.save!
    #log the change here
  end

end

และเรียกมันว่า:

LoggedUpdater.save!(user)

คุณสามารถขยายได้โดยการเพิ่มออบเจกต์การดำเนินการบันทึกภายหลัง

LoggedUpdater.save(user, [EmailLogger.new, MongoLogger.new])

และเพื่อเป็นตัวอย่างของ 'ความพิเศษ' คุณอาจต้องการทำสิ่งเหล่านี้ให้กระจ่าง:

class EmailLogger
  def call(msg)
    #send email with msg
  end
end

ถ้าคุณชอบวิธีนี้ฉันแนะนำให้อ่านโพสต์บล็อกของBryan Helmkamps 7 Patterns

แก้ไข: ฉันควรพูดถึงว่าวิธีการแก้ปัญหาข้างต้นช่วยให้การเพิ่มตรรกะการทำธุรกรรมเช่นกันเมื่อจำเป็น เช่นกับ ActiveRecord และฐานข้อมูลที่รองรับ:

class LoggedUpdater

  def self.save!([records])
    ActiveRecord::Base.transaction do
      records.each(&:save!)
      #log the changes here
    end
  end

end

0

เป็นเรื่องที่ควรกล่าวถึงว่าObservableโมดูลจากไลบรารีมาตรฐานของ Ruby ไม่สามารถใช้ในวัตถุที่มีลักษณะคล้ายเร็กคอร์ดได้เนื่องจากวิธีการแบบอินสแตนซ์changed?และchangedจะปะทะกับสิ่งActiveModel::Dirtyเหล่านั้นจาก

รายงานข้อผิดพลาดสำหรับ Rails 2.3.2


-2

ฉันมี probjem เดียวกัน! ฉันพบโซลูชัน ActiveModel :: Dirty เพื่อให้คุณสามารถติดตามการเปลี่ยนแปลงรูปแบบของคุณได้!

include ActiveModel::Dirty
before_save :notify_categories if :data_changed? 


def notify_categories
  self.categories.map!{|c| c.update_results(self.data)}
end

http://api.rubyonrails.org/classes/ActiveModel/Dirty.html

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.