amplitude-api 0.1.0 → 0.3.3

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.
@@ -1,3 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This class is 115 lines long. It's on the limit, it should be refactored before
4
+ # including more code.
5
+ #
6
+ # rubocop:disable Metrics/ClassLength
1
7
  class AmplitudeAPI
2
8
  # AmplitudeAPI::Event
3
9
  class Event
@@ -7,15 +13,46 @@ class AmplitudeAPI
7
13
 
8
14
  # Create a new Event
9
15
  #
10
- # See (Amplitude HTTP API Documentation)[https://amplitude.zendesk.com/hc/en-us/articles/204771828-HTTP-API]
16
+ # See (Amplitude HTTP API Documentation)[https://developers.amplitude.com/docs/http-api-v2]
11
17
  # for a list of valid parameters and their types.
12
18
  def initialize(attributes = {})
19
+ @extra_properties = []
13
20
  attributes.each do |k, v|
14
- send("#{k}=", v) if respond_to?("#{k}=")
21
+ send("#{k}=", v)
15
22
  end
16
23
  validate_arguments
17
24
  end
18
25
 
26
+ def method_missing(method_name, *args)
27
+ super if block_given?
28
+ super unless method_name.to_s.end_with? "="
29
+
30
+ property_name = method_name.to_s.delete_suffix("=")
31
+
32
+ @extra_properties << property_name
33
+
34
+ create_setter property_name
35
+ create_getter property_name
36
+
37
+ send("#{property_name}=".to_sym, *args)
38
+ end
39
+
40
+ def create_setter(attribute_name)
41
+ self.class.send(:define_method, "#{attribute_name}=".to_sym) do |value|
42
+ instance_variable_set("@" + attribute_name.to_s, value)
43
+ end
44
+ end
45
+
46
+ def create_getter(attribute_name)
47
+ self.class.send(:define_method, attribute_name.to_sym) do
48
+ instance_variable_get("@" + attribute_name.to_s)
49
+ end
50
+ end
51
+
52
+ def respond_to_missing?(method_name, *args)
53
+ @extra_properties.include?(method_name) || @extra_properties.include?("#{method_name}=") || super
54
+ end
55
+
19
56
  def user_id=(value)
20
57
  @user_id =
21
58
  if value.respond_to?(:id)
@@ -36,28 +73,42 @@ class AmplitudeAPI
36
73
  }
37
74
  event[:user_id] = user_id if user_id
38
75
  event[:device_id] = device_id if device_id
39
- event.merge(optional_properties).merge(revenue_hash)
76
+ event.merge(optional_properties).merge(revenue_hash).merge(extra_properties)
40
77
  end
41
78
  alias to_h to_hash
42
79
 
43
80
  # @return [ Hash ] Optional properties
81
+ #
82
+ # Returns optional properties (belong to the API but are optional)
44
83
  def optional_properties
45
- %i[device_id time ip platform country insert_id].map do |prop|
84
+ AmplitudeAPI::Config.optional_properties.map do |prop|
46
85
  val = prop == :time ? formatted_time : send(prop)
47
86
  val ? [prop, val] : nil
48
87
  end.compact.to_h
49
88
  end
50
89
 
90
+ # @return [ Hash ] Extra properties
91
+ #
92
+ # Returns optional properties (not belong to the API, are assigned by the user)
93
+ # This way, if the API is updated with new properties, the gem will be able
94
+ # to work with the new specification until the code is modified
95
+ def extra_properties
96
+ @extra_properties.map do |prop|
97
+ val = send(prop)
98
+ val ? [prop.to_sym, val] : nil
99
+ end.compact.to_h
100
+ end
101
+
51
102
  # @return [ true, false ]
52
103
  #
53
104
  # Returns true if the event type matches one reserved by Amplitude API.
54
105
  def reserved_event?(type)
55
- ['[Amplitude] Start Session',
56
- '[Amplitude] End Session',
57
- '[Amplitude] Revenue',
58
- '[Amplitude] Revenue (Verified)',
59
- '[Amplitude] Revenue (Unverified)',
60
- '[Amplitude] Merged User'].include?(type)
106
+ ["[Amplitude] Start Session",
107
+ "[Amplitude] End Session",
108
+ "[Amplitude] Revenue",
109
+ "[Amplitude] Revenue (Verified)",
110
+ "[Amplitude] Revenue (Unverified)",
111
+ "[Amplitude] Merged User"].include?(type)
61
112
  end
62
113
 
63
114
  # @return [ true, false ]
@@ -89,15 +140,23 @@ class AmplitudeAPI
89
140
  end
90
141
 
91
142
  def validate_required_arguments
92
- raise ArgumentError, 'You must provide user_id or device_id (or both)' unless user_id || device_id
93
- raise ArgumentError, 'You must provide event_type' unless event_type
94
- raise ArgumentError, 'Invalid event_type - cannot match a reserved event name' if reserved_event?(event_type)
143
+ raise ArgumentError, "You must provide user_id or device_id (or both)" unless user_id || device_id
144
+ raise ArgumentError, "You must provide event_type" unless event_type
145
+ raise ArgumentError, "Invalid event_type - cannot match a reserved event name" if reserved_event?(event_type)
95
146
  end
96
147
 
97
148
  def validate_revenue_arguments
98
- return self.quantity ||= 1 if price
99
- raise ArgumentError, 'You must provide a price in order to use the product_id' if product_id
100
- raise ArgumentError, 'You must provide a price in order to use the revenue_type' if revenue_type
149
+ return true if !revenue_type && !product_id
150
+ return true if revenue || price
151
+
152
+ raise ArgumentError, revenue_error_message
153
+ end
154
+
155
+ def revenue_error_message
156
+ error_field = "product_id" if product_id
157
+ error_field = "revenue_type" if revenue_type
158
+
159
+ "You must provide a price or a revenue in order to use the field #{error_field}"
101
160
  end
102
161
 
103
162
  def revenue_hash
@@ -106,6 +165,7 @@ class AmplitudeAPI
106
165
  revenue_hash[:revenueType] = revenue_type if revenue_type
107
166
  revenue_hash[:quantity] = quantity if quantity
108
167
  revenue_hash[:price] = price if price
168
+ revenue_hash[:revenue] = revenue if revenue
109
169
  revenue_hash
110
170
  end
111
171
 
@@ -114,3 +174,4 @@ class AmplitudeAPI
114
174
  end
115
175
  end
116
176
  end
177
+ # rubocop:enable Metrics/ClassLength
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class AmplitudeAPI
2
4
  # AmplitudeAPI::Identification
3
5
  class Identification
@@ -16,7 +18,7 @@ class AmplitudeAPI
16
18
  # @param [ String ] user_id a user_id to associate with the identification
17
19
  # @param [ String ] device_id a device_id to associate with the identification
18
20
  # @param [ Hash ] user_properties various properties to attach to the user identification
19
- def initialize(user_id: '', device_id: nil, user_properties: {})
21
+ def initialize(user_id: "", device_id: nil, user_properties: {})
20
22
  self.user_id = user_id
21
23
  self.device_id = device_id if device_id
22
24
  self.user_properties = user_properties
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class AmplitudeAPI
2
- VERSION = '0.1.0'.freeze
4
+ VERSION = "0.3.3"
3
5
  end
data/readme.md CHANGED
@@ -19,7 +19,7 @@ AmplitudeAPI.config.api_key = "abcdef123456"
19
19
 
20
20
 
21
21
  event = AmplitudeAPI::Event.new({
22
- user_id: "123",
22
+ user_id: "12345",
23
23
  event_type: "clicked on home",
24
24
  time: Time.now,
25
25
  insert_id: 'f47ac10b-58cc-4372-a567-0e02b2c3d479',
@@ -31,7 +31,38 @@ event = AmplitudeAPI::Event.new({
31
31
  AmplitudeAPI.track(event)
32
32
  ```
33
33
 
34
- ## GDPR Compliance
34
+ You can track multiple events with a single call, with the only limit of the payload
35
+ size imposed by Amplitude:
36
+
37
+ ```ruby
38
+ event_1 = AmplitudeAPI::Event.new(...)
39
+ event_2 = AmplitudeAPI::Event.new(...)
40
+
41
+ AmplitudeAPI.track(event_1, event_2)
42
+ ```
43
+
44
+ ```ruby
45
+ events = [event_1, event_2]
46
+ AmplitudeAPI.track(*events)
47
+ ```
48
+
49
+ In case you use an integer as the time, it is expected to be in seconds. Values in
50
+ the time field will be converted to milliseconds using `->(time) { time ? time.to_i * 1_000 : nil }`
51
+ You can change this behaviour and use your custom formatter. For example, in case
52
+ you wanted to use milliseconds instead of seconds you could do this:
53
+ ```ruby
54
+ AmplitudeAPI.config.time_formatter = ->(time) { time ? time.to_i : nil },
55
+ ```
56
+
57
+ You can speficy track options in the config. The options will be applied to all subsequent requests:
58
+
59
+ ```ruby
60
+ AmplitudeAPI.config.options = { min_id_length: 10 }
61
+ AmplitudeAPI.track(event)
62
+ ```
63
+
64
+
65
+ ## User Privacy APIs
35
66
 
36
67
  The following code snippet will delete a user from amplitude
37
68
 
@@ -42,12 +73,13 @@ AmplitudeAPI.config.api_key = "abcdef123456"
42
73
  # Configure your Amplitude Secret Key
43
74
  AmplitudeAPI.config.secret_key = "secretMcSecret"
44
75
 
45
- AmplitudeAPI.delete(user_ids: [233],
46
- requester: "privacy@mycompany.com"
76
+ AmplitudeAPI.delete(user_ids: ["12345"],
77
+ requester: "privacy@example.com"
47
78
  )
48
79
  ```
49
80
 
50
- Currently, we are using this in Rails and using ActiveJob to dispatch events asynchronously. I plan on moving background/asynchronous support into this gem.
81
+ Currently, we are using this in Rails and using ActiveJob to dispatch events asynchronously. I plan on moving
82
+ background/asynchronous support into this gem.
51
83
 
52
84
  ## What's Next
53
85
 
@@ -55,7 +87,7 @@ Currently, we are using this in Rails and using ActiveJob to dispatch events asy
55
87
  * Configurable default account to use when no `user_id` present
56
88
 
57
89
  ## Other useful resources
58
- * [Amplitude HTTP Api Documentation](https://amplitude.zendesk.com/hc/en-us/articles/204771828)
90
+ * [Amplitude HTTP API V2 Api Documentation](https://developers.amplitude.com/docs/http-api-v2)
59
91
  * [Segment.io Amplitude integration](https://segment.com/docs/integrations/amplitude/)
60
92
 
61
93
  ## Contributing
@@ -1,277 +1,318 @@
1
- require 'spec_helper'
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
2
4
 
3
5
  describe AmplitudeAPI::Event do
4
6
  user = Struct.new(:id)
5
7
 
6
- context 'with a user object' do
7
- describe '#body' do
8
+ context "with a user object" do
9
+ describe "#body" do
8
10
  it "populates with the user's id" do
9
11
  event = described_class.new(
10
12
  user_id: user.new(123),
11
- event_type: 'clicked on home'
13
+ event_type: "clicked on home"
12
14
  )
13
15
  expect(event.to_hash[:user_id]).to eq(123)
14
16
  end
15
17
  end
16
18
  end
17
19
 
18
- context 'with a user id' do
19
- describe '#body' do
20
+ context "with a user id" do
21
+ describe "#body" do
20
22
  it "populates with the user's id" do
21
23
  event = described_class.new(
22
24
  user_id: 123,
23
- event_type: 'clicked on home'
25
+ event_type: "clicked on home"
24
26
  )
25
27
  expect(event.to_hash[:user_id]).to eq(123)
26
28
  end
27
29
  end
28
30
  end
29
31
 
30
- context 'without a user' do
31
- describe '#body' do
32
- it 'populates with the unknown user' do
32
+ context "without a user" do
33
+ describe "#body" do
34
+ it "populates with the unknown user" do
33
35
  event = described_class.new(
34
36
  user_id: nil,
35
- event_type: 'clicked on home'
37
+ event_type: "clicked on home"
36
38
  )
37
39
  expect(event.to_hash[:user_id]).to eq(AmplitudeAPI::USER_WITH_NO_ACCOUNT)
38
40
  end
39
41
  end
40
42
  end
41
43
 
42
- describe 'init' do
43
- context 'attributes' do
44
- it 'accepts string attributes' do
44
+ describe "init" do
45
+ context "attributes" do
46
+ it "accepts string attributes" do
45
47
  time = Time.at(1_451_606_400_000 / 1_000)
46
48
  event = described_class.new(
47
- 'user_id' => 123,
48
- 'device_id' => 'abcd',
49
- 'event_type' => 'sausage',
50
- 'event_properties' => { 'a' => 'b' },
51
- 'user_properties' => { 'c' => 'd' },
52
- 'time' => time,
53
- 'ip' => '127.0.0.1',
54
- 'platform' => 'Web',
55
- 'country' => 'United States',
56
- 'insert_id' => 'bestId'
49
+ "user_id" => 123,
50
+ "device_id" => "abcd",
51
+ "event_type" => "sausage",
52
+ "event_properties" => { "a" => "b" },
53
+ "user_properties" => { "c" => "d" },
54
+ "time" => time,
55
+ "ip" => "127.0.0.1",
56
+ "platform" => "Web",
57
+ "country" => "United States",
58
+ "insert_id" => "bestId"
57
59
  )
58
60
 
59
- expect(event.to_hash).to eq(event_type: 'sausage',
61
+ expect(event.to_hash).to eq(event_type: "sausage",
60
62
  user_id: 123,
61
- device_id: 'abcd',
62
- event_properties: { 'a' => 'b' },
63
- user_properties: { 'c' => 'd' },
63
+ device_id: "abcd",
64
+ event_properties: { "a" => "b" },
65
+ user_properties: { "c" => "d" },
64
66
  time: 1_451_606_400_000,
65
- ip: '127.0.0.1',
66
- platform: 'Web',
67
- country: 'United States',
68
- insert_id: 'bestId')
67
+ ip: "127.0.0.1",
68
+ platform: "Web",
69
+ country: "United States",
70
+ insert_id: "bestId")
69
71
  end
70
72
 
71
- it 'accepts symbol attributes' do
73
+ it "accepts symbol attributes" do
72
74
  time = Time.at(1_451_606_400_000 / 1_000)
73
75
  event = described_class.new(
74
76
  user_id: 123,
75
- device_id: 'abcd',
76
- event_type: 'sausage',
77
- event_properties: { 'a' => 'b' },
78
- user_properties: { 'c' => 'd' },
77
+ device_id: "abcd",
78
+ event_type: "sausage",
79
+ event_properties: { "a" => "b" },
80
+ user_properties: { "c" => "d" },
79
81
  time: time,
80
- ip: '127.0.0.1',
81
- platform: 'Web',
82
- country: 'United States',
83
- insert_id: 'bestId'
82
+ ip: "127.0.0.1",
83
+ platform: "Web",
84
+ country: "United States",
85
+ insert_id: "bestId"
84
86
  )
85
87
 
86
- expect(event.to_hash).to eq(event_type: 'sausage',
88
+ expect(event.to_hash).to eq(event_type: "sausage",
87
89
  user_id: 123,
88
- device_id: 'abcd',
89
- event_properties: { 'a' => 'b' },
90
- user_properties: { 'c' => 'd' },
90
+ device_id: "abcd",
91
+ event_properties: { "a" => "b" },
92
+ user_properties: { "c" => "d" },
91
93
  time: 1_451_606_400_000,
92
- ip: '127.0.0.1',
93
- platform: 'Web',
94
- country: 'United States',
95
- insert_id: 'bestId')
94
+ ip: "127.0.0.1",
95
+ platform: "Web",
96
+ country: "United States",
97
+ insert_id: "bestId")
96
98
  end
97
99
  end
98
100
 
99
- context 'the user does not send in a price' do
100
- it 'raises an error if the user sends in a product_id' do
101
+ context "the user sends a revenue_type or a product_id" do
102
+ it "raises an error if there is not a price neither a revenue" do
103
+ expect do
104
+ described_class.new(
105
+ user_id: 123,
106
+ event_type: "bad event",
107
+ product_id: "hopscotch.4lyfe"
108
+ )
109
+ end.to raise_error ArgumentError, /You must provide a price or a revenue/
110
+
111
+ expect do
112
+ described_class.new(
113
+ user_id: 123,
114
+ event_type: "bad event",
115
+ revenue_type: "whatever"
116
+ )
117
+ end.to raise_error ArgumentError, /You must provide a price or a revenue/
118
+ end
119
+
120
+ it "does not raise an error if there is a price" do
121
+ expect do
122
+ described_class.new(
123
+ user_id: 123,
124
+ event_type: "bad event",
125
+ product_id: "hopscotch.4lyfe",
126
+ price: 10.2
127
+ )
128
+ end.not_to raise_error
129
+
101
130
  expect do
102
131
  described_class.new(
103
132
  user_id: 123,
104
- event_type: 'bad event',
105
- product_id: 'hopscotch.4lyfe'
133
+ event_type: "bad event",
134
+ revenue_type: "whatever",
135
+ price: 10.2
106
136
  )
107
- end.to raise_error(ArgumentError)
137
+ end.not_to raise_error
108
138
  end
109
139
 
110
- it 'raises an error if the user sends in a revenue_type' do
140
+ it "does not raise an error if there is a revenue" do
141
+ expect do
142
+ described_class.new(
143
+ user_id: 123,
144
+ event_type: "bad event",
145
+ product_id: "hopscotch.4lyfe",
146
+ revenue: 100.1
147
+ )
148
+ end.not_to raise_error
149
+
111
150
  expect do
112
151
  described_class.new(
113
152
  user_id: 123,
114
- event_type: 'bad event',
115
- revenue_type: 'tax return'
153
+ event_type: "bad event",
154
+ revenue_type: "whatever",
155
+ revenue: 100.1
116
156
  )
117
- end.to raise_error(ArgumentError)
157
+ end.not_to raise_error
118
158
  end
119
159
  end
120
160
  end
121
161
 
122
- describe '#to_hash' do
123
- it 'includes the event type' do
162
+ describe "#to_hash" do
163
+ it "includes the event type" do
124
164
  event = described_class.new(
125
165
  user_id: 123,
126
- event_type: 'clicked on home'
166
+ event_type: "clicked on home"
127
167
  )
128
- expect(event.to_hash[:event_type]).to eq('clicked on home')
168
+ expect(event.to_hash[:event_type]).to eq("clicked on home")
129
169
  end
130
170
 
131
- it 'includes arbitrary properties' do
171
+ it "includes arbitrary properties" do
132
172
  event = described_class.new(
133
173
  user_id: 123,
134
- event_type: 'clicked on home',
174
+ event_type: "clicked on home",
135
175
  event_properties: { abc: :def }
136
176
  )
137
177
  expect(event.to_hash[:event_properties]).to eq(abc: :def)
138
178
  end
139
179
 
140
- describe 'time' do
141
- it 'includes a time for the event' do
180
+ describe "time" do
181
+ it "includes a time for the event" do
142
182
  time = Time.at(1_451_606_400_000 / 1_000)
143
183
  event = described_class.new(
144
184
  user_id: 123,
145
- event_type: 'clicked on home',
185
+ event_type: "clicked on home",
146
186
  time: time
147
187
  )
148
188
  expect(event.to_hash[:time]).to eq(1_451_606_400_000)
149
189
  end
150
190
 
151
- it 'does not include time if it is not set' do
191
+ it "does not include time if it is not set" do
152
192
  event = described_class.new(
153
193
  user_id: 123,
154
- event_type: 'clicked on home'
194
+ event_type: "clicked on home"
155
195
  )
156
196
  expect(event.to_hash).not_to have_key(:time)
157
197
  end
158
198
  end
159
199
 
160
- describe 'insert_id' do
161
- it 'includes an insert_id for the event' do
200
+ describe "insert_id" do
201
+ it "includes an insert_id for the event" do
162
202
  event = described_class.new(
163
203
  user_id: 123,
164
- event_type: 'clicked on home',
165
- insert_id: 'foo-bar'
204
+ event_type: "clicked on home",
205
+ insert_id: "foo-bar"
166
206
  )
167
- expect(event.to_hash[:insert_id]).to eq('foo-bar')
207
+ expect(event.to_hash[:insert_id]).to eq("foo-bar")
168
208
  end
169
209
 
170
- it 'does not include insert_id if it is not set' do
210
+ it "does not include insert_id if it is not set" do
171
211
  event = described_class.new(
172
212
  user_id: 123,
173
- event_type: 'clicked on home'
213
+ event_type: "clicked on home"
174
214
  )
175
215
  expect(event.to_hash).not_to have_key(:insert_id)
176
216
  end
177
217
  end
178
218
 
179
- describe 'platform' do
180
- it 'includes the platform for the event' do
219
+ describe "platform" do
220
+ it "includes the platform for the event" do
181
221
  event = described_class.new(
182
222
  user_id: 123,
183
- event_type: 'clicked on home',
184
- platform: 'Web'
223
+ event_type: "clicked on home",
224
+ platform: "Web"
185
225
  )
186
- expect(event.to_hash[:platform]).to eq('Web')
226
+ expect(event.to_hash[:platform]).to eq("Web")
187
227
  end
188
228
 
189
- it 'does not include the platform if it is not set' do
229
+ it "does not include the platform if it is not set" do
190
230
  event = described_class.new(
191
231
  user_id: 123,
192
- event_type: 'clicked on home'
232
+ event_type: "clicked on home"
193
233
  )
194
234
  expect(event.to_hash).not_to have_key(:platform)
195
235
  end
196
236
  end
197
237
 
198
- describe 'country' do
199
- it 'includes the country for the event' do
238
+ describe "country" do
239
+ it "includes the country for the event" do
200
240
  event = described_class.new(
201
241
  user_id: 123,
202
- event_type: 'clicked on home',
203
- country: 'United States'
242
+ event_type: "clicked on home",
243
+ country: "United States"
204
244
  )
205
- expect(event.to_hash[:country]).to eq('United States')
245
+ expect(event.to_hash[:country]).to eq("United States")
206
246
  end
207
247
 
208
- it 'does not include the country if it is not set' do
248
+ it "does not include the country if it is not set" do
209
249
  event = described_class.new(
210
250
  user_id: 123,
211
- event_type: 'clicked on home'
251
+ event_type: "clicked on home"
212
252
  )
213
253
  expect(event.to_hash).not_to have_key(:country)
214
254
  end
215
255
  end
216
256
 
217
- describe 'revenue params' do
218
- it 'includes the price if it is set' do
257
+ describe "revenue params" do
258
+ it "includes the price if it is set" do
219
259
  price = 100_000.99
220
260
  event = described_class.new(
221
261
  user_id: 123,
222
- event_type: 'clicked on home',
262
+ event_type: "clicked on home",
223
263
  price: price
224
264
  )
225
265
  expect(event.to_hash[:price]).to eq(price)
226
266
  end
227
267
 
228
- it 'sets the quantity to 1 if the price is set and the quantity is not' do
229
- price = 100_000.99
268
+ it "includes the quantity if it is set" do
269
+ quantity = 100
230
270
  event = described_class.new(
231
271
  user_id: 123,
232
- event_type: 'clicked on home',
233
- price: price
272
+ event_type: "clicked on home",
273
+ quantity: quantity,
274
+ price: 10.99
234
275
  )
235
- expect(event.to_hash[:quantity]).to eq(1)
276
+ expect(event.to_hash[:quantity]).to eq(quantity)
236
277
  end
237
278
 
238
- it 'includes the quantity if it is set' do
239
- quantity = 100
279
+ it "includes the revenue if it is set" do
280
+ revenue = 100
240
281
  event = described_class.new(
241
282
  user_id: 123,
242
- event_type: 'clicked on home',
243
- quantity: quantity,
244
- price: 10.99
283
+ event_type: "clicked on home",
284
+ quantity: 456,
285
+ revenue: revenue
245
286
  )
246
- expect(event.to_hash[:quantity]).to eq(quantity)
287
+ expect(event.to_hash[:revenue]).to eq(revenue)
247
288
  end
248
289
 
249
- it 'includes the productID if set' do
250
- product_id = 'hopscotch.subscriptions.rule'
290
+ it "includes the productID if set" do
291
+ product_id = "hopscotch.subscriptions.rule"
251
292
  event = described_class.new(
252
293
  user_id: 123,
253
- event_type: 'clicked on home',
294
+ event_type: "clicked on home",
254
295
  price: 199.99,
255
296
  product_id: product_id
256
297
  )
257
298
  expect(event.to_hash[:productId]).to eq(product_id)
258
299
  end
259
300
 
260
- it 'includes the revenueType if set' do
261
- revenue_type = 'income'
301
+ it "includes the revenueType if set" do
302
+ revenue_type = "income"
262
303
  event = described_class.new(
263
304
  user_id: 123,
264
- event_type: 'clicked on home',
305
+ event_type: "clicked on home",
265
306
  price: 199.99,
266
307
  revenue_type: revenue_type
267
308
  )
268
309
  expect(event.to_hash[:revenueType]).to eq(revenue_type)
269
310
  end
270
311
 
271
- it 'does not include revenue params if they are not set' do
312
+ it "does not include revenue params if they are not set" do
272
313
  event = described_class.new(
273
314
  user_id: 123,
274
- event_type: 'clicked on home'
315
+ event_type: "clicked on home"
275
316
  )
276
317
  expect(event.to_hash).not_to have_key(:quantity)
277
318
  expect(event.to_hash).not_to have_key(:revenueType)
@@ -280,4 +321,63 @@ describe AmplitudeAPI::Event do
280
321
  end
281
322
  end
282
323
  end
324
+
325
+ describe "arbitrary properties" do
326
+ # We need to create a class for each test because the methods we are calling
327
+ # in this test group are modifying the class
328
+ let(:klass) { Class.new described_class }
329
+
330
+ let(:event) {
331
+ klass.new(
332
+ user_id: 123,
333
+ event_type: "bad event"
334
+ )
335
+ }
336
+
337
+ it "creates properties on initialization" do
338
+ property_value = "an arbitrary value"
339
+ creation_data = {
340
+ user_id: "whatever",
341
+ event_type: "something happened",
342
+ arbitrary_property: property_value
343
+ }
344
+
345
+ initialized_event = klass.new(creation_data)
346
+
347
+ expect(initialized_event.arbitrary_property).to eq property_value
348
+ end
349
+
350
+ it "creates arbitrary properties when assigning values" do
351
+ event.arbitrary_property = "arbitrary value"
352
+
353
+ expect(event.arbitrary_property).to eq "arbitrary value"
354
+ end
355
+
356
+ it "responds_to? arbitrary properties" do
357
+ event.arbitrary_property = "arbitrary value"
358
+
359
+ expect(event.respond_to?(:arbitrary_property)).to be true
360
+ expect(event.respond_to?(:arbitrary_property=)).to be true
361
+ end
362
+
363
+ it "does not define property until assigned" do
364
+ expect {
365
+ event.undefined_property
366
+ }.to raise_error NoMethodError, /undefined_property/
367
+ end
368
+
369
+ it "do not accepts blocks when assigning values to create properties" do
370
+ expect do
371
+ event.arbitrary_property { puts "whatever" }
372
+ end.to raise_error NoMethodError
373
+ end
374
+
375
+ it "includes arbitrary properties in the generated hash" do
376
+ event.arbitrary_property = "arbitrary value"
377
+
378
+ hash = event.to_hash
379
+
380
+ expect(hash).to include(arbitrary_property: "arbitrary value")
381
+ end
382
+ end
283
383
  end