syncer 0.0.1 → 0.0.4
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.
- data/lib/syncer.rb +1 -0
- data/lib/syncer/sync_record.rb +6 -0
- data/lib/syncer/syncable_record.rb +16 -2
- data/lib/syncer/syncer.rb +33 -4
- data/lib/syncer/version.rb +1 -1
- data/spec/support/active_record.rb +6 -0
- data/spec/syncable_record_spec.rb +47 -18
- data/spec/syncer_spec.rb +254 -130
- data/syncer.gemspec +2 -2
- metadata +5 -4
data/lib/syncer.rb
CHANGED
@@ -46,8 +46,10 @@ class SyncableRecord < ActiveRecord::Base
|
|
46
46
|
# if the key is of the form xxx_tmp_id
|
47
47
|
next unless /(?<foreign_key_prefix>.*)_tmp_id$/ =~ key
|
48
48
|
# then find the xxx class
|
49
|
+
# TODO assumption here is that model name are single words!!
|
49
50
|
next unless model_class = foreign_key_prefix.capitalize.to_class
|
50
51
|
# find the class model that matches the tmp_id
|
52
|
+
# TODO note that if it does not find it it will try to assign it and get a "Can't mass-assign protected attributes" error
|
51
53
|
next unless model = model_class.find_by_tmp_id(remote_record["#{foreign_key_prefix}_tmp_id"])
|
52
54
|
# save the actual model id
|
53
55
|
remote_record["#{foreign_key_prefix}_id"] = model.id
|
@@ -63,6 +65,8 @@ class SyncableRecord < ActiveRecord::Base
|
|
63
65
|
# TODO we do not currently check for id's per user
|
64
66
|
# TODO check both when creating and when accessing
|
65
67
|
# TODO we should at some point delete the tmp_ids
|
68
|
+
|
69
|
+
state_changed = false
|
66
70
|
|
67
71
|
remote_saved = remote_state['saved'] || []
|
68
72
|
remote_created = remote_state['created'] || []
|
@@ -110,6 +114,7 @@ class SyncableRecord < ActiveRecord::Base
|
|
110
114
|
if rec.persisted?
|
111
115
|
response = rec.to_rjson
|
112
116
|
created << response
|
117
|
+
state_changed = true
|
113
118
|
end
|
114
119
|
end
|
115
120
|
end
|
@@ -127,7 +132,10 @@ class SyncableRecord < ActiveRecord::Base
|
|
127
132
|
attrs = remote_record.dup
|
128
133
|
attrs.delete("id") # can't update id
|
129
134
|
attrs.delete("version") # can't update version
|
130
|
-
|
135
|
+
if rec.update_attributes(attrs)
|
136
|
+
updated << rec
|
137
|
+
state_changed = true
|
138
|
+
end
|
131
139
|
end
|
132
140
|
end
|
133
141
|
end
|
@@ -143,7 +151,10 @@ class SyncableRecord < ActiveRecord::Base
|
|
143
151
|
# destroy the record only if found and if it has not been updated on the server already
|
144
152
|
if rec && !rec.updated?(remote_record)
|
145
153
|
rec.destroy
|
146
|
-
|
154
|
+
if remote_record['id']
|
155
|
+
deleted << { :id => remote_record['id'] }
|
156
|
+
state_changed = true
|
157
|
+
end
|
147
158
|
end
|
148
159
|
end
|
149
160
|
end
|
@@ -152,6 +163,9 @@ class SyncableRecord < ActiveRecord::Base
|
|
152
163
|
response[:create] = created unless created.empty?
|
153
164
|
response[:update] = updated unless updated.empty?
|
154
165
|
response[:delete] = deleted unless deleted.empty?
|
166
|
+
|
167
|
+
# mark here if db state was actually changed requiring version increment
|
168
|
+
response[:state_changed] = true if state_changed
|
155
169
|
|
156
170
|
response
|
157
171
|
|
data/lib/syncer/syncer.rb
CHANGED
@@ -1,21 +1,50 @@
|
|
1
1
|
require 'xstring'
|
2
2
|
|
3
3
|
class Syncer
|
4
|
+
SYNC_VERSION_KEY = '_sync_version'
|
4
5
|
SYNC_ORDER_KEY = '_sync_order'
|
5
6
|
|
6
7
|
# request keys should be strings and not symbols
|
7
8
|
def self.sync(request)
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
|
10
|
+
# TODO probably check the model actually exists and responds to sync
|
11
|
+
# TODO We need to deal with security issues such as User associated models + same for relationships
|
12
|
+
|
13
|
+
state_changed = false
|
14
|
+
|
15
|
+
response = {}
|
16
|
+
all_sync_request = request.to_rjson
|
17
|
+
sync_order = all_sync_request[SYNC_ORDER_KEY]
|
12
18
|
# TODO if nil raise an exception
|
13
19
|
sync_order.each do |model|
|
14
20
|
model_sync_data = all_sync_request[model]
|
15
21
|
next unless model_sync_data # it is ok not to have data for each model in the order
|
16
22
|
sync_response = model.to_class.sync(model_sync_data)
|
17
23
|
response[model] = sync_response
|
24
|
+
state_changed = true if sync_response[:state_changed]
|
18
25
|
end
|
26
|
+
|
27
|
+
# handle version
|
28
|
+
increment_sync_state_version if state_changed
|
29
|
+
response[SYNC_VERSION_KEY] = sync_state.version
|
30
|
+
|
19
31
|
response.to_rjson
|
20
32
|
end
|
33
|
+
|
34
|
+
def self.version
|
35
|
+
{ SYNC_VERSION_KEY => sync_state.version }
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def self.sync_state
|
41
|
+
# TODO this currently supports only one sync account - add multi user support
|
42
|
+
|
43
|
+
# return the first sync and create it if doesn't exit
|
44
|
+
SyncRecord.first || SyncRecord.create
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.increment_sync_state_version
|
48
|
+
sync_state.increment!(:version)
|
49
|
+
end
|
21
50
|
end
|
data/lib/syncer/version.rb
CHANGED
@@ -42,6 +42,12 @@ ActiveRecord::Migration.create_table "trades", :force => true do |t|
|
|
42
42
|
t.datetime "updated_at"
|
43
43
|
end
|
44
44
|
|
45
|
+
ActiveRecord::Migration.create_table "sync_records", :force => true do |t|
|
46
|
+
t.integer "version", :default => 0
|
47
|
+
t.datetime "created_at"
|
48
|
+
t.datetime "updated_at"
|
49
|
+
end
|
50
|
+
|
45
51
|
#setup transactions
|
46
52
|
RSpec.configure do |config|
|
47
53
|
config.around do |example|
|
@@ -18,7 +18,7 @@ describe "SyncableRecord" do
|
|
18
18
|
@stock2 = { :market => 'us', :ticker => 'AAPL', :name => 'Apple', :tmp_id => '13' }
|
19
19
|
end
|
20
20
|
|
21
|
-
it "it should create the records
|
21
|
+
it "it should create the records, return create response for them, and mark state changed" do
|
22
22
|
remote_state = { 'created' => [@stock1, @stock2] }
|
23
23
|
|
24
24
|
response = Stock.sync(HashWithIndifferentAccess.new(remote_state))
|
@@ -40,6 +40,8 @@ describe "SyncableRecord" do
|
|
40
40
|
response[:create][1]['tmp_id'].should == @stock2[:tmp_id]
|
41
41
|
response[:delete].should be_nil
|
42
42
|
response[:update].should be_nil
|
43
|
+
|
44
|
+
response[:state_changed].should be_true
|
43
45
|
end
|
44
46
|
end
|
45
47
|
end
|
@@ -52,13 +54,15 @@ describe "SyncableRecord" do
|
|
52
54
|
|
53
55
|
describe "and no remote records" do
|
54
56
|
|
55
|
-
it "it should return create for server records" do
|
57
|
+
it "it should return create for server records, and no state change" do
|
56
58
|
response = Stock.sync({})
|
57
59
|
response[:create].length.should == 2
|
58
60
|
response[:create][0].should == @stock1
|
59
61
|
response[:create][1].should == @stock2
|
60
62
|
response[:delete].should be_nil
|
61
63
|
response[:update].should be_nil
|
64
|
+
|
65
|
+
response[:state_changed].should be_nil
|
62
66
|
end
|
63
67
|
end
|
64
68
|
|
@@ -73,13 +77,15 @@ describe "SyncableRecord" do
|
|
73
77
|
|
74
78
|
describe "one of the remote records is deleted" do
|
75
79
|
|
76
|
-
it "it should delete the deleted record
|
80
|
+
it "it should delete the deleted record,keep the saved record, and mark state changed" do
|
77
81
|
remote_state = { 'saved' => [@stock1], 'deleted' => [@stock2] }
|
78
82
|
response = Stock.sync(remote_state)
|
79
83
|
response[:delete].length.should == 1
|
80
84
|
response[:delete][0][:id].should == @stock2.id
|
81
85
|
response[:create].should be_nil
|
82
86
|
response[:update].should be_nil
|
87
|
+
|
88
|
+
response[:state_changed].should be_true
|
83
89
|
end
|
84
90
|
end
|
85
91
|
end
|
@@ -92,7 +98,7 @@ describe "SyncableRecord" do
|
|
92
98
|
|
93
99
|
describe "and some saved records on remote" do
|
94
100
|
|
95
|
-
it "it should return create for new records" do
|
101
|
+
it "it should return create for new records and no state change" do
|
96
102
|
remote_state = { 'saved' => [@stock1] }
|
97
103
|
|
98
104
|
response = Stock.sync(remote_state)
|
@@ -100,6 +106,8 @@ describe "SyncableRecord" do
|
|
100
106
|
response[:create][0].should == @new_stock
|
101
107
|
response[:delete].should be_nil
|
102
108
|
response[:update].should be_nil
|
109
|
+
|
110
|
+
response[:state_changed].should be_nil
|
103
111
|
end
|
104
112
|
end
|
105
113
|
|
@@ -109,7 +117,7 @@ describe "SyncableRecord" do
|
|
109
117
|
@stock4 = { :market => 'us', :ticker => 'AAPL', :name => 'Apple', :tmp_id => '13' }
|
110
118
|
end
|
111
119
|
|
112
|
-
it "it should create the new and created records and return create response for them" do
|
120
|
+
it "it should create the new and created records and return create response for them and mark state changed" do
|
113
121
|
remote_state = { 'saved' => [@stock1], 'created' => [@stock3, @stock4] }
|
114
122
|
|
115
123
|
response = Stock.sync(HashWithIndifferentAccess.new(remote_state))
|
@@ -135,6 +143,8 @@ describe "SyncableRecord" do
|
|
135
143
|
response[:create][2]['tmp_id'].should == @stock4[:tmp_id]
|
136
144
|
response[:delete].should be_nil
|
137
145
|
response[:update].should be_nil
|
146
|
+
|
147
|
+
response[:state_changed].should be_true
|
138
148
|
end
|
139
149
|
end
|
140
150
|
end
|
@@ -150,7 +160,7 @@ describe "SyncableRecord" do
|
|
150
160
|
|
151
161
|
describe "and only saved remote records" do
|
152
162
|
|
153
|
-
it "it should create the new, update the updated and delete the deleted" do
|
163
|
+
it "it should create the new, update the updated and delete the deleted and no state change" do
|
154
164
|
remote_state = { 'saved' => [@stock1, @stock2, @updated_stock.to_rjson, @deleted_stock] }
|
155
165
|
|
156
166
|
# do server changes: update a stock, delete a stock
|
@@ -171,13 +181,15 @@ describe "SyncableRecord" do
|
|
171
181
|
@updated_stock[:version].should == 1
|
172
182
|
@updated_stock[:name].should == "Apple Computers"
|
173
183
|
response[:delete].length.should == 1
|
174
|
-
response[:delete][0][:id].should == @deleted_stock.id
|
184
|
+
response[:delete][0][:id].should == @deleted_stock.id
|
185
|
+
|
186
|
+
response[:state_changed].should be_nil
|
175
187
|
end
|
176
188
|
end
|
177
189
|
|
178
190
|
describe "and saved & deleted remote records" do
|
179
191
|
|
180
|
-
it "it should create the new, delete the deleted and update the updated" do
|
192
|
+
it "it should create the new, delete the deleted and update the updated, and mark state changed" do
|
181
193
|
remote_state = { 'saved' => [@stock2, @updated_stock.to_rjson, @deleted_stock], 'deleted' => [@stock1] }
|
182
194
|
|
183
195
|
# do server changes: update a stock, delete a stock
|
@@ -199,12 +211,14 @@ describe "SyncableRecord" do
|
|
199
211
|
response[:delete].length.should == 2
|
200
212
|
response[:delete][0][:id].should == @deleted_stock.id
|
201
213
|
response[:delete][1][:id].should == @stock1.id
|
214
|
+
|
215
|
+
response[:state_changed].should be_true
|
202
216
|
end
|
203
217
|
end
|
204
218
|
|
205
219
|
describe "and saved & updated remote records" do
|
206
220
|
|
207
|
-
it "it should create the new, delete the deleted and update the updated" do
|
221
|
+
it "it should create the new, delete the deleted and update the updated and mark state changed" do
|
208
222
|
remote_update = @stock1
|
209
223
|
remote_update['name'] = 'This is an updated name'
|
210
224
|
remote_state = { 'saved' => [@stock1, @stock2, @updated_stock, @deleted_stock], 'updated' => [remote_update] }
|
@@ -233,13 +247,15 @@ describe "SyncableRecord" do
|
|
233
247
|
response[:update][1]['name'].should == remote_update['name']
|
234
248
|
response[:update][1]['version'].should == remote_update['version']
|
235
249
|
response[:delete].length.should == 1
|
236
|
-
response[:delete][0][:id].should == @deleted_stock[:id]
|
250
|
+
response[:delete][0][:id].should == @deleted_stock[:id]
|
251
|
+
|
252
|
+
response[:state_changed].should be_true
|
237
253
|
end
|
238
254
|
end
|
239
255
|
|
240
256
|
describe "and saved, created, updated, and deleted remote records" do
|
241
257
|
|
242
|
-
it "it should create the new, delete the deleted and update the updated" do
|
258
|
+
it "it should create the new, delete the deleted and update the updated and mark state changed" do
|
243
259
|
remote_update = @stock1
|
244
260
|
remote_update['name'] = 'This is an updated name'
|
245
261
|
remote_create = { :market => 'il', :ticker => 'Razio', :name => 'Razio Oil', :tmp_id => '19' }
|
@@ -276,7 +292,9 @@ describe "SyncableRecord" do
|
|
276
292
|
response[:update][1]['version'].should == remote_update['version']
|
277
293
|
response[:delete].length.should == 2
|
278
294
|
response[:delete][0][:id].should == @deleted_stock.id
|
279
|
-
response[:delete][1][:id].should == @stock2.id
|
295
|
+
response[:delete][1][:id].should == @stock2.id
|
296
|
+
|
297
|
+
response[:state_changed].should be_true
|
280
298
|
end
|
281
299
|
end
|
282
300
|
end
|
@@ -286,7 +304,7 @@ describe "SyncableRecord" do
|
|
286
304
|
@stock = FactoryGirl.create(:stock, :market => 'us', :ticker => 'IBM', :name => 'International Business Machines')
|
287
305
|
end
|
288
306
|
|
289
|
-
describe "between server deleted record and remote deleted record" do
|
307
|
+
describe "between server deleted record and remote deleted record and no state change" do
|
290
308
|
|
291
309
|
it "it should delete the record" do
|
292
310
|
remote_state = { 'deleted' => [@stock.to_rjson] }
|
@@ -305,12 +323,14 @@ describe "SyncableRecord" do
|
|
305
323
|
response[:update].should be_nil
|
306
324
|
response[:delete].length.should == 1
|
307
325
|
response[:delete][0][:id].should == @stock.id
|
326
|
+
|
327
|
+
response[:state_changed].should be_nil
|
308
328
|
end
|
309
329
|
end
|
310
330
|
|
311
331
|
describe "between server deleted record and remote updated record" do
|
312
332
|
|
313
|
-
it "it should delete the record" do
|
333
|
+
it "it should delete the record and no state change" do
|
314
334
|
remote_update = @stock.to_rjson
|
315
335
|
remote_update['name'] = 'Tabulation company'
|
316
336
|
|
@@ -330,12 +350,14 @@ describe "SyncableRecord" do
|
|
330
350
|
response[:update].should be_nil
|
331
351
|
response[:delete].length.should == 1
|
332
352
|
response[:delete][0][:id].should == @stock.id
|
353
|
+
|
354
|
+
response[:state_changed].should be_nil
|
333
355
|
end
|
334
356
|
end
|
335
357
|
|
336
358
|
describe "between server updated record and remote deleted record" do
|
337
359
|
|
338
|
-
it "it should create the record as if new" do
|
360
|
+
it "it should create the record as if new and no state change" do
|
339
361
|
remote_state = { 'deleted' => [@stock.to_rjson] }
|
340
362
|
|
341
363
|
# do server changes: update the stock
|
@@ -353,13 +375,15 @@ describe "SyncableRecord" do
|
|
353
375
|
response[:create][0]['id'].should == @stock[:id]
|
354
376
|
response[:create][0]['name'].should == @stock[:name]
|
355
377
|
response[:update].should be_nil
|
356
|
-
response[:delete].should be_nil
|
378
|
+
response[:delete].should be_nil
|
379
|
+
|
380
|
+
response[:state_changed].should be_nil
|
357
381
|
end
|
358
382
|
end
|
359
383
|
|
360
384
|
describe "between server updated record and remote updated record" do
|
361
385
|
|
362
|
-
it "it should update the record with the server version" do
|
386
|
+
it "it should update the record with the server version and no state change" do
|
363
387
|
remote_update = @stock.to_rjson
|
364
388
|
remote_update['name'] = 'Tabulation company'
|
365
389
|
|
@@ -382,7 +406,9 @@ describe "SyncableRecord" do
|
|
382
406
|
response[:update].length.should == 1
|
383
407
|
response[:update][0]['id'].should == @stock[:id]
|
384
408
|
response[:update][0]['name'].should == @stock[:name]
|
385
|
-
response[:delete].should be_nil
|
409
|
+
response[:delete].should be_nil
|
410
|
+
|
411
|
+
response[:state_changed].should be_nil
|
386
412
|
end
|
387
413
|
end
|
388
414
|
end
|
@@ -395,6 +421,8 @@ describe "SyncableRecord" do
|
|
395
421
|
response = Stock.sync(123)
|
396
422
|
|
397
423
|
response.should == { :error => "can't convert String into Integer" }
|
424
|
+
|
425
|
+
response[:state_changed].should be_nil
|
398
426
|
end
|
399
427
|
end
|
400
428
|
|
@@ -440,6 +468,7 @@ describe "SyncableRecord" do
|
|
440
468
|
|
441
469
|
response = Stock.sync(remote_state)
|
442
470
|
response[:error].should_not be_empty
|
471
|
+
response[:state_changed].should be_nil
|
443
472
|
end
|
444
473
|
end
|
445
474
|
end
|
data/spec/syncer_spec.rb
CHANGED
@@ -1,142 +1,266 @@
|
|
1
1
|
require 'spec_helper.rb'
|
2
2
|
|
3
|
-
describe "
|
3
|
+
describe "Syncer" do
|
4
4
|
|
5
|
+
let(:sync_order_key) { Syncer::SYNC_ORDER_KEY }
|
6
|
+
let(:sync_version_key) { Syncer::SYNC_VERSION_KEY }
|
7
|
+
|
5
8
|
let(:stock) { FactoryGirl.create(:stock) }
|
6
|
-
let(:
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
9
|
+
let(:sync_order) { [:Account, :Stock, :Trade] }
|
10
|
+
|
11
|
+
describe "sync" do
|
12
|
+
|
13
|
+
it "should sync a single Model" do
|
14
|
+
updated_stock = { :name => 'Teva Pharma', :id => stock.id, :version => stock.version }
|
15
|
+
stocks_sync_data = { :updated => [ updated_stock ] }
|
16
|
+
all_sync_data = { sync_order_key => sync_order, :Stock => stocks_sync_data }
|
17
|
+
|
18
|
+
result = Syncer.sync(all_sync_data)
|
19
|
+
|
20
|
+
stock_results = result['Stock']
|
21
|
+
stock_results.should_not be_nil
|
22
|
+
update_stock = stock_results['update'][0]
|
23
|
+
update_stock['id'].should == stock.id
|
24
|
+
update_stock['version'].should == stock.version + 1
|
25
|
+
update_stock['market'].should == stock.market
|
26
|
+
update_stock['ticker'].should == stock.ticker
|
27
|
+
update_stock['name'].should == updated_stock[:name]
|
28
|
+
end
|
25
29
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
30
|
+
it "should sync multiple independent Models" do
|
31
|
+
updated_stock = { :name => 'Teva Pharma', :id => stock.id, :version => stock.version }
|
32
|
+
stocks_sync_data = { :updated => [ updated_stock ] }
|
33
|
+
created_account = FactoryGirl.attributes_for(:account)
|
34
|
+
created_account['tmp_id'] = "12345abcde"
|
35
|
+
accounts_sync_data = { :created => [ created_account ] }
|
36
|
+
all_sync_data = { sync_order_key => sync_order, :Stock => stocks_sync_data, :Account => accounts_sync_data }
|
33
37
|
|
34
|
-
|
38
|
+
result = Syncer.sync(all_sync_data)
|
35
39
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
40
|
+
stock_results = result['Stock']
|
41
|
+
stock_results.should_not be_nil
|
42
|
+
update_stock = stock_results['update'][0]
|
43
|
+
update_stock['id'].should == stock.id
|
44
|
+
update_stock['version'].should == stock.version + 1
|
45
|
+
update_stock['market'].should == stock.market
|
46
|
+
update_stock['ticker'].should == stock.ticker
|
47
|
+
update_stock['name'].should == updated_stock[:name]
|
48
|
+
|
49
|
+
account_results = result['Account']
|
50
|
+
account_results.should_not be_nil
|
51
|
+
create_account = account_results['create'][0]
|
52
|
+
create_account['id'].should_not be_nil
|
53
|
+
create_account['tmp_id'].should == "12345abcde"
|
54
|
+
create_account['version'].should == 0
|
55
|
+
create_account['name'].should == created_account[:name]
|
56
|
+
end
|
53
57
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
58
|
+
it "should sync stock with updated account" do
|
59
|
+
created_account = FactoryGirl.attributes_for(:account)
|
60
|
+
created_account['tmp_id'] = "12345abcde"
|
61
|
+
accounts_sync_data = { :created => [ created_account ] }
|
62
|
+
updated_stock = stock.to_rjson
|
63
|
+
updated_stock['account_tmp_id'] = created_account['tmp_id']
|
64
|
+
stocks_sync_data = { :updated => [ updated_stock ] }
|
65
|
+
all_sync_data = { sync_order_key => sync_order, :Stock => stocks_sync_data, :Account => accounts_sync_data }
|
66
|
+
|
67
|
+
result = Syncer.sync(all_sync_data)
|
68
|
+
|
69
|
+
account_results = result['Account']
|
70
|
+
account_results.should_not be_nil
|
71
|
+
create_account = account_results['create'][0]
|
72
|
+
create_account['id'].should_not be_nil
|
73
|
+
create_account['tmp_id'].should == "12345abcde"
|
74
|
+
create_account['version'].should == 0
|
75
|
+
create_account['name'].should == created_account[:name]
|
76
|
+
|
77
|
+
stock_results = result['Stock']
|
78
|
+
stock_results.should_not be_nil
|
79
|
+
update_stock = stock_results['update'][0]
|
80
|
+
update_stock['account_id'].should == create_account['id']
|
81
|
+
update_stock['id'].should == stock.id
|
82
|
+
update_stock['version'].should == stock.version + 1
|
83
|
+
update_stock['market'].should == stock.market
|
84
|
+
update_stock['ticker'].should == stock.ticker
|
85
|
+
update_stock['name'].should == stock.name
|
86
|
+
end
|
87
|
+
|
88
|
+
it "should sync dependent Models with new model where dependency exists" do
|
89
|
+
updated_stock = { :name => 'Teva Pharma', :id => stock.id, :version => stock.version }
|
90
|
+
stocks_sync_data = { :updated => [ updated_stock ] }
|
91
|
+
created_trade = FactoryGirl.attributes_for(:trade, :stock_id => stock.id)
|
92
|
+
created_trade['tmp_id'] = "12345abcde"
|
93
|
+
trades_sync_data = { :created => [ created_trade ] }
|
94
|
+
all_sync_data = { sync_order_key => sync_order, :Trade => trades_sync_data, :Stock => stocks_sync_data }
|
95
|
+
|
96
|
+
result = Syncer.sync(all_sync_data)
|
63
97
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
98
|
+
stock_results = result['Stock']
|
99
|
+
update_stock = stock_results['update'][0]
|
100
|
+
update_stock['id'].should == stock.id
|
101
|
+
update_stock['version'].should == stock.version + 1
|
102
|
+
update_stock['market'].should == stock.market
|
103
|
+
update_stock['ticker'].should == stock.ticker
|
104
|
+
update_stock['name'].should == updated_stock[:name]
|
105
|
+
|
106
|
+
trade_results = result['Trade']
|
107
|
+
create_trade = trade_results['create'][0]
|
108
|
+
create_trade['id'].should_not be_nil
|
109
|
+
create_trade['tmp_id'].should == "12345abcde"
|
110
|
+
create_trade['version'].should == 0
|
111
|
+
create_trade['stock_id'].should == stock.id
|
112
|
+
create_trade['amount'].should == created_trade[:amount]
|
113
|
+
create_trade['buy'].should == created_trade[:buy]
|
114
|
+
# TODO maybe also test date that seems to return different ...
|
115
|
+
end
|
82
116
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
117
|
+
it "should sync dependent Models when both models are new" do
|
118
|
+
created_stock = FactoryGirl.attributes_for(:stock, :name => 'Teva Pharma')
|
119
|
+
created_stock['tmp_id'] = "abcde12345"
|
120
|
+
stocks_sync_data = { :saved => [ stock ], :created => [ created_stock ] }
|
121
|
+
created_trade = FactoryGirl.attributes_for(:trade)
|
122
|
+
created_trade['tmp_id'] = "12345abcde"
|
123
|
+
created_trade['stock_tmp_id'] = "abcde12345"
|
124
|
+
trades_sync_data = { :created => [ created_trade ] }
|
125
|
+
all_sync_data = { sync_order_key => sync_order, :Trade => trades_sync_data, :Stock => stocks_sync_data }
|
126
|
+
|
127
|
+
result = Syncer.sync(all_sync_data)
|
128
|
+
|
129
|
+
stock_results = result['Stock']
|
130
|
+
create_stock = stock_results['create'][0]
|
131
|
+
create_stock['id'].should_not be_nil
|
132
|
+
create_stock['tmp_id'].should == "abcde12345"
|
133
|
+
create_stock['version'].should == 0
|
134
|
+
create_stock['market'].should == created_stock[:market]
|
135
|
+
create_stock['ticker'].should == created_stock[:ticker]
|
136
|
+
create_stock['name'].should == created_stock[:name]
|
137
|
+
|
138
|
+
trade_results = result['Trade']
|
139
|
+
create_trade = trade_results['create'][0]
|
140
|
+
create_trade['id'].should_not be_nil
|
141
|
+
create_trade['tmp_id'].should == "12345abcde"
|
142
|
+
create_trade['stock_id'].should == create_stock['id']
|
143
|
+
create_trade['version'].should == 0
|
144
|
+
create_trade['amount'].should == created_trade[:amount]
|
145
|
+
create_trade['buy'].should == created_trade[:buy]
|
146
|
+
# TODO maybe also test date that seems to return different ...
|
147
|
+
end
|
148
|
+
|
149
|
+
it "should sync dependent Models with new model where dependent exists and is updated to point to new Model" do
|
150
|
+
updated_stock = { :name => 'Teva Pharma', :id => stock.id, :version => stock.version, :account_tmp_id => "12345abcde" }
|
151
|
+
stocks_sync_data = { :updated => [ updated_stock ] }
|
152
|
+
created_account = FactoryGirl.attributes_for(:account)
|
153
|
+
created_account['tmp_id'] = "12345abcde"
|
154
|
+
accounts_sync_data = { :created => [ created_account ] }
|
155
|
+
all_sync_data = { sync_order_key => sync_order, :Account => accounts_sync_data, :Stock => stocks_sync_data }
|
156
|
+
|
157
|
+
result = Syncer.sync(all_sync_data)
|
158
|
+
|
159
|
+
account_results = result['Account']
|
160
|
+
account_results.should_not be_nil
|
161
|
+
create_account = account_results['create'][0]
|
162
|
+
create_account['id'].should_not be_nil
|
163
|
+
create_account['tmp_id'].should == "12345abcde"
|
164
|
+
create_account['version'].should == 0
|
165
|
+
create_account['name'].should == created_account[:name]
|
132
166
|
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
167
|
+
stock_results = result['Stock']
|
168
|
+
update_stock = stock_results['update'][0]
|
169
|
+
update_stock['id'].should == stock.id
|
170
|
+
update_stock['version'].should == stock.version + 1
|
171
|
+
update_stock['market'].should == stock.market
|
172
|
+
update_stock['ticker'].should == stock.ticker
|
173
|
+
update_stock['name'].should == updated_stock[:name]
|
174
|
+
update_stock['account_id'].should == create_account['id']
|
175
|
+
end
|
176
|
+
|
177
|
+
it "returns a database state version number" do
|
178
|
+
updated_stock = { :name => 'Teva Pharma', :id => stock.id, :version => stock.version }
|
179
|
+
stocks_sync_data = { :updated => [ updated_stock ] }
|
180
|
+
all_sync_data = { sync_order_key => sync_order, :Stock => stocks_sync_data }
|
181
|
+
|
182
|
+
result = Syncer.sync(all_sync_data)
|
183
|
+
|
184
|
+
result[sync_version_key].should_not be_nil
|
185
|
+
result[sync_version_key].should be_kind_of(Integer)
|
186
|
+
end
|
187
|
+
|
188
|
+
it "increments the database state version on sync" do
|
189
|
+
updated_stock = { :name => 'Teva Pharma', :id => stock.id, :version => stock.version }
|
190
|
+
stocks_sync_data = { :updated => [ updated_stock ] }
|
191
|
+
all_sync_data = { sync_order_key => sync_order, :Stock => stocks_sync_data }
|
192
|
+
|
193
|
+
result = Syncer.sync(all_sync_data)
|
194
|
+
|
195
|
+
db_version = result[sync_version_key]
|
196
|
+
|
197
|
+
updated_stock = { :name => 'Teva Pharma 22', :id => stock.id, :version => stock.version + 1 }
|
198
|
+
stocks_sync_data = { :updated => [ updated_stock ] }
|
199
|
+
all_sync_data = { sync_order_key => sync_order, :Stock => stocks_sync_data }
|
200
|
+
|
201
|
+
result = Syncer.sync(all_sync_data)
|
202
|
+
|
203
|
+
result[sync_version_key].should == db_version + 1
|
204
|
+
end
|
205
|
+
|
206
|
+
it "does NOT increment the database state version if nothing is synced" do
|
207
|
+
updated_stock = { :name => 'Teva Pharma', :id => stock.id, :version => stock.version }
|
208
|
+
stocks_sync_data = { :updated => [ updated_stock ] }
|
209
|
+
all_sync_data = { sync_order_key => sync_order, :Stock => stocks_sync_data }
|
210
|
+
|
211
|
+
result = Syncer.sync(all_sync_data)
|
212
|
+
|
213
|
+
db_version = result[sync_version_key]
|
214
|
+
|
215
|
+
# empty sync
|
216
|
+
all_sync_data = { sync_order_key => sync_order }
|
217
|
+
|
218
|
+
result = Syncer.sync(all_sync_data)
|
219
|
+
|
220
|
+
result[sync_version_key].should == db_version
|
221
|
+
end
|
141
222
|
end
|
142
|
-
|
223
|
+
|
224
|
+
describe "version" do
|
225
|
+
|
226
|
+
it "returns the database state version" do
|
227
|
+
result = Syncer.version
|
228
|
+
|
229
|
+
result[sync_version_key].should_not be_nil
|
230
|
+
result[sync_version_key].should be_kind_of(Integer)
|
231
|
+
end
|
232
|
+
|
233
|
+
it "returns the same version as the last sync" do
|
234
|
+
updated_stock = { :name => 'Teva Pharma', :id => stock.id, :version => stock.version }
|
235
|
+
stocks_sync_data = { :updated => [ updated_stock ] }
|
236
|
+
all_sync_data = { sync_order_key => sync_order, :Stock => stocks_sync_data }
|
237
|
+
|
238
|
+
result = Syncer.sync(all_sync_data)
|
239
|
+
|
240
|
+
db_version = result[sync_version_key]
|
241
|
+
|
242
|
+
result = Syncer.version
|
243
|
+
|
244
|
+
result[sync_version_key].should == db_version
|
245
|
+
end
|
246
|
+
|
247
|
+
it "returns an incremented version after a sync" do
|
248
|
+
result = Syncer.version
|
249
|
+
|
250
|
+
db_version = result[sync_version_key]
|
251
|
+
|
252
|
+
updated_stock = { :name => 'Teva Pharma', :id => stock.id, :version => stock.version }
|
253
|
+
stocks_sync_data = { :updated => [ updated_stock ] }
|
254
|
+
all_sync_data = { sync_order_key => sync_order, :Stock => stocks_sync_data }
|
255
|
+
|
256
|
+
result = Syncer.sync(all_sync_data)
|
257
|
+
|
258
|
+
result = Syncer.version
|
259
|
+
|
260
|
+
result[sync_version_key].should == db_version + 1
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
|
266
|
+
|
data/syncer.gemspec
CHANGED
@@ -4,8 +4,8 @@ require File.expand_path('../lib/syncer/version', __FILE__)
|
|
4
4
|
Gem::Specification.new do |gem|
|
5
5
|
gem.authors = ["Harry Hornreich"]
|
6
6
|
gem.email = ["harryhorn@gmail.com"]
|
7
|
-
gem.description = %q{ActiveRecord syncer}
|
8
|
-
gem.summary = %q{ActiveRecord syncer}
|
7
|
+
gem.description = %q{ActiveRecord syncer - prerelease}
|
8
|
+
gem.summary = %q{ActiveRecord syncer - prerelease}
|
9
9
|
gem.homepage = ""
|
10
10
|
|
11
11
|
gem.add_runtime_dependency 'activerecord', '>= 2.0'
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: syncer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.4
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2013-06-17 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activerecord
|
@@ -91,7 +91,7 @@ dependencies:
|
|
91
91
|
- - ~>
|
92
92
|
- !ruby/object:Gem::Version
|
93
93
|
version: 3.1.0
|
94
|
-
description: ActiveRecord syncer
|
94
|
+
description: ActiveRecord syncer - prerelease
|
95
95
|
email:
|
96
96
|
- harryhorn@gmail.com
|
97
97
|
executables: []
|
@@ -106,6 +106,7 @@ files:
|
|
106
106
|
- README.md
|
107
107
|
- Rakefile
|
108
108
|
- lib/syncer.rb
|
109
|
+
- lib/syncer/sync_record.rb
|
109
110
|
- lib/syncer/syncable_record.rb
|
110
111
|
- lib/syncer/syncer.rb
|
111
112
|
- lib/syncer/version.rb
|
@@ -141,7 +142,7 @@ rubyforge_project:
|
|
141
142
|
rubygems_version: 1.8.21
|
142
143
|
signing_key:
|
143
144
|
specification_version: 3
|
144
|
-
summary: ActiveRecord syncer
|
145
|
+
summary: ActiveRecord syncer - prerelease
|
145
146
|
test_files:
|
146
147
|
- spec/spec_helper.rb
|
147
148
|
- spec/support/account.rb
|