basecrm 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 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