clever_tap_dubit 0.3.2

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 (59) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +13 -0
  3. data/.rspec +1 -0
  4. data/.rubocop.yml +48 -0
  5. data/.travis.yml +6 -0
  6. data/Gemfile +16 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +164 -0
  9. data/Rakefile +6 -0
  10. data/bin/console +8 -0
  11. data/bin/setup +8 -0
  12. data/clever_tap.gemspec +33 -0
  13. data/lib/clever_tap/client.rb +113 -0
  14. data/lib/clever_tap/config.rb +25 -0
  15. data/lib/clever_tap/entity.rb +87 -0
  16. data/lib/clever_tap/event.rb +30 -0
  17. data/lib/clever_tap/failed_response.rb +28 -0
  18. data/lib/clever_tap/profile.rb +7 -0
  19. data/lib/clever_tap/response.rb +34 -0
  20. data/lib/clever_tap/successful_response.rb +30 -0
  21. data/lib/clever_tap/uploader.rb +72 -0
  22. data/lib/clever_tap/version.rb +3 -0
  23. data/lib/clever_tap.rb +79 -0
  24. data/lib/clevertap-ruby.rb +1 -0
  25. data/spec/factories/profile.rb +36 -0
  26. data/spec/integrations/clever_tap_spec.rb +81 -0
  27. data/spec/rubocop_spec.rb +12 -0
  28. data/spec/shared/clever_tap_client.rb +13 -0
  29. data/spec/shared/entity.rb +105 -0
  30. data/spec/spec_helper.rb +18 -0
  31. data/spec/units/clever_tap_client_spec.rb +277 -0
  32. data/spec/units/clever_tap_spec.rb +88 -0
  33. data/spec/units/event_spec.rb +43 -0
  34. data/spec/units/failed_response_spec.rb +31 -0
  35. data/spec/units/profile_spec.rb +29 -0
  36. data/spec/units/response_spec.rb +63 -0
  37. data/spec/units/successful_response_spec.rb +112 -0
  38. data/spec/units/uploader_spec.rb +129 -0
  39. data/spec/vcr_cassettes/CleverTap/uploading_a_many_profiles/when_only_some_are_valid/partially_succeds.yml +42 -0
  40. data/spec/vcr_cassettes/CleverTap/uploading_a_profile/when_is_invalid/fails.yml +41 -0
  41. data/spec/vcr_cassettes/CleverTap/uploading_a_profile/when_is_valid/succeed.yml +35 -0
  42. data/spec/vcr_cassettes/CleverTap/uploading_an_event/when_is_valid/succeed.yml +34 -0
  43. data/spec/vcr_cassettes/CleverTap_Client/_upload/when_upload_records_are_homogenous/and_invalid_records/calls_on_failed_upload_once.yml +38 -0
  44. data/spec/vcr_cassettes/CleverTap_Client/_upload/when_upload_records_are_homogenous/and_invalid_records/returns_an_array_with_one_failed_Response_object.yml +38 -0
  45. data/spec/vcr_cassettes/CleverTap_Client/_upload/when_upload_records_are_homogenous/and_valid_records/and_objects_do_not_fit_upload_limit_/calls_on_successful_upload_proc_twice.yml +67 -0
  46. data/spec/vcr_cassettes/CleverTap_Client/_upload/when_upload_records_are_homogenous/and_valid_records/and_objects_do_not_fit_upload_limit_/returns_an_array_with_two_successful_Response_objects.yml +67 -0
  47. data/spec/vcr_cassettes/CleverTap_Client/_upload/when_upload_records_are_homogenous/and_valid_records/and_objects_fit_upload_limit_/calls_on_successful_upload_proc_once.yml +36 -0
  48. data/spec/vcr_cassettes/CleverTap_Client/_upload/when_upload_records_are_homogenous/and_valid_records/and_objects_fit_upload_limit_/returns_an_array_with_one_successful_Response_object.yml +36 -0
  49. data/spec/vcr_cassettes/CleverTap_Uploader/_call/when_age_is_invalid/behaves_like_validation_failure/failed_to_upload_the_profiles.yml +48 -0
  50. data/spec/vcr_cassettes/CleverTap_Uploader/_call/when_education_status_is_invalid/behaves_like_validation_failure/failed_to_upload_the_profiles.yml +49 -0
  51. data/spec/vcr_cassettes/CleverTap_Uploader/_call/when_email_is_invalid/behaves_like_validation_failure/failed_to_upload_the_profiles.yml +48 -0
  52. data/spec/vcr_cassettes/CleverTap_Uploader/_call/when_employment_status_is_invalid/behaves_like_validation_failure/failed_to_upload_the_profiles.yml +48 -0
  53. data/spec/vcr_cassettes/CleverTap_Uploader/_call/when_marital_status_is_invalid/behaves_like_validation_failure/failed_to_upload_the_profiles.yml +48 -0
  54. data/spec/vcr_cassettes/CleverTap_Uploader/_call/when_phone_is_invalid/behaves_like_validation_failure/failed_to_upload_the_profiles.yml +48 -0
  55. data/spec/vcr_cassettes/CleverTap_Uploader/_call/when_the_creation_date_field_is_missing/behaves_like_validation_failure/failed_to_upload_the_profiles.yml +48 -0
  56. data/spec/vcr_cassettes/CleverTap_Uploader/_call/with_invalid_credentials/failed_to_upload_the_profiles.yml +36 -0
  57. data/spec/vcr_cassettes/CleverTap_Uploader/_call/with_valid_data/makes_successful_upload.yml +36 -0
  58. data/spec/vcr_config.rb +13 -0
  59. metadata +192 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: bc381afc5e3bb56358a3b82b80c5bd708869afd4bae69d9b372aac143972a6dd
4
+ data.tar.gz: c90a3514b5a31f292e5ecc7187a94741e2efe98e0671acc54d1851f48d98dd33
5
+ SHA512:
6
+ metadata.gz: 25a6f726cfb4f9621fcef18cfe450a9e8d5b8f5ba9b949ce8e863bd37fd1db0bfb18e5924a302857daa5ef7e2e1e95df2135142db7d30e784b285123d0415c1a
7
+ data.tar.gz: 2f21f704d1b73a781cdededa55d5870c2645146b32123baae43686db50bda9b1830976efe04ed5affd5994f12f867df14b12a8a23dcc573cd7686095468a280c
data/.gitignore ADDED
@@ -0,0 +1,13 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+
11
+ # rspec failure tracking
12
+ .rspec_status
13
+ .byebug_history
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color --format documentation
data/.rubocop.yml ADDED
@@ -0,0 +1,48 @@
1
+ ################################## Layout #######################################
2
+ Layout/IndentHash:
3
+ EnforcedStyle: consistent
4
+
5
+ Layout/SpaceInLambdaLiteral:
6
+ EnforcedStyle: require_space
7
+
8
+ ################################## Naming #######################################
9
+ Naming/FileName:
10
+ Exclude:
11
+ - 'lib/clevertap-ruby.rb'
12
+
13
+ ################################## Style #######################################
14
+
15
+ Style/FormatString:
16
+ EnforcedStyle: format
17
+
18
+ Style/FrozenStringLiteralComment:
19
+ Enabled: false
20
+
21
+ Style/MultilineBlockChain:
22
+ Enabled: false
23
+
24
+ Style/EmptyCaseCondition:
25
+ Enabled: false
26
+
27
+ ################################## Metrics #####################################
28
+
29
+ Metrics/AbcSize:
30
+ Max: 120
31
+
32
+ Metrics/BlockLength:
33
+ Enabled: false
34
+
35
+ Metrics/CyclomaticComplexity:
36
+ Max: 40
37
+
38
+ Metrics/LineLength:
39
+ Max: 140
40
+
41
+ Metrics/MethodLength:
42
+ Max: 70
43
+
44
+ Metrics/ModuleLength:
45
+ Max: 200
46
+
47
+ Metrics/PerceivedComplexity:
48
+ Max: 10
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ sudo: false
2
+ language: ruby
3
+ rbenv:
4
+ - 2.3.1
5
+ - 2.4.0
6
+ before_install: gem install bundler -v 1.14.3
data/Gemfile ADDED
@@ -0,0 +1,16 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'faraday', '~> 1.0'
4
+ gem 'json'
5
+
6
+ group :test do
7
+ gem 'rspec', '>= 3.3'
8
+ gem 'rubocop', '~> 0.52.0'
9
+ gem 'vcr'
10
+ end
11
+
12
+ group :test, :development do
13
+ gem 'pry-byebug'
14
+ end
15
+
16
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 Tradeo team
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,164 @@
1
+ clevertap-ruby
2
+ ==============
3
+
4
+ Module providing access to the [CleverTap](https://clevertap.com/) API
5
+
6
+ ## Install
7
+ Add to your Gemfile
8
+
9
+ ```ruby
10
+ gem 'clever_tap', github: 'tradeo/clevertap-ruby'
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ### Configure the client
16
+
17
+ Create an instance of `CleverTap` object:
18
+ __Available in v0.2.0 but will be depricated as of v1.0.0__
19
+ ```ruby
20
+ CLEVER_TAP = CleverTap.new(account_id: '<your account ID>', passcode: '<your passcode>')
21
+ ```
22
+
23
+ You can add configuration settings as parameters like above and/or using a block.
24
+ ```ruby
25
+ CLEVER_TAP = CleverTap.new do |config|
26
+ config.account_id = '<your account ID>' # mandatory
27
+ config.passcode = '<your passcode>' # mandatory
28
+ config.identity_field = 'ID' # default value "identity"
29
+
30
+ config.configure_faraday do |faraday_config| # optional
31
+ faraday_config.adapter :httpclient # default adapter "net_http"
32
+ end
33
+ end
34
+ ```
35
+
36
+ __As of v0.3.0 the new way of setting up CleverTap is:__
37
+ ```ruby
38
+ CleverTap.setup do |config|
39
+ # Default ID could be reset later
40
+ # case by case when uploading Profile/Event
41
+ config.identity_field = 'ID'
42
+
43
+ config.account_id = 'the-account-id'
44
+ config.account_passcode = 'the-passcode'
45
+ end
46
+ ```
47
+
48
+ Then creating a shared instance to use trough the app (v0.3.0 and above)
49
+ ```ruby
50
+ clevertap = CleverTap::Client.new
51
+
52
+ # You can add callbacks for successfull and failed calls as so:
53
+ clevertap.on_successful_upload do |response|
54
+ # log the response as response.to_s
55
+ # response is of type CleverTap::Response
56
+ # having methods `success` (true or false)
57
+ # and `failures` (empty if success == true)
58
+ # contains hash with errors returned from CleverTap endpoint
59
+ end
60
+
61
+ clevertap.on_failed_upload do |response|
62
+ ...
63
+ end
64
+ ```
65
+
66
+ ### Upload a profile
67
+
68
+ `.upload_profile` accepts as a first argument an object responding to `#to_h` and `#[]`.
69
+ ```ruby
70
+ profile = {
71
+ 'identity' => '666',
72
+ 'Name' => 'John Bravo'
73
+ }
74
+
75
+ client = CleverTap.new(account_id: '<your account ID>', passcode: '<your passcode>')
76
+ response = client.upload_profile(profile)
77
+
78
+ response.success # => true / false
79
+ response.status # => 'success' / 'partial' / 'fail'
80
+ response.errors # => [ { }, ...]
81
+ ```
82
+
83
+ __Date field__ used as a time stamp is optional.
84
+ If it's missing the current time stamp will be send instead.
85
+ The value should respond to `.to_i` and return epoch time.
86
+
87
+ ```ruby
88
+ profile = CleverTap::Profile.new(
89
+ data: { 'ID' => 1, 'Name' => 'John Doe' }, # MANDATORY, expects to receive a hash containing the identity field specified in `CleverTap.setup`, or below
90
+ identity_field: 'field_name' | false, # optional
91
+ fbid: '34322423', # optional, facebook id, can replace original identity
92
+ gpid: '34322423', # optional, google plus id, can replace original identity
93
+ objectId: '0f5d5fff698245f1ac5f192c', # optional, uniq CleverTap identifier
94
+ timestamp_field: 'Created At', # optional, has to be present in the `data` hash, else it throws
95
+ custom_timestamp: 1468308340 # optional, custom time stamp if user needs to set a particular timestamp, not presented in the object, takes precedence
96
+ )
97
+
98
+ clever_tap.upload(profile)
99
+ clever_tap.upload(profiles) # works as well with [CleverTap::Profile]
100
+ ```
101
+
102
+ ### Upload an event
103
+
104
+ __Available in v0.2.0 but will be depricated as of v1.0.0__
105
+ `.upload_event` accepts as a first argument an object responding to `#to_h` and `#[]` and a second parameter keyword argument `name: <name>`.
106
+ ```ruby
107
+ event = {
108
+ 'identity' => '666',
109
+ 'Name' => 'Jonh Bravo',
110
+ 'Cookie ID' => '424242'
111
+ }
112
+
113
+ client = CleverTap.new(account_id: '<your account ID>', passcode: '<your passcode>')
114
+ response = client.upload_event(event, name: 'registration')
115
+
116
+ response.success # => true / false
117
+ response.status # => 'success' / 'partial' / 'fail'
118
+ response.errors # => [ { }, ...]
119
+ ```
120
+ __As of v0.3.0 the new way of uploading Event(s) is:__
121
+ ```ruby
122
+ event = CleverTap::Event.new(
123
+ data: { 'ID' => 1, 'Field' => 'Value' } # MANDATORY, expects to receive a hash containing the identity field specified in `CleverTap.setup`, or below
124
+ name: 'Event Name', # MANDATORY
125
+ identity_field: 'field_name' | false, # optional, has to be present in the `data` hash, else it throws
126
+ fbid: '34322423', # optional, facebook id, can replace original identity
127
+ gpid: '34322423', # optional, google plus id, can replace original identity
128
+ objectId: '0f5d5fff698245f1ac5f192c', # optional, uniq CleverTap identifier, can replace identity
129
+ timestamp_field: 'Open Time', # optional, has to be present in the `data` hash, else it throws
130
+ custom_timestamp: 1468308340, # optional, custom time stamp if user needs to set a particular timestamp, not presented in the object
131
+ )
132
+
133
+ clevertap.upload(event)
134
+ clevertap.upload(events) # Works as well with [CleverTap::Event]
135
+ ```
136
+
137
+ ### Send requests as *Dry Run*
138
+
139
+ Passing parameter `dry_run: true` to upload methods you can test the data submitted for a validation errors.
140
+ The record won't be persisted.
141
+
142
+ __Available in v0.2.0 but will be depricated as of v1.0.0__
143
+ ```ruby
144
+ client = CleverTap.new(account_id: '<your account ID>', passcode: '<your passcode>')
145
+ client.upload_profile(profile, dry_run: true)
146
+ ```
147
+
148
+ __As of v0.3.0 the new way of using a Dry Run is :__
149
+ ```ruby
150
+ clevertap.upload(event, dry_run: true)
151
+ ```
152
+
153
+ ### Handle the response
154
+
155
+ The CleverTap response object has the following interface:
156
+ 1. `#status` - __"success"__ / __"partial"__ / __"fail"__
157
+ 2. `#success` - `true` when is the status is __"success"__ and `false` otherwise
158
+ 3. `#errors` - it's actually `unprocessed` synonym returned when the request is successful(code 200), but will contains the failed records even when the code is different than 200.
159
+ 4. `#code` - codes from 200-500. When it's __200__, errors can contains a validation
160
+ error codes. When it's __-1__ the error is custom and more info can be found in the message. More info about the codes can find [CleverTap Docs](https://support.clevertap.com/docs/api/working-with-user-profiles.html#uploading-user-profiles)
161
+
162
+
163
+
164
+ #### __More documentation you can find in the specs__
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task default: :spec
data/bin/console ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'clever_tap'
5
+
6
+ Bundler.require(:default, :development)
7
+
8
+ Pry.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,33 @@
1
+ $LOAD_PATH.push File.expand_path('../lib', __FILE__)
2
+
3
+ require 'clever_tap/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'clever_tap_dubit'
7
+ spec.version = CleverTap::VERSION
8
+ spec.authors = ['Kamen Kanev', 'Svetoslav Blyahov']
9
+ spec.email = ['opensource@tradeo.com']
10
+ spec.license = 'MIT'
11
+ spec.homepage = 'https://github.com/tradeo/clevertap-ruby'
12
+ spec.summary = 'CleverTap API client'
13
+ spec.description = 'Gem providing easy access to the CleverTap API'
14
+
15
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(spec)/}) }
16
+ spec.test_files = Dir['spec/**/*']
17
+ spec.require_paths = ['lib']
18
+
19
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
20
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
21
+ if spec.respond_to?(:metadata)
22
+ spec.metadata['allowed_push_host'] = "https://rubygems.org"
23
+ else
24
+ raise 'RubyGems 2.0 or newer is required to protect against ' \
25
+ 'public gem pushes.'
26
+ end
27
+
28
+ spec.add_dependency 'faraday', '~> 1.0'
29
+ spec.add_dependency 'json'
30
+
31
+ spec.add_development_dependency 'bundler', '> 1.14'
32
+ spec.add_development_dependency 'rake', '~> 10.0'
33
+ end
@@ -0,0 +1,113 @@
1
+ require 'faraday'
2
+
3
+ class CleverTap
4
+ class NotConsistentArrayError < RuntimeError
5
+ def message
6
+ 'Some elements in the collection are of different type than the others'
7
+ end
8
+ end
9
+
10
+ class Client
11
+ DOMAIN = 'https://api.clevertap.com'.freeze
12
+ API_VERSION = 1
13
+ HTTP_PATH = 'upload'.freeze
14
+ DEFAULT_SUCCESS = proc { |r| r.to_s }
15
+ DEFAULT_FAILURE = proc { |r| r.to_s }
16
+
17
+ ACCOUNT_HEADER = 'X-CleverTap-Account-Id'.freeze
18
+ PASSCODE_HEADER = 'X-CleverTap-Passcode'.freeze
19
+
20
+ attr_accessor :account_id, :passcode, :configure, :on_success, :on_failure
21
+
22
+ def initialize(account_id = nil, passcode = nil, &configure)
23
+ @account_id = assign_account_id(account_id)
24
+ @passcode = assign_passcode(passcode)
25
+ @configure = configure || proc {}
26
+ @on_success = DEFAULT_SUCCESS
27
+ @on_failure = DEFAULT_FAILURE
28
+ end
29
+
30
+ def connection
31
+ # TODO: pass the config to a block
32
+ @connection ||= Faraday.new("#{DOMAIN}/#{API_VERSION}") do |config|
33
+ configure.call(config)
34
+
35
+ # NOTE: set adapter only if there isn't one set
36
+ config.adapter :net_http if config.builder.adapter.nil?
37
+
38
+ config.headers['Content-Type'] = 'application/json'
39
+ config.headers[ACCOUNT_HEADER] = account_id
40
+ config.headers[PASSCODE_HEADER] = passcode
41
+ end
42
+ end
43
+
44
+ def post(*args, &block)
45
+ connection.post(*args, &block)
46
+ end
47
+
48
+ def get(*args, &block)
49
+ connection.get(*args, &block)
50
+ end
51
+
52
+ def on_successful_upload(&block)
53
+ @on_success = block
54
+ end
55
+
56
+ def on_failed_upload(&block)
57
+ @on_failure = block
58
+ end
59
+
60
+ def upload(records, dry_run: 0)
61
+ payload = ensure_array(records)
62
+ entity = determine_type(payload)
63
+ all_responses = []
64
+ batched_upload(entity, payload, dry_run) do |response|
65
+ all_responses << response
66
+ end
67
+
68
+ all_responses
69
+ end
70
+
71
+ private
72
+
73
+ def batched_upload(entity, payload, dry_run)
74
+ payload.each_slice(entity.upload_limit) do |group|
75
+ response = post(HTTP_PATH, request_body(group)) do |request|
76
+ request.params.merge!(dryRun: dry_run)
77
+ end
78
+
79
+ clevertap_response = Response.new(response)
80
+
81
+ if clevertap_response.success
82
+ @on_success.call(clevertap_response)
83
+ else
84
+ @on_failure.call(clevertap_response)
85
+ end
86
+
87
+ yield(clevertap_response) if block_given?
88
+ end
89
+ end
90
+
91
+ def request_body(records)
92
+ { 'd' => records.map(&:to_h) }.to_json
93
+ end
94
+
95
+ def determine_type(records)
96
+ types = records.map(&:class).uniq
97
+ raise NotConsistentArrayError unless types.one?
98
+ types.first
99
+ end
100
+
101
+ def ensure_array(records)
102
+ Array(records)
103
+ end
104
+
105
+ def assign_account_id(account_id)
106
+ account_id || CleverTap.account_id || raise('Clever Tap `account_id` missing')
107
+ end
108
+
109
+ def assign_passcode(passcode)
110
+ passcode || CleverTap.account_passcode || raise('Clever Tap `passcode` missing')
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,25 @@
1
+ class CleverTap
2
+ # CleverTap instance's config store object
3
+ class Config
4
+ DEFAULT_IDENTITY_FIELD = 'identity'.freeze
5
+
6
+ attr_accessor :account_id, :passcode, :identity_field
7
+
8
+ def initialize(**config)
9
+ @account_id = config[:account_id]
10
+ @passcode = config[:passcode]
11
+ @identity_field = config[:identity_field] || DEFAULT_IDENTITY_FIELD
12
+ @configure_faraday = config[:configure_faraday]
13
+ end
14
+
15
+ # NOTE: reader or writer depending if the block is given
16
+ def configure_faraday(&block)
17
+ block ? @configure_faraday = block : @configure_faraday
18
+ end
19
+
20
+ def validate
21
+ raise 'Missing authentication parameter `account_id`' unless account_id
22
+ raise 'Missing authentication parameter `passcode`' unless passcode
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,87 @@
1
+ class CleverTap
2
+ class NoDataError < RuntimeError
3
+ def message
4
+ 'No `data` param provided for Event'
5
+ end
6
+ end
7
+
8
+ class MissingIdentityError < RuntimeError
9
+ def message
10
+ "Couldn'n find `identity` in CleverTap.config or `data`"
11
+ end
12
+ end
13
+
14
+ class Entity
15
+ ALLOWED_IDENTITIES = %w(objectId FBID GPID).freeze
16
+ IDENTITY_STRING = 'identity'.freeze
17
+ TIMESTAMP_STRING = 'ts'.freeze
18
+ TYPE_KEY_STRING = 'type'.freeze
19
+ UPLOAD_LIMIT = 'Needs child class value'.freeze
20
+ TYPE_VALUE_STRING = 'Needs child class value'.freeze
21
+
22
+ class << self
23
+ def upload_limit
24
+ self::UPLOAD_LIMIT
25
+ end
26
+
27
+ def all_same_type?(items)
28
+ items.all? { |i| i.class == self }
29
+ end
30
+ end
31
+
32
+ def initialize(**args)
33
+ @data = args[:data]
34
+ @identity = choose_identity(args)
35
+ @timestamp = choose_timestamp(args)
36
+ end
37
+
38
+ def to_h
39
+ put_identity_pair
40
+ .merge(put_timestamp_pair)
41
+ .merge(put_type_pair)
42
+ .merge(put_data)
43
+ end
44
+
45
+ private
46
+
47
+ def put_identity_pair
48
+ raise NoDataError if @data.to_h.empty?
49
+ raise MissingIdentityError if @identity == '' || @data[@identity].nil?
50
+ return { @identity => @data[@identity].to_s } if allowed?(@identity)
51
+ { IDENTITY_STRING => @data[@identity].to_s }
52
+ end
53
+
54
+ def put_timestamp_pair
55
+ return {} unless @timestamp
56
+ { TIMESTAMP_STRING => @timestamp }
57
+ end
58
+
59
+ def put_type_pair
60
+ { TYPE_KEY_STRING => self.class::TYPE_VALUE_STRING }
61
+ end
62
+
63
+ def put_data
64
+ raise NoDataError if @data.to_h.empty?
65
+ @data.delete(@identity) if allowed?(@identity)
66
+ {
67
+ self.class::DATA_STRING => @data
68
+ }
69
+ end
70
+
71
+ def choose_identity(args)
72
+ identity = args[:identity].to_s
73
+
74
+ return identity if allowed?(identity) && @data.to_h.key?(identity)
75
+ CleverTap.identity_field.to_s
76
+ end
77
+
78
+ def choose_timestamp(args)
79
+ return args[:custom_timestamp].to_i if args[:custom_timestamp]
80
+ return @data.delete(args[:timestamp_field].to_s).to_i if args[:timestamp_field]
81
+ end
82
+
83
+ def allowed?(identity)
84
+ ALLOWED_IDENTITIES.include?(identity)
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,30 @@
1
+ class CleverTap
2
+ class MissingEventNameError < RuntimeError
3
+ def message
4
+ "Couldn't find `name:` with value in Event#new(options)"
5
+ end
6
+ end
7
+
8
+ class Event < Entity
9
+ DATA_STRING = 'evtData'.freeze
10
+ EVENT_NAME_STRING = 'evtName'.freeze
11
+ TYPE_VALUE_STRING = 'event'.freeze
12
+ UPLOAD_LIMIT = 1000
13
+
14
+ def initialize(**args)
15
+ super(**args)
16
+ @name = args[:name]
17
+ end
18
+
19
+ def to_h
20
+ super.merge(put_event_name_pair)
21
+ end
22
+
23
+ private
24
+
25
+ def put_event_name_pair
26
+ raise MissingEventNameError if @name.nil?
27
+ { EVENT_NAME_STRING => @name }
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,28 @@
1
+ class CleverTap
2
+ # Introduce unified interface as the `SuccessfulResponse`
3
+ class FailedResponse
4
+ FAIL_STATUS = 'fail'.freeze
5
+
6
+ attr_reader :records, :message, :code
7
+
8
+ def initialize(records:, message:, code: -1)
9
+ @records = records
10
+ @message = message
11
+ @code = code
12
+ end
13
+
14
+ def status
15
+ FAIL_STATUS
16
+ end
17
+
18
+ def success
19
+ false
20
+ end
21
+
22
+ def errors
23
+ records.map do |record|
24
+ { 'status' => FAIL_STATUS, 'code' => code, 'error' => message, 'record' => record }
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,7 @@
1
+ class CleverTap
2
+ class Profile < Entity
3
+ DATA_STRING = 'profileData'.freeze
4
+ TYPE_VALUE_STRING = 'profile'.freeze
5
+ UPLOAD_LIMIT = 100
6
+ end
7
+ end
@@ -0,0 +1,34 @@
1
+ class CleverTap
2
+ class Response
3
+ attr_accessor :response, :success, :failures
4
+
5
+ def initialize(response)
6
+ @response = response.success? ? JSON.parse(response.body) : extract_json_body(response)
7
+ process_response
8
+ end
9
+
10
+ private
11
+
12
+ def extract_json_body(response)
13
+ return JSON.parse(response.body)
14
+ rescue JSON::ParserError, TypeError
15
+ return { resp_string: response.body.to_s }.to_json
16
+ end
17
+
18
+ def process_response
19
+ return process_success if response['status'] == 'success'
20
+ @success = false
21
+ @failures = [response]
22
+ end
23
+
24
+ def process_success
25
+ if response['unprocessed'].to_a.empty?
26
+ @success = true
27
+ @failures = []
28
+ else
29
+ @success = false
30
+ @failures = response['unprocessed']
31
+ end
32
+ end
33
+ end
34
+ end