web-dev-qa-db-ja.com

Rails 4.0のオブザーバーの代替

オブザーバーが公式に Rails 4.0から削除されました で、他の開発者が代わりに使用しているものに興味があります。 (抽出されたgemを使用する以外)。オブザーバーは確かに悪用され、時々扱いにくくなる可能性がありますが、キャッシュクリア以外にも多くのユースケースがありました。

たとえば、モデルへの変更を追跡する必要があるアプリケーションを考えてみましょう。オブザーバーは、モデルAの変更を簡単に監視し、データベースのモデルBでそれらの変更を記録できます。複数のモデルにわたる変更を監視したい場合は、1人のオブザーバーがそれを処理できます。

Rails 4では、他の開発者がObserverの代わりにその機能を再現するためにどのような戦略を使用しているのか興味があります。

個人的には、これらの変更が各モデルコントローラーの作成/更新/削除メソッドで追跡される、一種の「脂肪コントローラー」実装に傾倒しています。各コントローラーの動作はわずかに大きくなりますが、すべてのコードが1か所にあるため、読みやすさと理解に役立ちます。欠点は、いくつかのコントローラーに非常に類似したコードが散在していることです。そのコードをヘルパーメソッドに抽出することはオプションですが、それらのメソッドの呼び出しはどこにでも散らばっています。世界の終わりではなく、「スキニーコントローラー」の精神でもありません。

ActiveRecordコールバックは別の可能なオプションですが、私の意見では2つの異なるモデルをあまりにも密接に結合する傾向があるため個人的には好きではありません。

Rails 4、オブザーバーの世界では、別のレコードが作成/更新/破棄された後に新しいレコードを作成する必要がある場合、どのデザインパターンを使用しますか?ファットコントローラー、ActiveRecordコールバック、またはまったく別のものですか?

ありがとうございました。

149
kennyc

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

あなたが何をしているのかにもよりますが、これはオブザーバーなしであなたを近づけます。

77
UncleAdam

それらは 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
33
Kris

私の提案は、James Golickのブログ投稿 を読むことです。http://jamesgolick.com/2010/3/14/crazy-heretical-and-awesome-the-way-i-write-Rails -apps.html (控えめなタイトルの音を無視してみてください)。

当時はすべて「脂肪モデル、スキニーコントローラー」でした。その後、特にテスト中に、脂肪モデルは大きな頭痛の種になりました。最近では、プッシュはスキニーモデル用です。各クラスは1つの責任を処理し、モデルの仕事はデータをデータベースに永続化するという考え方です。では、複雑なビジネスロジックはどこに行き着くのでしょうか?ビジネスロジッククラス-トランザクションを表すクラス。

ロジックが複雑になり始めると、このアプローチは泥沼になります。ただし、コンセプトは健全です-コールバックやテストやデバッグが難しいオブザーバーで暗黙的にトリガーするのではなく、モデルの上にロジックを階層化するクラスで明示的にトリガーします。

21
MikeJ

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
13
opsb

場合によっては、単に 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
8
Panic

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つの注意点は、すべてのモデルで公開されるすべてのイベントの名前は一意でなければならないということです。

4
Mark Schneider

オブザーバーが廃止される問題は、オブザーバー自身が悪かったということではなく、虐待されていることだと思います。

この問題に対する適切な解決策がObserverパターンに既に存在する場合、コールバックにロジックを追加しすぎたり、単にコードを移動してオブザーバーの振る舞いをシミュレートしたりしないように注意します。

オブザーバーを使用することが理にかなっている場合は、必ずオブザーバーを使用してください。オブザーバーロジックが、たとえばSOLIDなどのサウンドコーディングプラクティスに従っていることを確認する必要があることを理解してください。

オブザーバーgemは、プロジェクトに追加したい場合、rubygemsで利用可能です https://github.com/Rails/rails-observers

この短いスレッドを参照してください。完全な包括的な議論ではありませんが、基本的な議論は有効だと思います。 https://github.com/Rails/rails-observers/issues/2

3
hraynaud

https://github.com/TiagoCardoso1983/association_observers を試すことができます。 Rails 4(まだリリースされていません)のテストはまだ行われておらず、さらにコラボレーションが必要ですが、それがうまくいくかどうかを確認できます。

2
ChuckE

代わりに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
1
Houen

インスタンスメソッドchanged?およびObservableのため、Ruby標準ライブラリの changed モジュールをアクティブレコードのようなオブジェクトで使用できないことに注意してください ActiveModel::Dirty のものと衝突します。

Rails 2.3.2 のバグレポート

0
Artur Beljajev