clever_tap 0.3.0

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