Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhance position option with :append and :prepend options to inject code at start or end of head or body tags #36

Closed
wants to merge 7 commits into from
18 changes: 15 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `</head>`. You can change the position in your handler-code:
found right before `</head>`. 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 `</body>`.
The snippet will then be rendered right before `</body>`.

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

To enable the *tracker dsl* functionality in your controllers
you need to implement the `track` class method on your handler:
Expand Down
16 changes: 15 additions & 1 deletion lib/rack/tracker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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.position}>}, handler.render + "</#{handler.position}>")
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{</#{tag}>}, rendered_handlers + '\0')
else
response.sub!(%r{<#{tag}[^>]*>}, '\0' + rendered_handlers)
end
end
end

response
end

Expand Down
2 changes: 1 addition & 1 deletion lib/rack/tracker/criteo/criteo.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion lib/rack/tracker/facebook/facebook.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion lib/rack/tracker/google_tag_manager/google_tag_manager.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
17 changes: 13 additions & 4 deletions lib/rack/tracker/handler.rb
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
2 changes: 1 addition & 1 deletion lib/rack/tracker/zanox/zanox.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 4 additions & 3 deletions spec/handler/criteo_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -119,4 +120,4 @@ def env
end
end

end
end
5 changes: 3 additions & 2 deletions spec/handler/facebook_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions spec/handler/go_squared_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions spec/handler/google_adwords_conversion_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions spec/handler/google_analytics_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 5 additions & 5 deletions spec/handler/google_tag_manager_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
5 changes: 3 additions & 2 deletions spec/handler/vwo_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
7 changes: 4 additions & 3 deletions spec/handler/zanox_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions spec/integration/criteo_integration_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion spec/integration/google_analytics_integration_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion spec/support/fake_handler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
20 changes: 17 additions & 3 deletions spec/tracker/tracker_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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|
Expand All @@ -28,7 +33,7 @@ def app
when '/' then
[200, {'Content-Type' => 'application/html'}, ['<head>Hello world</head>']]
when '/body' then
[200, {'Content-Type' => 'application/html'}, ['<body>bob here</body>']]
[200, {'Content-Type' => 'application/html'}, ['<body class="dummy">bob here</body>']]
when '/body-head' then
[200, {'Content-Type' => 'application/html'}, ['<head></head><body></body>']]
when '/test.xml' then
Expand All @@ -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
Expand All @@ -69,18 +75,26 @@ 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("<body class=\"dummy\"><script type=\"text/javascript\">\n alert('this is a dummy class');\n\n console.log('body_opening');\n</script>")
expect(last_response.body).to include("<script type=\"text/javascript\">\n alert('this is a dummy class');\n\n console.log('body');\n</script>\n</body>")
end
end

describe 'when head and body is present' do
it 'will pass options to the Handler' do
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

Expand Down