clever_tap 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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.rb +79 -0
  14. data/lib/clever_tap/client.rb +113 -0
  15. data/lib/clever_tap/config.rb +25 -0
  16. data/lib/clever_tap/entity.rb +87 -0
  17. data/lib/clever_tap/event.rb +30 -0
  18. data/lib/clever_tap/failed_response.rb +28 -0
  19. data/lib/clever_tap/profile.rb +7 -0
  20. data/lib/clever_tap/response.rb +28 -0
  21. data/lib/clever_tap/successful_response.rb +30 -0
  22. data/lib/clever_tap/uploader.rb +72 -0
  23. data/lib/clever_tap/version.rb +3 -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 +279 -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 +48 -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 +199 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 8765e8268c637623be90102da19286b9b6f38ad8
4
+ data.tar.gz: 8e34ed9c22626404a4e618b11d916b377e1780bf
5
+ SHA512:
6
+ metadata.gz: a4e852922b234dfda8bd0056ef26e1c0ad34bc39209812b70e0aa8f175341cd73062591e86c0794da4d817947a241f527c8b21255bf273249fb038e500f2298e
7
+ data.tar.gz: 44cfa234e3fb180d807563f25b5044df182858a6df7a9f44bd548bea6853c7140410e147d99efc910139341b5d927ed1e62f311f4c9406ae6c262543fbe0e042
@@ -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
@@ -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
@@ -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', '>= 0.8', '~> 0.14.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
@@ -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.
@@ -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__
@@ -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
@@ -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
@@ -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'
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', '>= 0.8', '<= 0.14.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,79 @@
1
+ require 'json'
2
+
3
+ require 'clever_tap/config'
4
+ require 'clever_tap/client'
5
+ require 'clever_tap/entity'
6
+ require 'clever_tap/event'
7
+ require 'clever_tap/profile'
8
+ require 'clever_tap/uploader'
9
+ require 'clever_tap/response'
10
+ require 'clever_tap/successful_response'
11
+ require 'clever_tap/failed_response'
12
+
13
+ # the main module of the system
14
+ class CleverTap
15
+ attr_reader :config
16
+
17
+ class << self
18
+ # Never instantiated. Variables are stored in the singleton_class.
19
+ private_class_method :new
20
+
21
+ attr_accessor :identity_field
22
+ attr_accessor :account_id
23
+ attr_accessor :account_passcode
24
+
25
+ def setup
26
+ yield(self)
27
+ end
28
+ end
29
+
30
+ def initialize(**params)
31
+ @config = Config.new(params)
32
+ yield(@config) if block_given?
33
+
34
+ @config.validate
35
+ @config.freeze
36
+ end
37
+
38
+ def client
39
+ @client ||= Client.new(config.account_id, config.passcode, &config.configure_faraday)
40
+ end
41
+
42
+ def upload_events(events, name:, **rest)
43
+ options = rest.merge(event_name: name, identity_field: config.identity_field)
44
+
45
+ response = Uploader.new(events, options).call(client)
46
+
47
+ normalize_response(response, records: events)
48
+ rescue Faraday::Error::TimeoutError, Faraday::Error::ClientError => e
49
+ FailedResponse.new(records: events, message: e.message)
50
+ end
51
+
52
+ def upload_event(event, **options)
53
+ upload_events([event], options)
54
+ end
55
+
56
+ def upload_profiles(profiles, **options)
57
+ options = options.merge(identity_field: config.identity_field)
58
+ response = Uploader.new(profiles, **options).call(client)
59
+
60
+ normalize_response(response, records: profiles)
61
+ rescue Faraday::Error::TimeoutError, Faraday::Error::ClientError => e
62
+ FailedResponse.new(records: profiles, message: e.message)
63
+ end
64
+
65
+ def upload_profile(profile, **options)
66
+ upload_profiles([profile], options)
67
+ end
68
+
69
+ private
70
+
71
+ def normalize_response(response, records:)
72
+ # TODO: handle JSON::ParserError
73
+ if response.success?
74
+ SuccessfulResponse.new(JSON.parse(response.body))
75
+ else
76
+ FailedResponse.new(records: records, code: response.status, message: response.body)
77
+ end
78
+ end
79
+ 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.handlers.empty?
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