マーケターの味方 rack-tracker でWebTrackingを前に進めよう
最近はマーケティング部でエンジニアをしている加藤です。
この記事はMoneyForward Advent Calendar 2015の9日目(えっ?)の記事です。
私はもっぱらRubyでコードを書いていましたが、しばしばマーケティング方面の方々から「リスティング、アフィリエイト、リターゲティング、行動分析などのタグをいろんな場所において欲しい」という要望が稀によくあります。
いままで、やっつけ仕事感覚でpartial
として書くことが多かったのですが最近知ったrack-tracker
というrack middleware
が便利だったのでご紹介。
Usage
rack-trackerはrack-middleware google-analyticsのみならず、色々なtagが出力できる
こんな感じで設定すると
config.middleware.use(Rack::Tracker) do
handler :google_analytics, { tracker: 'U-XXXXX-Y' }
end
各ページに、こんな感じで出力されます。
<script type="text/javascript">
if(typeof ga === 'undefined') {
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'U-XXXXX-Y', {});
}
ga("send",{"hitType":"event","eventCategory":"link","eventAction":"click","eventLabel":"top_right","eventValue":"1"});
ga('send', 'pageview', window.location.pathname + window.location.search);
</script>
詳しくはREADMEをみてもらうのが良いかと。
どうやってるのか?
rack-middleware
ですし、基本的な構えはrackのresponseを上書きする感じ。
# lib/rack/tracker.rb
def call(env)
@status, @headers, @body = @app.call(env)
return [@status, @headers, @body] unless html?
response = Rack::Response.new([], @status, @headers)
env[EVENT_TRACKING_KEY] ||= {}
if session = env["rack.session"]
env[EVENT_TRACKING_KEY].deep_merge!(session.delete(EVENT_TRACKING_KEY) || {}) { |key, old, new| Array.wrap(old) + Array.wrap(new) }
end
if response.redirection? && session
session[EVENT_TRACKING_KEY] = env[EVENT_TRACKING_KEY]
end
@body.each { |fragment| response.write inject(env, fragment) } # <= ここ
@body.close if @body.respond_to?(:close)
response.finish
end
このinject
でナンカしてる。
# lib/rack/tracker.rb
def inject(env, response)
@handlers.each(env) do |handler|
response.sub! %r{</#{handler.position}>} do |m|
handler.render << m.to_s
end
end
response
end
</#{handler.position}>
の位置でsub!
してる。つまりreplaceしてる。
内容はhandler.render
した内容を</#{handler.position}>
の手前に追記している。なるほど。
んでhandler
がこちら。
# lib/rack/tracker/handler.rb
class Rack::Tracker::Handler
class_attribute :position
self.position = :head
attr_accessor :options
attr_accessor :env
# Allow javascript escaping in view templates
include Rack::Tracker::JavaScriptHelper
def initialize(env, options = {})
self.env = env
self.options = options
self.position = options[:position] if options.has_key?(:position)
end
def events
events = env.fetch('tracker', {})[self.class.to_s.demodulize.underscore] || []
events.map{ |ev| "#{self.class}::#{ev['class_name']}".constantize.new(ev.except('class_name')) }
end
def render
raise NotImplementedError.new('needs implementation')
end
def self.track(name, event)
raise NotImplementedError.new("class method `#{__callee__}` is not implemented.")
end
end
raise NotImplementedError.new('needs implementation')
とあるので、コレを継承して各々のtrackerを作る様子。
で、このgemは色々なタグの出力に対応しているんだけど、このhandler
を継承して色々作ることで実現している。
Google Tag Managerで殆ど対応できる気もするけど、自前にモノも割と簡単に作れてヨサソウ。
本質的でないコードはなるべくRails外に出そう
こういうトラッキングだとかログとかそういうものはどんどんRailsの外にだすべきだよねって思いました。
現場からは以上です。
photo by © Domaina