オブザーバーが公式に Rails 4.0から削除されました で、他の開発者が代わりに使用しているものに興味があります。 (抽出されたgemを使用する以外)。オブザーバーは確かに悪用され、時々扱いにくくなる可能性がありますが、キャッシュクリア以外にも多くのユースケースがありました。
たとえば、モデルへの変更を追跡する必要があるアプリケーションを考えてみましょう。オブザーバーは、モデルAの変更を簡単に監視し、データベースのモデルBでそれらの変更を記録できます。複数のモデルにわたる変更を監視したい場合は、1人のオブザーバーがそれを処理できます。
Rails 4では、他の開発者がObserverの代わりにその機能を再現するためにどのような戦略を使用しているのか興味があります。
個人的には、これらの変更が各モデルコントローラーの作成/更新/削除メソッドで追跡される、一種の「脂肪コントローラー」実装に傾倒しています。各コントローラーの動作はわずかに大きくなりますが、すべてのコードが1か所にあるため、読みやすさと理解に役立ちます。欠点は、いくつかのコントローラーに非常に類似したコードが散在していることです。そのコードをヘルパーメソッドに抽出することはオプションですが、それらのメソッドの呼び出しはどこにでも散らばっています。世界の終わりではなく、「スキニーコントローラー」の精神でもありません。
ActiveRecordコールバックは別の可能なオプションですが、私の意見では2つの異なるモデルをあまりにも密接に結合する傾向があるため個人的には好きではありません。
Rails 4、オブザーバーの世界では、別のレコードが作成/更新/破棄された後に新しいレコードを作成する必要がある場合、どのデザインパターンを使用しますか?ファットコントローラー、ActiveRecordコールバック、またはまったく別のものですか?
ありがとうございました。
Concerns をご覧ください
モデルディレクトリに懸念と呼ばれるフォルダーを作成します。そこにモジュールを追加します:
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
あなたが何をしているのかにもよりますが、これはオブザーバーなしであなたを近づけます。
それらは plugin になりました。
代替 もお勧めできますか?
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
私の提案は、James Golickのブログ投稿 を読むことです。http://jamesgolick.com/2010/3/14/crazy-heretical-and-awesome-the-way-i-write-Rails -apps.html (控えめなタイトルの音を無視してみてください)。
当時はすべて「脂肪モデル、スキニーコントローラー」でした。その後、特にテスト中に、脂肪モデルは大きな頭痛の種になりました。最近では、プッシュはスキニーモデル用です。各クラスは1つの責任を処理し、モデルの仕事はデータをデータベースに永続化するという考え方です。では、複雑なビジネスロジックはどこに行き着くのでしょうか?ビジネスロジッククラス-トランザクションを表すクラス。
ロジックが複雑になり始めると、このアプローチは泥沼になります。ただし、コンセプトは健全です-コールバックやテストやデバッグが難しいオブザーバーで暗黙的にトリガーするのではなく、モデルの上にロジックを階層化するクラスで明示的にトリガーします。
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
場合によっては、単に Active Support Instrumentation を使用します
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
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
1つの注意点は、すべてのモデルで公開されるすべてのイベントの名前は一意でなければならないということです。
オブザーバーが廃止される問題は、オブザーバー自身が悪かったということではなく、虐待されていることだと思います。
この問題に対する適切な解決策がObserverパターンに既に存在する場合、コールバックにロジックを追加しすぎたり、単にコードを移動してオブザーバーの振る舞いをシミュレートしたりしないように注意します。
オブザーバーを使用することが理にかなっている場合は、必ずオブザーバーを使用してください。オブザーバーロジックが、たとえばSOLIDなどのサウンドコーディングプラクティスに従っていることを確認する必要があることを理解してください。
オブザーバーgemは、プロジェクトに追加したい場合、rubygemsで利用可能です https://github.com/Rails/rails-observers
この短いスレッドを参照してください。完全な包括的な議論ではありませんが、基本的な議論は有効だと思います。 https://github.com/Rails/rails-observers/issues/2
https://github.com/TiagoCardoso1983/association_observers を試すことができます。 Rails 4(まだリリースされていません)のテストはまだ行われておらず、さらにコラボレーションが必要ですが、それがうまくいくかどうかを確認できます。
代わりに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
インスタンスメソッドchanged?
およびObservable
のため、Ruby標準ライブラリの changed
モジュールをアクティブレコードのようなオブジェクトで使用できないことに注意してください ActiveModel::Dirty
のものと衝突します。
Rails 2.3.2 のバグレポート