diff --git a/README.md b/README.md index e67d1eb..ea8a44f 100644 --- a/README.md +++ b/README.md @@ -469,17 +469,29 @@ Everything you're passing to the `handler` will be available as `#options` in yo template, so you'll also gain access to the `env`-hash belonging to the current request. Run your application and make a request, the result of the above template can be -found right before ``. You can change the position in your handler-code: +found right before ``. You can change the `position` in your handler-code: ```ruby class MyHandler < Rack::Tracker::Handler - self.position = :body + self.position body: :append ... end ``` -The snippit will then be rendered right before ``. +The snippet will then be rendered right before ``. + +You can also change the `:append` option to `:prepend` in your handler-code: + +```ruby +class MyHandler < Rack::Tracker::Handler + self.position body: :prepend + + ... +end +``` + +The snippet will then be rendered right after `` To enable the *tracker dsl* functionality in your controllers you need to implement the `track` class method on your handler: diff --git a/lib/rack/tracker.rb b/lib/rack/tracker.rb index 8e24639..35b79b5 100644 --- a/lib/rack/tracker.rb +++ b/lib/rack/tracker.rb @@ -55,9 +55,23 @@ def call(env) def html?; @headers['Content-Type'] =~ /html/; end def inject(env, response) + handlers_by_position = {} + @handlers.each(env) do |handler| - response.gsub!(%r{}, handler.render + "") + handlers_by_position[handler.position_options] = '' if handlers_by_position[handler.position_options].blank? + handlers_by_position[handler.position_options] += handler.render end + + handlers_by_position.map do |position, rendered_handlers| + position.map do |tag, insert| + if insert == :append + response.sub!(%r{}, rendered_handlers + '\0') + else + response.sub!(%r{<#{tag}[^>]*>}, '\0' + rendered_handlers) + end + end + end + response end diff --git a/lib/rack/tracker/criteo/criteo.rb b/lib/rack/tracker/criteo/criteo.rb index b52a880..a3f75f0 100644 --- a/lib/rack/tracker/criteo/criteo.rb +++ b/lib/rack/tracker/criteo/criteo.rb @@ -13,7 +13,7 @@ def write end end - self.position = :body + self.position body: :append # global events (setSiteType, setAccount, ...) for each tracker instance def tracker_events diff --git a/lib/rack/tracker/facebook/facebook.rb b/lib/rack/tracker/facebook/facebook.rb index 3f87247..06b607c 100644 --- a/lib/rack/tracker/facebook/facebook.rb +++ b/lib/rack/tracker/facebook/facebook.rb @@ -5,7 +5,7 @@ def write end end - self.position = :body + self.position body: :append def render Tilt.new( File.join( File.dirname(__FILE__), 'template/facebook.erb') ).render(self) diff --git a/lib/rack/tracker/google_adwords_conversion/google_adwords_conversion.rb b/lib/rack/tracker/google_adwords_conversion/google_adwords_conversion.rb index 68c95ab..901b4a5 100644 --- a/lib/rack/tracker/google_adwords_conversion/google_adwords_conversion.rb +++ b/lib/rack/tracker/google_adwords_conversion/google_adwords_conversion.rb @@ -3,7 +3,7 @@ class Rack::Tracker::GoogleAdwordsConversion < Rack::Tracker::Handler class Conversion < OpenStruct end - self.position = :body + self.position body: :append def render Tilt.new( File.join( File.dirname(__FILE__), 'template', 'google_adwords_conversion.erb') ).render(self) diff --git a/lib/rack/tracker/google_tag_manager/google_tag_manager.rb b/lib/rack/tracker/google_tag_manager/google_tag_manager.rb index be01ed7..78fa8aa 100644 --- a/lib/rack/tracker/google_tag_manager/google_tag_manager.rb +++ b/lib/rack/tracker/google_tag_manager/google_tag_manager.rb @@ -17,7 +17,7 @@ def attributes # It is strongly recommended to put the google_tag_manager snippet only in the body tag # https://developers.google.com/tag-manager/quickstart - self.position = :body + self.position body: :prepend def container options[:container].respond_to?(:call) ? options[:container].call(env) : options[:container] diff --git a/lib/rack/tracker/handler.rb b/lib/rack/tracker/handler.rb index bcd2101..ae644ac 100644 --- a/lib/rack/tracker/handler.rb +++ b/lib/rack/tracker/handler.rb @@ -1,14 +1,14 @@ class Rack::Tracker::Handler - class_attribute :position - self.position = :head + class_attribute :position_options + self.position_options = { head: :append } attr_accessor :options attr_accessor :env def initialize(env, options = {}) self.env = env - self.options = options - self.position = options[:position] if options.has_key?(:position) + self.options = options + self.position_options = options[:position] if options[:position] end def events @@ -23,4 +23,13 @@ def render def self.track(name, event) raise NotImplementedError.new("class method `#{__callee__}` is not implemented.") end + + def self.position(options=nil) + self.position_options = options if options + self.position_options + end + + def position + self.position_options + end end diff --git a/lib/rack/tracker/zanox/zanox.rb b/lib/rack/tracker/zanox/zanox.rb index 580c34e..02a5c8e 100644 --- a/lib/rack/tracker/zanox/zanox.rb +++ b/lib/rack/tracker/zanox/zanox.rb @@ -14,7 +14,7 @@ def write end end - self.position = :body + self.position body: :append def mastertag # First event should be stronger, e.g. one signs up and gets redirected to homepage diff --git a/spec/handler/criteo_spec.rb b/spec/handler/criteo_spec.rb index 8feded3..26a1a68 100644 --- a/spec/handler/criteo_spec.rb +++ b/spec/handler/criteo_spec.rb @@ -14,8 +14,9 @@ def env end it 'will be placed in the body' do - expect(described_class.position).to eq(:body) - expect(described_class.new(env).position).to eq(:body) + expect(described_class.position).to eq({ body: :append }) + expect(described_class.new(env).position).to eq({ body: :append }) + expect(described_class.new(env, position: { head: :append }).position).to eq({ head: :append }) end describe '#render' do @@ -119,4 +120,4 @@ def env end end -end \ No newline at end of file +end diff --git a/spec/handler/facebook_spec.rb b/spec/handler/facebook_spec.rb index 6a24738..9230147 100644 --- a/spec/handler/facebook_spec.rb +++ b/spec/handler/facebook_spec.rb @@ -13,8 +13,9 @@ def env end it 'will be placed in the body' do - expect(described_class.position).to eq(:body) - expect(described_class.new(env).position).to eq(:body) + expect(described_class.position).to eq({ body: :append }) + expect(described_class.new(env).position).to eq({ body: :append }) + expect(described_class.new(env, position: { head: :append }).position).to eq({ head: :append }) end describe 'with custom audience id' do diff --git a/spec/handler/go_squared_spec.rb b/spec/handler/go_squared_spec.rb index 4d15bab..a25dbd6 100644 --- a/spec/handler/go_squared_spec.rb +++ b/spec/handler/go_squared_spec.rb @@ -5,8 +5,9 @@ def env end it 'will be placed in the head' do - expect(described_class.position).to eq(:head) - expect(described_class.new(env).position).to eq(:head) + expect(described_class.position).to eq({ head: :append }) + expect(described_class.new(env).position).to eq({ head: :append }) + expect(described_class.new(env, position: { body: :append }).position).to eq({ body: :append }) end describe "with events" do diff --git a/spec/handler/google_adwords_conversion_spec.rb b/spec/handler/google_adwords_conversion_spec.rb index a81dd15..c87ba72 100644 --- a/spec/handler/google_adwords_conversion_spec.rb +++ b/spec/handler/google_adwords_conversion_spec.rb @@ -8,9 +8,9 @@ def env end it 'will be placed in the body' do - expect(described_class.position).to eq(:body) - expect(described_class.new(env).position).to eq(:body) - expect(described_class.new(env, position: :body).position).to eq(:body) + expect(described_class.position).to eq({ body: :append }) + expect(described_class.new(env).position).to eq({ body: :append }) + expect(described_class.new(env, position: { head: :append }).position).to eq({ head: :append }) end describe "with events" do diff --git a/spec/handler/google_analytics_spec.rb b/spec/handler/google_analytics_spec.rb index 66d842c..4454d4b 100644 --- a/spec/handler/google_analytics_spec.rb +++ b/spec/handler/google_analytics_spec.rb @@ -7,9 +7,9 @@ def env end it 'will be placed in the head' do - expect(described_class.position).to eq(:head) - expect(described_class.new(env).position).to eq(:head) - expect(described_class.new(env, position: :body).position).to eq(:body) + expect(described_class.position).to eq({ head: :append }) + expect(described_class.new(env).position).to eq({ head: :append }) + expect(described_class.new(env, position: { body: :append }).position).to eq({ body: :append }) end describe '#ecommerce_events' do diff --git a/spec/handler/google_tag_manager_spec.rb b/spec/handler/google_tag_manager_spec.rb index 77f7b1d..65ba30c 100644 --- a/spec/handler/google_tag_manager_spec.rb +++ b/spec/handler/google_tag_manager_spec.rb @@ -8,9 +8,9 @@ def env end it 'will be placed in the body by default' do - expect(described_class.position).to eq(:body) - expect(described_class.new(env).position).to eq(:body) - expect(described_class.new(env, position: :head).position).to eq(:head) + expect(described_class.position).to eq({ body: :prepend }) + expect(described_class.new(env).position).to eq({ body: :prepend }) + expect(described_class.new(env, position: { head: :append }).position).to eq({ head: :append }) end describe "with events" do @@ -31,10 +31,10 @@ def env end end - describe "with dynamic tracker" do + describe "with dynamic container" do subject { described_class.new(env, { container: lambda { |env| return env[:misc] }}).render } - it 'will call tracker lambdas to obtain tracking codes' do + it 'will call container lambdas to obtain container codes' do expect(subject).to match(%r{\(window,document,'script','dataLayer','foobar'\)}) end end diff --git a/spec/handler/vwo_spec.rb b/spec/handler/vwo_spec.rb index 5b4fab6..1de224e 100644 --- a/spec/handler/vwo_spec.rb +++ b/spec/handler/vwo_spec.rb @@ -5,8 +5,9 @@ def env end it 'will be placed in the head' do - expect(described_class.position).to eq(:head) - expect(described_class.new(env).position).to eq(:head) + expect(described_class.position).to eq({ head: :append }) + expect(described_class.new(env).position).to eq({ head: :append }) + expect(described_class.new(env, position: { body: :append }).position).to eq({ body: :append }) end end diff --git a/spec/handler/zanox_spec.rb b/spec/handler/zanox_spec.rb index 4e3090e..ba16091 100644 --- a/spec/handler/zanox_spec.rb +++ b/spec/handler/zanox_spec.rb @@ -13,9 +13,10 @@ def env {} end - it 'will be placed in the body' do - expect(described_class.position).to eq(:body) - expect(described_class.new(env).position).to eq(:body) + it 'will be placed in the body by default' do + expect(described_class.position).to eq({ body: :append }) + expect(described_class.new(env).position).to eq({ body: :append }) + expect(described_class.new(env, position: { head: :append }).position).to eq({ head: :append }) end describe '#render a sale #tracking_event' do diff --git a/spec/integration/criteo_integration_spec.rb b/spec/integration/criteo_integration_spec.rb index 77364ec..dd57a71 100644 --- a/spec/integration/criteo_integration_spec.rb +++ b/spec/integration/criteo_integration_spec.rb @@ -30,14 +30,14 @@ describe 'adjust tracker position via options' do before do setup_app(action: :criteo) do |tracker| - tracker.handler :criteo, { set_account: '1234', position: :head } + tracker.handler :criteo, { set_account: '1234', position: { head: :append } } end visit '/' end it "will be placed in the specified tag" do - expect(page.find("body")).to_not have_content('criteo') - expect(page.find("head")).to have_content("{\"event\":\"setAccount\",\"account\":\"1234\"}") + expect(page.find("body")).to_not have_content('criteo') + expect(page.find("head")).to have_content("{\"event\":\"setAccount\",\"account\":\"1234\"}") end end diff --git a/spec/integration/google_analytics_integration_spec.rb b/spec/integration/google_analytics_integration_spec.rb index 14bf48b..b2c8cce 100644 --- a/spec/integration/google_analytics_integration_spec.rb +++ b/spec/integration/google_analytics_integration_spec.rb @@ -19,7 +19,7 @@ describe 'adjust tracker position via options' do before do setup_app(action: :google_analytics) do |tracker| - tracker.handler :google_analytics, { tracker: 'U-XXX-Y', position: :body } + tracker.handler :google_analytics, { tracker: 'U-XXX-Y', position: { body: :append } } end visit '/' end diff --git a/spec/support/fake_handler.rb b/spec/support/fake_handler.rb index c40c1e5..1d11cf6 100644 --- a/spec/support/fake_handler.rb +++ b/spec/support/fake_handler.rb @@ -14,7 +14,7 @@ def track_me class AnotherHandler < Rack::Tracker::Handler - self.position = :body + self.position body: :append def render Tilt.new( File.join( File.dirname(__FILE__), '../fixtures/another_handler.erb') ).render(self) diff --git a/spec/tracker/tracker_spec.rb b/spec/tracker/tracker_spec.rb index 1f5a530..8a4ead4 100644 --- a/spec/tracker/tracker_spec.rb +++ b/spec/tracker/tracker_spec.rb @@ -9,7 +9,11 @@ def dummy_alert end class BodyHandler < DummyHandler - self.position = :body + self.position body: :append +end + +class BodyOpeningHandler < DummyHandler + self.position body: :prepend end RSpec.describe Rack::Tracker do @@ -20,6 +24,7 @@ def app use Rack::Tracker do handler DummyHandler, { foo: 'head' } handler BodyHandler, { foo: 'body' } + handler BodyOpeningHandler, { foo: 'body_opening' } end run lambda {|env| @@ -28,7 +33,7 @@ def app when '/' then [200, {'Content-Type' => 'application/html'}, ['Hello world']] when '/body' then - [200, {'Content-Type' => 'application/html'}, ['bob here']] + [200, {'Content-Type' => 'application/html'}, ['bob here']] when '/body-head' then [200, {'Content-Type' => 'application/html'}, ['']] when '/test.xml' then @@ -55,6 +60,7 @@ def app get '/' expect(last_response.body).to include("console.log('head');") expect(last_response.body).to_not include("console.log('body');") + expect(last_response.body).to_not include("console.log('body_opening');") end it 'injects custom variables that was directly assigned' do @@ -69,11 +75,18 @@ def app end describe 'when body is present' do - it 'will not inject the body handler code' do + it 'will inject only the body handler code' do get '/body' expect(last_response.body).to include("console.log('body');") + expect(last_response.body).to include("console.log('body_opening');") expect(last_response.body).to_not include("console.log('head');") end + + it 'will inject the handlers correctly using append or prepend' do + get '/body' + expect(last_response.body).to include("") + expect(last_response.body).to include("\n") + end end describe 'when head and body is present' do @@ -81,6 +94,7 @@ def app get '/body-head' expect(last_response.body).to include("console.log('head');") expect(last_response.body).to include("console.log('body');") + expect(last_response.body).to include("console.log('body_opening');") end end