Alternatif Pengamat Rel untuk 4.0

154

Dengan Pengamat secara resmi dihapus dari Rails 4.0 , saya ingin tahu apa yang pengembang lain gunakan di tempat mereka. (Selain menggunakan permata yang diekstraksi.) Sementara Pengamat tentu saja dilecehkan dan dapat dengan mudah menjadi susah di kali, ada banyak kasus penggunaan di luar hanya cache-kliring di mana mereka menguntungkan.

Ambil, misalnya, aplikasi yang perlu melacak perubahan ke model. Pengamat dapat dengan mudah melihat perubahan pada Model A dan mencatat perubahan tersebut dengan Model B dalam database. Jika Anda ingin melihat perubahan di beberapa model, maka satu pengamat dapat mengatasinya.

Di Rails 4, saya ingin tahu strategi apa yang digunakan pengembang lain sebagai pengganti Pengamat untuk menciptakan kembali fungsionalitas itu.

Secara pribadi, saya condong ke arah semacam implementasi "pengendali lemak", di mana perubahan ini dilacak di setiap metode membuat / memperbarui / menghapus pengontrol model. Meskipun sedikit membesarkan perilaku masing-masing pengontrol, itu membantu dalam keterbacaan dan pemahaman karena semua kode berada di satu tempat. The downside adalah bahwa sekarang ada kode yang sangat mirip tersebar di beberapa pengontrol. Mengekstrak kode itu ke metode pembantu adalah sebuah opsi, tetapi Anda masih memiliki panggilan untuk metode-metode yang berserakan di mana-mana. Bukan akhir dunia, tetapi tidak cukup dalam semangat "pengendali kurus" juga.

Callback ActiveRecord adalah pilihan lain yang mungkin, meskipun yang saya pribadi tidak suka karena cenderung menyandingkan dua model yang berbeda terlalu dekat menurut pendapat saya.

Jadi di dunia Rails 4, dunia tanpa Pengamat, jika Anda harus membuat catatan baru setelah catatan lain dibuat / diperbarui / dihancurkan, pola desain apa yang akan Anda gunakan? Pengontrol gemuk, panggilan balik ActiveRecord, atau yang lainnya?

Terima kasih.

kennyc
sumber
4
Saya sangat terkejut tidak ada jawaban yang diposting untuk pertanyaan ini. Agak membingungkan.
courtimas

Jawaban:

82

Lihatlah Kekhawatiran

Buat folder di direktori model Anda yang disebut kekhawatiran. Tambahkan modul di sana:

module MyConcernModule
  extend ActiveSupport::Concern

  included do
    after_save :do_something
  end

  def do_something
     ...
  end
end

Selanjutnya, sertakan dalam model yang Anda ingin jalankan after_save di:

class MyModel < ActiveRecord::Base
  include MyConcernModule
end

Tergantung pada apa yang Anda lakukan, ini mungkin membuat Anda dekat tanpa pengamat.

UncleAdam
sumber
20
Ada masalah dengan pendekatan ini. Khususnya, itu tidak membersihkan model Anda; termasuk menyalin metode dari modul kembali ke kelas Anda. Mengekstraksi metode kelas ke modul dapat mengelompokkannya berdasarkan kekhawatiran, tetapi kelas tersebut masih sama kembungnya.
Steven Soroka
15
Judulnya adalah 'Rails Observer Alternatives for 4.0' bukan 'Bagaimana cara meminimalkan mengasapi'. Bagaimana kekhawatiran itu tidak melakukan pekerjaan Steven? Dan tidak, menyarankan bahwa 'mengasapi' adalah alasan mengapa ini tidak akan berfungsi sebagai pengganti pengamat tidak cukup baik. Anda harus mengajukan saran yang lebih baik untuk membantu masyarakat atau menjelaskan mengapa kekhawatiran tidak akan berfungsi sebagai pengganti pengamat. Semoga Anda akan menyatakan keduanya = D
UncleAdam
10
Kembung selalu menjadi perhatian. Alternatif yang lebih baik adalah lebih bijak , yang, jika diimplementasikan dengan benar, memungkinkan Anda untuk membersihkan masalah dengan mengekstraksi mereka ke kelas terpisah yang tidak tergabung erat dengan model. Ini juga membuatnya jauh lebih mudah untuk diuji secara terpisah
Steven Soroka
4
Model mengasapi atau Aplikasi Utuh mengasapi dengan menarik Permata untuk melakukan ini - kita bisa menyerahkannya ke preferensi individu. Terima kasih atas saran tambahannya.
UncleAdam
Itu hanya akan menggembung menu metode auto-complete IDE, yang seharusnya baik untuk banyak orang.
lulalala
33

Mereka ada di plugin sekarang.

Dapatkah saya juga merekomendasikan alternatif yang akan memberi Anda pengendali seperti:

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
Keris
sumber
Bagaimana dengan ActiveSupport :: Pemberitahuan?
svoop
@svoop ActiveSupport::Notificationsdiarahkan pada instrumentasi, bukan sub / pub generik.
Kris
@ Kris - Anda benar. Ini digunakan untuk instrumentasi terutama, tapi saya ingin tahu apa yang mencegahnya digunakan sebagai metode umum untuk pub / sub? itu memang memberikan blok bangunan dasar, kan? Dengan kata lain, apa kelebihan / kekurangan dibandingkan dengan wisper ActiveSupport::Notifications?
gingerlime
Saya belum Notificationsbanyak menggunakan tetapi saya katakan Wispermemiliki API yang lebih bagus dan fitur seperti 'pelanggan global', 'awalan' dan 'pemetaan acara' yang Notificationstidak. Rilis di masa depan Wisperjuga akan memungkinkan penerbitan async melalui SideKiq / Resque / Celluloid. Juga, berpotensi, dalam rilis Rails di masa depan, API untuk Notificationsdapat berubah menjadi lebih terfokus pada instrumentasi.
Kris
21

Saran saya adalah membaca posting blog James Golick di http://jamesgolick.com/2010/3/14/crazy-heretical-and-awesome-the-way-i-write-rails-apps.html (coba abaikan caranya judul tidak sopan terdengar).

Kembali pada hari itu semua "model gemuk, pengontrol kurus". Kemudian model gemuk menjadi sakit kepala raksasa, terutama saat pengujian. Baru-baru ini dorongan untuk model kurus - gagasan bahwa setiap kelas harus menangani satu tanggung jawab dan tugas model adalah untuk mempertahankan data Anda ke database. Jadi, di mana semua logika bisnis kompleks saya berakhir? Dalam kelas logika bisnis - kelas yang mewakili transaksi.

Pendekatan ini dapat berubah menjadi quagmire (giggity) ketika logika mulai rumit. Konsepnya masuk akal - alih-alih memicu hal-hal secara implisit dengan panggilan balik atau pengamat yang sulit untuk diuji dan didebug, memicu hal-hal secara eksplisit dalam kelas yang melapisi logika di atas model Anda.

MikeJ
sumber
4
Saya telah melakukan sesuatu seperti ini untuk sebuah proyek selama beberapa bulan terakhir. Anda memang berakhir dengan banyak layanan kecil, tetapi kemudahan pengujian dan pemeliharaannya pasti lebih besar daripada kerugiannya. Spesifikasi saya yang cukup luas pada sistem berukuran sedang ini masih hanya membutuhkan waktu 5 detik untuk berjalan :)
Luca Spiller
Juga dikenal sebagai PORO (Benda-Benda Ruby Lama Biasa), atau objek layanan
Cyril Duchon-Doris
13

Menggunakan panggilan balik rekaman aktif hanya membalik ketergantungan kopling Anda. Misalnya, jika Anda memiliki modelAdan CacheObservermengamati modelAgaya 3 rails, Anda dapat menghapus CacheObservertanpa masalah. Sekarang, alih-alih katakan Aharus secara manual memanggil CacheObserverafter save, yang akan menjadi rail 4. Anda cukup memindahkan dependensi Anda sehingga Anda dapat menghapus dengan aman Atetapi tidak CacheObserver.

Sekarang, dari menara gading saya, saya lebih suka pengamat bergantung pada model yang diamati. Apakah saya cukup peduli untuk mengacaukan pengendali saya? Bagi saya, jawabannya adalah tidak.

Mungkin Anda telah memikirkan mengapa Anda ingin / membutuhkan pengamat, dan dengan demikian menciptakan model yang bergantung pada pengamatnya bukanlah tragedi yang mengerikan.

Saya juga memiliki rasa (cukup beralasan, saya pikir) untuk segala jenis pengamat yang bergantung pada tindakan pengontrol. Tiba-tiba Anda harus menyuntikkan pengamat Anda dalam tindakan pengontrol apa pun (atau model lain) yang dapat memperbarui model yang ingin Anda amati. Jika Anda dapat menjamin aplikasi Anda hanya akan memodifikasi instance melalui buat / perbarui tindakan pengontrol, lebih banyak kekuatan untuk Anda, tapi itu bukan asumsi saya akan membuat tentang aplikasi rel (pertimbangkan bentuk bersarang, asosiasi pembaruan logika model bisnis, dll.)

agmin
sumber
1
Terima kasih atas komentarnya @ agmin. Saya senang bisa beralih dari menggunakan Observer jika ada pola desain yang lebih baik di luar sana. Saya paling tertarik dengan bagaimana orang lain menyusun kode dan ketergantungan mereka untuk menyediakan fungsionalitas yang sama (tidak termasuk caching). Dalam kasus saya, saya ingin merekam perubahan ke model kapan saja atributnya diperbarui. Saya dulu menggunakan Observer untuk melakukan itu. Sekarang saya mencoba untuk memutuskan antara pengontrol gemuk, panggilan balik AR atau sesuatu yang tidak saya pikirkan. Tidak terlihat elegan saat ini.
kennyc
13

Wisper adalah solusi hebat. Preferensi pribadi saya untuk callback adalah bahwa mereka dipecat oleh model tetapi acara hanya didengarkan ketika permintaan masuk yaitu saya tidak ingin callback dipecat saat saya sedang menyiapkan model dalam tes dll. Tapi saya ingin mereka dipecat setiap kali pengendali terlibat. Ini sangat mudah diseting dengan Wisper karena Anda bisa mengatakannya hanya mendengarkan acara di dalam blok.

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
opsb
sumber
9

Dalam beberapa kasus saya cukup menggunakan Instrumentasi Dukungan Aktif

ActiveSupport::Notifications.instrument "my.custom.event", this: :data do
  # do your stuff here
end

ActiveSupport::Notifications.subscribe "my.custom.event" do |*args|
  data = args.extract_options! # {:this=>:data}
end
Panik
sumber
4

Alternatif saya untuk Rails 3 Observers adalah implementasi manual yang memanfaatkan callback yang didefinisikan dalam model namun berhasil (seperti yang dinyatakan agmin dalam jawaban di atas) "flip the dependency ... coupling".

Objek saya mewarisi dari kelas dasar yang menyediakan untuk mendaftar pengamat:

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

(Memang, dalam semangat komposisi atas warisan, kode di atas dapat ditempatkan dalam modul dan dicampur dalam masing-masing model.)

Penginisialisasi mendaftar pengamat:

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

Setiap model kemudian dapat menentukan peristiwa yang dapat diamati sendiri, di luar panggilan balik ActiveRecord dasar. Misalnya, model Pengguna saya memaparkan 2 peristiwa:

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

Setiap pengamat yang ingin menerima pemberitahuan untuk acara-acara tersebut hanya perlu (1) mendaftar dengan model yang memaparkan acara dan (2) memiliki metode yang namanya cocok dengan acara tersebut. Seperti yang diduga, beberapa pengamat dapat mendaftar untuk acara yang sama, dan (mengacu pada paragraf 2 dari pertanyaan awal) pengamat dapat menonton acara di beberapa model.

Kelas pengamat NotificationSender dan ProfilePictureCreator di bawah ini menetapkan metode untuk peristiwa yang diungkapkan oleh berbagai model:

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

Satu peringatan adalah bahwa nama-nama semua peristiwa yang terungkap di semua model harus unik.

Mark Schneider
sumber
3

Saya pikir masalah dengan Pengamat yang ditinggalkan bukanlah bahwa pengamat buruk dalam diri mereka sendiri tetapi bahwa mereka dilecehkan.

Saya akan berhati-hati agar tidak menambahkan terlalu banyak logika di callback Anda atau hanya memindahkan kode untuk mensimulasikan perilaku pengamat ketika sudah ada solusi yang bagus untuk masalah ini, pola Observer.

Jika masuk akal untuk menggunakan pengamat maka tentu saja gunakan pengamat. Hanya mengerti bahwa Anda perlu memastikan bahwa logika pengamat Anda mengikuti praktik pengkodean suara misalnya SOLID.

Permata pengamat tersedia di rubygems jika Anda ingin menambahkannya kembali ke proyek Anda https://github.com/rails/rails-observers

lihat utas singkat ini, sementara tidak diskusi lengkap yang lengkap saya pikir argumen dasarnya valid. https://github.com/rails/rails-observers/issues/2

hraynaud
sumber
2

Bagaimana kalau menggunakan PORO saja?

Logika di balik ini adalah bahwa 'tindakan ekstra menghemat' Anda kemungkinan akan menjadi logika bisnis. Ini saya ingin tetap terpisah dari kedua model AR (yang seharusnya sesederhana mungkin) dan pengontrol (yang merepotkan untuk diuji dengan benar)

class LoggedUpdater

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

end

Dan cukup sebut saja:

LoggedUpdater.save!(user)

Anda bahkan dapat mengembangkannya, dengan menyuntikkan objek tindakan pasca-simpan ekstra

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

Dan untuk memberi contoh 'ekstra'. Anda mungkin ingin sedikit mempercantik mereka:

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

Jika Anda menyukai pendekatan ini, saya sarankan membaca posting blog Bryan Helmkamps 7 Patterns .

EDIT: Saya juga harus menyebutkan bahwa solusi di atas memungkinkan untuk menambahkan logika transaksi juga saat diperlukan. Misalnya dengan ActiveRecord dan database yang didukung:

class LoggedUpdater

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

end
Houen
sumber
-2

Saya memiliki masalah yang sama! Saya menemukan solusi ActiveModel :: Kotor sehingga Anda dapat melacak perubahan model Anda!

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

msroot
sumber