ahoy_email 1.0.0 → 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: acb3c3744cff970d75c695f1c79edec32e7dd7a9bea7342576b7fc87be2d3e4a
4
- data.tar.gz: 1cd08f10de1fa686aa09312cdee054694debe9f7835bb97fd9eb35aa9fc09c01
3
+ metadata.gz: 2998bcf65131bcbb1ae5d6b5c2d0e1bf21496571a8c8c3e96230f21c9b3b2a74
4
+ data.tar.gz: 4e0291a50f134fcc95cdf8626aaf368bf2cb7c4ce763bbc008691d88421abba3
5
5
  SHA512:
6
- metadata.gz: 3eb7a246d87edfec081d873c8a464c90a682b81acd5d38877c8551770ba911f0c222bf778b03c2abf390fadf5bf48edabe863187627740c673b86718e082a9e4
7
- data.tar.gz: 15c5a59995db66c0d76bfa5273e8a7f4404b11d80078fbf73765fcfb157d8ed3ff744b7bc694e75c8eb8fc2b4afc5494727fa68fb2f60769f91cf2d1f86e7dec
6
+ metadata.gz: 9fd0fd7d531e72f1c4d930cf35d398747ec7052b3f940597f80010e07f45d37c70ddb52f9d60d2c3b9bdb49a6760ed28a353e381f87e8849c57f7a5a6b6f8871
7
+ data.tar.gz: 04616042077a12cea5b86dd682339f91104c201bc0ce35a95716a45bf71cad4e80c964b0b57d813090d464d7cf66e7f4edc4f2c214c00723214342e00c9eccfd
data/CHANGELOG.md CHANGED
@@ -1,4 +1,29 @@
1
- ## 1.0.0
1
+ ## 1.1.1 (2021-03-06)
2
+
3
+ - Added support for classes for subscribers
4
+ - Use `datetime` type in migration
5
+
6
+ ## 1.1.0 (2019-07-15)
7
+
8
+ - Made `opened_at` optional with click tracking
9
+ - Fixed secret token for environment variables
10
+ - Removed support for Rails 4.2
11
+
12
+ ## 1.0.3 (2019-02-18)
13
+
14
+ - Fixed custom message model
15
+ - Fixed `message` option with proc
16
+
17
+ ## 1.0.2 (2018-10-02)
18
+
19
+ - Fixed error with Ruby < 2.5
20
+ - Fixed UTM parameters storage on model
21
+
22
+ ## 1.0.1 (2018-09-27)
23
+
24
+ - Use observer instead of interceptor
25
+
26
+ ## 1.0.0 (2018-09-27)
2
27
 
3
28
  - Removed support for Rails < 4.2
4
29
 
@@ -11,91 +36,91 @@ Breaking changes
11
36
  - `AhoyEmail.track` was removed in favor of `AhoyEmail.default_options`
12
37
  - The `heuristic_parse` option was removed and is now the default
13
38
 
14
- ## 0.5.2
39
+ ## 0.5.2 (2018-04-26)
15
40
 
16
41
  - Fixed secret token for Rails 5.2
17
42
  - Added `heuristic_parse` option
18
43
 
19
- ## 0.5.1
44
+ ## 0.5.1 (2018-04-19)
20
45
 
21
46
  - Fixed deprecation warning in Rails 5.2
22
47
  - Added `unsubscribe_links` option
23
48
  - Allow `message_model` to accept a proc
24
49
  - Use `references` in migration
25
50
 
26
- ## 0.5.0
51
+ ## 0.5.0 (2017-05-01)
27
52
 
28
53
  - Added support for Rails 5.1
29
54
  - Added `invalid_redirect_url`
30
55
 
31
- ## 0.4.0
56
+ ## 0.4.0 (2016-09-01)
32
57
 
33
58
  - Fixed `belongs_to` error in Rails 5
34
59
  - Include `safely_block` gem without polluting global namespace
35
60
 
36
- ## 0.3.2
61
+ ## 0.3.2 (2016-07-27)
37
62
 
38
63
  - Fixed deprecation warning for Rails 5
39
64
  - Do not track content by default on fresh installations
40
65
 
41
- ## 0.3.1
66
+ ## 0.3.1 (2016-05-11)
42
67
 
43
68
  - Fixed deprecation warnings
44
69
  - Fixed `stack level too deep` error
45
70
 
46
- ## 0.3.0
71
+ ## 0.3.0 (2015-12-16)
47
72
 
48
73
  - Added safely for error reporting
49
74
  - Fixed error with `to`
50
75
  - Prevent duplicate records when mail called multiple times
51
76
 
52
- ## 0.2.4
77
+ ## 0.2.4 (2015-07-29)
53
78
 
54
79
  - Added `extra` option for extra attributes
55
80
 
56
- ## 0.2.3
81
+ ## 0.2.3 (2015-03-22)
57
82
 
58
83
  - Save utm parameters
59
84
  - Added `url_options`
60
85
  - Skip tracking for `mailto` links
61
86
  - Only set secret token if not already set
62
87
 
63
- ## 0.2.2
88
+ ## 0.2.2 (2014-08-31)
64
89
 
65
90
  - Fixed secret token for Rails 4.1
66
91
  - Fixed links with href
67
92
  - Fixed message id for Rails 3.1
68
93
 
69
- ## 0.2.1
94
+ ## 0.2.1 (2014-05-26)
70
95
 
71
96
  - Added `only` and `except` options
72
97
 
73
- ## 0.2.0
98
+ ## 0.2.0 (2014-05-10)
74
99
 
75
100
  - Enable tracking when track is called by default
76
101
 
77
- ## 0.1.5
102
+ ## 0.1.5 (2014-05-09)
78
103
 
79
104
  - Rails 3 fix
80
105
 
81
- ## 0.1.4
106
+ ## 0.1.4 (2014-05-04)
82
107
 
83
108
  - Try not to rewrite unsubscribe links
84
109
 
85
- ## 0.1.3
110
+ ## 0.1.3 (2014-05-03)
86
111
 
87
112
  - Added `to` and `mailer` fields
88
113
  - Added subscribers for open and click events
89
114
 
90
- ## 0.1.2
115
+ ## 0.1.2 (2014-05-01)
91
116
 
92
117
  - Added `AhoyEmail.track` (fix)
93
118
 
94
- ## 0.1.1
119
+ ## 0.1.1 (2014-04-30)
95
120
 
96
121
  - Use secure compare for signature verification
97
122
  - Fixed deprecation warnings
98
123
 
99
- ## 0.1.0
124
+ ## 0.1.0 (2014-04-29)
100
125
 
101
126
  - First major release
data/LICENSE.txt CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2014-2018 Andrew Kane
1
+ Copyright (c) 2014-2021 Andrew Kane
2
2
 
3
3
  MIT License
4
4
 
data/README.md CHANGED
@@ -1,20 +1,12 @@
1
1
  # Ahoy Email
2
2
 
3
- :postbox: Email analytics for Rails
3
+ First-party email analytics for Rails
4
4
 
5
- You get:
6
-
7
- - A history of emails sent to each user
8
- - Easy UTM tagging
9
- - Optional open and click tracking
10
-
11
- **Ahoy Email 1.0 was recently released!** See [how to upgrade](#upgrading)
5
+ :fire: For web and native app analytics, check out [Ahoy](https://github.com/ankane/ahoy)
12
6
 
13
7
  :bullettrain_side: To manage unsubscribes, check out [Mailkick](https://github.com/ankane/mailkick)
14
8
 
15
- :fire: To track visits and events, check out [Ahoy](https://github.com/ankane/ahoy)
16
-
17
- [![Build Status](https://travis-ci.org/ankane/ahoy_email.svg?branch=master)](https://travis-ci.org/ankane/ahoy_email)
9
+ [![Build Status](https://github.com/ankane/ahoy_email/workflows/build/badge.svg?branch=master)](https://github.com/ankane/ahoy_email/actions)
18
10
 
19
11
  ## Installation
20
12
 
@@ -31,14 +23,20 @@ rails generate ahoy_email:install
31
23
  rails db:migrate
32
24
  ```
33
25
 
34
- ## How It Works
26
+ ## Getting Started
27
+
28
+ There are three main features:
35
29
 
36
- ### Message History
30
+ - [Message history](#message-history)
31
+ - [UTM tagging](#utm-tagging)
32
+ - [Open & click analytics](#open--click-analytics)
37
33
 
38
- Ahoy creates an `Ahoy::Message` record for each email sent by default. You can disable history for a mailer:
34
+ ## Message History
35
+
36
+ Ahoy Email creates an `Ahoy::Message` record for each email sent by default. You can disable history for a mailer:
39
37
 
40
38
  ```ruby
41
- class UserMailer < ApplicationMailer
39
+ class CouponMailer < ApplicationMailer
42
40
  track message: false # use only/except to limit actions
43
41
  end
44
42
  ```
@@ -51,14 +49,14 @@ AhoyEmail.default_options[:message] = false
51
49
 
52
50
  ### Users
53
51
 
54
- Ahoy records the user a message is sent to - not just the email address. This gives you a full history of messages for each user, even if he or she changes addresses.
52
+ Ahoy Email records the user a message is sent to - not just the email address. This gives you a history of messages for each user, even if they change addresses.
55
53
 
56
- By default, Ahoy tries `@user` then `params[:user]` then `User.find_by(email: message.to.first)` to find the user.
54
+ By default, Ahoy tries `@user` then `params[:user]` then `User.find_by(email: message.to)` to find the user.
57
55
 
58
56
  You can pass a specific user with:
59
57
 
60
58
  ```ruby
61
- class UserMailer < ApplicationMailer
59
+ class CouponMailer < ApplicationMailer
62
60
  track user: -> { params[:some_user] }
63
61
  end
64
62
  ```
@@ -86,7 +84,7 @@ Record extra attributes on the `Ahoy::Message` model.
86
84
  Create a migration to add extra attributes to the `ahoy_messages` table. For example:
87
85
 
88
86
  ```ruby
89
- class AddCouponIdToAhoyMessages < ActiveRecord::Migration[5.2]
87
+ class AddCouponIdToAhoyMessages < ActiveRecord::Migration[6.1]
90
88
  def change
91
89
  add_column :ahoy_messages, :coupon_id, :integer
92
90
  end
@@ -109,9 +107,15 @@ class CouponMailer < ApplicationMailer
109
107
  end
110
108
  ```
111
109
 
112
- ### UTM Tagging
110
+ ## UTM Tagging
113
111
 
114
- Automatically add UTM parameters to links.
112
+ Use UTM tagging to attribute a conversion (like an order) to an email campaign. If you use [Ahoy](https://github.com/ankane/ahoy) for web analytics:
113
+
114
+ 1. Send an email with UTM parameters
115
+ 2. When a user visits the site, Ahoy will create a visit with the UTM parameters
116
+ 3. When a user orders, the visit will be associated with the order (if [configured](https://github.com/ankane/ahoy#associated-models))
117
+
118
+ Add UTM parameters to links with:
115
119
 
116
120
  ```ruby
117
121
  class CouponMailer < ApplicationMailer
@@ -139,22 +143,25 @@ Skip specific links with:
139
143
  <%= link_to "Go", some_url, data: {skip_utm_params: true} %>
140
144
  ```
141
145
 
142
- ### Opens & Clicks
146
+ ## Open & Click Analytics
143
147
 
144
- #### Setup
148
+ While it’s nice to get feedback on the performance of your emails, we discourage the use of open tracking. If you do decide to use open or click tracking, be sure to get consent from your users and consider a short retention period. Check out [this article](https://www.eff.org/deeplinks/2019/01/stop-tracking-my-emails) for more best practices.
145
149
 
146
- Additional setup is required to track opens and clicks.
150
+ ### Setup
147
151
 
148
152
  Create a migration with:
149
153
 
150
154
  ```ruby
151
- class AddTokenToAhoyMessages < ActiveRecord::Migration[5.2]
155
+ class AddTokenToAhoyMessages < ActiveRecord::Migration[6.1]
152
156
  def change
153
157
  add_column :ahoy_messages, :token, :string
158
+ add_index :ahoy_messages, :token
159
+
160
+ # for opens
154
161
  add_column :ahoy_messages, :opened_at, :timestamp
155
- add_column :ahoy_messages, :clicked_at, :timestamp
156
162
 
157
- add_index :ahoy_messages, :token
163
+ # for clicks
164
+ add_column :ahoy_messages, :clicked_at, :timestamp
158
165
  end
159
166
  end
160
167
  ```
@@ -168,12 +175,28 @@ AhoyEmail.api = true
168
175
  And add to mailers you want to track:
169
176
 
170
177
  ```ruby
171
- class UserMailer < ApplicationMailer
172
- track open: true, click: true # use only/except to limit actions
178
+ class CouponMailer < ApplicationMailer
179
+ track open: true, click: true
180
+ end
181
+ ```
182
+
183
+ Use only and except to limit actions
184
+
185
+ ```ruby
186
+ class CouponMailer < ApplicationMailer
187
+ track click: true, only: [:welcome]
173
188
  end
174
189
  ```
175
190
 
176
- #### How It Works
191
+ Or make it conditional
192
+
193
+ ```ruby
194
+ class CouponMailer < ApplicationMailer
195
+ track click: -> { params[:user].opted_in? }
196
+ end
197
+ ```
198
+
199
+ ### How It Works
177
200
 
178
201
  For opens, an invisible pixel is added right before the `</body>` tag in HTML emails. If the recipient has images enabled in their email client, the pixel is loaded and the open time recorded.
179
202
 
@@ -209,7 +232,7 @@ You can specify the domain to use with:
209
232
  AhoyEmail.default_options[:url_options] = {host: "mydomain.com"}
210
233
  ```
211
234
 
212
- #### Events
235
+ ### Events
213
236
 
214
237
  Subscribe to open and click events by adding to the initializer:
215
238
 
@@ -243,6 +266,36 @@ end
243
266
  AhoyEmail.subscribers << EmailSubscriber.new
244
267
  ```
245
268
 
269
+ ## Data Protection
270
+
271
+ We recommend encrypting the `to` field (as well as the `subject` if it’s sensitive). [Lockbox](https://github.com/ankane/lockbox) is great for this. Use [Blind Index](https://github.com/ankane/blind_index) if you need to query by the `to` field.
272
+
273
+ Create `app/models/ahoy/message.rb` with:
274
+
275
+ ```ruby
276
+ class Ahoy::Message < ApplicationRecord
277
+ self.table_name = "ahoy_messages"
278
+ belongs_to :user, polymorphic: true, optional: true
279
+
280
+ encrypts :to
281
+ blind_index :to
282
+ end
283
+ ```
284
+
285
+ ## Data Retention
286
+
287
+ Delete older data with:
288
+
289
+ ```ruby
290
+ Ahoy::Message.where("created_at < ?", 1.year.ago).in_batches.delete_all
291
+ ```
292
+
293
+ Delete data for a specific user with:
294
+
295
+ ```ruby
296
+ Ahoy::Message.where(user_id: 1).in_batches.delete_all
297
+ ```
298
+
246
299
  ## Reference
247
300
 
248
301
  Set global options
@@ -265,36 +318,22 @@ AhoyEmail.track_method = lambda do |data|
265
318
  end
266
319
  ```
267
320
 
268
- ## Upgrading
269
-
270
- ### 1.0
271
-
272
- Breaking changes
273
-
274
- - UTM tagging, open tracking, and click tracking are no longer enabled by default. To enable, create an initializer with:
275
-
276
- ```ruby
277
- AhoyEmail.api = true
278
-
279
- AhoyEmail.default_options[:open] = true
280
- AhoyEmail.default_options[:click] = true
281
- AhoyEmail.default_options[:utm_params] = true
282
- ```
321
+ ## Mongoid
283
322
 
284
- - Only sent emails are recorded
285
- - Proc options are now executed in the context of the mailer and take no arguments
323
+ If you prefer to use Mongoid instead of Active Record, create `app/models/ahoy/message.rb` with:
286
324
 
287
- ```ruby
288
- # old
289
- user: ->(mailer, message) { User.find_by(email: message.to.first) }
325
+ ```ruby
326
+ class Ahoy::Message
327
+ include Mongoid::Document
290
328
 
291
- # new
292
- user: -> { User.find_by(email: message.to.first) }
293
- ```
329
+ belongs_to :user, polymorphic: true, optional: true, index: true
294
330
 
295
- - Invalid options now throw an `ArgumentError`
296
- - `AhoyEmail.track` was removed in favor of `AhoyEmail.default_options`
297
- - The `heuristic_parse` option was removed and is now the default
331
+ field :to, type: String
332
+ field :mailer, type: String
333
+ field :subject, type: String
334
+ field :sent_at, type: Time
335
+ end
336
+ ```
298
337
 
299
338
  ## History
300
339
 
@@ -308,3 +347,12 @@ Everyone is encouraged to help improve this project. Here are a few ways you can
308
347
  - Fix bugs and [submit pull requests](https://github.com/ankane/ahoy_email/pulls)
309
348
  - Write, clarify, or fix documentation
310
349
  - Suggest or add new features
350
+
351
+ To get started with development:
352
+
353
+ ```sh
354
+ git clone https://github.com/ankane/ahoy_email.git
355
+ cd ahoy_email
356
+ bundle install
357
+ bundle exec rake test
358
+ ```
@@ -1,17 +1,14 @@
1
1
  module Ahoy
2
2
  class MessagesController < ApplicationController
3
3
  filters = _process_action_callbacks.map(&:filter) - AhoyEmail.preserve_callbacks
4
- if Rails::VERSION::MAJOR >= 5
5
- skip_before_action(*filters, raise: false)
6
- skip_after_action(*filters, raise: false)
7
- skip_around_action(*filters, raise: false)
8
- else
9
- skip_action_callback *filters
10
- end
4
+ skip_before_action(*filters, raise: false)
5
+ skip_after_action(*filters, raise: false)
6
+ skip_around_action(*filters, raise: false)
11
7
 
12
8
  before_action :set_message
13
9
 
14
10
  def open
11
+ # TODO move to MessageSubscriber in 2.0
15
12
  if @message && !@message.opened_at
16
13
  @message.opened_at = Time.now
17
14
  @message.save!
@@ -23,9 +20,10 @@ module Ahoy
23
20
  end
24
21
 
25
22
  def click
23
+ # TODO move to MessageSubscriber in 2.0
26
24
  if @message && !@message.clicked_at
27
25
  @message.clicked_at = Time.now
28
- @message.opened_at ||= @message.clicked_at
26
+ @message.opened_at ||= @message.clicked_at if @message.respond_to?(:opened_at=)
29
27
  @message.save!
30
28
  end
31
29
 
@@ -41,6 +39,7 @@ module Ahoy
41
39
 
42
40
  redirect_to url
43
41
  else
42
+ # TODO show link expired page with link to invalid redirect url in 2.0
44
43
  redirect_to AhoyEmail.invalid_redirect_url || main_app.root_url
45
44
  end
46
45
  end
@@ -48,14 +47,22 @@ module Ahoy
48
47
  protected
49
48
 
50
49
  def set_message
51
- @message = AhoyEmail.message_model.where(token: params[:id]).first
50
+ @token = params[:id]
51
+
52
+ model = AhoyEmail.message_model
53
+
54
+ return if model.respond_to?(:column_names) && !model.column_names.include?("token")
55
+
56
+ @message = model.where(token: @token).first
52
57
  end
53
58
 
54
59
  def publish(name, event = {})
55
60
  AhoyEmail.subscribers.each do |subscriber|
61
+ subscriber = subscriber.new if subscriber.is_a?(Class) && !subscriber.respond_to?(name)
56
62
  if subscriber.respond_to?(name)
57
63
  event[:message] = @message
58
64
  event[:controller] = self
65
+ event[:token] = @token
59
66
  subscriber.send name, event
60
67
  end
61
68
  end
@@ -2,6 +2,6 @@ module Ahoy
2
2
  class Message < ActiveRecord::Base
3
3
  self.table_name = "ahoy_messages"
4
4
 
5
- belongs_to :user, (ActiveRecord::VERSION::MAJOR >= 5 ? {optional: true} : {}).merge(polymorphic: true)
5
+ belongs_to :user, polymorphic: true, optional: true
6
6
  end
7
7
  end
data/lib/ahoy_email.rb CHANGED
@@ -8,7 +8,7 @@ require "safely/core"
8
8
  # modules
9
9
  require "ahoy_email/processor"
10
10
  require "ahoy_email/tracker"
11
- require "ahoy_email/interceptor"
11
+ require "ahoy_email/observer"
12
12
  require "ahoy_email/mailer"
13
13
  require "ahoy_email/version"
14
14
  require "ahoy_email/engine" if defined?(Rails)
@@ -29,7 +29,7 @@ module AhoyEmail
29
29
  utm_term: nil,
30
30
  utm_content: nil,
31
31
  utm_campaign: -> { action_name },
32
- user: -> { @user || (respond_to?(:params) && params && params[:user]) || (message.to.size == 1 ? (User.find_by(email: message.to.first) rescue nil) : nil) },
32
+ user: -> { (defined?(@user) && @user) || (respond_to?(:params) && params && params[:user]) || (message.to.try(:size) == 1 ? (User.find_by(email: message.to.first) rescue nil) : nil) },
33
33
  mailer: -> { "#{self.class.name}##{action_name}" },
34
34
  url_options: {},
35
35
  extra: {},
@@ -41,8 +41,7 @@ module AhoyEmail
41
41
 
42
42
  ahoy_message = AhoyEmail.message_model.new
43
43
  ahoy_message.to = Array(message.to).join(", ") if ahoy_message.respond_to?(:to=)
44
- ahoy_message.user_type = data[:user_type]
45
- ahoy_message.user_id = data[:user_id]
44
+ ahoy_message.user = data[:user] if ahoy_message.respond_to?(:user=)
46
45
 
47
46
  ahoy_message.mailer = data[:mailer] if ahoy_message.respond_to?(:mailer=)
48
47
  ahoy_message.subject = message.subject if ahoy_message.respond_to?(:subject=)
@@ -58,14 +57,18 @@ module AhoyEmail
58
57
 
59
58
  ahoy_message.sent_at = Time.now
60
59
  ahoy_message.save!
60
+
61
+ ahoy_message
61
62
  end
62
63
 
63
64
  self.subscribers = []
64
65
 
65
66
  self.preserve_callbacks = []
66
67
 
68
+ self.message_model = -> { ::Ahoy::Message }
69
+
67
70
  def self.message_model
68
- model = (defined?(@message_model) && @message_model) || ::Ahoy::Message
71
+ model = defined?(@@message_model) && @@message_model
69
72
  model = model.call if model.respond_to?(:call)
70
73
  model
71
74
  end
@@ -73,5 +76,6 @@ end
73
76
 
74
77
  ActiveSupport.on_load(:action_mailer) do
75
78
  include AhoyEmail::Mailer
76
- register_interceptor AhoyEmail::Interceptor
79
+ register_observer AhoyEmail::Observer
80
+ Mail::Message.send(:attr_accessor, :ahoy_data, :ahoy_message)
77
81
  end
@@ -13,7 +13,9 @@ module AhoyEmail
13
13
  app.config
14
14
  end
15
15
 
16
- creds.respond_to?(:secret_key_base) ? creds.secret_key_base : creds.secret_token
16
+ token = creds.respond_to?(:secret_key_base) ? creds.secret_key_base : creds.secret_token
17
+ token ||= app.secret_key_base # should come first, but need to maintain backward compatibility
18
+ token
17
19
  end
18
20
  end
19
21
  end
@@ -24,10 +24,14 @@ module AhoyEmail
24
24
  end
25
25
 
26
26
  def save_ahoy_options
27
- if ahoy_options[:message]
28
- Safely.safely do
27
+ Safely.safely do
28
+ # do message first for performance
29
+ message = ahoy_options[:message]
30
+ message = message.respond_to?(:call) ? instance_exec(&message) : message
31
+
32
+ if message
29
33
  options = {}
30
- ahoy_options.each do |k, v|
34
+ ahoy_options.except(:message).each do |k, v|
31
35
  # execute options in mailer content
32
36
  options[k] = v.respond_to?(:call) ? instance_exec(&v) : v
33
37
  end
@@ -0,0 +1,7 @@
1
+ module AhoyEmail
2
+ class Observer
3
+ def self.delivered_email(message)
4
+ AhoyEmail::Tracker.new(message).perform
5
+ end
6
+ end
7
+ end
@@ -31,9 +31,11 @@ module AhoyEmail
31
31
  def track_message
32
32
  data = {
33
33
  mailer: options[:mailer],
34
- extra: options[:extra]
34
+ extra: options[:extra],
35
+ user: options[:user]
35
36
  }
36
37
 
38
+ # legacy, remove in next major version
37
39
  user = options[:user]
38
40
  if user
39
41
  data[:user_type] = user.model_name.name
@@ -46,17 +48,19 @@ module AhoyEmail
46
48
  end
47
49
 
48
50
  if options[:utm_params]
49
- UTM_PARAMETERS.each do |k|
50
- data[k] = options[k.to_sym] if options[k.to_sym]
51
+ UTM_PARAMETERS.map(&:to_sym).each do |k|
52
+ data[k] = options[k] if options[k]
51
53
  end
52
54
  end
53
55
 
54
- mailer.message["Ahoy-Message"] = data.to_json
56
+ mailer.message.ahoy_data = data
55
57
  end
56
58
 
57
59
  def track_open
58
60
  if html_part?
59
- raw_source = (message.html_part || message).body.raw_source
61
+ part = message.html_part || message
62
+ raw_source = part.body.raw_source
63
+
60
64
  regex = /<\/body>/i
61
65
  url =
62
66
  url_for(
@@ -69,18 +73,19 @@ module AhoyEmail
69
73
 
70
74
  # try to add before body tag
71
75
  if raw_source.match(regex)
72
- raw_source.gsub!(regex, "#{pixel}\\0")
76
+ part.body = raw_source.gsub(regex, "#{pixel}\\0")
73
77
  else
74
- raw_source << pixel
78
+ part.body = raw_source + pixel
75
79
  end
76
80
  end
77
81
  end
78
82
 
79
83
  def track_links
80
84
  if html_part?
81
- body = (message.html_part || message).body
85
+ part = message.html_part || message
82
86
 
83
- doc = Nokogiri::HTML(body.raw_source)
87
+ # TODO use Nokogiri::HTML::DocumentFragment.parse in 2.0
88
+ doc = Nokogiri::HTML(part.body.raw_source)
84
89
  doc.css("a[href]").each do |link|
85
90
  uri = parse_uri(link["href"])
86
91
  next unless trackable?(uri)
@@ -96,6 +101,8 @@ module AhoyEmail
96
101
  end
97
102
 
98
103
  if options[:click] && !skip_attribute?(link, "click")
104
+ raise "Secret token is empty" unless AhoyEmail.secret_token
105
+
99
106
  # TODO sign more than just url and transition to HMAC-SHA256
100
107
  signature = OpenSSL::HMAC.hexdigest("SHA1", AhoyEmail.secret_token, link["href"])
101
108
  link["href"] =
@@ -109,8 +116,13 @@ module AhoyEmail
109
116
  end
110
117
  end
111
118
 
112
- # hacky
113
- body.raw_source.sub!(body.raw_source, doc.to_s)
119
+ # ampersands converted to &amp;
120
+ # https://github.com/sparklemotion/nokogiri/issues/1127
121
+ # not ideal, but should be equivalent in html5
122
+ # https://stackoverflow.com/questions/15776556/whats-the-difference-between-and-amp-in-html5
123
+ # escaping technically required before html5
124
+ # https://stackoverflow.com/questions/3705591/do-i-encode-ampersands-in-a-href
125
+ part.body = doc.to_s
114
126
  end
115
127
  end
116
128
 
@@ -7,15 +7,13 @@ module AhoyEmail
7
7
  end
8
8
 
9
9
  def perform
10
- if message.perform_deliveries && (data_header = message["Ahoy-Message"])
11
- Safely.safely do
12
- data = JSON.parse(data_header.to_s).symbolize_keys
13
- data[:message] = message
14
- AhoyEmail.track_method.call(data)
10
+ Safely.safely do
11
+ # perform_deliveries check still needed in observer
12
+ if message.perform_deliveries && message.ahoy_data
13
+ data = message.ahoy_data.merge(message: message)
14
+ message.ahoy_message = AhoyEmail.track_method.call(data)
15
15
  end
16
16
  end
17
- ensure
18
- message["Ahoy-Message"] = nil if message["Ahoy-Message"]
19
17
  end
20
18
  end
21
19
  end
@@ -1,3 +1,3 @@
1
1
  module AhoyEmail
2
- VERSION = "1.0.0"
2
+ VERSION = "1.1.1"
3
3
  end
@@ -1,34 +1,17 @@
1
- # taken from https://github.com/collectiveidea/audited/blob/master/lib/generators/audited/install_generator.rb
2
- require "rails/generators"
3
- require "rails/generators/migration"
4
- require "active_record"
5
1
  require "rails/generators/active_record"
6
2
 
7
3
  module AhoyEmail
8
4
  module Generators
9
5
  class InstallGenerator < Rails::Generators::Base
10
- include Rails::Generators::Migration
11
-
12
- source_root File.expand_path("../templates", __FILE__)
13
-
14
- # Implement the required interface for Rails::Generators::Migration.
15
- def self.next_migration_number(dirname) #:nodoc:
16
- next_migration_number = current_migration_number(dirname) + 1
17
- if ActiveRecord::Base.timestamped_migrations
18
- [Time.now.utc.strftime("%Y%m%d%H%M%S"), "%.14d" % next_migration_number].max
19
- else
20
- "%.3d" % next_migration_number
21
- end
22
- end
6
+ include ActiveRecord::Generators::Migration
7
+ source_root File.join(__dir__, "templates")
23
8
 
24
9
  def copy_migration
25
10
  migration_template "install.rb", "db/migrate/create_ahoy_messages.rb", migration_version: migration_version
26
11
  end
27
12
 
28
13
  def migration_version
29
- if ActiveRecord::VERSION::MAJOR >= 5
30
- "[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
31
- end
14
+ "[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
32
15
  end
33
16
  end
34
17
  end
@@ -5,7 +5,7 @@ class <%= migration_class_name %> < ActiveRecord::Migration<%= migration_version
5
5
  t.text :to
6
6
  t.string :mailer
7
7
  t.text :subject
8
- t.timestamp :sent_at
8
+ t.datetime :sent_at
9
9
  end
10
10
  end
11
11
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ahoy_email
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kane
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-09-27 00:00:00.000000000 Z
11
+ date: 2021-03-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: actionmailer
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '4.2'
19
+ version: '5'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '4.2'
26
+ version: '5'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: addressable
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -136,20 +136,6 @@ dependencies:
136
136
  - - ">="
137
137
  - !ruby/object:Gem::Version
138
138
  version: '0'
139
- - !ruby/object:Gem::Dependency
140
- name: rails
141
- requirement: !ruby/object:Gem::Requirement
142
- requirements:
143
- - - ">="
144
- - !ruby/object:Gem::Version
145
- version: '0'
146
- type: :development
147
- prerelease: false
148
- version_requirements: !ruby/object:Gem::Requirement
149
- requirements:
150
- - - ">="
151
- - !ruby/object:Gem::Version
152
- version: '0'
153
139
  - !ruby/object:Gem::Dependency
154
140
  name: sqlite3
155
141
  requirement: !ruby/object:Gem::Requirement
@@ -164,8 +150,8 @@ dependencies:
164
150
  - - ">="
165
151
  - !ruby/object:Gem::Version
166
152
  version: '0'
167
- description:
168
- email: andrew@chartkick.com
153
+ description:
154
+ email: andrew@ankane.org
169
155
  executables: []
170
156
  extensions: []
171
157
  extra_rdoc_files: []
@@ -179,8 +165,8 @@ files:
179
165
  - config/routes.rb
180
166
  - lib/ahoy_email.rb
181
167
  - lib/ahoy_email/engine.rb
182
- - lib/ahoy_email/interceptor.rb
183
168
  - lib/ahoy_email/mailer.rb
169
+ - lib/ahoy_email/observer.rb
184
170
  - lib/ahoy_email/processor.rb
185
171
  - lib/ahoy_email/tracker.rb
186
172
  - lib/ahoy_email/version.rb
@@ -190,7 +176,7 @@ homepage: https://github.com/ankane/ahoy_email
190
176
  licenses:
191
177
  - MIT
192
178
  metadata: {}
193
- post_install_message:
179
+ post_install_message:
194
180
  rdoc_options: []
195
181
  require_paths:
196
182
  - lib
@@ -198,16 +184,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
198
184
  requirements:
199
185
  - - ">="
200
186
  - !ruby/object:Gem::Version
201
- version: '2.2'
187
+ version: '2.4'
202
188
  required_rubygems_version: !ruby/object:Gem::Requirement
203
189
  requirements:
204
190
  - - ">="
205
191
  - !ruby/object:Gem::Version
206
192
  version: '0'
207
193
  requirements: []
208
- rubyforge_project:
209
- rubygems_version: 2.7.7
210
- signing_key:
194
+ rubygems_version: 3.2.3
195
+ signing_key:
211
196
  specification_version: 4
212
- summary: Email analytics for Rails
197
+ summary: First-party email analytics for Rails
213
198
  test_files: []
@@ -1,9 +0,0 @@
1
- module AhoyEmail
2
- class Interceptor
3
- class << self
4
- def delivering_email(message)
5
- AhoyEmail::Tracker.new(message).perform
6
- end
7
- end
8
- end
9
- end