ruby-on-rails - 在 Rails 中是否有比 Observers 更直接的方式来执行发布/订阅模式?

标签 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 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 Image < ActiveRecord::Base
  belongs_to :imageable, polymorphic: true
  after_save { update_any_associated_magazine }

  def update_any_associated_magazine
    # figure out if this belongs to a magazine and trigger
    # screenshot to regenerate
  end
end

...但是图片不应该代表杂志做事

然而,图片可以用于许多不同的对象,实际上不应该对杂志进行特定的操作,因为这不是图片的责任。该图像也可能附加到页面或文章,并且不需要为它们做各种事情。

“正常”的 rails 方法是使用观察者

如果我们采用 Rails(y) 方法,那么我们可以创建一个第三方观察器,然后触发相关杂志上的事件:

class ImageObserver < ActiveRecord::Observer
  observe :image

  def after_save image
    Magazine.update_magazine_if_includes_image image
  end
end

然而,这对我来说是一个糟糕的解决方案。

我们通过更新杂志避免了 Image 的负担,这很棒,但我们实际上只是将问题推到了下游。这个观察者的存在并不明显,在 Magazine 对象内部不清楚图像的更新实际上会触发对杂志的更新,我们有一个奇怪的 float 对象,它具有真正只属于 Magazine 的逻辑。

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

有什么方法可以直接从另一个模型订阅一个模型的更改吗?

我更愿意做的是让杂志直接订阅图片上的事件。所以代码看起来像:

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

这是一种有效的方法吗?如果不是,为什么不呢?

这种模式对我来说似乎很合理。它使用类变量和方法,因此不会对特定实例可用做出任何假设。

然而,我现在已经足够成熟和聪明了,我知道如果一些看似显而易见的事情还没有在 Rails 中完成,那可能是有充分理由的。

我觉得这很酷。它有什么问题呢?为什么我在第三方对象中看到的所有其他解决方案都是草案来处理事情?这行得通吗?

最佳答案

我使用 Redis:

在初始化程序中我设置了 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 To Go。

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

class MyModel < ActiveRecord::Base
  after_save { REDIS.publish 'my_channel', to_json }
end

然后我可以从任何地方订阅,例如我用来使用 Event Source 推送事件的 Controller

class Admin::EventsController < Admin::BaseController
  include ActionController::Live

  def show
    response.headers["Content-Type"] = "text/event-stream"

    REDIS.psubscribe params[:event] do |on|
      on.pmessage do |pattern, event, data|
        response.stream.write "event: #{event}\n"
        response.stream.write "data: #{data}\n\n"
      end
    end
  rescue IOError => e
    logger.info "Stream closed: #{e.message}"
  ensure
    redis.quit
    response.stream.close
  end
end

Redis 非常适合灵活的发布/订阅。我在 Controller 中的代码可以放在任何地方,比如在初始化程序中:

# config/initializers/subscribers.rb

REDIS.psubscribe "image_update_channel" do |on|
  on.pmessage do |pattern, event, data|
    image = Image.find data['id']
    image.imageable # update that shiz
  end
end

现在它将在您更新图像时处理消息:

class Image < ActiveRecord::Base
  belongs_to :imageable, polymorphic: true
  after_save { REDIS.publish 'image_update_channel', to_json }
end

关于ruby-on-rails - 在 Rails 中是否有比 Observers 更直接的方式来执行发布/订阅模式?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24962319/

相关文章:

ruby-on-rails - 在 Devise on Rails 中使用单独的身份验证模型

c# - react 性扩展 - 引发异步事件并订阅特定线程

xmpp - xmpp 书签的自动加入选项不起作用

ruby-on-rails - 使用地理编码器 gem 和带有 RGeo 的 PostGIS 数据库对地址进行地理编码

mysql - 与 Rails 一起迷失 has_many : through multiple search

ruby-on-rails - 将文件上传到另一个 Rails 应用程序的最佳方式是什么?

ruby-on-rails - 在 Ruby 1.9.2 中使用 brew

ruby - Ruby 的非线性或二次规划求解器

ruby-on-rails - 在 rails 3.2 中使用 paypal 预先批准的付款

ruby-on-rails - Ruby on Rails -- Faye 框架 -- private_pub