Ruby-on-rails 4.0 的 Rails Observer 替代品
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/15165260/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Rails Observer Alternatives for 4.0
提问by kennyc
With Observers officially removed from Rails 4.0, I'm curious what other developers are using in their place. (Other than using the extracted gem.) While Observers were certainly abused and could easily become unwieldily at times, there were many use-cases outside of just cache-clearing where they were beneficial.
随着观察者正式从 Rails 4.0 中删除,我很好奇其他开发人员正在使用什么来代替他们。(除了使用提取的 gem。)虽然 Observers 肯定会被滥用并且有时很容易变得笨拙,但除了缓存清除之外,还有许多用例是有益的。
Take, for example, an application that needs to track changes to a model. An Observer could easily watch for changes on Model A and record those changes with Model B in the database. If you wanted to watch for changes across several models, then a single observer could handle that.
以需要跟踪模型更改的应用程序为例。观察者可以轻松地观察模型 A 的变化,并在数据库中记录模型 B 的这些变化。如果您想观察多个模型之间的变化,那么一个观察者就可以处理。
In Rails 4, I'm curious what strategies other developers are using in place of Observers to recreate that functionality.
在 Rails 4 中,我很好奇其他开发人员使用什么策略来代替 Observers 来重新创建该功能。
Personally, I'm leaning towards a sort of "fat controller" implementation, where these changes are tracked in each models controller's create/update/delete method. While it bloats the behavior of each controller slightly, it does help in readability and understanding as all the code is in one place. The downside is that there's now code that is very similar scattered throughout several controllers. Extracting that code into helper methods is an option, but you're still left with calls to those methods littered everywhere. Not the end of the world, but not quite in the spirit of "skinny controllers" either.
就我个人而言,我倾向于一种“胖控制器”实现,其中在每个模型控制器的创建/更新/删除方法中跟踪这些更改。虽然它稍微增加了每个控制器的行为,但它确实有助于提高可读性和理解性,因为所有代码都在一个地方。缺点是现在有非常相似的代码分散在几个控制器中。将该代码提取到辅助方法中是一种选择,但您仍然需要调用那些随处可见的方法。不是世界末日,但也不完全符合“瘦控制器”的精神。
ActiveRecord callbacks are another possible option, though one I don't personally like as it tends to couple two different models too closely together in my opinion.
ActiveRecord 回调是另一种可能的选择,尽管我个人并不喜欢它,因为在我看来,它倾向于将两个不同的模型过于紧密地结合在一起。
So in the Rails 4, no-Observers world, if you had to create a new record after another record was created/updated/destroyed, what design pattern would you use? Fat controllers, ActiveRecord callbacks, or something else entirely?
因此,在 Rails 4 的无观察者世界中,如果您必须在创建/更新/销毁另一条记录后创建一条新记录,您会使用什么设计模式?胖控制器、ActiveRecord 回调或其他完全不同的东西?
Thank you.
谢谢你。
回答by UncleAdam
Take a look at Concerns
看看顾虑
Create a folder in your models directory called concerns. Add a module there:
在您的模型目录中创建一个名为关注的文件夹。在那里添加一个模块:
module MyConcernModule
extend ActiveSupport::Concern
included do
after_save :do_something
end
def do_something
...
end
end
Next, include that in the models you wish to run the after_save in:
接下来,将其包含在您希望在其中运行 after_save 的模型中:
class MyModel < ActiveRecord::Base
include MyConcernModule
end
Depending on what you're doing, this might get you close without observers.
根据你在做什么,这可能会让你在没有观察者的情况下接近。
回答by Kris
They are in a pluginnow.
他们现在在一个插件中。
Can I also recommend an alternativewhich will give you controllers like:
我还可以推荐一个替代方案,它会给你这样的控制器:
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
回答by MikeJ
My suggestion is to read James Golick's blog post at http://jamesgolick.com/2010/3/14/crazy-heretical-and-awesome-the-way-i-write-rails-apps.html(try to ignore how immodest the title sounds).
我的建议是阅读 James Golick 的博客文章,网址为http://jamesgolick.com/2010/3/14/crazy-heretical-and-awesome-the-way-i-write-rails-apps.html(尽量忽略如何标题听起来不谦虚)。
Back in the day it was all "fat model, skinny controller". Then the fat models became a giant headache, especially during testing. More recently the push has been for skinny models -- the idea being that each class should be handling one responsibility and a model's job is to persist your data to a database. So where does all my complex business logic end up? In business logic classes -- classes that represent transactions.
在过去,它都是“胖模型,瘦控制器”。然后脂肪模型成为一个巨大的头痛,尤其是在测试期间。最近推动了瘦模型——这个想法是每个类都应该处理一个责任,而模型的工作是将您的数据保存到数据库中。那么我所有复杂的业务逻辑最终会在哪里呢?在业务逻辑类中——表示事务的类。
This approach can turn into a quagmire (giggity) when the logic starts getting complicated. The concept is sound though -- instead of triggering things implicitly with callbacks or observers that are hard to test and debug, trigger things explicitly in a class that layers logic on top of your model.
当逻辑开始变得复杂时,这种方法可能会变成泥潭(giggity)。虽然这个概念是合理的——而不是使用难以测试和调试的回调或观察者隐式触发事物,而是在模型顶部分层逻辑的类中显式触发事物。
回答by agmin
Using active record callbacks simply flips the dependency of your coupling. For instance, if you have modelAand a CacheObserverobserving modelArails 3 style, you can remove CacheObserverwith no issue. Now, instead say Ahas to manually invoke the CacheObserverafter save, which would be rails 4. You've simply moved your dependency so you can safely remove Abut not CacheObserver.
使用活动记录回调只会翻转耦合的依赖关系。例如,如果您有modelA一个CacheObserverObservation modelArails 3 样式,则可以毫无问题地将其移除CacheObserver。现在,改为 sayA必须手动调用CacheObserverafter save,这将是 rails 4。您已经简单地移动了您的依赖项,因此您可以安全地删除A但不是CacheObserver.
Now, from my ivory tower I prefer the observer to be dependent on the model it's observing. Do I care enough to clutter up my controllers? For me, the answer is no.
现在,从我的象牙塔来看,我更喜欢观察者依赖于它正在观察的模型。我是否足够在意弄乱我的控制器?对我来说,答案是否定的。
Presumably you've put some thought into why you want/need the observer, and thus creating a model dependent upon its observer is not a terrible tragedy.
大概您已经考虑过为什么需要/需要观察者,因此创建一个依赖于观察者的模型并不是一个可怕的悲剧。
I also have a (reasonably grounded, I think) distaste for any sort of observer being dependent on a controller action. Suddenly you have to inject your observer in any controller action (or another model) that may update the model you want observed. If you can guarantee your app will only ever modify instances via create/update controller actions, more power to you, but that's not an assumption I would make about a rails application (consider nested forms, model business logic updating associations, etc.)
我也对任何依赖控制器动作的观察者(我认为是合理的)感到厌恶。突然之间,您必须将观察者注入任何可能更新您想要观察的模型的控制器操作(或其他模型)中。如果您可以保证您的应用程序只会通过创建/更新控制器操作修改实例,那么您将获得更多权力,但这不是我对 Rails 应用程序所做的假设(考虑嵌套表单、模型业务逻辑更新关联等)
回答by opsb
Wisperis a great solution. My personal preference for callbacks is that they're fired by the models but the events are only listened to when a request comes in i.e. I don't want callbacks fired while I'm setting up models in tests etc. but I do want them fired whenever controllers are involved. This is really easy to setup with Wisper because you can tell it to only listen to events inside a block.
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
回答by Panic
In some cases I simply use Active Support Instrumentation
在某些情况下,我只是使用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
回答by Mark Schneider
My alternative to Rails 3 Observers is a manual implementation which utilizes a callback defined within the model yet manages to (as agmin states in his answer above) "flip the dependency...coupling".
我对 Rails 3 Observers 的替代方案是手动实现,它利用模型中定义的回调,但设法(如 agmin 在上面的回答中所述)“翻转依赖...耦合”。
My objects inherit from a base class which provides for registering 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
(Granted, in the spirit of composition over inheritance, the above code could be placed in a module and mixed in each model.)
(当然,本着组合胜于继承的精神,上述代码可以放在一个模块中并混合在每个模型中。)
An initializer registers observers:
初始化程序注册观察者:
User.add_observer(NotificationSender)
User.add_observer(ProfilePictureCreator)
Each model can then define its own observable events, beyond the basic ActiveRecord callbacks. For instance, my User model exposes 2 events:
除了基本的 ActiveRecord 回调之外,每个模型然后可以定义自己的可观察事件。例如,我的 User 模型公开了 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
Any observer that wishes to receive notifications for those events merely needs to (1) register with the model that exposes the event and (2) have a method whose name matches the event. As one might expect, multiple observers can register for the same event, and (in reference to the 2nd paragraph of the original question) an observer can watch for events across several models.
任何希望接收这些事件通知的观察者只需要 (1) 向暴露事件的模型注册,以及 (2) 拥有一个名称与事件匹配的方法。正如人们所料,多个观察者可以注册同一个事件,并且(参考原始问题的第 2 段)一个观察者可以跨多个模型观察事件。
The NotificationSender and ProfilePictureCreator observer classes below define methods for the events exposed by various models:
下面的 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
One caveat is that the names of all events exposed across all the models must be unique.
一个警告是所有模型中公开的所有事件的名称必须是唯一的。
回答by hraynaud
I think the the issue with Observers being deprecated is not that observers were bad in and of themselves but that they were being abused.
我认为观察者被弃用的问题不是观察者本身不好,而是他们被滥用了。
I would caution against adding too much logic in your callbacks or simply moving code around to simulate the behavior of an observer when there is already a sound solution to this problem the Observer pattern.
我会告诫不要在你的回调中添加太多的逻辑或者只是移动代码来模拟观察者的行为,当观察者模式这个问题已经有了一个合理的解决方案时。
If it makes sense to use observers then by all means use observers. Just understand that you will need to make sure that your observer logic follows sound coding practices for example SOLID.
如果使用观察者有意义,那么一定要使用观察者。只要明白您需要确保您的观察者逻辑遵循合理的编码实践,例如 SOLID。
The observer gem is available on rubygems if you want to add it back to your project https://github.com/rails/rails-observers
如果您想将其添加回项目https://github.com/rails/rails-observers,则观察者 gem 在 rubygems 上可用
see this brief thread, while not full comprehensive discussion I think the basic argument is valid. https://github.com/rails/rails-observers/issues/2
看到这个简短的主题,虽然不是全面全面的讨论,但我认为基本论点是有效的。 https://github.com/rails/rails-observers/issues/2
回答by Houen
How about using a PORO instead?
改用PORO怎么样?
The logic behind this is that your 'extra actions on save' are likely going to be business logic. This I like to keep separate from both AR models (which should be as simple as possible) and controllers (which are bothersome to test properly)
这背后的逻辑是您的“保存时的额外操作”可能是业务逻辑。我喜欢将这与 AR 模型(应该尽可能简单)和控制器(很难正确测试)分开
class LoggedUpdater
def self.save!(record)
record.save!
#log the change here
end
end
And simply call it as such:
并简单地这样称呼它:
LoggedUpdater.save!(user)
You could even expand on it, by injecting extra post-save action objects
您甚至可以通过注入额外的保存后操作对象来扩展它
LoggedUpdater.save(user, [EmailLogger.new, MongoLogger.new])
And to give an example of the 'extras'. You might want to spiffy them up a bit though:
并举一个“额外”的例子。不过,您可能想稍微修饰一下它们:
class EmailLogger
def call(msg)
#send email with msg
end
end
If you like this approach, I recommend a read of Bryan Helmkamps 7 Patternsblog post.
如果您喜欢这种方法,我建议您阅读Bryan Helmkamps 7 Patterns博客文章。
EDIT: I should also mention that the above solution allows for adding transaction logic as well when needed. E.g. with ActiveRecord and a supported database:
编辑:我还应该提到,上述解决方案还允许在需要时添加事务逻辑。例如,使用 ActiveRecord 和受支持的数据库:
class LoggedUpdater
def self.save!([records])
ActiveRecord::Base.transaction do
records.each(&:save!)
#log the changes here
end
end
end
回答by ChuckE
You could try https://github.com/TiagoCardoso1983/association_observers. It is not yet tested for rails 4 (which wasn't launched yet), and needs some more collaboration, but you can check if it does the trick for you.
你可以试试https://github.com/TiagoCardoso1983/association_observers。它尚未针对 rails 4(尚未发布)进行测试,需要更多协作,但您可以检查它是否适合您。

