mixpal 0.0.5 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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