mixpal 0.0.5 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. checksums.yaml +5 -5
  2. data/.rubocop.yml +24 -0
  3. data/.travis.yml +7 -0
  4. data/Dockerfile +6 -0
  5. data/README.md +51 -11
  6. data/Rakefile +10 -1
  7. data/docker-compose.yml +14 -0
  8. data/lib/mixpal.rb +27 -7
  9. data/lib/mixpal/event.rb +4 -4
  10. data/lib/mixpal/integration.rb +12 -8
  11. data/lib/mixpal/revenue.rb +32 -0
  12. data/lib/mixpal/tracker.rb +44 -27
  13. data/lib/mixpal/user.rb +9 -7
  14. data/lib/mixpal/util.rb +3 -3
  15. data/lib/mixpal/version.rb +1 -1
  16. data/mixpanel_assistant.gemspec +8 -7
  17. data/spec/lib/mixpal/event_spec.rb +20 -18
  18. data/spec/lib/mixpal/revenue_spec.rb +60 -0
  19. data/spec/lib/mixpal/tracker_spec.rb +181 -153
  20. data/spec/lib/mixpal/user_spec.rb +37 -31
  21. data/spec/lib/mixpal/util_spec.rb +15 -14
  22. data/spec/lib/mixpal_spec.rb +13 -2
  23. data/spec/spec_helper.rb +4 -7
  24. data/spec/support/custom_events_module.rb +5 -0
  25. data/spec/support/matchers/element_matchers.rb +2 -2
  26. data/test_app/.gitignore +18 -0
  27. data/test_app/Gemfile +11 -0
  28. data/test_app/README.rdoc +28 -0
  29. data/test_app/Rakefile +6 -0
  30. data/test_app/app/assets/images/.keep +0 -0
  31. data/test_app/app/assets/javascripts/application.js +16 -0
  32. data/test_app/app/assets/stylesheets/application.css +15 -0
  33. data/test_app/app/controllers/application_controller.rb +12 -0
  34. data/test_app/app/controllers/concerns/.keep +0 -0
  35. data/test_app/app/controllers/redirects_controller.rb +10 -0
  36. data/test_app/app/helpers/application_helper.rb +2 -0
  37. data/test_app/app/mailers/.keep +0 -0
  38. data/test_app/app/models/.keep +0 -0
  39. data/test_app/app/models/concerns/.keep +0 -0
  40. data/test_app/app/views/layouts/application.html.erb +16 -0
  41. data/test_app/app/views/redirects/new.html.erb +1 -0
  42. data/test_app/bin/bundle +3 -0
  43. data/test_app/bin/rails +8 -0
  44. data/test_app/bin/rake +8 -0
  45. data/test_app/bin/spring +18 -0
  46. data/test_app/config.ru +4 -0
  47. data/test_app/config/application.rb +30 -0
  48. data/test_app/config/boot.rb +4 -0
  49. data/test_app/config/environment.rb +5 -0
  50. data/test_app/config/environments/development.rb +37 -0
  51. data/test_app/config/environments/production.rb +82 -0
  52. data/test_app/config/environments/test.rb +39 -0
  53. data/test_app/config/initializers/assets.rb +8 -0
  54. data/test_app/config/initializers/backtrace_silencers.rb +7 -0
  55. data/test_app/config/initializers/cookies_serializer.rb +3 -0
  56. data/test_app/config/initializers/filter_parameter_logging.rb +4 -0
  57. data/test_app/config/initializers/inflections.rb +16 -0
  58. data/test_app/config/initializers/mime_types.rb +4 -0
  59. data/test_app/config/initializers/session_store.rb +3 -0
  60. data/test_app/config/initializers/wrap_parameters.rb +14 -0
  61. data/test_app/config/locales/en.yml +23 -0
  62. data/test_app/config/routes.rb +4 -0
  63. data/test_app/config/secrets.yml +22 -0
  64. data/test_app/db/seeds.rb +7 -0
  65. data/test_app/lib/assets/.keep +0 -0
  66. data/test_app/lib/tasks/.keep +0 -0
  67. data/test_app/log/.keep +0 -0
  68. data/test_app/public/404.html +67 -0
  69. data/test_app/public/422.html +67 -0
  70. data/test_app/public/500.html +66 -0
  71. data/test_app/public/favicon.ico +0 -0
  72. data/test_app/public/robots.txt +5 -0
  73. data/test_app/vendor/assets/javascripts/.keep +0 -0
  74. data/test_app/vendor/assets/stylesheets/.keep +0 -0
  75. metadata +112 -44
  76. data/spec/support/mock_rails.rb +0 -6
  77. data/spec/support/mock_storage.rb +0 -19
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 5f7b06bf4a86525182d6d44a19848532e62cc51a
4
- data.tar.gz: eb38535ae4ef5a101b61a8d30c0a4ed041c04b9d
2
+ SHA256:
3
+ metadata.gz: 70923329c62f76ee4d6b0796547cdd2e367038940c30d8f5ccb10b044f506d36
4
+ data.tar.gz: 492801097fa1ac2fb85f4600273b2db3fc6e0e8383c9b17112e0302100fc9020
5
5
  SHA512:
6
- metadata.gz: 8d4079b4038dad025ca08dfaefcb1239abeacbedccb1c0c7553e1cdaa8cea64849cd03717d83cf2315ede897b85634a55d6950e6de299dbd61f3354f9d97a08d
7
- data.tar.gz: 883548ba1bb19454caa6052e2d812efe80f09680b22b1647034865f0c885042b462d1412a37ac152ca1c2786ee89e61ea827a721f6fce11331aab1346907380f
6
+ metadata.gz: b8dbd5bd3051e8edf9ab7b130c0cd6a5af876d52c8fb6992e82c4ae3b22b58eeb726591bffc67368c9cc3c40e60faa2a1f5d5090a3392fe5fc96f374dde1af6e
7
+ data.tar.gz: e32c5e8e487ea31dc875df217e26f962c634978cd07b1d036b97a10f3e7eaa0fb8d446b0e8c00d89c4a5eacce591012400d01e3b541bd98238114a4ea1785aaf
@@ -0,0 +1,24 @@
1
+ Encoding:
2
+ Enabled: false
3
+
4
+ Documentation:
5
+ Enabled: false
6
+
7
+ ClassAndModuleChildren:
8
+ Enabled: false
9
+
10
+ ClassLength:
11
+ Enabled: false
12
+
13
+ MethodLength:
14
+ Enabled: false
15
+
16
+ DoubleNegation:
17
+ Enabled: false
18
+
19
+ AllCops:
20
+ NewCops: enable
21
+ Include:
22
+ - "Rakefile"
23
+ Exclude:
24
+ - "test_app/**/*"
@@ -0,0 +1,7 @@
1
+ language: ruby
2
+ before_install:
3
+ - gem install bundler
4
+ rvm:
5
+ - 2.5.8
6
+ - 2.6.6
7
+ - 2.7.1
@@ -0,0 +1,6 @@
1
+ FROM ruby:2.7.1
2
+
3
+ WORKDIR /workspace
4
+
5
+ COPY . .
6
+ RUN bundle install
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Mixpal
1
+ # Mixpal [![Build Status](https://travis-ci.org/philosophie/mixpal.svg?branch=master)](https://travis-ci.org/philosophie/mixpal)
2
2
 
3
3
  As the JavaScript library is Mixpanel's preferred method of usage,
4
4
  Mixpal aims to make it easier to work with from your Rails backend.
@@ -78,9 +78,41 @@ mixpanel.update_user email: "mynewemail@example.com"
78
78
 
79
79
  As with `register_user`, this method will also identify "special properties".
80
80
 
81
+ ### Custom Events
82
+
83
+ Mixpal allows you to define custom mixpal methods to use in your controllers/views
84
+
85
+ 1. create a custom module and define your mixpal events
86
+ ```ruby
87
+ module YourCustomEventsModule
88
+ def sign_up(user)
89
+ register_user user.attributes.slice('name', 'email')
90
+ track 'User signed up'
91
+ end
92
+ end
93
+ ```
94
+
95
+ 2. create a mixpal.rb initializer and configure mixpal to use your module
96
+ ```ruby
97
+ Mixpal.configure do |config|
98
+ config.helper_module = YourCustomEventsModule
99
+ end
100
+ ```
101
+
102
+ 3. use in controllers/views
103
+ ```ruby
104
+ class UserController < ActionController::Base
105
+ def create
106
+ # ... do cool stuff ...
107
+ mixpal.sign_up(user)
108
+ redirect_to root_path
109
+ end
110
+ end
111
+ ```
112
+
81
113
  ### Persistance Across Redirects
82
114
 
83
- Mixpal stores any tracked events or user data in `Rails.cache` when
115
+ Mixpal stores any tracked events or user data in the session when
84
116
  it detects a redirect so it can output the appropriate Mixpanel JS integration
85
117
  code to the client on the following render. This enables us to do cool things
86
118
  like:
@@ -104,16 +136,12 @@ class UsersController < ActionController::Base
104
136
  end
105
137
  ```
106
138
 
107
- #### Customizing the storage adapter
108
-
109
- You can specify a custom persistence storage adapter like so:
110
-
111
- ```ruby
112
- Mixpal::Tracker.storage = MyCustomAdapter.new
113
- ```
139
+ #### A note about `CookieStore` size limit
114
140
 
115
- Storage adapters must implement the following API: `write(key, value)`,
116
- `read(key)`, and `delete(key)`.
141
+ When using Rails' default `ActionDispatch::Session::CookieStore`, a 4K cookie
142
+ size limit is enforced. This cookie is shared by anything using the session.
143
+ If you anticipate tracking many events or large data sets to Mixpal,
144
+ [consider a different session store](http://guides.rubyonrails.org/action_controller_overview.html#session).
117
145
 
118
146
  ## Contributing
119
147
 
@@ -124,3 +152,15 @@ Storage adapters must implement the following API: `write(key, value)`,
124
152
  1. Commit your changes (`git commit -am 'Add some feature'`)
125
153
  1. Push to the branch (`git push origin feature/my-new-feature`)
126
154
  1. Create new Pull Request
155
+
156
+ ## Releasing
157
+
158
+ Bump `lib/mixpal/version.rb` then build + release with docker-compose. If you
159
+ prefer local development, inspect the Dockerfile to get your local env built.
160
+
161
+ ```
162
+ docker-compose build \
163
+ --build-arg USER_ID=$(id -u) \
164
+ --build-arg GROUP_ID=$(id -g)
165
+ docker-compose run rake release
166
+ ```
data/Rakefile CHANGED
@@ -1 +1,10 @@
1
- require "bundler/gem_tasks"
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+ require 'rubocop/rake_task'
6
+
7
+ RSpec::Core::RakeTask.new(:spec)
8
+ RuboCop::RakeTask.new
9
+
10
+ task default: %i[spec rubocop]
@@ -0,0 +1,14 @@
1
+ version: "3.6"
2
+
3
+ volumes:
4
+ bundle:
5
+
6
+ services:
7
+ gem:
8
+ build: .
9
+ volumes:
10
+ - .:/workspace
11
+ - bundle:/usr/local/bundle
12
+ - $HOME/.ssh:/root/.ssh:ro
13
+ - $HOME/.gitconfig:/root/.gitconfig:ro
14
+ - $HOME/.gem/credentials:/root/.gem/credentials
@@ -1,10 +1,30 @@
1
- require "mixpal/version"
2
- require "active_support/core_ext"
1
+ require 'mixpal/version'
2
+ require 'active_support'
3
+ require 'active_support/core_ext'
3
4
 
4
5
  module Mixpal
5
- autoload :Util, "mixpal/util"
6
- autoload :Tracker, "mixpal/tracker"
7
- autoload :Event, "mixpal/event"
8
- autoload :User, "mixpal/user"
9
- autoload :Integration, "mixpal/integration"
6
+ autoload :Util, 'mixpal/util'
7
+ autoload :Tracker, 'mixpal/tracker'
8
+ autoload :Event, 'mixpal/event'
9
+ autoload :User, 'mixpal/user'
10
+ autoload :Revenue, 'mixpal/revenue'
11
+ autoload :Integration, 'mixpal/integration'
12
+
13
+ class << self
14
+ def configuration
15
+ @configuration ||= Configuration.new
16
+ end
17
+
18
+ def configure
19
+ yield(configuration)
20
+ end
21
+ end
22
+
23
+ class Configuration
24
+ attr_writer :helper_module
25
+
26
+ def helper_module
27
+ @helper_module ||= Module.new
28
+ end
29
+ end
10
30
  end
@@ -14,13 +14,13 @@ module Mixpal
14
14
 
15
15
  def to_store
16
16
  {
17
- name: name,
18
- properties: properties,
17
+ 'name' => name,
18
+ 'properties' => properties
19
19
  }
20
20
  end
21
21
 
22
22
  def self.from_store(data)
23
- new(data[:name], data[:properties])
23
+ new(data['name'], data['properties'])
24
24
  end
25
25
  end
26
- end
26
+ end
@@ -4,31 +4,35 @@ module Mixpal
4
4
 
5
5
  included do
6
6
  helper_method :mixpanel
7
- after_filter :store_mixpanel_if_redirecting
7
+ if Rails::VERSION::MAJOR >= 4
8
+ after_action :store_mixpanel_if_redirecting
9
+ else
10
+ after_filter :store_mixpanel_if_redirecting
11
+ end
8
12
 
9
13
  class_attribute :mixpanel_identity_data
10
14
  def self.mixpanel_identity(object_method, attribute_method)
11
15
  self.mixpanel_identity_data = {
12
16
  object_method: object_method,
13
- attribute_method: attribute_method,
17
+ attribute_method: attribute_method
14
18
  }
15
19
  end
16
20
  end
17
21
 
18
22
  def mixpanel
19
23
  @mixpanel ||= begin
20
- identity = if data = self.class.mixpanel_identity_data
21
- send(data[:object_method]).try(data[:attribute_method])
22
- end
24
+ identity = if (data = self.class.mixpanel_identity_data)
25
+ send(data[:object_method]).try(data[:attribute_method])
26
+ end
23
27
 
24
- Mixpal::Tracker.new(identity: identity)
28
+ Mixpal::Tracker.new(identity: identity).tap { |t| t.restore!(session) }
25
29
  end
26
30
  end
27
31
 
28
32
  private
29
33
 
30
34
  def store_mixpanel_if_redirecting
31
- mixpanel.store! if status == 302
35
+ mixpanel.store!(session) if status == 302
32
36
  end
33
37
  end
34
- end
38
+ end
@@ -0,0 +1,32 @@
1
+ module Mixpal
2
+ class Revenue
3
+ attr_reader :amount, :properties
4
+
5
+ def initialize(amount, properties)
6
+ @amount = amount
7
+ @properties = properties
8
+ end
9
+
10
+ def render
11
+ args = "#{amount}, #{properties_as_js_object_for_mixpanel}"
12
+ "mixpanel.people.track_charge(#{args});".html_safe
13
+ end
14
+
15
+ def to_store
16
+ {
17
+ 'amount' => amount,
18
+ 'properties' => properties
19
+ }
20
+ end
21
+
22
+ def self.from_store(data)
23
+ new(data['amount'], data['properties'])
24
+ end
25
+
26
+ private
27
+
28
+ def properties_as_js_object_for_mixpanel
29
+ Mixpal::Util.hash_to_js_object_string(properties)
30
+ end
31
+ end
32
+ end
@@ -1,16 +1,15 @@
1
1
  module Mixpal
2
2
  class Tracker
3
- attr_reader :events, :user_updates, :identity, :alias_user
3
+ attr_reader :events, :user_updates, :revenue_updates, :identity, :alias_user
4
4
 
5
- STORAGE_KEY = "mixpal"
6
- class_attribute :storage
7
- self.storage = Rails.cache
5
+ STORAGE_KEY = 'mixpal'
6
+
7
+ def initialize(args = {})
8
+ extend Mixpal.configuration.helper_module
8
9
 
9
- def initialize(args={})
10
10
  @events = []
11
11
  @user_updates = []
12
-
13
- restore!
12
+ @revenue_updates = []
14
13
 
15
14
  @identity = args[:identity]
16
15
  end
@@ -24,44 +23,62 @@ module Mixpal
24
23
  user_updates << Mixpal::User.new(properties)
25
24
  end
26
25
 
27
- def track(name, properties={})
26
+ def track(name, properties = {})
28
27
  events << Mixpal::Event.new(name, properties)
29
28
  end
30
29
 
30
+ def track_charge(amount, properties = {})
31
+ revenue_updates << Mixpal::Revenue.new(amount, properties)
32
+ end
33
+
31
34
  def render
32
- "".tap do |html|
33
- html << "<script type=\"text/javascript\">"
35
+ ''.tap do |html|
36
+ html << '<script type="text/javascript">'
34
37
  html << "mixpanel.alias(\"#{identity}\");" if alias_user
35
38
  html << "mixpanel.identify(\"#{identity}\");" if identity
36
- html << events.map(&:render).join("")
37
- html << user_updates.map(&:render).join("")
38
- html << "</script>"
39
+ html << events.map(&:render).join('')
40
+ html << user_updates.map(&:render).join('')
41
+ html << revenue_updates.map(&:render).join('')
42
+ html << '</script>'
39
43
  end.html_safe
40
44
  end
41
45
 
42
- def store!
43
- self.class.storage.write(STORAGE_KEY, to_store)
46
+ def store!(session)
47
+ session[STORAGE_KEY] = to_store
44
48
  end
45
49
 
46
- private
50
+ def restore!(session)
51
+ data = session[STORAGE_KEY] || {}
47
52
 
48
- def restore!
49
- data = self.class.storage.read(STORAGE_KEY) || {}
53
+ @alias_user = data['alias_user']
54
+ @identity ||= data['identity']
50
55
 
51
- @alias_user = data[:alias_user]
52
- @identity = data[:identity]
53
- @events = data[:events].map { |e| Mixpal::Event.from_store(e) } if data[:events]
54
- @user_updates = data[:user_updates].map { |u| Mixpal::User.from_store(u) } if data[:user_updates]
56
+ if data['events']
57
+ @events = data['events'].map { |e| Mixpal::Event.from_store(e) }
58
+ end
55
59
 
56
- self.class.storage.delete(STORAGE_KEY)
60
+ if data['user_updates']
61
+ @user_updates =
62
+ data['user_updates'].map { |u| Mixpal::User.from_store(u) }
63
+ end
64
+
65
+ if data['revenue_updates']
66
+ @revenue_updates =
67
+ data['revenue_updates'].map { |u| Mixpal::Revenue.from_store(u) }
68
+ end
69
+
70
+ session.delete(STORAGE_KEY)
57
71
  end
58
72
 
73
+ private
74
+
59
75
  def to_store
60
76
  {
61
- alias_user: @alias_user,
62
- identity: identity,
63
- events: events.map(&:to_store),
64
- user_updates: user_updates.map(&:to_store),
77
+ 'alias_user' => alias_user,
78
+ 'identity' => identity,
79
+ 'events' => events.map(&:to_store),
80
+ 'user_updates' => user_updates.map(&:to_store),
81
+ 'revenue_updates' => revenue_updates.map(&:to_store)
65
82
  }
66
83
  end
67
84
  end
@@ -12,12 +12,12 @@ module Mixpal
12
12
 
13
13
  def to_store
14
14
  {
15
- properties: properties,
15
+ 'properties' => properties
16
16
  }
17
17
  end
18
18
 
19
19
  def self.from_store(data)
20
- new(data[:properties])
20
+ new(data['properties'])
21
21
  end
22
22
 
23
23
  private
@@ -29,15 +29,17 @@ module Mixpal
29
29
  # Isolate special properties and rename their keys to align with
30
30
  # Mixpanel's naming.
31
31
  def properties_for_mixpanel
32
- Hash[properties.map {|k, v| [mixpanel_special_properties_map[k] || k, v] }]
32
+ Hash[
33
+ properties.map { |k, v| [mixpanel_special_properties_map[k] || k, v] }
34
+ ]
33
35
  end
34
36
 
35
37
  def mixpanel_special_properties_map
36
38
  {
37
- name: "$name",
38
- email: "$email",
39
- created_at: "$created",
39
+ name: '$name',
40
+ email: '$email',
41
+ created_at: '$created'
40
42
  }.with_indifferent_access
41
43
  end
42
44
  end
43
- end
45
+ end