观察者和控制器之间如何通信

发布于 2024-12-05 11:10:19 字数 2053 浏览 0 评论 0原文

我知道 Rails 观察者不应该直接访问控制器。这是有道理的,不知道观察者将从什么上下文中被调用。然而,我有一个案例,我认为值得两者之间进行间接沟通,我想知道如何实现它。

记录和写入分析事件

我想使用观察者来触发 Google Analytics(分析)中的某些事件。当前的工作方式是应用程序控制器有一个记录事件的方法,然后 application.html.erb 模板将相关的 javascript 打印到页面中:

class ApplicationController < ActionController::Base

  def logGAEvent category, action, opt_hash={}
    event = { :category => category,
              :action => action,
              :label => opt_hash[:label],
              :value => opt_hash[:value]}
    (session[:ga_events] ||= []) << event
  end

end

Application.html.erb< /strong>

<html>
  <head>
    ...

    <script type="text/javascript">
      <%= print_ga_events_js %>
    </script>

  </head>
  ...
</html>

示例事件:

class UsersController < ApplicationController
  ...

  def create
     ...
     if @new_user
       logGAEvent('user', 'signup')
     end
  end
end

为什么我想在观察者和控制器之间进行通信

目前,在某些值得注意的事件(有人注册)之后,控制器中会调用 logGAEvent 方法,创建新的配置文件等)。

将这些事件的大部分抽象到观察者中将是一个更好的模式。这将整理控制器,也将减少跟踪的临时性。然而,一旦它们进入观察者,模板仍然需要有一种方法来访问观察者的数据并将其打印出来。

我希望能够做什么

由于观察者不应该真正了解控制器,所以我想做的是将这些事件记录到一次性缓冲区中,以便它们在每次调用结束,但控制器也可以访问它们以写入文档:

class UserObserver < ActiveRecord::Observer
  after_create user
    # I have no idea what would constitue request.buffer but this is
    # the pattern I'm looking for
    request.buffer.ga_events << createGAEvent('user', 'create')
  end
end
end

application.html.erb(使用缓冲区)

Application.html.erb

<html>
  <head>
    ...
    <script type="text/javascript">
    <%= print_ga_events_js(request.buffer.ga_events) %>
    </script>

  </head>
  ...
</html>

这是在有可能吗?对我来说,这似乎不是一个不合理的设计模式,它会让应用程序更加干净。

I understand that a Rails observer should not have direct access to the controller. That makes sense, there is no telling what context the observer is going to be called from. However I have a case that I think merits indirect communication between the two and I'm wondering how to achieve it.

logging and writing analytics events

I would like to use an observer to trigger certain events in Google Analytics. The way this currently works is that the application controller has a method that logs the event and then the application.html.erb template prints the relevant javascript into the page:

class ApplicationController < ActionController::Base

  def logGAEvent category, action, opt_hash={}
    event = { :category => category,
              :action => action,
              :label => opt_hash[:label],
              :value => opt_hash[:value]}
    (session[:ga_events] ||= []) << event
  end

end

Application.html.erb

<html>
  <head>
    ...

    <script type="text/javascript">
      <%= print_ga_events_js %>
    </script>

  </head>
  ...
</html>

Example event:

class UsersController < ApplicationController
  ...

  def create
     ...
     if @new_user
       logGAEvent('user', 'signup')
     end
  end
end

Why I would like to communicate between an observer and the controller

At the moment the logGAEvent method is called in controllers after certain noteworthy events (someone signs up, creates a new profile etc).

It would be a far nicer pattern to abstract the majority of these events into an observer. This would tidy up the controller and would also make the tracking less ad-hoc. However, once they go into the observer, there still needs to be a way for the template to access the observer's data and print it out.

What I would like to be able to do

Since the observer shouldn't really know about the controller, what I would like to do is record these events into a one-time buffer so that they are flushed at the end of each call but they are also accessible to the controller to write into the document:

class UserObserver < ActiveRecord::Observer
  after_create user
    # I have no idea what would constitue request.buffer but this is
    # the pattern I'm looking for
    request.buffer.ga_events << createGAEvent('user', 'create')
  end
end
end

application.html.erb (using buffer)

Application.html.erb

<html>
  <head>
    ...
    <script type="text/javascript">
    <%= print_ga_events_js(request.buffer.ga_events) %>
    </script>

  </head>
  ...
</html>

Is this in some way possible? It doesn't seem like an unreasonable design pattern to me and it would make the app much cleaner.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

清欢 2024-12-12 11:10:19

我在向应用程序添加 mixpanel 事件时遇到了类似的问题。我解决这个问题的方法是使用控制器过滤器而不是观察者。您会得到与观察者非常相似的行为,但通过过滤器,您实际上可以访问请求和响应对象。

这是一个示例过滤器,大致改编自我的过滤器,但经过足够的更改,我不会将其称为测试代码:

module GoogleAnalytics
  class TrackCreation   
    def self.for(*args)
        self.new(*args)
    end

    # pass in the models we want to track creation of
    def initialize(*args)
        @models = args.map do |name|
            name.to_s.tableize.singularize
        end
    end

    # This is called after the create action has happened at the controller level
    def filter(controller)
        #
        controller.params.select{ |model, model_params| filter_for.include? model }.each do |model, model_params|
            #you may want to perform some sort of validation that the creation was successful - say check the response code
            track_event(model, "create", model_params)
        end
  end

    def track_event(category, action, *args)
        flash[:ga] ||= []
        flash[:ga] << ["_trackEvent", type, args]
    end

    def filter_for
        @models
    end
  end
end

class ApplicationController < ActionController::Base
after_filter GoogleAnalytics::TrackCreation.for(:foo, :bar), :only => :create
end

在过滤器中,我只是设置了一个自定义闪光灯: flash[:mixpanel] ||= [] 。 Flash 很好,因为它会自动清除请求之间的内容。我遇到了一些问题:

  1. 注意 flashflash.now。当您跟踪正在创建、销毁的资源或任何其他将发生重定向的情况时,Flash 非常有用。但如果您确实想在即时响应中跟踪事件,您将需要使用 flash.now。
  2. after_filter 可以访问响应,但您无法更改该响应。因此,如果您想吐出当前请求的跟踪事件,您需要在构建响应之前执行此操作。

要实际输出分析代码本身,只需更新您的 flash 部分以获取 flash[:ga] 中的每个元素并将其写出。

I had a similar issue with adding mixpanel events to an application. The way I solved it was I used Controller filters instead of observers. You get very similar sorts of behavior to an observer but with a filter you actually have access to the request and response objects.

Here's an example filter, loosely adapted from mine but with enough changes I wouldn't call it tested code:

module GoogleAnalytics
  class TrackCreation   
    def self.for(*args)
        self.new(*args)
    end

    # pass in the models we want to track creation of
    def initialize(*args)
        @models = args.map do |name|
            name.to_s.tableize.singularize
        end
    end

    # This is called after the create action has happened at the controller level
    def filter(controller)
        #
        controller.params.select{ |model, model_params| filter_for.include? model }.each do |model, model_params|
            #you may want to perform some sort of validation that the creation was successful - say check the response code
            track_event(model, "create", model_params)
        end
  end

    def track_event(category, action, *args)
        flash[:ga] ||= []
        flash[:ga] << ["_trackEvent", type, args]
    end

    def filter_for
        @models
    end
  end
end

class ApplicationController < ActionController::Base
after_filter GoogleAnalytics::TrackCreation.for(:foo, :bar), :only => :create
end

Within the filter I just set up a custom flash: flash[:mixpanel] ||= []. Flash is nice because it will automagically clear contents between requests. There are a couple gotchas I ran into:

  1. Be aware of the difference between flash and flash.now. Flash is great when you are tracking a resource being created, destroyed, or any other situation where a redirect will happen. But if you actually want to track an event in the immediate response you'll want to use flash.now.
  2. An after_filter has access to the response, but you don't get to change that response. So if you want to spit out tracking events for the current request you need to do it before the response is built.

To actually output the analytics code itself just update your flash partial to take each element in flash[:ga] and write it out.

面犯桃花 2024-12-12 11:10:19

这不是有点违反模型-视图-控制器原则吗?观察者属于模型,因此它无法访问会话/请求等。
但是,您可以在模型中创建一个实例变量,用于存储 GA 事件,并将所有业务逻辑整齐地封装在观察者中,然后在控制器中检索该变量...

例如,

class User < ActiveRecord::Base

  attr_accessor :ga_events
  @ga_events=[]

  #rest of User class
end

class UserObserver < ActiveRecord::Observer
  def after_create(user)
    user.ga_events << createGAEvent('user', 'create')
  end
end

class UsersController < ApplicationController
  ...
  def create
     ...
     if @new_user
       @[email protected]_events
     end
  end
end

<html>
  <head>
    ...
    <script type="text/javascript">
    <%= print_ga_events_js(@ga_events) %>
    </script>

  </head>
  ...
</html>

Whatchya 认为? :)

Isn't this a bit against the model-view-controller principle? The observer belongs to the model, so it can't have access to the session/request/etc.
However, you could create an instance variable in the model that stores the GA events with all the business logic neatly encapsulated in an observer, and then retrieve that in a controller...

e.g.

class User < ActiveRecord::Base

  attr_accessor :ga_events
  @ga_events=[]

  #rest of User class
end

class UserObserver < ActiveRecord::Observer
  def after_create(user)
    user.ga_events << createGAEvent('user', 'create')
  end
end

class UsersController < ApplicationController
  ...
  def create
     ...
     if @new_user
       @[email protected]_events
     end
  end
end

<html>
  <head>
    ...
    <script type="text/javascript">
    <%= print_ga_events_js(@ga_events) %>
    </script>

  </head>
  ...
</html>

Whatchya reckon? :)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文