4.0 için Rails Observer Alternatifleri


154

Gözlemciler resmi olarak Rails 4.0'dan kaldırıldığında, diğer geliştiricilerin yerinde ne kullandığını merak ediyorum. (Çıkarılan mücevher kullanmak dışında.) Gözlemciler kesinlikle istismar edilmiş ve zaman zaman kolayca yararsızlaşabilirken, yararlı oldukları yerde önbellek temizliğinin dışında birçok kullanım durumu vardı.

Örneğin, bir modeldeki değişiklikleri izlemesi gereken bir uygulamayı ele alalım. Bir Gözlemci, Model A'daki değişiklikleri kolayca izleyebilir ve bu değişiklikleri Model B ile veritabanına kaydedebilir. Birkaç modeldeki değişiklikleri izlemek istiyorsanız, tek bir gözlemci bunu halledebilir.

Rails 4'te, diğer geliştiricilerin bu işlevselliği yeniden oluşturmak için Gözlemciler yerine hangi stratejileri kullandığını merak ediyorum.

Şahsen, bu değişikliklerin her model denetleyicisinin oluşturma / güncelleme / silme yönteminde izlendiği bir tür "yağ kontrolörü" uygulamasına yöneliyorum. Her denetleyicinin davranışını hafifçe şişirirken, tüm kod tek bir yerde olduğu için okunabilirliğe ve anlaşılmasına yardımcı olur. Dezavantajı, şimdi birkaç kontrolöre dağılmış benzer bir kod olmasıdır. Bu kodu yardımcı yöntemlere ayıklamak bir seçenektir, ancak yine de her yerde bulunan bu yöntemlere çağrılarla kaldınız. Dünyanın sonu değil, “sıska kontrolörler” ruhuyla da değil.

ActiveRecord geri çağrıları, başka bir olası seçenektir, ancak bence iki farklı modeli birbirine çok yakın birleştirmeye meyilli olduğu için kişisel olarak sevmiyorum.

Rails 4, Observers dünyasında, başka bir kayıt oluşturulduktan / güncellendi / yok edildikten sonra yeni bir kayıt oluşturmak zorunda olsaydınız, hangi tasarım desenini kullanırdınız? Yağ kontrolörleri, ActiveRecord geri çağrıları veya tamamen başka bir şey mi?

Teşekkür ederim.


4
Bu soru için daha fazla yanıt gönderilmemesi beni gerçekten şaşırttı. Biraz rahatsız edici.
courtsimas

Yanıtlar:


82

Endişelere bir göz atın

Model dizininizde endişeler adlı bir klasör oluşturun. Oraya bir modül ekleyin:

module MyConcernModule
  extend ActiveSupport::Concern

  included do
    after_save :do_something
  end

  def do_something
     ...
  end
end

Sonra, after_save'ı çalıştırmak istediğiniz modellere şunları ekleyin:

class MyModel < ActiveRecord::Base
  include MyConcernModule
end

Ne yaptığınıza bağlı olarak, bu sizi gözlemci olmadan yakınlaştırabilir.


20
Bu yaklaşımla ilgili sorunlar var. Özellikle, modellerinizi temizlemez; include , modüldeki yöntemleri sınıfınıza kopyalar. Sınıf yöntemlerini bir modüle çıkarmak, bunları endişe ile gruplayabilir, ancak sınıf hala şişirilmiş gibidir.
Steven Soroka

15
Başlık 'şişkinliği nasıl en aza indirebilirim' değil '4.0 için Rails Gözlemci Alternatifleri'dir. Endişeler Steven'ın işi yapmaması nasıl? Ve hayır, 'şişkinliğin' bunun gözlemcilerin yerine geçmeyeceğinin bir nedeni olduğunu öne sürmek yeterince iyi değil. Topluluğa yardım etmek veya endişelerin neden gözlemcilerin yerine geçmeyeceğini açıklamak için daha iyi bir öneri bulmalısınız. Umarım her ikisini de
belirtirsiniz

10
Şişirme her zaman bir endişe kaynağıdır. Daha iyi bir alternatif, doğru bir şekilde uygulandığında, endişeleri, modellere sıkıca bağlı olmayan ayrı sınıflara çıkararak temizlemenizi sağlayan daha akıllıdır . Bu aynı zamanda izole bir şekilde test etmeyi çok daha kolay hale getirir
Steven Soroka

4
Bunu yapmak için bir Mücevher çekerek şişkinlik veya Tüm Uygulama şişkinliğini modelleyin - bireysel tercihlere bırakabiliriz. Ek öneri için teşekkürler.
AmcaAdam

Sadece IDE'nin otomatik tamamlama menüsünü şişirirdi, bu da birçok insan için iyi olmalı.
lulalala

33

Şimdi bir eklentide .

Ayrıca aşağıdaki gibi denetleyicileri verecek bir alternatif önerebilir miyim :

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 :: Bildirimler nasıl olur?
svoop

@svoop ActiveSupport::Notificationsjenerik alt / pub'a değil enstrümantasyona yöneliktir.
Kris

@Kris - haklısın. Öncelikle enstrümantasyon için kullanılır, ancak pub / sub için genel bir yöntem olarak kullanılmasını engelleyen şeyin ne olduğunu merak ediyorum? temel yapı taşlarını sağlıyor, değil mi? Başka bir deyişle, wisper için yukarı / aşağı yönleri ActiveSupport::Notificationsnelerdir?
gingerlime

Ben Notificationsçok kullanmadım ama Wispergüzel bir API ve 'global aboneler', 'önek' ve 'olay eşleme' gibi özellikleri olmayan söyleyebilirim Notifications. Gelecekte piyasaya sürülmesi, WisperSideKiq / Resque / Celluloid aracılığıyla zaman uyumsuz yayın yapılmasına da olanak tanıyacaktır. Ayrıca, potansiyel olarak, gelecekteki Rails sürümlerinde, API Notificationsdaha fazla enstrümantasyon odaklı olacak şekilde değişebilir.
Kris

21

Benim önerim, http://jamesgolick.com/2010/3/14/crazy-heretical-and-awesome-the-way-i-write-rails-apps.html adresindeki James Golick'in blog gönderisini okumaktır (nasıl görmezden gelmeyi deneyin) başlık seslerini utanmaz).

Geri gün tüm "yağ modeli, sıska denetleyicisi" oldu. Daha sonra yağ modelleri, özellikle test sırasında dev bir baş ağrısı haline geldi. Daha yakın zamanda, sıska modeller için itici güç oldu - her sınıfın bir sorumluluğu ele alması gerektiği ve bir modelin işi verilerinizi bir veritabanına devam ettirmektir. Peki tüm karmaşık iş mantığım nereye gidiyor? İş mantığı sınıflarında - işlemleri temsil eden sınıflar.

Bu yaklaşım, mantık karmaşıklaşmaya başladığında bataklığa (kıkırdağa) dönüşebilir. Kavram sağlam olsa da - test edilmesi ve hata ayıklaması zor olan geri aramalar veya gözlemcilerle örtülü olarak şeyleri tetiklemek yerine, modelinizin üstünde mantığı katmanlaştıran bir sınıfta şeyleri açıkça tetikleyin.


4
Son birkaç aydır bir proje için böyle bir şey yapıyorum. Çok sayıda küçük hizmetle sonuçlanırsınız, ancak test ve bakım kolaylığı kesinlikle dezavantajlardan ağır basar. Bu orta büyüklükteki sistemdeki oldukça kapsamlı özelliklerim hala 5 saniye sürüyor :)
Luca Spiller

Ayrıca PORO (Düz Eski Yakut Nesneler) veya hizmet nesneleri olarak da bilinir
Cyril Duchon-Doris

13

Aktif kayıt geri çağrılarını kullanmak, kuplajınızın bağımlılığını tersine çevirir. Örneğin, modelAbir CacheObservergözlemleme modelArayları 3 stiliniz varsa ve sorunsuz bir şekilde kaldırabilirsiniz CacheObserver. Şimdi, bunun yerine , rayların 4 olacağı Akayıttan CacheObserversonra manuel olarak çağırması gerektiğini söyleyin . Bağımlılığınızı taşıdınız, böylece güvenle kaldırabilirsiniz, AancakCacheObserver .

Şimdi, fildişi kulemden gözlemcinin gözlemlediği modele bağımlı olmasını tercih ediyorum. Denetleyicilerimi darmadağın edecek kadar umursuyorum? Benim için cevap hayır.

Muhtemelen gözlemciyi neden istediğiniz / ihtiyaç duyduğunuzu düşünmüşsünüz ve böylece gözlemcisine bağlı bir model oluşturmak korkunç bir trajedi değildir.

Ayrıca, her türlü gözlemcinin bir denetleyici eylemine bağımlı olması için (makul şekilde topraklanmış, bence) bir hoşnutsuzluğum var. Aniden gözlemcinizi, gözlemlemek istediğiniz modeli güncelleyebilecek herhangi bir denetleyici eylemine (veya başka bir modele) enjekte etmeniz gerekir. Uygulamanızın örnekleri yalnızca oluşturma / güncelleme denetleyicisi eylemleri, size daha fazla güç yoluyla değiştireceğini garanti ederseniz, bu bir ray uygulaması hakkında yapacağım bir varsayım değildir (iç içe formları, model iş mantığı güncelleme ilişkilendirmelerini vb. Göz önünde bulundurun).


1
@Agmin yorumlarınız için teşekkürler. Orada daha iyi bir tasarım deseni varsa bir Gözlemci kullanmaktan uzaklaştığım için mutluyum. Ben en çok nasıl benzer insanlar (önbellekleme hariç) sağlamak için kendi kod ve bağımlılıkları yapılandırmakla ilgileniyorum. Benim durumumda, özellikleri her güncellendiğinde bir modele yapılan değişiklikleri kaydetmek istiyorum. Bunu yapmak için bir Gözlemci kullanırdım. Şimdi bir yağ kontrolörü, AR geri arama veya aklıma gelmediğim başka bir şey arasında karar vermeye çalışıyorum. Şu anda ikisi de zarif görünmüyor.
kennyc

13

Wisper harika bir çözüm. Geri aramalar için kişisel tercihim, modeller tarafından tetiklendikleri, ancak etkinliklerin yalnızca bir istek geldiğinde dinlendiği, yani testlerde modeller kurarken geri aramaların tetiklenmesini istemiyorum, ancak bunları istiyorum kontrolörler söz konusu olduğunda ateşlenir. Wisper ile kurulum yapmak gerçekten çok kolay çünkü sadece bir blok içindeki olayları dinlemesini söyleyebilirsiniz.

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

9

Bazı durumlarda sadece Aktif Destek Enstrümantasyonunu kullanıyorum

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

4

Rails 3 Observers'a alternatifim, modelde tanımlanan bir geri çağırma kullanan ancak (yukarıdaki cevabında agmin durumları gibi) "bağımlılığı çevirme ... kuplajı" yöneten manuel bir uygulamadır.

Nesnelerim, gözlemcilerin kaydedilmesini sağlayan bir temel sınıftan miras alıyor:

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

(Kompozisyon kalıtım üzerine ruhuyla, yukarıdaki kod bir modüle yerleştirilebilir ve her modelde karıştırılabilir.)

Bir başlatıcı gözlemcileri kaydeder:

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

Her model daha sonra temel ActiveRecord geri çağrılarının ötesinde kendi gözlemlenebilir olaylarını tanımlayabilir. Örneğin, Kullanıcı modelim 2 etkinlik ortaya çıkarıyor:

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

Bu olaylar için bildirim almak isteyen her gözlemcinin (1) olayı ortaya koyan modele kaydolması ve (2) adı olayla eşleşen bir yöntemi olması gerekir. Tahmin edilebileceği gibi, birden fazla gözlemci aynı etkinliğe kaydolabilir ve (orijinal sorunun 2. paragrafına istinaden) bir gözlemci birkaç modeldeki olayları izleyebilir.

Aşağıdaki NotificationSender ve ProfilePictureCreator gözlemci sınıfları, çeşitli modellerde ortaya çıkan olaylar için yöntemler tanımlar:

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

Bir uyarı, tüm modellerde maruz kalan tüm olayların adlarının benzersiz olması gerektiğidir.


3

Gözlemcilerin kullanımdan kaldırılmasıyla ilgili sorunun, gözlemcilerin kendileri için kötü olmadıkları değil, istismar edildikleri olduğunu düşünüyorum.

Bu soruna Gözlemci modeli için sağlam bir çözüm olduğunda, bir gözlemcinin davranışını simüle etmek için geri aramalarınıza çok fazla mantık eklemeye veya sadece kodu hareket ettirmeye karşı dikkatli olurum.

Gözlemcileri kullanmak mantıklıysa, gözlemcileri kullanın. Sadece gözlemci mantığınızın örneğin SOLID gibi ses kodlama uygulamalarını izlediğinden emin olmanız gerektiğini anlamanız yeterlidir.

Eğer projenize geri eklemek istiyorsanız gözlemci mücevher rubygems'te mevcuttur. Https://github.com/rails/rails-observers

Bu kısa konuya bakın, tam kapsamlı tartışma olmasa da temel argümanın geçerli olduğunu düşünüyorum. https://github.com/rails/rails-observers/issues/2



2

Bunun yerine PORO kullanmaya ne dersiniz?

Bunun ardındaki mantık, 'tasarrufla ilgili ekstra eylemlerinizin muhtemelen iş mantığı olacağıdır. Bu, hem AR modellerinden (mümkün olduğunca basit olmalı) hem de kontrolörlerden (düzgün test etmek için rahatsız edici) ayrı tutmak istiyorum.

class LoggedUpdater

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

end

Ve sadece şöyle deyin:

LoggedUpdater.save!(user)

Ek kaydetme sonrası eylem nesneleri enjekte ederek üzerinde bile genişleyebilirsin

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

Ve 'ekstralara' bir örnek vermek. Gerçi onları biraz havalandırmak isteyebilirsiniz:

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

Bu yaklaşımı beğendiyseniz , Bryan Helmkamps 7 Patterns blog gönderisini okumanızı tavsiye ederim .

EDIT: Yukarıdaki çözüm gerektiğinde de işlem mantığı eklemeye izin verdiğini belirtmek gerekir. ActiveRecord ve desteklenen bir veritabanı ile:

class LoggedUpdater

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

end


-2

Aynı sezona sahibim! Bir çözüm buldum ActiveModel :: Kirli böylece model değişikliklerinizi takip edebilirsiniz!

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

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.