Ruby on rails 在Rails中是否有比观察者更直接的方式来执行pub/sub模式?

Ruby on rails 在Rails中是否有比观察者更直接的方式来执行pub/sub模式?,ruby-on-rails,ruby,publish-subscribe,Ruby On Rails,Ruby,Publish Subscribe,我有一个模型,它依赖于一个单独的、连接的模型 class Magazine < ActiveRecord::Base has_one :cover_image, dependent: :destroy, as: :imageable end class Image < ActiveRecord::Base belongs_to :imageable, polymorphic: true end class杂志

我有一个模型,它依赖于一个单独的、连接的模型

class Magazine < ActiveRecord::Base
  has_one :cover_image, dependent: :destroy, as: :imageable
end

class Image < ActiveRecord::Base
  belongs_to :imageable, polymorphic: true
end
class杂志
图像是多态的,可以附加到许多对象(页面和文章),而不仅仅是杂志

当相关图像发生任何变化时,杂志需要更新自身 该杂志还存储了一个可用于宣传的自身截图:

class Magazine < ActiveRecord::Base
  has_one :cover_image, dependent: :destroy, as: :imageable
  has_one :screenshot

  def generate_screenshot
    # go and create a screenshot of the magazine
  end
end
class杂志
现在,如果图像发生变化,该杂志还需要更新其屏幕截图。所以杂志真的需要知道图像什么时候出了问题

因此,我们可以天真地直接从封面图像触发屏幕截图更新
类映像
……然而,这张照片不应该代表杂志做任何事情 然而,图像可以用于许多不同的对象,并且确实不应该做特定于杂志的动作,因为这不是图像的责任。图像也可以附加到页面或文章上,不需要为它们做各种事情

“正常”rails方法是使用观察者 如果我们采用Rails(y)方法,那么我们可以创建第三方观察员,然后触发相关杂志上的事件:

class ImageObserver < ActiveRecord::Observer
  observe :image

  def after_save image
    Magazine.update_magazine_if_includes_image image
  end
end
class ImageObserver
然而,这对我来说是一个糟糕的解决方案

我们避免了因更新杂志而造成的图像负担,这很好,但我们实际上只是把问题抛到了下游。这个观察者是否存在并不明显,在杂志对象内部,不清楚图像的更新是否会触发杂志的更新,我们得到了一个奇怪的浮动对象,它具有真正属于杂志的逻辑

我不需要观察者——我只希望一个对象能够订阅另一个对象上的事件

有没有办法直接从另一个模型订阅一个模型的更改? 我更愿意让杂志直接订阅图片上的事件。因此,代码将改为:

class Magazine < ActiveRecord::Base
  ...

  Image.add_after_save_listener Magazine, :handle_image_after_save

  def self.handle_image_after_save image
    # determine if image belongs to magazine and if so update it
  end
end

class Image < ActiveRecord::Base
  ...

  def self.add_after_save_listener class_name, method
    @@after_save_listeners << [class_name, method]
  end

  after_save :notify_after_save_listeners

  def notify_after_save_listeners
    @@after_save_listeners.map{ |listener|
      class_name = listener[0]
      listener_method = listener[1]
      class_name.send listener_method
    }
end
class杂志
在初始化器中,我设置了Redis:

# config/initializers/redis.rb
uri = URI.parse ENV.fetch("REDISTOGO_URL", 'http://127.0.0.1:6379')
REDIS_CONFIG = { host: uri.host, port: uri.port, password: uri.password }
REDIS = Redis.new REDIS_CONFIG
在开发中,它将默认为我的本地redis安装,但在Heroku上,它将使用redis

然后我使用模型回调发布:

class MyModel < ActiveRecord::Base
  after_save { REDIS.publish 'my_channel', to_json }
end
现在,当您更新图像时,它将处理消息:

class Image < ActiveRecord::Base
  belongs_to :imageable, polymorphic: true
  after_save { REDIS.publish 'image_update_channel', to_json }
end
类映像
Rails中有一种实现pub/sub的机制

首先,您应该定义将发布事件的工具

class Image < ActiveRecord::Base
  ...

  after_save :publish_image_changed

  private

  def publish_image_changed
    ActiveSupport::Notifications.instrument('image.changed', image: self)
  end
end
我会试试看

使用public_send通知父类更改:

class BaseModel < ActiveRecord::Base
  has_one :child_model

  def respond_to_child
    # now generate the screenshot
  end
end


class ChildModel < ActiveRecord::Base                                                                           
  belongs_to :base_model

  after_update :alert_base                                                                                      

  def alert_base                                                                                                
    self.base_model.public_send( :respond_to_child )                                                            
  end                                                                                                           

end
classbasemodel
我同意观察者和回调。。。有一种有趣的宝石叫做wisper,它也有类似的功能。在这里查看:我已经实现了您在Laravel中讨论的模式,它非常容易创建,并且总体上运行良好。我能想到的唯一缺点是1)需要一个旁路机制2)调试,很难追溯什么时候被调用,很容易导致意想不到的后果,但我认为这可以用好的工具来弥补。这非常好。我喜欢它,我喜欢简单的酒吧/酒吧方法。然而,为什么要去Redis呢?除了SOA之外,我所建议的内存解决方案还有什么缺点?如果您像Heroku一样部署到云端,这会更可靠。Dynos会经常重启,redis会给你持久的信息。这很有趣,我给了你奖金,因为它绝对是最接近我想要的。不过,其中还有一些我不明白的地方——您是否推荐一些特别的文章来进一步探索这种方法?@PeterNixey,谢谢。这里有一篇好文章可以作为开头:。在用redis和Rails搜索pub sub时,您还可以找到其他文章。如果还有什么不清楚的地方,请随时发布另一个问题,我很乐意帮忙。谢谢
class Image < ActiveRecord::Base
  belongs_to :imageable, polymorphic: true
  after_save { REDIS.publish 'image_update_channel', to_json }
end
class Image < ActiveRecord::Base
  ...

  after_save :publish_image_changed

  private

  def publish_image_changed
    ActiveSupport::Notifications.instrument('image.changed', image: self)
  end
end
ActiveSupport::Notifications.subscribe('image.changed') do |*args|
  event = ActiveSupport::Notifications::Event.new(*args)
  image = event.payload[:image]

  # If you have no other cases than magazine, you can check it when you publish event.
  return unless image.imageable.is_a?(Magazine)

  MagazineImageUpdater.new(image.imageable).run
end
class BaseModel < ActiveRecord::Base
  has_one :child_model

  def respond_to_child
    # now generate the screenshot
  end
end


class ChildModel < ActiveRecord::Base                                                                           
  belongs_to :base_model

  after_update :alert_base                                                                                      

  def alert_base                                                                                                
    self.base_model.public_send( :respond_to_child )                                                            
  end                                                                                                           

end