basecrm 1.0.0 → 1.1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8112801481b6cff60fccc01b1218206447b30b35
4
- data.tar.gz: b30ccfcdbfe04ea0a5b518c83e77d3d9c4631827
3
+ metadata.gz: 9b39240e639171c284af2016c897affee25c599f
4
+ data.tar.gz: 4b67891d5ff434ee7e0394fca959b6e06d2d70b0
5
5
  SHA512:
6
- metadata.gz: 492f991f87a6d464938ba7f1ab986571283103afa40a3fc6ee6e9a4d0475c7783a172193d09df6e9ad7f87c6c428a95103a08947d333247422749c5974a4305f
7
- data.tar.gz: aaba445b34861708afebec9697d8eab35160f55b2ffe2f0ac34086ca682858e4c7ee1cb685cc8a9b46fe37a5ae0c397753c55d5dd85a93d07aa7fef0b921da7a
6
+ metadata.gz: 339f88a6b215e7c3c717d3200c6fa753dfdba54aba7418c72cf9dc5d69893fd4b004ac456ca97d1c3e4f5aa947556a5965cc81fe59c21d3eb87176b57355d0a7
7
+ data.tar.gz: a535ff335c8c6972c0a0f369e358cd90bcbd257b23003bc7033aea5b553a53a579dd6eb069310e38bc4587faeb60aed0ebd34a662af7e5956f4c8b5edaa87dec
data/README.md CHANGED
@@ -9,7 +9,7 @@ Make sure you have [rubygems](https://rubygems.org) installed.
9
9
  The gem is available via Rubygems. To install, use the following command:
10
10
 
11
11
  ```ruby
12
- sudo gem install basecrm
12
+ gem install basecrm
13
13
  ```
14
14
 
15
15
  If you use Bundler, put the line below in your Gemfile:
@@ -75,7 +75,7 @@ client.deals.all.map { |deal| deal.name } # => Array<String>
75
75
  To retrieve list of resources and use filtering you will call `#where` method:
76
76
 
77
77
  ```ruby
78
- client = BaseCRM::Client.new(accss_token: "<YOUR_PERSONAL_ACCESS_TOKEN>")
78
+ client = BaseCRM::Client.new(access_token: "<YOUR_PERSONAL_ACCESS_TOKEN>")
79
79
  client.deals.where(organization_id: google.id, hot: true) # => Array<BaseCRM::Deal>
80
80
  ```
81
81
 
@@ -102,7 +102,7 @@ client.deals.update(deal) # => BaseCRM::Deal
102
102
  To destroy a resource use `#destroy` method:
103
103
 
104
104
  ```ruby
105
- client = BaseCRM::Client.new(access_token: "<YOU_PERSONAL_ACCESS_TOKEN>")
105
+ client = BaseCRM::Client.new(access_token: "<YOUR_PERSONAL_ACCESS_TOKEN>")
106
106
  client.deals.destroy(id) # => true
107
107
  ```
108
108
 
@@ -120,6 +120,28 @@ lead.website = "http://www.designservices.com"
120
120
  client.leads.update(lead)
121
121
  ```
122
122
 
123
+ ## Sync API
124
+
125
+ The following sample code shows how to perform a full synchronization flow using high-level wrapper.
126
+
127
+ First of all you need an instance of `BaseCRM::Client`. High-level `BaseCRM::Sync` wrapper is using `BaseCRM::SyncService` to interact with the Sync API.
128
+ In addition to the client instance, you must provide a device’s UUID within `device_uuid` parameter. The device’s UUID must not change between synchronization sessions, otherwise the sync service will not recognize the device and will send all the data again.
129
+
130
+ ```ruby
131
+ client = BaseCRM::Client.new(access_token: "<YOUR_PERSONAL_ACCESS_TOKEN>")
132
+ sync = BaseCRM::Sync.new(client: client, device_uuid: "<YOUR_DEVICES_UUID>")
133
+ ```
134
+
135
+ Now all you have to do is to call `#fetch` method and pass a block that you might use to store fetched data to a database.
136
+
137
+ ```ruby
138
+ sync.fetch do |sync_meta, resource|
139
+ DB.send(sync_meta.event_type, resource) ? sync_meta.ack : sync_meta.nack
140
+ end
141
+ ```
142
+
143
+ Notice that you must call either `#ack` or `#nack` method.
144
+
123
145
  ## Resources and actions
124
146
 
125
147
  Documentation for every action can be found in corresponding service files under `lib/basecrm/services` directory.
@@ -127,7 +149,7 @@ Documentation for every action can be found in corresponding service files under
127
149
  ### Account
128
150
 
129
151
  ```ruby
130
- client = BaseCRM::Client.new(access_token: "<YOUR_PERSONL_ACCESS_TOKEN>")
152
+ client = BaseCRM::Client.new(access_token: "<YOUR_PERSONAL_ACCESS_TOKEN>")
131
153
  client.accounts # => BaseCRM::AccountsService
132
154
  ```
133
155
 
@@ -137,7 +159,7 @@ Actions:
137
159
  ### AssociatedContact
138
160
 
139
161
  ```ruby
140
- client = BaseCRM::Client.new(access_token: "<YOUR_PERSONL_ACCESS_TOKEN>")
162
+ client = BaseCRM::Client.new(access_token: "<YOUR_PERSONAL_ACCESS_TOKEN>")
141
163
  client.associated_contacts # => BaseCRM::AssociatedContactsService
142
164
  ```
143
165
 
@@ -149,7 +171,7 @@ Actions:
149
171
  ### Contact
150
172
 
151
173
  ```ruby
152
- client = BaseCRM::Client.new(access_token: "<YOUR_PERSONL_ACCESS_TOKEN>")
174
+ client = BaseCRM::Client.new(access_token: "<YOUR_PERSONAL_ACCESS_TOKEN>")
153
175
  client.contacts # => BaseCRM::ContactsService
154
176
  ```
155
177
 
@@ -163,7 +185,7 @@ Actions:
163
185
  ### Deal
164
186
 
165
187
  ```ruby
166
- client = BaseCRM::Client.new(access_token: "<YOUR_PERSONL_ACCESS_TOKEN>")
188
+ client = BaseCRM::Client.new(access_token: "<YOUR_PERSONAL_ACCESS_TOKEN>")
167
189
  client.deals # => BaseCRM::DealsService
168
190
  ```
169
191
 
@@ -177,7 +199,7 @@ Actions:
177
199
  ### Lead
178
200
 
179
201
  ```ruby
180
- client = BaseCRM::Client.new(access_token: "<YOUR_PERSONL_ACCESS_TOKEN>")
202
+ client = BaseCRM::Client.new(access_token: "<YOUR_PERSONAL_ACCESS_TOKEN>")
181
203
  client.leads # => BaseCRM::LeadsService
182
204
  ```
183
205
 
@@ -191,7 +213,7 @@ Actions:
191
213
  ### LossReason
192
214
 
193
215
  ```ruby
194
- client = BaseCRM::Client.new(access_token: "<YOUR_PERSONL_ACCESS_TOKEN>")
216
+ client = BaseCRM::Client.new(access_token: "<YOUR_PERSONAL_ACCESS_TOKEN>")
195
217
  client.loss_reasons # => BaseCRM::LossReasonsService
196
218
  ```
197
219
 
@@ -205,7 +227,7 @@ Actions:
205
227
  ### Note
206
228
 
207
229
  ```ruby
208
- client = BaseCRM::Client.new(access_token: "<YOUR_PERSONL_ACCESS_TOKEN>")
230
+ client = BaseCRM::Client.new(access_token: "<YOUR_PERSONAL_ACCESS_TOKEN>")
209
231
  client.notes # => BaseCRM::NotesService
210
232
  ```
211
233
 
@@ -219,7 +241,7 @@ Actions:
219
241
  ### Pipeline
220
242
 
221
243
  ```ruby
222
- client = BaseCRM::Client.new(access_token: "<YOUR_PERSONL_ACCESS_TOKEN>")
244
+ client = BaseCRM::Client.new(access_token: "<YOUR_PERSONAL_ACCESS_TOKEN>")
223
245
  client.pipelines # => BaseCRM::PipelinesService
224
246
  ```
225
247
 
@@ -229,7 +251,7 @@ Actions:
229
251
  ### Source
230
252
 
231
253
  ```ruby
232
- client = BaseCRM::Client.new(access_token: "<YOUR_PERSONL_ACCESS_TOKEN>")
254
+ client = BaseCRM::Client.new(access_token: "<YOUR_PERSONAL_ACCESS_TOKEN>")
233
255
  client.sources # => BaseCRM::SourcesService
234
256
  ```
235
257
 
@@ -243,7 +265,7 @@ Actions:
243
265
  ### Stage
244
266
 
245
267
  ```ruby
246
- client = BaseCRM::Client.new(access_token: "<YOUR_PERSONL_ACCESS_TOKEN>")
268
+ client = BaseCRM::Client.new(access_token: "<YOUR_PERSONAL_ACCESS_TOKEN>")
247
269
  client.stages # => BaseCRM::StagesService
248
270
  ```
249
271
 
@@ -253,7 +275,7 @@ Actions:
253
275
  ### Tag
254
276
 
255
277
  ```ruby
256
- client = BaseCRM::Client.new(access_token: "<YOUR_PERSONL_ACCESS_TOKEN>")
278
+ client = BaseCRM::Client.new(access_token: "<YOUR_PERSONAL_ACCESS_TOKEN>")
257
279
  client.tags # => BaseCRM::TagsService
258
280
  ```
259
281
 
@@ -267,7 +289,7 @@ Actions:
267
289
  ### Task
268
290
 
269
291
  ```ruby
270
- client = BaseCRM::Client.new(access_token: "<YOUR_PERSONL_ACCESS_TOKEN>")
292
+ client = BaseCRM::Client.new(access_token: "<YOUR_PERSONAL_ACCESS_TOKEN>")
271
293
  client.tasks # => BaseCRM::TasksService
272
294
  ```
273
295
 
@@ -281,7 +303,7 @@ Actions:
281
303
  ### User
282
304
 
283
305
  ```ruby
284
- client = BaseCRM::Client.new(access_token: "<YOUR_PERSONL_ACCESS_TOKEN>")
306
+ client = BaseCRM::Client.new(access_token: "<YOUR_PERSONAL_ACCESS_TOKEN>")
285
307
  client.users # => BaseCRM::UsersService
286
308
  ```
287
309
 
data/lib/basecrm.rb CHANGED
@@ -22,6 +22,9 @@ require 'basecrm/models/stage'
22
22
  require 'basecrm/models/tag'
23
23
  require 'basecrm/models/task'
24
24
  require 'basecrm/models/user'
25
+ require 'basecrm/models/sync_queue'
26
+ require 'basecrm/models/sync_session'
27
+ require 'basecrm/models/sync_meta'
25
28
 
26
29
  require 'basecrm/paginated_resource'
27
30
  require 'basecrm/services/accounts_service'
@@ -37,6 +40,9 @@ require 'basecrm/services/stages_service'
37
40
  require 'basecrm/services/tags_service'
38
41
  require 'basecrm/services/tasks_service'
39
42
  require 'basecrm/services/users_service'
43
+ require 'basecrm/services/sync_service'
44
+
45
+ require 'basecrm/sync'
40
46
 
41
47
  module BaseCRM
42
48
  class Client
@@ -66,121 +72,129 @@ module BaseCRM
66
72
  @http_client = HttpClient.new(@config)
67
73
  end
68
74
 
69
- # Access all Accounts related actions.
70
- # @see AccountsService
75
+ # Access all Accounts related actions.
76
+ # @see AccountsService
71
77
  # @see Account
72
- #
78
+ #
73
79
  # @return [AccountsService] Service object for resources.
74
80
  def accounts
75
81
  @accounts ||= AccountsService.new(@http_client)
76
82
  end
77
83
 
78
- # Access all AssociatedContacts related actions.
79
- # @see AssociatedContactsService
84
+ # Access all AssociatedContacts related actions.
85
+ # @see AssociatedContactsService
80
86
  # @see AssociatedContact
81
- #
87
+ #
82
88
  # @return [AssociatedContactsService] Service object for resources.
83
89
  def associated_contacts
84
90
  @associated_contacts ||= AssociatedContactsService.new(@http_client)
85
91
  end
86
92
 
87
- # Access all Contacts related actions.
88
- # @see ContactsService
93
+ # Access all Contacts related actions.
94
+ # @see ContactsService
89
95
  # @see Contact
90
- #
96
+ #
91
97
  # @return [ContactsService] Service object for resources.
92
98
  def contacts
93
99
  @contacts ||= ContactsService.new(@http_client)
94
100
  end
95
101
 
96
- # Access all Deals related actions.
97
- # @see DealsService
102
+ # Access all Deals related actions.
103
+ # @see DealsService
98
104
  # @see Deal
99
- #
105
+ #
100
106
  # @return [DealsService] Service object for resources.
101
107
  def deals
102
108
  @deals ||= DealsService.new(@http_client)
103
109
  end
104
110
 
105
- # Access all Leads related actions.
106
- # @see LeadsService
111
+ # Access all Leads related actions.
112
+ # @see LeadsService
107
113
  # @see Lead
108
- #
114
+ #
109
115
  # @return [LeadsService] Service object for resources.
110
116
  def leads
111
117
  @leads ||= LeadsService.new(@http_client)
112
118
  end
113
119
 
114
- # Access all LossReasons related actions.
115
- # @see LossReasonsService
120
+ # Access all LossReasons related actions.
121
+ # @see LossReasonsService
116
122
  # @see LossReason
117
- #
123
+ #
118
124
  # @return [LossReasonsService] Service object for resources.
119
125
  def loss_reasons
120
126
  @loss_reasons ||= LossReasonsService.new(@http_client)
121
127
  end
122
128
 
123
- # Access all Notes related actions.
124
- # @see NotesService
129
+ # Access all Notes related actions.
130
+ # @see NotesService
125
131
  # @see Note
126
- #
132
+ #
127
133
  # @return [NotesService] Service object for resources.
128
134
  def notes
129
135
  @notes ||= NotesService.new(@http_client)
130
136
  end
131
137
 
132
- # Access all Pipelines related actions.
133
- # @see PipelinesService
138
+ # Access all Pipelines related actions.
139
+ # @see PipelinesService
134
140
  # @see Pipeline
135
- #
141
+ #
136
142
  # @return [PipelinesService] Service object for resources.
137
143
  def pipelines
138
144
  @pipelines ||= PipelinesService.new(@http_client)
139
145
  end
140
146
 
141
- # Access all Sources related actions.
142
- # @see SourcesService
147
+ # Access all Sources related actions.
148
+ # @see SourcesService
143
149
  # @see Source
144
- #
150
+ #
145
151
  # @return [SourcesService] Service object for resources.
146
152
  def sources
147
153
  @sources ||= SourcesService.new(@http_client)
148
154
  end
149
155
 
150
- # Access all Stages related actions.
151
- # @see StagesService
156
+ # Access all Stages related actions.
157
+ # @see StagesService
152
158
  # @see Stage
153
- #
159
+ #
154
160
  # @return [StagesService] Service object for resources.
155
161
  def stages
156
162
  @stages ||= StagesService.new(@http_client)
157
163
  end
158
164
 
159
- # Access all Tags related actions.
160
- # @see TagsService
165
+ # Access all Tags related actions.
166
+ # @see TagsService
161
167
  # @see Tag
162
- #
168
+ #
163
169
  # @return [TagsService] Service object for resources.
164
170
  def tags
165
171
  @tags ||= TagsService.new(@http_client)
166
172
  end
167
173
 
168
- # Access all Tasks related actions.
169
- # @see TasksService
174
+ # Access all Tasks related actions.
175
+ # @see TasksService
170
176
  # @see Task
171
- #
177
+ #
172
178
  # @return [TasksService] Service object for resources.
173
179
  def tasks
174
180
  @tasks ||= TasksService.new(@http_client)
175
181
  end
176
182
 
177
- # Access all Users related actions.
178
- # @see UsersService
183
+ # Access all Users related actions.
184
+ # @see UsersService
179
185
  # @see User
180
- #
186
+ #
181
187
  # @return [UsersService] Service object for resources.
182
188
  def users
183
189
  @users ||= UsersService.new(@http_client)
184
190
  end
191
+
192
+ # Access Sync API related low-level actions.
193
+ # @see SyncService
194
+ #
195
+ # @return [SyncService] Service object for Sync API.
196
+ def sync
197
+ @sync ||= SyncService.new(@http_client)
198
+ end
185
199
  end
186
200
  end
@@ -30,25 +30,25 @@ module BaseCRM
30
30
  end
31
31
  end
32
32
 
33
- def get(path, params={})
34
- request(:get, path, params)
33
+ def get(path, params={}, headers={})
34
+ request(:get, path, params, headers)
35
35
  end
36
36
 
37
- def post(path, body={})
38
- request(:post, path, body)
37
+ def post(path, body={}, headers={})
38
+ request(:post, path, body, headers)
39
39
  end
40
40
 
41
- def put(path, body={})
42
- request(:put, path, body)
41
+ def put(path, body={}, headers={})
42
+ request(:put, path, body, headers)
43
43
  end
44
44
 
45
- def delete(path, params={})
45
+ def delete(path, params={}, headers={})
46
46
  request(:delete, path, params)
47
47
  end
48
48
 
49
- def request(method, path, data={})
49
+ def request(method, path, data={}, headers={})
50
50
  options = {
51
- headers: @headers
51
+ headers: @headers.merge(headers.to_h)
52
52
  }
53
53
 
54
54
  case method.to_s.downcase.to_sym
@@ -0,0 +1,29 @@
1
+ module BaseCRM
2
+ class SyncMeta < Model
3
+ # @attribute [r] event_type
4
+ # @return [String] An event type. Possible values: `created`, `updated`, `deleted`.
5
+ # attr_reader :event_type
6
+
7
+ # @attribute [r] ack_key
8
+ # @return [String] An acknowledgement key.
9
+ # attr_reader :ack_key
10
+
11
+ # @attribute [r] revision
12
+ # @return [String] Data revision.
13
+ # attr_reader :revision
14
+
15
+ def acknowledged?
16
+ !!@acknowledged
17
+ end
18
+
19
+ def ack
20
+ @acknowledged = true
21
+ [:ack, self.ack_key]
22
+ end
23
+
24
+ def nack
25
+ @acknowledged = true
26
+ [:nack, self.ack_key]
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,15 @@
1
+ module BaseCRM
2
+ class SyncQueue < Model
3
+ # @attribute [r] name
4
+ # @return [String] Name of the queue.
5
+ # attr_reader :name
6
+
7
+ # @attribute [r] pages
8
+ # @return [Integer] Number of pages to request.
9
+ # attr_reader :pages
10
+
11
+ # @attribute [r] total_count
12
+ # @return [Integer] Total number of items in the queue to synchronize.
13
+ # attr_reader :total_count
14
+ end
15
+ end
@@ -0,0 +1,11 @@
1
+ module BaseCRM
2
+ class SyncSession < Model
3
+ # @attribute [r] id
4
+ # @return [String] Unique identifier for the sync session.
5
+ # attr_reader :id
6
+
7
+ # @attribute [r] queues
8
+ # @return [Array<SyncQueue>] A list of sync queues.
9
+ # attr_reader :queues
10
+ end
11
+ end
@@ -0,0 +1,94 @@
1
+ module BaseCRM
2
+ class SyncService
3
+
4
+ def initialize(client)
5
+ @client = client
6
+ end
7
+
8
+ # Start synchronization flow
9
+ #
10
+ # post '/sync/start'
11
+ #
12
+ # Starts a new synchronization session.
13
+ # This is the first endpoint to call, in order to start a new synchronization session.
14
+ #
15
+ # @param device_uuid [String] Device's UUID for which to perform synchronization.
16
+ # @return [SyncSession] The resulting object is the synchronization session object or nil if there is nothing to synchronize.
17
+ def start(device_uuid)
18
+ validate_device!(device_uuid)
19
+
20
+ status, _, root = @client.post("/sync/start", {}, build_headers(device_uuid))
21
+ return nil if status == 204
22
+
23
+ build_session(root)
24
+ end
25
+
26
+ # Get data from queue
27
+ #
28
+ # get '/sync/{session_id}/queues/main'
29
+ #
30
+ # Fetch fresh data from the main queue.
31
+ # Using session identifier you call continously the `#fetch` method to drain the main queue.
32
+ #
33
+ # @param device_uuid [String] Device's UUID for which to perform synchronization
34
+ # @param session_id [String] Unique identifier of a synchronization session.
35
+ # @param queue [String|Symbol] Queue name.
36
+ # @return [Array<Array<SyncMeta, Model>>] The list of sync's metadata associated with data and data.
37
+ def fetch(device_uuid, session_id, queue='main')
38
+ validate_device!(device_uuid)
39
+ raise ArgumentError, "session_id must not be nil nor empty" unless session_id && !session_id.strip.empty?
40
+ raise ArgumentError, "queue name must not be nil nor empty" unless queue && !queue.strip.empty?
41
+
42
+ status, _, root = @client.get("/sync/#{session_id}/queues/#{queue}", {}, build_headers(device_uuid))
43
+ return [] if status == 204
44
+
45
+ root[:items].map do |item|
46
+ klass = classify_type(item[:meta][:type])
47
+ [SyncMeta.new(item[:meta][:sync]), klass.new(item[:data])]
48
+ end
49
+ end
50
+
51
+ # Acknowledge received data
52
+ #
53
+ # post '/sync/ack'
54
+ #
55
+ # Send acknowledgement keys to let know the Sync service which data you have.
56
+ # As you fetch new data, you need to send acknowledgement keys.
57
+ #
58
+ # @param device_uuid [String] Device's UUID for which to perform synchronization.
59
+ # @param ack_keys [Array<String>] The list of acknowledgement keys.
60
+ # @return [Boolean] Status of the operation.
61
+ def ack(device_uuid, ack_keys)
62
+ validate_device!(device_uuid)
63
+ raise ArgumentError, "ack_keys must not be nil and an array" unless ack_keys && ack_keys.is_a?(Array)
64
+ return true if ack_keys.empty?
65
+
66
+ payload = {
67
+ :ack_keys => ack_keys.map(&:to_s)
68
+ }
69
+ status, _, _ = @client.post('/sync/ack', payload, build_headers(device_uuid))
70
+ status == 202
71
+ end
72
+
73
+ private
74
+ def validate_device!(device_uuid)
75
+ raise ArgumentError, "device_uuid must not be nil nor empty" unless device_uuid && !device_uuid.strip.empty?
76
+ end
77
+
78
+ def build_headers(device_uuid)
79
+ {
80
+ "X-Basecrm-Device-UUID" => device_uuid
81
+ }
82
+ end
83
+
84
+ def classify_type(type)
85
+ BaseCRM.const_get(type.split('_').map(&:capitalize).join)
86
+ end
87
+
88
+ def build_session(root)
89
+ session_data = root[:data]
90
+ session_data[:queues] = session_data[:queues].map { |queue| SyncQueue.new(queue[:data]) }
91
+ SyncSession.new(session_data)
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,82 @@
1
+ module BaseCRM
2
+ class Sync
3
+ attr_reader :device_uuid
4
+ attr_reader :client
5
+
6
+ # Intantiate a new BaseCRM Sync API V2 high-level wrapper
7
+ #
8
+ # @param options[Hash] Wrapper options
9
+ # @option options [String] :device_uuid Device's UUID.
10
+ # @option options [BaseCRM::Client] :client BaseCRM API v2 client instance.
11
+ #
12
+ # @raise [ConfigurationError] if no device's uuid provided
13
+ # @raise [ConfigurationError] if no client instance provided
14
+ #
15
+ # @return [Sync] New wrapper instance
16
+ #
17
+ # @see Client
18
+ # @see SyncService
19
+ def initialize(options)
20
+ @device_uuid = options[:device_uuid]
21
+ @client = options[:client]
22
+
23
+ validate!
24
+ end
25
+
26
+ # Perform a full synchronization flow.
27
+ # See the following example:
28
+ #
29
+ # client = BaseCRM::Client.new(access_token: "<YOUR_ACCESS_TOKEN>")
30
+ # sync = BaseCRM::Sync.new(client: client, device_uuid: "<YOUR_DEVICES_UUID>")
31
+ # sync.fetch do |sync_meta, resource|
32
+ # DB.send(sync_meta.event_type, entity) ? sync_meta.ack : sync_meta.nack
33
+ # end
34
+ #
35
+ # @param block [Proc] Procedure that will be called for every item in the queue. Takes two input arguments: SyncMeta instance
36
+ # associated with the resource, and the resource. You should use either `SyncQueue#ack` or `SyncQueue#nack` to return value from the block.
37
+ #
38
+ # @return nil
39
+ def fetch(&block)
40
+ return unless block_given?
41
+
42
+ # Set up a new synchronization session for given device's UUID
43
+ session = @client.sync.start(@device_uuid)
44
+
45
+ # Check if there is anything to synchronize
46
+ return unless session && session.id
47
+
48
+ # Drain the main queue unitl there is no more data (empty array)
49
+ loop do
50
+ queued_data = @client.sync.fetch(@device_uuid, session.id)
51
+
52
+ # nothing more to synchronize ?
53
+ break if queued_data.empty?
54
+
55
+ ack_keys = []
56
+ queued_data.each do |sync_meta, resource|
57
+ op, ack_key = block.call(sync_meta, resource)
58
+ ack_keys << ack_key if op == :ack
59
+ end
60
+
61
+ # As we fetch new data, we need to send ackwledgement keys - if any
62
+ @client.sync.ack(@device_uuid, ack_keys) unless ack_keys.empty?
63
+ end
64
+ end
65
+
66
+ private
67
+ def validate!
68
+ unless @device_uuid
69
+ raise ConfigurationError.new('No device UUID provided. '\
70
+ 'The UUID must not change between synchronization sessions. '\
71
+ 'Set your device\'s UUID during wrapper initialization using: '\
72
+ '"Base::Sync.new(device_uuid: <YOUR_DEVICES_UUID>, client: client)".')
73
+ end
74
+
75
+ unless @client
76
+ raise ConfigurationError.new('No "BaseCRM::Client" instance provided. '\
77
+ 'The high-level sync wrapper is using "BaseCRM::SyncService"\'s lowl-level interface '\
78
+ 'exposed within "BaseCRM::Client" scope.')
79
+ end
80
+ end
81
+ end
82
+ end
@@ -1,3 +1,3 @@
1
1
  module BaseCRM
2
- VERSION = "1.0.0"
2
+ VERSION = "1.1.0"
3
3
  end
@@ -0,0 +1,94 @@
1
+ require 'spec_helper'
2
+
3
+ describe BaseCRM::Sync do
4
+ let(:device_uuid) { '6dadcec8-6e61-4691-b318-1aab27b8fecf' }
5
+ let(:session_id) { '29f2aeeb-8d68-4ea7-95c3-a2c8e151f5a3' }
6
+
7
+ subject { BaseCRM::Sync.new(device_uuid: device_uuid, client: client) }
8
+
9
+ describe 'Responds to' do
10
+ it { should respond_to :fetch }
11
+ end
12
+
13
+ describe :initialize do
14
+ describe 'validation' do
15
+ context 'no device_uuid option' do
16
+ it 'raises BaseCRM::ConfigurationError exception' do
17
+ expect { BaseCRM::Sync.new(client: client) }.to raise_error BaseCRM::ConfigurationError
18
+ end
19
+ end
20
+
21
+ context 'no client option' do
22
+ it 'raises BaseCRM::ConfigurationError exception' do
23
+ expect { BaseCRM::Sync.new(device_uuid: device_uuid) }.to raise_error BaseCRM::ConfigurationError
24
+ end
25
+ end
26
+
27
+ context 'required options passed' do
28
+ it 'raises no exception' do
29
+ expect { BaseCRM::Sync.new(client: client, device_uuid: device_uuid) }.not_to raise_error
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ describe :fetch do
36
+ context 'no block passed' do
37
+ it 'does nothing' do
38
+ expect(client).not_to receive(:sync)
39
+ subject.fetch
40
+ end
41
+ end
42
+
43
+ context 'nothing to synchronize' do
44
+ before :each do
45
+ expect(client.sync).to receive(:start).with(device_uuid).and_return(nil)
46
+ end
47
+
48
+ it 'returns early' do
49
+ expect(subject.fetch { |s, r| :nop }).to be_nil
50
+ end
51
+ end
52
+
53
+ context 'fresh data to synchronize' do
54
+ let(:session) do
55
+ BaseCRM::SyncSession.new(id: session_id)
56
+ end
57
+
58
+ let(:ack_keys) do
59
+ ['User-1234-1', 'Source-1234-1']
60
+ end
61
+
62
+ let(:queue_items) do
63
+ [
64
+ [BaseCRM::SyncMeta.new(event_type: 'created', ack_key: 'User-1234-1'), BaseCRM::User.new(id: 1)],
65
+ [BaseCRM::SyncMeta.new(event_type: 'created', ack_key: 'Source-1234-1'), BaseCRM::Source.new(id: 1)]
66
+ ]
67
+ end
68
+
69
+ before :each do
70
+ expect(client.sync).to receive(:start).with(device_uuid).and_return(session)
71
+ expect(client.sync).to receive(:fetch).with(device_uuid, session_id).and_return(queue_items)
72
+ expect(client.sync).to receive(:fetch).with(device_uuid, session_id).and_return([])
73
+ expect(client.sync).to receive(:ack).with(device_uuid, ack_keys).and_return(true)
74
+ end
75
+
76
+ it 'does whole synchronization flow' do
77
+ subject.fetch { |s, r| s.ack }
78
+ end
79
+
80
+ it 'calls a provided block as many times as items in the queue' do
81
+ counter = 0
82
+ subject.fetch { |s, r| counter += 1; s.ack }
83
+ expect(counter).to eq(2)
84
+ end
85
+
86
+ it 'passes two elements to provided block: first element is BaseCRM::SyncMeta and the second is a resource' do
87
+ subject.fetch do |s, r|
88
+ expect(s).to be_a BaseCRM::SyncMeta
89
+ s.ack
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,59 @@
1
+ require 'spec_helper'
2
+
3
+ describe BaseCRM::SyncMeta do
4
+ describe 'Responds to' do
5
+ it { should respond_to :ack }
6
+ it { should respond_to :nack }
7
+ it { should respond_to :acknowledged? }
8
+ end
9
+
10
+ describe :ack do
11
+ subject { BaseCRM::SyncMeta }
12
+
13
+ it 'returns array' do
14
+ expect(subject.new.ack).to be_an Array
15
+ end
16
+
17
+ it 'returns two elements array' do
18
+ expect(subject.new(ack_key: "123").ack).to eq([:ack, "123"])
19
+ end
20
+ end
21
+
22
+ describe :nack do
23
+ subject { BaseCRM::SyncMeta }
24
+
25
+ it 'returns array' do
26
+ expect(subject.new.nack).to be_an Array
27
+ end
28
+
29
+ it 'returns two elements array' do
30
+ expect(subject.new(ack_key: "123").nack).to eq([:nack, "123"])
31
+ end
32
+ end
33
+
34
+ describe :acknowledged? do
35
+ subject { BaseCRM::SyncMeta }
36
+
37
+ context 'neither ack nor nack method called' do
38
+ it 'return false value' do
39
+ expect(subject.new.acknowledged?).to eq(false)
40
+ end
41
+ end
42
+
43
+ context 'ack method called' do
44
+ it 'return true value' do
45
+ ack_meta = subject.new
46
+ ack_meta.ack
47
+ expect(ack_meta.acknowledged?).to eq(true)
48
+ end
49
+ end
50
+
51
+ context 'nack method called' do
52
+ it 'return true value' do
53
+ ack_meta = subject.new
54
+ ack_meta.nack
55
+ expect(ack_meta.acknowledged?).to eq(true)
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,283 @@
1
+ require 'spec_helper'
2
+
3
+ describe BaseCRM::SyncService do
4
+ let(:device_uuid) { '6dadcec8-6e61-4691-b318-1aab27b8fecf' }
5
+ let(:session_id) { '29f2aeeb-8d68-4ea7-95c3-a2c8e151f5a3' }
6
+
7
+ describe 'Responds to' do
8
+ subject { BaseCRM::SyncService.new(double) }
9
+
10
+ it { should respond_to :start }
11
+ it { should respond_to :ack }
12
+ it { should respond_to :fetch }
13
+ end
14
+
15
+ describe 'Client respond to' do
16
+ subject { client }
17
+
18
+ it { should respond_to :sync }
19
+ end
20
+
21
+ describe 'Client#sync' do
22
+ it 'returns BaseCRM::SyncService instance' do
23
+ expect(client.sync).to be_a BaseCRM::SyncService
24
+ end
25
+ end
26
+
27
+ describe :start do
28
+ describe 'validation' do
29
+ context 'device_uuid is nil' do
30
+ it 'raises ArgumentError exception' do
31
+ expect { client.sync.start(nil) }.to raise_error(ArgumentError)
32
+ end
33
+ end
34
+
35
+ context 'device_uuid is empty' do
36
+ it 'raises ArgumentError exception' do
37
+ expect { client.sync.start(" ") }.to raise_error(ArgumentError)
38
+ end
39
+ end
40
+ end
41
+
42
+ context 'nothing new to fetch' do
43
+ let(:http_response) do
44
+ [204, {}, nil]
45
+ end
46
+
47
+ it 'returns nil' do
48
+ expect(client.http_client).to receive(:post).with('/sync/start', {}, {'X-Basecrm-Device-UUID' => device_uuid}).and_return(http_response)
49
+ expect(client.sync.start(device_uuid)).to be_nil
50
+ end
51
+ end
52
+
53
+ context 'we have to data to synchronize' do
54
+ let(:payload) do
55
+ {
56
+ data: {
57
+ id: session_id,
58
+ queues: [
59
+ data: {
60
+ name: 'main',
61
+ pages: 1,
62
+ total_count: 2
63
+ },
64
+ meta: {
65
+ type: :sync_queue
66
+ }
67
+ ]
68
+ },
69
+ meta: {
70
+ type: :sync_session
71
+ }
72
+ }
73
+ end
74
+
75
+ let(:http_response) do
76
+ [201, {}, payload]
77
+ end
78
+
79
+ before :each do
80
+ expect(client.http_client).to receive(:post).with('/sync/start', {}, {'X-Basecrm-Device-UUID' => device_uuid}).and_return(http_response)
81
+ end
82
+
83
+ it 'returns an instance of BaseCRM::SyncSession' do
84
+ expect(client.sync.start(device_uuid)).to be_an BaseCRM::SyncSession
85
+ end
86
+
87
+ it "flattens BaseCRM::SyncSession's queues" do
88
+ client.sync.start(device_uuid).queues.each do |queue|
89
+ expect(queue).to be_an BaseCRM::SyncQueue
90
+ end
91
+ end
92
+ end
93
+ end
94
+
95
+ describe :ack do
96
+ let(:http_response) do
97
+ [202, {}, nil]
98
+ end
99
+
100
+ let(:ack_keys) do
101
+ ['User-1234-1', 'Source-1234-1']
102
+ end
103
+
104
+ describe 'validation' do
105
+ context 'device_uuid is nil' do
106
+ it 'raises ArgumentError exception' do
107
+ expect { client.sync.ack(nil, ack_keys) }.to raise_error(ArgumentError)
108
+ end
109
+ end
110
+
111
+ context 'device_uuid is empty' do
112
+ it 'raises ArgumentError exception' do
113
+ expect { client.sync.ack(" ", ack_keys) }.to raise_error(ArgumentError)
114
+ end
115
+ end
116
+
117
+ context 'ack_keys is nil' do
118
+ it 'raises ArgumentError exception' do
119
+ expect { client.sync.ack(device_uuid, nil) }.to raise_error(ArgumentError)
120
+ end
121
+ end
122
+
123
+ context 'ack_keys is not an Array' do
124
+ it 'raises ArgumentError exception' do
125
+ expect { client.sync.ack(device_uuid, {}) }.to raise_error(ArgumentError)
126
+ end
127
+ end
128
+ end
129
+
130
+ context 'empty ack_keys array' do
131
+ let(:ack_keys) do
132
+ []
133
+ end
134
+
135
+ it 'returns imedieatly with true value' do
136
+ expect(client.http_client).not_to receive(:post)
137
+ expect(client.sync.ack(device_uuid, ack_keys)).to eq(true)
138
+ end
139
+ end
140
+
141
+ context 'non empty ack_keys call' do
142
+ it 'returns true value' do
143
+ expect(client.http_client).to receive(:post).with('/sync/ack', {ack_keys: ack_keys}, {'X-Basecrm-Device-UUID' => device_uuid}).and_return(http_response)
144
+ expect(client.sync.ack(device_uuid, ack_keys)).to eq(true)
145
+ end
146
+ end
147
+ end
148
+
149
+ describe :fetch do
150
+ context 'validation' do
151
+ context 'device_uuid is nil' do
152
+ it 'raises ArgumentError exception' do
153
+ expect { client.sync.fetch(nil, session_id) }.to raise_error(ArgumentError)
154
+ end
155
+ end
156
+
157
+ context 'device_uuid is empty' do
158
+ it 'raises ArgumentError exception' do
159
+ expect { client.sync.fetch(" ", session_id) }.to raise_error(ArgumentError)
160
+ end
161
+ end
162
+
163
+ context 'session_id is nil' do
164
+ it 'raises ArgumentError exception' do
165
+ expect { client.sync.fetch(device_uuid, nil) }.to raise_error(ArgumentError)
166
+ end
167
+ end
168
+
169
+ context 'session_id is empty' do
170
+ it 'raises ArgumentError exception' do
171
+ expect { client.sync.fetch(device_uuid, ' ') }.to raise_error(ArgumentError)
172
+ end
173
+ end
174
+
175
+ context 'queue names is nil' do
176
+ it 'raises ArgumentError exception' do
177
+ expect { client.sync.fetch(device_uuid, session_id, nil) }.to raise_error(ArgumentError)
178
+ end
179
+ end
180
+
181
+ context 'queue names is empty' do
182
+ it 'raises ArgumentError exception' do
183
+ expect { client.sync.fetch(device_uuid, session_id, ' ') }.to raise_error(ArgumentError)
184
+ end
185
+ end
186
+ end
187
+
188
+ context 'no more data to fetch' do
189
+ let(:http_response) do
190
+ [204, {}, nil]
191
+ end
192
+
193
+ it 'returns an empty array' do
194
+ expect(client.http_client).to receive(:get).with("/sync/#{session_id}/queues/main", {}, {'X-Basecrm-Device-UUID' => device_uuid}).and_return(http_response)
195
+ expect(client.sync.fetch(device_uuid, session_id)).to eq([])
196
+ end
197
+ end
198
+
199
+ context 'there is still data in the main queue' do
200
+ let(:payload) do
201
+ {
202
+ items: [
203
+ {
204
+ data: {
205
+ id: 1
206
+ },
207
+ meta: {
208
+ type: 'user',
209
+ sync: {
210
+ event_type: 'created',
211
+ ack_key: 'User-123-1',
212
+ revision: 1
213
+ }
214
+ }
215
+ },
216
+ {
217
+ data: {
218
+ id: 1
219
+ },
220
+ meta: {
221
+ type: 'source',
222
+ sync: {
223
+ event_type: 'created',
224
+ ack_key: 'Source-123-1',
225
+ revision: 1
226
+ }
227
+ }
228
+ }
229
+ ],
230
+ meta: {
231
+ type: 'collection',
232
+ count: 2,
233
+ count_left: 0
234
+ }
235
+ }
236
+ end
237
+
238
+ let(:http_response) do
239
+ [200, {}, payload]
240
+ end
241
+
242
+ before :each do
243
+ expect(client.http_client).to receive(:get).with("/sync/#{session_id}/queues/main", {}, {'X-Basecrm-Device-UUID' => device_uuid}).and_return(http_response)
244
+ end
245
+
246
+ it 'returns an array' do
247
+ expect(client.sync.fetch(device_uuid, session_id)).to be_an Array
248
+ end
249
+
250
+ it 'returns a non empty array' do
251
+ expect(client.sync.fetch(device_uuid, session_id)).not_to be_empty
252
+ end
253
+
254
+ it 'returns an array of two items' do
255
+ expect(client.sync.fetch(device_uuid, session_id).length).to eq(2)
256
+ end
257
+
258
+ it 'returns an array of arrays' do
259
+ client.sync.fetch(device_uuid, session_id).each do |item|
260
+ expect(item).to be_an Array
261
+ expect(item).not_to be_empty
262
+ expect(item.length).to eq(2)
263
+ end
264
+ end
265
+
266
+ it 'returns an array where the first element is BaseCRM::SyncMeta and the second is a model' do
267
+ items = client.sync.fetch(device_uuid, session_id)
268
+
269
+ sync_meta, user = items[0]
270
+ expect(sync_meta).to be_a BaseCRM::SyncMeta
271
+ expect(user).to be_a BaseCRM::User
272
+ expect(sync_meta.ack_key).to eq('User-123-1')
273
+ expect(user.id).to eq(1)
274
+
275
+ sync_meta, source = items[1]
276
+ expect(sync_meta).to be_a BaseCRM::SyncMeta
277
+ expect(source).to be_a BaseCRM::Source
278
+ expect(sync_meta.ack_key).to eq('Source-123-1')
279
+ expect(source.id).to eq(1)
280
+ end
281
+ end
282
+ end
283
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: basecrm
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - BaseCRM developers
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-04-17 00:00:00.000000000 Z
11
+ date: 2015-05-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -148,6 +148,9 @@ files:
148
148
  - lib/basecrm/models/pipeline.rb
149
149
  - lib/basecrm/models/source.rb
150
150
  - lib/basecrm/models/stage.rb
151
+ - lib/basecrm/models/sync_meta.rb
152
+ - lib/basecrm/models/sync_queue.rb
153
+ - lib/basecrm/models/sync_session.rb
151
154
  - lib/basecrm/models/tag.rb
152
155
  - lib/basecrm/models/task.rb
153
156
  - lib/basecrm/models/user.rb
@@ -162,10 +165,13 @@ files:
162
165
  - lib/basecrm/services/pipelines_service.rb
163
166
  - lib/basecrm/services/sources_service.rb
164
167
  - lib/basecrm/services/stages_service.rb
168
+ - lib/basecrm/services/sync_service.rb
165
169
  - lib/basecrm/services/tags_service.rb
166
170
  - lib/basecrm/services/tasks_service.rb
167
171
  - lib/basecrm/services/users_service.rb
172
+ - lib/basecrm/sync.rb
168
173
  - lib/basecrm/version.rb
174
+ - spec/basecrm/sync_spec.rb
169
175
  - spec/factories/associated_contact.rb
170
176
  - spec/factories/contact.rb
171
177
  - spec/factories/deal.rb
@@ -175,6 +181,7 @@ files:
175
181
  - spec/factories/source.rb
176
182
  - spec/factories/tag.rb
177
183
  - spec/factories/task.rb
184
+ - spec/models/sync_meta_spec.rb
178
185
  - spec/services/accounts_service_spec.rb
179
186
  - spec/services/associated_contacts_service_spec.rb
180
187
  - spec/services/contacts_service_spec.rb
@@ -185,6 +192,7 @@ files:
185
192
  - spec/services/pipelines_service_spec.rb
186
193
  - spec/services/sources_service_spec.rb
187
194
  - spec/services/stages_service_spec.rb
195
+ - spec/services/sync_service_spec.rb
188
196
  - spec/services/tags_service_spec.rb
189
197
  - spec/services/tasks_service_spec.rb
190
198
  - spec/services/users_service_spec.rb
@@ -210,11 +218,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
210
218
  version: '0'
211
219
  requirements: []
212
220
  rubyforge_project:
213
- rubygems_version: 2.2.2
221
+ rubygems_version: 2.4.3
214
222
  signing_key:
215
223
  specification_version: 4
216
224
  summary: BaseCRM Official API V2 library client for ruby
217
225
  test_files:
226
+ - spec/basecrm/sync_spec.rb
218
227
  - spec/factories/associated_contact.rb
219
228
  - spec/factories/contact.rb
220
229
  - spec/factories/deal.rb
@@ -224,6 +233,7 @@ test_files:
224
233
  - spec/factories/source.rb
225
234
  - spec/factories/tag.rb
226
235
  - spec/factories/task.rb
236
+ - spec/models/sync_meta_spec.rb
227
237
  - spec/services/accounts_service_spec.rb
228
238
  - spec/services/associated_contacts_service_spec.rb
229
239
  - spec/services/contacts_service_spec.rb
@@ -234,6 +244,7 @@ test_files:
234
244
  - spec/services/pipelines_service_spec.rb
235
245
  - spec/services/sources_service_spec.rb
236
246
  - spec/services/stages_service_spec.rb
247
+ - spec/services/sync_service_spec.rb
237
248
  - spec/services/tags_service_spec.rb
238
249
  - spec/services/tasks_service_spec.rb
239
250
  - spec/services/users_service_spec.rb