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

时间:2011-09-17 11:08:34

标签: ruby-on-rails google-analytics

我知道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

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

这在某种程度上是可能的吗?它对我来说似乎不是一种不合理的设计模式,它会使应用程序更清晰。

2 个答案:

答案 0 :(得分:3)

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

这是一个示例过滤器,从我的松散改编,但有足够的变化,我不会称之为测试代码:

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可以访问响应,但您无法更改该响应。因此,如果您想为当前请求吐出跟踪事件,则需要在构建响应之前执行此操作。
  3. 要实际输出分析代码本身,只需更新闪存部分以获取闪存[:ga]中的每个元素并将其写出来。

答案 1 :(得分:0)

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

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
       @ga_events=@user.ga_events
     end
  end
end

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

  </head>
  ...
</html>
Whatchya估计? :)