trakio-ruby 0.1.4 → 0.2.0

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
  SHA1:
3
- metadata.gz: 2f0f9ac5b8ceaa20fd8a33d9f461a5e86c6be2ee
4
- data.tar.gz: b8752c480a4f6283c43f4e814cabd24bdaddcf70
3
+ metadata.gz: 7197ea21e8c7439a450e60da07fd3578341f0bbd
4
+ data.tar.gz: 8781505a279089e009d40ed3b9bb30ccb920a20c
5
5
  SHA512:
6
- metadata.gz: 7f054a3bfde7856c73967229185e4be803e971c66fda25d3c6a48355268deb903478b666175c5f6dff31d017c76f8c99755d6b6fb3fc5b370660597ed47487fc
7
- data.tar.gz: b7b930459be4683ba41b95f305de49d43c42c04dc90ef2a167ae820ff317ff03174ffff400b7ead85dce2d35e1fcf067c164f2abad785c14ee8905496ac4e314
6
+ metadata.gz: 5ebd1fb4edd5aa85ef6c087556994cb86c86a864ae500453b11d8736c4154e019a4bf11f3e59fee3d1d2890270fbfecb02a71f1790ff6ac9c5d406c111ef8eed
7
+ data.tar.gz: 1c7d375387fad1e594692fe84a09b860e01489b31242a8b2e7e526cb4c54548a9f87448b6d9ea19102e321d00d0a9e231a8ee2b94c22eecd7accfa735c44edd7
data/README.md CHANGED
@@ -31,7 +31,7 @@ For more indepth documentation see: http://docs.trak.io/ruby.html
31
31
  trakio = Trakio.new 'my_api_token'
32
32
  # track my-event
33
33
  resp = trakio.track distinct_id: 'user@example.com', event: 'my-event'
34
- # resp will look like { 'status': 'success', 'trak_id': '12345' }
34
+ # resp will look like { 'status': 'success' }
35
35
  ```
36
36
 
37
37
  ### Creating a default instance, and then tracking an event.
@@ -39,22 +39,23 @@ For more indepth documentation see: http://docs.trak.io/ruby.html
39
39
  # set token on default instance
40
40
  Trakio.init 'my_api_token'
41
41
  # track our event
42
- resp = Trakio.track distinct_id: 'user@example.com', event: 'my-event'
43
- # resp will look like { 'status': 'success', 'trak_id': '12345' }
42
+ resp = Trakio.track distinct_id: 'user@example.com', company_id: 'acme_ltd', event: 'my-event'
43
+ # resp will look like { 'status': 'success' }
44
44
  ```
45
45
 
46
+
46
47
  ### Creating an instance and aliasing an entry
47
48
  ```ruby
48
49
  # set token on default instance
49
50
  Trakio.init 'my_api_token'
50
51
 
51
52
  resp = Trakio.alias distinct_id: 'u1@example.com', alias: ['u2@example.com']
52
- # resp will look like { 'status': 'success', 'trak_id': '12345', 'distinct_ids': ['u1@example.com', 'u2@example.com'] }
53
+ # resp will look like { 'status': 'success' }
53
54
 
54
55
  # an equivilent is shown below
55
56
 
56
57
  resp = Trakio.alias distinct_id: 'u1@example.com', alias: 'u2@example.com'
57
- # resp will look like { 'status': 'success', 'trak_id': '12345', 'distinct_ids': ['u1@example.com', 'u2@example.com'] }
58
+ # resp will look like { 'status': 'success' }
58
59
  ```
59
60
 
60
61
  ### Creating an instance and using identify
@@ -63,7 +64,16 @@ For more indepth documentation see: http://docs.trak.io/ruby.html
63
64
  Trakio.init 'my_api_token'
64
65
 
65
66
  resp = Trakio.identify distinct_id: 'user@example.com', properties: { name: 'Tobie' }
66
- # resp will look like { 'status': 'success', 'trak_id': '12345', 'distinct_ids': ['user@example.com'] }
67
+ # resp will look like { 'status': 'success' }
68
+ ```
69
+
70
+ ### Creating an instance and using company
71
+ ```ruby
72
+ # set token on default instance
73
+ Trakio.init 'my_api_token'
74
+
75
+ resp = Trakio.company company_id: 'acme_ltd', properties: { name: 'Tobie' }
76
+ # resp will look like { 'status': 'success' }
67
77
  ```
68
78
 
69
79
  ### Creating an instance and using annotate
@@ -72,7 +82,7 @@ For more indepth documentation see: http://docs.trak.io/ruby.html
72
82
  Trakio.init 'my_api_token'
73
83
 
74
84
  resp = Trakio.annotate event: 'event', channel: 'channel'
75
- # resp will look like { 'status': 'success', 'trak_id': '12345', 'properties': {} }
85
+ # resp will look like { 'status': 'success' }
76
86
  ```
77
87
 
78
88
  ## Creating and Running Tests
@@ -0,0 +1,30 @@
1
+ class Trakio
2
+ class Alias < EndPoint
3
+
4
+ def run p = {}
5
+ alias_ = p[:alias]
6
+ distinct_id = p[:distinct_id] || self.distinct_id
7
+ check_parameters alias_, distinct_id
8
+
9
+ params = {
10
+ distinct_id: distinct_id,
11
+ alias: alias_,
12
+ }
13
+
14
+ send_request('alias', params)
15
+ end
16
+
17
+ def check_parameters alias_, distinct_id
18
+ unless distinct_id
19
+ raise Trakio::Exceptions::MissingParameter.new('The `distinct_id` parameter must be provided.')
20
+ end
21
+ unless alias_
22
+ raise Trakio::Exceptions::MissingParameter.new('The `alias` parameter must be provided.')
23
+ end
24
+ unless alias_.is_a?(String) or alias_.is_a?(Array)
25
+ raise Trakio::Exceptions::InvalidParameter.new('The `alias` parameter must be a string or an array.')
26
+ end
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,29 @@
1
+ class Trakio
2
+ class Annotate < EndPoint
3
+
4
+ def run p = {}
5
+ event = p[:event]
6
+ properties = p[:properties] || {}
7
+ channel = p[:channel] || self.channel
8
+ check_parameters event, properties
9
+
10
+ params = {
11
+ event: event
12
+ }
13
+ params[:channel] = channel if channel
14
+ params[:properties] = properties if properties
15
+
16
+ send_request('annotate', params)
17
+ end
18
+
19
+ def check_parameters event, properties
20
+ unless event
21
+ raise Trakio::Exceptions::MissingParameter.new("The `event` parameter must be provided.")
22
+ end
23
+ unless properties.is_a?(Hash)
24
+ raise Trakio::Exceptions::InvalidParameter.new("The `properties` parameter must be a hash.")
25
+ end
26
+ end
27
+
28
+ end
29
+ end
@@ -0,0 +1,41 @@
1
+ class Trakio
2
+ class Company < EndPoint
3
+
4
+ def run p = {}
5
+ properties = p[:properties] || {}
6
+ company_id = p[:company_id] || self.company_id
7
+ check_parameters company_id, properties, (p[:people_distinct_ids] || [])
8
+
9
+ params = {
10
+ company_id: company_id,
11
+ properties: properties,
12
+ }
13
+ distinct_ids = distinct_ids_from_params p
14
+ params[:people_distinct_ids] = distinct_ids unless distinct_ids.empty?
15
+
16
+ send_request 'company', params
17
+ end
18
+
19
+ def distinct_ids_from_params p
20
+ ids = p[:people_distinct_ids] || []
21
+ distinct_id = p[:distinct_id] || self.distinct_id
22
+ ids << distinct_id if distinct_id
23
+ ids.reject!(&:nil?)
24
+ ids.map!(&:to_s)
25
+ ids
26
+ end
27
+
28
+ def check_parameters company_id, properties, distinct_ids
29
+ if !company_id
30
+ raise Trakio::Exceptions::MissingParameter.new('The `company_id` parameter must be provided.')
31
+ end
32
+ if !properties.is_a?(Hash)
33
+ raise Trakio::Exceptions::InvalidParameter.new("The `properties` parameter must be a hash.")
34
+ end
35
+ if !distinct_ids.is_a?(Array)
36
+ raise Trakio::Exceptions::InvalidParameter.new('The `people_distinct_ids` parameter must be an array.')
37
+ end
38
+ end
39
+
40
+ end
41
+ end
@@ -0,0 +1,68 @@
1
+ class Trakio
2
+ class EndPoint
3
+
4
+ extend Forwardable
5
+
6
+ attr_accessor :trakio
7
+ def_delegator :@trakio, :api_token
8
+ def_delegator :@trakio, :https
9
+ def_delegator :@trakio, :host
10
+ def_delegator :@trakio, :channel
11
+ def_delegator :@trakio, :distinct_id
12
+ def_delegator :@trakio, :company_id
13
+
14
+ def initialize trakio
15
+ self.trakio ||= trakio
16
+ end
17
+
18
+ protected
19
+
20
+ def send_request endpoint, params
21
+ protocol = https ? "https" : "http"
22
+ url = "#{protocol}://#{host}/#{endpoint}"
23
+ data = { token: api_token, data: params }.to_json
24
+ resp = RestClient.post url, data, :content_type => :json, :accept => :json
25
+ result = JSON.parse(resp.body, :symbolize_names => true)
26
+ return result if result[:status] == 'success'
27
+
28
+ # status must be error
29
+ # here we will raise the required exception as in the API
30
+ exception = constantize(result[:exception].sub! 'TrakioAPI::', 'Trakio::') # name of the class
31
+ message = result[:message] # extra information for the exception
32
+ raise exception.new(message)
33
+ end
34
+
35
+ def constantize camel_cased_word # Taken from ActiveSupport
36
+ names = camel_cased_word.split('::')
37
+
38
+ # Trigger a builtin NameError exception including the ill-formed constant in the message.
39
+ Object.const_get(camel_cased_word) if names.empty?
40
+
41
+ # Remove the first blank element in case of '::ClassName' notation.
42
+ names.shift if names.size > 1 && names.first.empty?
43
+
44
+ names.inject(Object) do |constant, name|
45
+ if constant == Object
46
+ constant.const_get(name)
47
+ else
48
+ candidate = constant.const_get(name)
49
+ next candidate if constant.const_defined?(name, false)
50
+ next candidate unless Object.const_defined?(name)
51
+
52
+ # Go down the ancestors to check it it's owned
53
+ # directly before we reach Object or the end of ancestors.
54
+ constant = constant.ancestors.inject do |const, ancestor|
55
+ break const if ancestor == Object
56
+ break ancestor if ancestor.const_defined?(name, false)
57
+ const
58
+ end
59
+
60
+ # owner is in Object, so raise
61
+ constant.const_get(name, false)
62
+ end
63
+ end
64
+ end
65
+
66
+
67
+ end
68
+ end
@@ -0,0 +1,22 @@
1
+ class Trakio
2
+
3
+ class Exceptions
4
+ class Uninitiated < StandardError; end
5
+ class NoDistinctIdForDefaultInstance < StandardError; end
6
+ class NoCompanyIdForDefaultInstance < StandardError; end
7
+ class DataObjectInvalidJson < StandardError; end
8
+ class DataObjectInvalidBase64 < StandardError; end
9
+ class DataObjectInvalidType < StandardError; end
10
+ class InvalidToken < StandardError; end
11
+ class MissingParameter < StandardError; end
12
+ class InvalidParameter < StandardError; end
13
+ class MissingProperty < StandardError; end
14
+ class InvalidProperty < StandardError; end
15
+ class RouteNotFound < StandardError; end
16
+ class PropertiesObjectInvalid < StandardError; end
17
+ class RequestInvalidJson < StandardError; end
18
+ class RevenuePropertyInvalid < StandardError; end
19
+ class InternalServiceError < StandardError; end
20
+ end
21
+
22
+ end
@@ -0,0 +1,66 @@
1
+ class Trakio
2
+ class Identify < EndPoint
3
+
4
+ def run p = {}
5
+ properties = p[:properties] || {}
6
+ distinct_id = p[:distinct_id] || self.distinct_id
7
+ company_id = p[:company_id] || self.company_id
8
+ check_parameters distinct_id, properties
9
+ properties = process_companies company_id, properties
10
+
11
+ params = {
12
+ distinct_id: distinct_id,
13
+ properties: properties
14
+ }
15
+
16
+ send_request 'identify', params
17
+ end
18
+
19
+ def process_companies company_id, properties
20
+
21
+ # String company should be moved to company_name
22
+ if properties[:company].is_a? String
23
+ properties[:company_name] = properties.delete :company
24
+ end
25
+
26
+ # Company must be an array
27
+ [:company, :companies].each do |x|
28
+ properties[x] ||= []
29
+ unless properties[x].is_a?(Array)
30
+ properties[x] = [properties[x]]
31
+ end
32
+ end
33
+
34
+ # Merge companies and company
35
+ properties[:company] += properties.delete(:companies) || []
36
+
37
+ check_companies properties[:company]
38
+
39
+ # Inject current company
40
+ if company_id && properties[:company].none?{ |x| x[:company_id] == company_id }
41
+ properties[:company] << { company_id: company_id }
42
+ end
43
+
44
+ # Clean up company
45
+ properties.delete(:company) if properties[:company].empty?
46
+
47
+ properties
48
+ end
49
+
50
+ def check_parameters distinct_id, properties
51
+ unless properties.is_a?(Hash)
52
+ raise Trakio::Exceptions::InvalidParameter.new("The `properties` parameter must be a hash.")
53
+ end
54
+ unless distinct_id
55
+ raise Trakio::Exceptions::MissingParameter.new('The `distinct_id` parameter must be provided.')
56
+ end
57
+ end
58
+
59
+ def check_companies companies
60
+ unless companies.all?{ |x| x.is_a?(Hash) } && companies.all?{ |x| x.include? :company_id }
61
+ raise Trakio::Exceptions::InvalidProperty.new('The `companies` property must be an array of hashes each with a value for `company_id`')
62
+ end
63
+ end
64
+
65
+ end
66
+ end
@@ -0,0 +1,72 @@
1
+ class Trakio
2
+ class Track < EndPoint
3
+
4
+ def run p = {}
5
+ event = p[:event]
6
+ distinct_id = p[:distinct_id] || self.distinct_id
7
+ company_id = p[:company_id] || self.company_id
8
+ channel = p[:channel] || self.channel
9
+ properties = p[:properties] || {}
10
+ check_parameters event, distinct_id, company_id, properties
11
+
12
+ params = {
13
+ event: event
14
+ }
15
+ params[:time] = process_time p[:time]
16
+ params[:channel] = channel if channel
17
+ params[:properties] = properties if properties && !properties.empty?
18
+ params[:company_id] = company_id if company_id
19
+ params[:distinct_id] = distinct_id if distinct_id
20
+
21
+ send_request('track', params)
22
+ end
23
+
24
+ def page_view p
25
+ args = { event: 'Page view' }
26
+ distinct_id = p[:distinct_id] || self.distinct_id
27
+ url = p[:url]
28
+ title = p[:title]
29
+ check_page_view_parameters url
30
+
31
+ properties = {
32
+ url: url,
33
+ }
34
+ properties[:title] = title if title
35
+ args[:properties] = properties
36
+ args[:distinct_id] = distinct_id if distinct_id
37
+
38
+ run args # right now page_view is an alias of track
39
+ end
40
+
41
+ def process_time time
42
+ if time
43
+ if !time.is_a? String
44
+ time.iso8601
45
+ else
46
+ time
47
+ end
48
+ else
49
+ DateTime.now.iso8601
50
+ end
51
+ end
52
+
53
+ def check_parameters event, distinct_id, company_id, properties
54
+ unless event
55
+ raise Trakio::Exceptions::MissingParameter.new("The `event` parameter must be provided.")
56
+ end
57
+ unless distinct_id || company_id
58
+ raise Trakio::Exceptions::MissingParameter.new('Either a `company_id` or `distinct_id` parameter must be provided.')
59
+ end
60
+ unless properties.is_a?(Hash)
61
+ raise Trakio::Exceptions::InvalidParameter.new("The `properties` parameter must be a hash.")
62
+ end
63
+ end
64
+
65
+ def check_page_view_parameters url
66
+ unless url
67
+ raise Trakio::Exceptions::InvalidParameter.new("The `url` parameter must be provided.")
68
+ end
69
+ end
70
+
71
+ end
72
+ end
@@ -1,3 +1,3 @@
1
1
  class Trakio
2
- VERSION = "0.1.4"
2
+ VERSION = "0.2.0"
3
3
  end
data/lib/trakio.rb CHANGED
@@ -1,3 +1,10 @@
1
+ require "trakio/end_point"
2
+ require "trakio/alias"
3
+ require "trakio/annotate"
4
+ require "trakio/company"
5
+ require "trakio/exceptions"
6
+ require "trakio/identify"
7
+ require "trakio/track"
1
8
  require "trakio/version"
2
9
  require "rest_client"
3
10
  require "json"
@@ -6,20 +13,12 @@ require "date"
6
13
 
7
14
  class Trakio
8
15
 
9
- class Exceptions
10
- class Uninitiated < StandardError; end
11
- class NoDistinctIdForDefaultInstance < StandardError; end
12
- class DataObjectInvalidJson < StandardError; end
13
- class DataObjectInvalidBase64 < StandardError; end
14
- class DataObjectInvalidType < StandardError; end
15
- class InvalidToken < StandardError; end
16
- class MissingParameter < StandardError; end
17
- class RouteNotFound < StandardError; end
18
- class PropertiesObjectInvalid < StandardError; end
19
- class RequestInvalidJson < StandardError; end
20
- class RevenuePropertyInvalid < StandardError; end
21
- class InternalServiceError < StandardError; end
22
- end
16
+ attr_accessor :api_token
17
+ attr_accessor :https
18
+ attr_accessor :host
19
+ attr_accessor :channel
20
+ attr_accessor :distinct_id
21
+ attr_accessor :company_id
23
22
 
24
23
  class << self
25
24
 
@@ -27,6 +26,7 @@ class Trakio
27
26
  api_token, params = args
28
27
  raise Trakio::Exceptions::InvalidToken.new('Missing API Token') unless api_token
29
28
  raise Trakio::Exceptions::NoDistinctIdForDefaultInstance if params and params.has_key?(:distinct_id)
29
+ raise Trakio::Exceptions::NoCompanyIdForDefaultInstance if params and params.has_key?(:company_id)
30
30
  @default_instance = Trakio.new(*args)
31
31
  end
32
32
 
@@ -39,13 +39,15 @@ class Trakio
39
39
  @default_instance = instance
40
40
  end
41
41
 
42
- def distinct_id
42
+ def distinct_id value=nil
43
43
  raise Trakio::Exceptions::NoDistinctIdForDefaultInstance
44
44
  end
45
+ alias :distinct_id= :distinct_id
45
46
 
46
- def distinct_id=(distinct_id)
47
- raise Trakio::Exceptions::NoDistinctIdForDefaultInstance
47
+ def company_id value=nil
48
+ raise Trakio::Exceptions::NoCompanyIdForDefaultInstance
48
49
  end
50
+ alias :company_id= :company_id
49
51
 
50
52
  def method_missing(method, *args, &block)
51
53
  # passes to the default_instance so that
@@ -55,14 +57,6 @@ class Trakio
55
57
 
56
58
  end
57
59
 
58
- attr_accessor :api_token
59
-
60
- # the following are set via params
61
- attr_accessor :https
62
- attr_accessor :host
63
- attr_accessor :channel # channel is some form of category
64
- attr_accessor :distinct_id
65
-
66
60
  def initialize(*args)
67
61
  api_token, params = args
68
62
  api_token = Trakio.default_instance.api_token unless api_token
@@ -71,158 +65,33 @@ class Trakio
71
65
  @https = true
72
66
  @host = 'api.trak.io/v1'
73
67
 
74
- %w{https host channel distinct_id}.each do |name|
68
+ %w{https host channel distinct_id company_id}.each do |name|
75
69
  instance_variable_set("@#{name}", params[name.to_sym]) if params && params.has_key?(name.to_sym)
76
70
  end
77
71
  end
78
72
 
79
- def track(parameters)
80
- parameters.default = nil
81
-
82
- event = parameters[:event] or raise "No event specified"
83
-
84
- channel = parameters[:channel]
85
- channel = @channel unless channel
86
-
87
- properties = parameters[:properties]
88
-
89
- params = {
90
- distinct_id: distinct_id_from_parameters(parameters),
91
- event: event,
92
- }
93
- if parameters[:time] # if specified
94
- params[:time] = parameters[:time]
95
- params[:time] = params[:time].iso8601 unless params[:time].is_a? String
96
- else # if nots specified default to now
97
- params[:time] = DateTime.now.iso8601
98
- end
99
- params[:channel] = channel if channel
100
- params[:properties] = properties if properties
101
-
102
- send_request('track', params)
103
- end
104
-
105
- def identify(parameters)
106
- parameters.default = nil
107
-
108
- properties = parameters[:properties]
109
- raise "Properties must be specified" unless properties and properties.length > 0
110
-
111
- params = {
112
- distinct_id: distinct_id_from_parameters(parameters),
113
- properties: properties,
114
- }
115
- send_request('identify', params)
116
- end
117
-
118
- def alias(parameters)
119
- parameters.default = nil
120
-
121
- alias_ = parameters[:alias]
122
- raise "No alias specified" unless alias_
123
- raise "alias must be string or array" unless alias_.is_a?(String) or alias_.is_a?(Array)
124
-
125
- params = {
126
- distinct_id: distinct_id_from_parameters(parameters),
127
- alias: alias_,
128
- }
129
- send_request('alias', params)
130
- end
131
-
132
- def annotate(parameters)
133
- parameters.default = nil
134
-
135
- event = parameters[:event]
136
- raise "No event specified" unless event
137
-
138
- properties = parameters[:properties]
139
- properties = {} unless properties
140
-
141
- channel = parameters[:channel]
142
- channel = @channel unless channel
143
-
144
- params = {
145
- event: event,
146
- }
147
- params[:channel] = channel if channel
148
- params[:properties] = properties if properties
149
- send_request('annotate', params)
150
- end
151
-
152
- def page_view(parameters)
153
- parameters.default = nil
154
- args = {
155
- event: 'Page view'
156
- }
157
-
158
- distinct_id = distinct_id_from_parameters(parameters)
159
- args[:distinct_id] = distinct_id if distinct_id
160
-
161
- raise "Must specify URL" unless parameters.has_key?(:url)
162
- raise "Must specify Title" unless parameters.has_key?(:title)
163
-
164
- properties = {
165
- url: parameters[:url],
166
- title: parameters[:title],
167
- }
168
- args[:properties] = properties
169
-
170
- track args # right now page_view is an alias of track
73
+ ['Alias', 'Annotate', 'Company', 'Identify', 'Track'].each do |method_object|
74
+ Trakio.class_eval "
75
+ def #{method_object.downcase} *args
76
+ @#{method_object.downcase} ||= #{method_object}.new(self)
77
+ @#{method_object.downcase}.run(*args)
78
+ end
79
+ "
171
80
  end
172
81
 
173
- def send_request(endpoint, params)
174
- protocol = @https ? "https" : "http"
175
- url = "#{protocol}://#{@host}/#{endpoint}"
176
- data = { token: @api_token, data: params }.to_json
177
- resp = RestClient.post url, data, :content_type => :json, :accept => :json
178
- result = JSON.parse(resp.body, :symbolize_names => true)
179
- return result if result[:status] == 'success'
180
-
181
- # status must be error
182
- # here we will raise the required exception as in the API
183
- exception = constantize(result[:exception].sub! 'TrakioAPI::', 'Trakio::') # name of the class
184
- message = result[:message] # extra information for the exception
185
- raise exception.new(message)
82
+ def page_view *args
83
+ @track ||= Track.new(self)
84
+ @track.page_view(*args)
186
85
  end
187
86
 
188
87
  protected
189
88
 
190
- def constantize(camel_cased_word) # Taken from ActiveSupport
191
- names = camel_cased_word.split('::')
192
-
193
- # Trigger a builtin NameError exception including the ill-formed constant in the message.
194
- Object.const_get(camel_cased_word) if names.empty?
195
-
196
- # Remove the first blank element in case of '::ClassName' notation.
197
- names.shift if names.size > 1 && names.first.empty?
198
-
199
- names.inject(Object) do |constant, name|
200
- if constant == Object
201
- constant.const_get(name)
202
- else
203
- candidate = constant.const_get(name)
204
- next candidate if constant.const_defined?(name, false)
205
- next candidate unless Object.const_defined?(name)
206
-
207
- # Go down the ancestors to check it it's owned
208
- # directly before we reach Object or the end of ancestors.
209
- constant = constant.ancestors.inject do |const, ancestor|
210
- break const if ancestor == Object
211
- break ancestor if ancestor.const_defined?(name, false)
212
- const
213
- end
214
-
215
- # owner is in Object, so raise
216
- constant.const_get(name, false)
89
+ ['distinct','company'].each do |x|
90
+ define_method :"#{x}_id_from_parameters" do |parameters|
91
+ id = parameters[:"#{x}_id"]
92
+ id = self.instance_variable_get("@#{x}_id") unless id
93
+ id
217
94
  end
218
95
  end
219
- end
220
-
221
- def distinct_id_from_parameters parameters
222
- distinct_id = parameters[:distinct_id]
223
- distinct_id = @distinct_id unless distinct_id
224
- raise "No distinct_id specified" unless distinct_id
225
- distinct_id
226
- end
227
96
 
228
97
  end
data/spec/spec_helper.rb CHANGED
@@ -3,6 +3,7 @@ Coveralls.wear!
3
3
  require 'trakio'
4
4
  require 'webmock/rspec'
5
5
  require 'json'
6
+ require 'pry'
6
7
  require 'active_support/core_ext/numeric/time'
7
8
  require 'active_support/core_ext/date/calculations'
8
9
 
@@ -59,7 +59,7 @@ describe Trakio do
59
59
  context "when no alias is provided" do
60
60
  it "raises an error" do
61
61
  trakio = Trakio.new 'my_api_token'
62
- expect { trakio.alias distinct_id: 'user@example.com' }.to raise_error RuntimeError
62
+ expect { trakio.alias distinct_id: 'user@example.com' }.to raise_error Trakio::Exceptions::MissingParameter
63
63
  end
64
64
  end
65
65
 
@@ -90,7 +90,7 @@ describe Trakio do
90
90
  context "when there is not one set on the instance" do
91
91
  it "raises an error" do
92
92
  trakio = Trakio.new 'my_api_token'
93
- expect { trakio.alias alias: 'alias1@example.com' }.to raise_error RuntimeError
93
+ expect { trakio.alias alias: 'alias1@example.com' }.to raise_error Trakio::Exceptions::MissingParameter
94
94
  end
95
95
  end
96
96
 
@@ -109,21 +109,14 @@ describe Trakio do
109
109
  context "when a channel is provided" do
110
110
  it "raises an error" do
111
111
  trakio = Trakio.new 'my_api_token'
112
- expect { trakio.annotate channel: 'channel' }.to raise_error RuntimeError
112
+ expect { trakio.annotate channel: 'channel' }.to raise_error Trakio::Exceptions::MissingParameter
113
113
  end
114
114
  end
115
115
 
116
116
  context "when properties are provided" do
117
117
  it "raises an error" do
118
118
  trakio = Trakio.new 'my_api_token'
119
- expect { trakio.annotate properties: { name: 'tobie' } }.to raise_error RuntimeError
120
- end
121
- end
122
-
123
- context "when no arguments are provided" do
124
- it "raises an error" do
125
- trakio = Trakio.new 'my_api_token'
126
- expect { trakio.annotate }.to raise_error ArgumentError
119
+ expect { trakio.annotate properties: { name: 'tobie' } }.to raise_error Trakio::Exceptions::MissingParameter
127
120
  end
128
121
  end
129
122
 
@@ -41,6 +41,14 @@ describe Trakio do
41
41
  end
42
42
  end
43
43
 
44
+ context "when a company_id is provided" do
45
+ it "raises an exception" do
46
+ expect{
47
+ Trakio.init 'my_api_token', company_id: 'acme_ldt'
48
+ }.to raise_error Trakio::Exceptions::NoCompanyIdForDefaultInstance
49
+ end
50
+ end
51
+
44
52
  context "when a channel is provided" do
45
53
  it "sets the channel option" do
46
54
  Trakio.init 'my_api_token', channel: 'my-channel'
@@ -145,6 +153,17 @@ describe Trakio do
145
153
 
146
154
  end
147
155
 
156
+ describe '.company' do
157
+ it "calls company on the default Trakio instance" do
158
+ default_instance = double(Trakio)
159
+
160
+ Trakio.default_instance = default_instance
161
+ expect(Trakio.default_instance).to receive(:company)
162
+
163
+ Trakio.company
164
+ end
165
+ end
166
+
148
167
 
149
168
  describe '.distinct_id' do
150
169
  it "raise an error" do
@@ -152,6 +171,13 @@ describe Trakio do
152
171
  end
153
172
  end
154
173
 
174
+
175
+ describe '.company_id' do
176
+ it "raise an error" do
177
+ expect{ Trakio.company_id }.to raise_error Trakio::Exceptions::NoCompanyIdForDefaultInstance
178
+ end
179
+ end
180
+
155
181
  describe '.channel' do
156
182
 
157
183
  it "calls channel on the default Trakio instance" do
@@ -0,0 +1,147 @@
1
+ require 'spec_helper'
2
+
3
+ describe Trakio do
4
+
5
+ subject { Trakio }
6
+
7
+ after {
8
+ Trakio.default_instance = nil
9
+ }
10
+
11
+ describe '#company' do
12
+
13
+ context "when a company_id is provided" do
14
+
15
+ context "when properties are provided" do
16
+
17
+ it "sends an company request" do
18
+ stub = stub_request(:post, "https://api.trak.io/v1/company").
19
+ with(:body => {
20
+ token: 'my_api_token',
21
+ data: {
22
+ company_id: 'acme_ltd',
23
+ properties: {
24
+ name: 'Acme Ltd',
25
+ },
26
+ }
27
+ }).to_return(:body => {
28
+ status: 'success'
29
+ }.to_json)
30
+
31
+ trakio = Trakio.new 'my_api_token'
32
+ trakio.company company_id: 'acme_ltd', properties: { name: 'Acme Ltd' }
33
+
34
+ expect(stub).to have_been_requested
35
+ end
36
+
37
+ end
38
+
39
+ end
40
+
41
+ context "when a company_id isn't provided but is set on instance" do
42
+
43
+ context "when properties are provided" do
44
+
45
+ it "sends an company request" do
46
+ stub = stub_request(:post, "https://api.trak.io/v1/company").
47
+ with(:body => {
48
+ token: 'my_api_token',
49
+ data: {
50
+ company_id: 'acme_ltd',
51
+ properties: {
52
+ name: 'Acme Ltd',
53
+ },
54
+ }
55
+ }).to_return(:body => {
56
+ status: 'success'
57
+ }.to_json)
58
+
59
+ trakio = Trakio.new 'my_api_token', company_id: 'acme_ltd'
60
+ trakio.company properties: { name: 'Acme Ltd' }
61
+
62
+ expect(stub).to have_been_requested
63
+ end
64
+
65
+ end
66
+
67
+ context "when distinct_id is provided" do
68
+
69
+ context "via arguments" do
70
+
71
+ it "adds it to the company's people_distinct_ids" do
72
+ stub = stub_request(:post, "https://api.trak.io/v1/company").
73
+ with(:body => {
74
+ token: 'my_api_token',
75
+ data: {
76
+ company_id: 'acme_ltd',
77
+ properties: {
78
+ name: 'Acme Ltd',
79
+ },
80
+ people_distinct_ids: ['existing_distinct_id','distinct_id']
81
+ }
82
+ }.to_json).to_return(:body => {
83
+ status: 'success'
84
+ }.to_json)
85
+
86
+ trakio = Trakio.new 'my_api_token', company_id: 'not_the_company_id_to_use', distinct_id: 'not_the_distinct_id_to_use'
87
+ trakio.company company_id: 'acme_ltd', distinct_id: 'distinct_id', properties: { name: 'Acme Ltd' }, people_distinct_ids: ['existing_distinct_id']
88
+
89
+ expect(stub).to have_been_requested
90
+ end
91
+
92
+ end
93
+
94
+ context "via configuration" do
95
+
96
+ it "adds it to the company's people_distinct_ids" do
97
+ stub = stub_request(:post, "https://api.trak.io/v1/company").
98
+ with(body: {
99
+ token: 'my_api_token',
100
+ data: {
101
+ company_id: 'acme_ltd',
102
+ properties: {
103
+ name: 'Acme Ltd',
104
+ },
105
+ people_distinct_ids: ['existing_distinct_id', '1234','distinct_id']
106
+ }
107
+ }.to_json).to_return(:body => {
108
+ status: 'success'
109
+ }.to_json)
110
+
111
+ trakio = Trakio.new 'my_api_token', company_id: 'acme_ltd', distinct_id: 'distinct_id'
112
+ trakio.company properties: { name: 'Acme Ltd' }, people_distinct_ids: ['existing_distinct_id', 1234]
113
+
114
+ expect(stub).to have_been_requested
115
+ end
116
+
117
+ end
118
+
119
+ context "and is not an array" do
120
+
121
+ it "raises an error" do
122
+ trakio = Trakio.new 'my_api_token', company_id: 'acme_ltd'
123
+ expect {
124
+ trakio.company properties: { name: 'Acme Ltd' }, people_distinct_ids: 1234
125
+ }.to raise_error Trakio::Exceptions::InvalidParameter
126
+ end
127
+
128
+ end
129
+
130
+ end
131
+
132
+ end
133
+
134
+ context "when a company_id isn't provided" do
135
+
136
+ context "when properties are provided" do
137
+ it "raises an error" do
138
+ trakio = Trakio.new 'my_api_token'
139
+ expect { trakio.identify properties: { name: 'Acme Ltd' } }.to raise_error Trakio::Exceptions::MissingParameter
140
+ end
141
+ end
142
+
143
+ end
144
+
145
+ end
146
+
147
+ end
@@ -67,15 +67,87 @@ describe Trakio do
67
67
  context "when properties are provided" do
68
68
  it "raises an error" do
69
69
  trakio = Trakio.new 'my_api_token'
70
- expect { trakio.identify properties: { name: 'Tobie' } }.to raise_error RuntimeError
70
+ expect { trakio.identify properties: { name: 'Tobie' } }.to raise_error Trakio::Exceptions::MissingParameter
71
71
  end
72
72
  end
73
73
 
74
- context "when properties aren't provided" do
75
- it "raises an error" do
76
- trakio = Trakio.new 'my_api_token', distinct_id: 'user@example.com'
77
- expect { trakio.identify }.to raise_error ArgumentError
74
+ end
75
+
76
+ context "when distinct_id and company_id are both set" do
77
+
78
+ context "via arguments" do
79
+
80
+ it "adds it to the company's people_distinct_ids" do
81
+ stub = stub_request(:post, "https://api.trak.io/v1/identify").
82
+ with(:body => {
83
+ token: 'my_api_token',
84
+ data: {
85
+ distinct_id: 'user@example.com',
86
+ properties: {
87
+ name: 'Tobie',
88
+ company: [{ company_id: 'massive_dynamics', role: 'widgets' }, { company_id: 'acme_ltd' }]
89
+ },
90
+ }
91
+ }.to_json).to_return(:body => {
92
+ status: 'success'
93
+ }.to_json)
94
+
95
+ trakio = Trakio.new 'my_api_token'
96
+ trakio.identify distinct_id: 'user@example.com', company_id: 'acme_ltd', properties: { name: 'Tobie', company: { company_id: 'massive_dynamics', role: 'widgets' } }
97
+
98
+ expect(stub).to have_been_requested
78
99
  end
100
+
101
+ end
102
+
103
+ context "via configuration" do
104
+
105
+ it "adds it to the company's people_distinct_ids" do
106
+ stub = stub_request(:post, "https://api.trak.io/v1/identify").
107
+ with(:body => {
108
+ token: 'my_api_token',
109
+ data: {
110
+ distinct_id: 'user@example.com',
111
+ properties: {
112
+ name: 'Tobie',
113
+ company: [{ company_id: 'massive_dynamics', role: 'widgets' }, { company_id: 'monarch', role: 'widgets' }, { company_id: 'acme_ltd' }],
114
+ },
115
+ }
116
+ }.to_json).to_return(:body => {
117
+ status: 'success'
118
+ }.to_json)
119
+
120
+ trakio = Trakio.new 'my_api_token', distinct_id: 'user@example.com', company_id: 'acme_ltd'
121
+ trakio.identify properties: { name: 'Tobie', company: [{ company_id: 'massive_dynamics', role: 'widgets' }], companies: [{ company_id: 'monarch', role: 'widgets' }] }
122
+
123
+ expect(stub).to have_been_requested
124
+ end
125
+
126
+ end
127
+
128
+ context "but company is also passed in properties" do
129
+
130
+ it "doesn't send duplicates" do
131
+ stub = stub_request(:post, "https://api.trak.io/v1/identify").
132
+ with(:body => {
133
+ token: 'my_api_token',
134
+ data: {
135
+ distinct_id: 'user@example.com',
136
+ properties: {
137
+ name: 'Tobie',
138
+ company: [{ company_id: 'massive_dynamics', role: 'widgets' }, { company_id: 'monarch', role: 'widgets' }],
139
+ },
140
+ }
141
+ }.to_json).to_return(:body => {
142
+ status: 'success'
143
+ }.to_json)
144
+
145
+ trakio = Trakio.new 'my_api_token', distinct_id: 'user@example.com', company_id: 'monarch'
146
+ trakio.identify properties: { name: 'Tobie', company: [{ company_id: 'massive_dynamics', role: 'widgets' }], companies: [{ company_id: 'monarch', role: 'widgets' }] }
147
+
148
+ expect(stub).to have_been_requested
149
+ end
150
+
79
151
  end
80
152
 
81
153
  end
@@ -36,6 +36,13 @@ describe Trakio do
36
36
  end
37
37
  end
38
38
 
39
+ context "when a company_id is provided" do
40
+ it "sets that for this instance" do
41
+ trakio = Trakio.new "my_api_token", company_id: 'acme_ltd'
42
+ expect(trakio.company_id).to eql 'acme_ltd'
43
+ end
44
+ end
45
+
39
46
  context "when a https option is provided" do
40
47
  it "sets https option" do
41
48
  trakio = Trakio.new 'my_api_token', https: false
@@ -37,6 +37,35 @@ describe Trakio do
37
37
 
38
38
  end
39
39
 
40
+ describe '#company_id=' do
41
+
42
+ it "sets the company_id to be used by this Interface" do
43
+ trakio = Trakio.new 'api_token'
44
+ trakio.company_id = 'acme_ltd'
45
+ expect(trakio.instance_variable_get('@company_id')).to eql 'acme_ltd'
46
+ end
47
+
48
+ context "when this is the default Interface" do
49
+
50
+ it "raises an exception" do
51
+ Trakio.init 'my_api_token'
52
+ expect{ Trakio.company_id = 'acme_ltd' }.to raise_error Trakio::Exceptions::NoCompanyIdForDefaultInstance
53
+ end
54
+
55
+ end
56
+
57
+ end
58
+
59
+ describe '#company_id' do
60
+
61
+ it "returns the current value" do
62
+ trakio = Trakio.new 'api_token'
63
+ trakio.instance_variable_set('@company_id','acme_ltd')
64
+ expect(trakio.company_id).to eql 'acme_ltd'
65
+ end
66
+
67
+ end
68
+
40
69
  describe '#channel=' do
41
70
 
42
71
  it "sets the channel to be used by this Interface" do
@@ -39,16 +39,6 @@ describe Trakio do
39
39
  expect(stub).to have_been_requested
40
40
  end
41
41
  end
42
-
43
- context "when a title is not specified" do
44
- it "raises an error" do
45
- trakio = Trakio.new 'my_api_token'
46
- expect {
47
- trakio.page_view distinct_id: 'user@example.com',
48
- url: 'http://test.test/test'
49
- }.to raise_error RuntimeError
50
- end
51
- end
52
42
  end
53
43
 
54
44
  context "when a url is not specified" do
@@ -56,16 +46,7 @@ describe Trakio do
56
46
  trakio = Trakio.new 'my_api_token'
57
47
  expect {
58
48
  trakio.page_view distinct_id: 'user@example.com'
59
- }.to raise_error RuntimeError
60
- end
61
-
62
- context "when a title is specified" do
63
- it "raises an error" do
64
- trakio = Trakio.new 'my_api_token'
65
- expect {
66
- trakio.page_view distinct_id: 'user@example.com', title: 'Test Title'
67
- }.to raise_error RuntimeError
68
- end
49
+ }.to raise_error Trakio::Exceptions::InvalidParameter
69
50
  end
70
51
  end
71
52
 
@@ -190,22 +190,94 @@ describe Trakio do
190
190
 
191
191
  it "raises an exception" do
192
192
  trakio = Trakio.new 'my_api_token'
193
- expect { trakio.track distinct_id: 'user@example.com' }.to raise_error RuntimeError
193
+ expect { trakio.track distinct_id: 'user@example.com' }.to raise_error Trakio::Exceptions::MissingParameter
194
194
  end
195
195
 
196
196
  end
197
197
 
198
198
  end
199
199
 
200
- context "when a distinct_id isn't provided" do
200
+ context "when a company_id is provided as an argument" do
201
201
 
202
- context "when an event is provided" do
202
+ it "sends a track request to api.trak.io" do
203
+ stub = stub_request(:post, "https://api.trak.io/v1/track").
204
+ with(:body => {
205
+ token: 'my_api_token',
206
+ data: {
207
+ time: /.+/,
208
+ company_id: 'acme_ltd',
209
+ event: 'my-event'
210
+ }
211
+ }).to_return(:body => {
212
+ status: 'success',
213
+ }.to_json)
203
214
 
204
- it "raises an error" do
205
- trakio = Trakio.new 'my_api_token'
206
- expect { trakio.track event: 'my-event' }.to raise_error RuntimeError
207
- end
215
+ trakio = Trakio.new 'my_api_token'
216
+ resp = trakio.track company_id: 'acme_ltd', event: 'my-event'
217
+
218
+ expect(resp[:status]).to eql 'success'
219
+
220
+ expect(stub).to have_been_requested
221
+ end
222
+
223
+ end
224
+
225
+ context "when a company_id is configured" do
226
+
227
+ it "sends a track request to api.trak.io" do
228
+ stub = stub_request(:post, "https://api.trak.io/v1/track").
229
+ with(:body => {
230
+ token: 'my_api_token',
231
+ data: {
232
+ time: /.+/,
233
+ company_id: 'acme_ltd',
234
+ event: 'my-event'
235
+ }
236
+ }).to_return(:body => {
237
+ status: 'success'
238
+ }.to_json)
239
+
240
+ trakio = Trakio.new 'my_api_token', company_id: 'acme_ltd'
241
+ resp = trakio.track event: 'my-event'
242
+
243
+ expect(resp[:status]).to eql 'success'
244
+
245
+ expect(stub).to have_been_requested
246
+ end
247
+
248
+ end
249
+
250
+ context "when both a company_id and distinct_id are provided as arguments" do
251
+
252
+ it "sends a track request to api.trak.io" do
253
+ stub = stub_request(:post, "https://api.trak.io/v1/track").
254
+ with(:body => {
255
+ token: 'my_api_token',
256
+ data: {
257
+ time: /.+/,
258
+ distinct_id: '1234567',
259
+ company_id: 'acme_ltd',
260
+ event: 'my-event'
261
+ }
262
+ }).to_return(:body => {
263
+ status: 'success'
264
+ }.to_json)
265
+
266
+ trakio = Trakio.new 'my_api_token'
267
+ resp = trakio.track event: 'my-event', distinct_id: '1234567', company_id: 'acme_ltd'
268
+
269
+ expect(resp[:status]).to eql 'success'
270
+
271
+ expect(stub).to have_been_requested
272
+ end
273
+
274
+ end
275
+
276
+ context "when neither distinct_id or company_id is provided" do
208
277
 
278
+ it "raises an error" do
279
+ trakio = Trakio.new 'my_api_token'
280
+ expect { trakio.track event: 'my-event' }.to raise_error Trakio::Exceptions::MissingParameter
209
281
  end
210
282
 
211
283
  end
data/trakio-ruby.gemspec CHANGED
@@ -23,6 +23,7 @@ Gem::Specification.new do |spec|
23
23
  spec.add_development_dependency 'rspec', '~> 3'
24
24
  spec.add_development_dependency 'webmock'
25
25
  spec.add_development_dependency 'fuubar'
26
+ spec.add_development_dependency 'pry'
26
27
  spec.add_development_dependency 'coveralls'
27
28
  spec.add_development_dependency 'activesupport'
28
29
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: trakio-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matthew Spence
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-08-12 00:00:00.000000000 Z
12
+ date: 2014-09-23 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -81,6 +81,20 @@ dependencies:
81
81
  - - ">="
82
82
  - !ruby/object:Gem::Version
83
83
  version: '0'
84
+ - !ruby/object:Gem::Dependency
85
+ name: pry
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ type: :development
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
84
98
  - !ruby/object:Gem::Dependency
85
99
  name: coveralls
86
100
  requirement: !ruby/object:Gem::Requirement
@@ -140,17 +154,24 @@ files:
140
154
  - Rakefile
141
155
  - lib/trakio-ruby.rb
142
156
  - lib/trakio.rb
157
+ - lib/trakio/alias.rb
158
+ - lib/trakio/annotate.rb
159
+ - lib/trakio/company.rb
160
+ - lib/trakio/end_point.rb
161
+ - lib/trakio/exceptions.rb
162
+ - lib/trakio/identify.rb
163
+ - lib/trakio/track.rb
143
164
  - lib/trakio/version.rb
144
165
  - spec/spec_helper.rb
145
166
  - spec/trakio/alias_spec.rb
146
167
  - spec/trakio/annotate_spec.rb
147
168
  - spec/trakio/class_methods_spec.rb
169
+ - spec/trakio/company_spec.rb
148
170
  - spec/trakio/exception_spec.rb
149
171
  - spec/trakio/identify_spec.rb
150
172
  - spec/trakio/initialize_spec.rb
151
173
  - spec/trakio/instance_methods_spec.rb
152
174
  - spec/trakio/page_view_spec.rb
153
- - spec/trakio/protected_instance_methods_spec.rb
154
175
  - spec/trakio/track_spec.rb
155
176
  - trakio-ruby.gemspec
156
177
  homepage: https://trak.io
@@ -182,10 +203,10 @@ test_files:
182
203
  - spec/trakio/alias_spec.rb
183
204
  - spec/trakio/annotate_spec.rb
184
205
  - spec/trakio/class_methods_spec.rb
206
+ - spec/trakio/company_spec.rb
185
207
  - spec/trakio/exception_spec.rb
186
208
  - spec/trakio/identify_spec.rb
187
209
  - spec/trakio/initialize_spec.rb
188
210
  - spec/trakio/instance_methods_spec.rb
189
211
  - spec/trakio/page_view_spec.rb
190
- - spec/trakio/protected_instance_methods_spec.rb
191
212
  - spec/trakio/track_spec.rb
@@ -1,40 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe Trakio do
4
-
5
- subject { Trakio }
6
- let(:trakio) { Trakio.init 'my_api_token' }
7
-
8
- after { Trakio.default_instance = nil }
9
-
10
- before { Trakio.send(:public, *Trakio.protected_instance_methods) }
11
-
12
- describe '#distinct_id_from_parameters' do
13
-
14
- context "when the :distinct_id key is provided in parameters" do
15
- it "returns that value" do
16
- expect(trakio.distinct_id_from_parameters({ distinct_id: 'my_distinct_id'})).to eq 'my_distinct_id'
17
- end
18
- end
19
-
20
- context "when the :distinct_id key isn't provided in parameters" do
21
-
22
- it "returns @distinct_id" do
23
- trakio.distinct_id = 'other_distinct_id'
24
- expect(trakio.distinct_id_from_parameters({ distinct_id: nil})).to eq 'other_distinct_id'
25
- end
26
-
27
-
28
- context "and @distinct_id has not been set" do
29
-
30
- it "raises an exception" do
31
- expect{ trakio.distinct_id_from_parameters({ distinct_id: nil}) }.to raise_error RuntimeError
32
- end
33
-
34
- end
35
-
36
- end
37
-
38
- end
39
-
40
- end