remotable 0.2.4 → 0.2.5.beta1

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: e84548b3d422a45df52d3a730cbe4d41d78c954e
4
- data.tar.gz: 4734d71d361ec0ca5f9634a660f53a1375ad5670
3
+ metadata.gz: dced86b6d1612537910a489e785e2dbb9b280018
4
+ data.tar.gz: db13ecad5bf99f21719f5e03ed4057fce8ed14f1
5
5
  SHA512:
6
- metadata.gz: 1d89d1ffb5585fc6f9219460f901c61e9c3512260272daa87fa15d3c68a0c3bf914bbf921a23d6f62def17d04ff0186583bebff9a6d20cda3cc3bc0cc5a1d451
7
- data.tar.gz: c4da1216f5058cc9d7b4a1b265601edc41f44769efdf05950fa2c8cc0f40020d2c1895aa7b306e6176095fa7679bff799c3e8b35060033cde1a3460f013d8ae5
6
+ metadata.gz: 9fcb24667b7b4914f7d64efd18a7e27f8906e22adbc17c14ca2191d05a53f31d3e25a9d9fb58dbd244d47fad34bd63a9b7908d1526d871271dd980664a7bddbb
7
+ data.tar.gz: 189ac92190ec9add70eb40d364d434b7706dba41b49459e276500db31db14f99b15f63cd1270d5ca33aebf9e2bf8399bdf32731c445620634c33c45f6b17d1df
data/Gemfile.lock CHANGED
@@ -9,104 +9,105 @@ PATH
9
9
  GEM
10
10
  remote: http://rubygems.org/
11
11
  specs:
12
- actionmailer (3.2.13)
13
- actionpack (= 3.2.13)
14
- mail (~> 2.5.3)
15
- actionpack (3.2.13)
16
- activemodel (= 3.2.13)
17
- activesupport (= 3.2.13)
18
- builder (~> 3.0.0)
12
+ actionmailer (4.0.2)
13
+ actionpack (= 4.0.2)
14
+ mail (~> 2.5.4)
15
+ actionpack (4.0.2)
16
+ activesupport (= 4.0.2)
17
+ builder (~> 3.1.0)
19
18
  erubis (~> 2.7.0)
20
- journey (~> 1.0.4)
21
- rack (~> 1.4.5)
22
- rack-cache (~> 1.2)
23
- rack-test (~> 0.6.1)
24
- sprockets (~> 2.2.1)
25
- active_resource_simulator (0.0.1)
19
+ rack (~> 1.5.2)
20
+ rack-test (~> 0.6.2)
21
+ active_resource_simulator (0.0.2)
26
22
  activeresource
27
- activemodel (3.2.13)
28
- activesupport (= 3.2.13)
29
- builder (~> 3.0.0)
30
- activerecord (3.2.13)
31
- activemodel (= 3.2.13)
32
- activesupport (= 3.2.13)
33
- arel (~> 3.0.2)
34
- tzinfo (~> 0.3.29)
35
- activeresource (3.2.13)
36
- activemodel (= 3.2.13)
37
- activesupport (= 3.2.13)
38
- activesupport (3.2.13)
39
- i18n (= 0.6.1)
40
- multi_json (~> 1.0)
23
+ activemodel (4.0.2)
24
+ activesupport (= 4.0.2)
25
+ builder (~> 3.1.0)
26
+ activerecord (4.0.2)
27
+ activemodel (= 4.0.2)
28
+ activerecord-deprecated_finders (~> 1.0.2)
29
+ activesupport (= 4.0.2)
30
+ arel (~> 4.0.0)
31
+ activerecord-deprecated_finders (1.0.3)
32
+ activeresource (4.0.0)
33
+ activemodel (~> 4.0)
34
+ activesupport (~> 4.0)
35
+ rails-observers (~> 0.1.1)
36
+ activesupport (4.0.2)
37
+ i18n (~> 0.6, >= 0.6.4)
38
+ minitest (~> 4.2)
39
+ multi_json (~> 1.3)
40
+ thread_safe (~> 0.1)
41
+ tzinfo (~> 0.3.37)
41
42
  ansi (1.4.3)
42
- arel (3.0.2)
43
- builder (3.0.4)
44
- coderay (1.0.8)
45
- database_cleaner (0.6.7)
43
+ arel (4.0.1)
44
+ atomic (1.1.14)
45
+ builder (3.1.4)
46
+ coderay (1.1.0)
47
+ database_cleaner (1.2.0)
48
+ docile (1.1.2)
46
49
  erubis (2.7.0)
47
- factory_girl (2.0.4)
48
- hike (1.2.1)
49
- i18n (0.6.1)
50
- journey (1.0.4)
51
- json (1.7.7)
52
- mail (2.5.3)
53
- i18n (>= 0.4.0)
50
+ factory_girl (2.0.5)
51
+ hike (1.2.3)
52
+ i18n (0.6.9)
53
+ mail (2.5.4)
54
54
  mime-types (~> 1.16)
55
55
  treetop (~> 1.4.8)
56
- method_source (0.8.1)
57
- mime-types (1.21)
58
- minitest (2.10.0)
59
- multi_json (1.0.3)
56
+ method_source (0.8.2)
57
+ mime-types (1.25.1)
58
+ minitest (4.7.5)
59
+ multi_json (1.8.4)
60
60
  polyglot (0.3.3)
61
- pry (0.9.10)
62
- coderay (~> 1.0.5)
61
+ pry (0.9.12.4)
62
+ coderay (~> 1.0)
63
63
  method_source (~> 0.8)
64
- slop (~> 3.3.1)
65
- rack (1.4.5)
66
- rack-cache (1.2)
67
- rack (>= 0.4)
68
- rack-ssl (1.3.3)
69
- rack
64
+ slop (~> 3.4)
65
+ rack (1.5.2)
70
66
  rack-test (0.6.2)
71
67
  rack (>= 1.0)
72
- rails (3.2.13)
73
- actionmailer (= 3.2.13)
74
- actionpack (= 3.2.13)
75
- activerecord (= 3.2.13)
76
- activeresource (= 3.2.13)
77
- activesupport (= 3.2.13)
78
- bundler (~> 1.0)
79
- railties (= 3.2.13)
80
- railties (3.2.13)
81
- actionpack (= 3.2.13)
82
- activesupport (= 3.2.13)
83
- rack-ssl (~> 1.3.2)
68
+ rails (4.0.2)
69
+ actionmailer (= 4.0.2)
70
+ actionpack (= 4.0.2)
71
+ activerecord (= 4.0.2)
72
+ activesupport (= 4.0.2)
73
+ bundler (>= 1.3.0, < 2.0)
74
+ railties (= 4.0.2)
75
+ sprockets-rails (~> 2.0.0)
76
+ rails-observers (0.1.2)
77
+ activemodel (~> 4.0)
78
+ railties (4.0.2)
79
+ actionpack (= 4.0.2)
80
+ activesupport (= 4.0.2)
84
81
  rake (>= 0.8.7)
85
- rdoc (~> 3.4)
86
- thor (>= 0.14.6, < 2.0)
87
- rake (10.0.3)
88
- rdoc (3.12.2)
89
- json (~> 1.4)
90
- rr (1.0.4)
91
- simplecov (0.5.3)
92
- multi_json (~> 1.0.3)
93
- simplecov-html (~> 0.5.3)
94
- simplecov-html (0.5.3)
95
- slop (3.3.3)
96
- sprockets (2.2.2)
82
+ thor (>= 0.18.1, < 2.0)
83
+ rake (10.1.1)
84
+ rr (1.1.2)
85
+ simplecov (0.8.2)
86
+ docile (~> 1.1.0)
87
+ multi_json
88
+ simplecov-html (~> 0.8.0)
89
+ simplecov-html (0.8.0)
90
+ slop (3.4.7)
91
+ sprockets (2.10.1)
97
92
  hike (~> 1.2)
98
93
  multi_json (~> 1.0)
99
94
  rack (~> 1.0)
100
95
  tilt (~> 1.1, != 1.3.0)
101
- sqlite3 (1.3.7)
102
- thor (0.17.0)
103
- tilt (1.3.6)
104
- treetop (1.4.12)
96
+ sprockets-rails (2.0.1)
97
+ actionpack (>= 3.0)
98
+ activesupport (>= 3.0)
99
+ sprockets (~> 2.8)
100
+ sqlite3 (1.3.8)
101
+ thor (0.18.1)
102
+ thread_safe (0.1.3)
103
+ atomic
104
+ tilt (1.4.1)
105
+ treetop (1.4.15)
105
106
  polyglot
106
107
  polyglot (>= 0.3.1)
107
108
  turn (0.9.6)
108
109
  ansi
109
- tzinfo (0.3.37)
110
+ tzinfo (0.3.38)
110
111
 
111
112
  PLATFORMS
112
113
  ruby
@@ -114,10 +115,9 @@ PLATFORMS
114
115
  DEPENDENCIES
115
116
  active_resource_simulator
116
117
  database_cleaner
117
- factory_girl
118
- minitest
118
+ factory_girl (~> 2.0.4)
119
119
  pry
120
- rails
120
+ rails (>= 3.1.0, < 5.0.0)
121
121
  remotable!
122
122
  rr
123
123
  simplecov
data/MIT-LICENSE CHANGED
File without changes
@@ -1,6 +1,7 @@
1
1
  require "remotable/core_ext"
2
2
  require "active_support/concern"
3
3
  require "active_support/core_ext/array/wrap"
4
+ require "benchmark"
4
5
 
5
6
 
6
7
  module Remotable
@@ -9,7 +10,8 @@ module Remotable
9
10
  include Nosync
10
11
 
11
12
  def nosync?
12
- self.class.nosync? || super
13
+ return super if nosync_value?
14
+ self.class.nosync?
13
15
  end
14
16
 
15
17
 
@@ -26,6 +28,7 @@ module Remotable
26
28
  @remote_attribute_map ||= default_remote_attributes.map_to_self
27
29
  @local_attribute_routes ||= {}
28
30
  @expires_after ||= 1.day
31
+ @remote_timeout = { :index => 4, :show => 1, :update => 2, :create => 2, :destroy => 2 }
29
32
  end
30
33
 
31
34
 
@@ -34,7 +37,9 @@ module Remotable
34
37
  include Nosync
35
38
 
36
39
  def nosync?
37
- Remotable.nosync? || remote_model.nil? || super
40
+ return true if remote_model.nil?
41
+ return super if nosync_value?
42
+ Remotable.nosync?
38
43
  end
39
44
 
40
45
  # Sets the key with which a resource is identified remotely.
@@ -84,6 +89,14 @@ module Remotable
84
89
  @local_attribute_routes = {} # reset routes
85
90
  end
86
91
 
92
+ def remote_timeout(*args)
93
+ if args.any?
94
+ @remote_timeout = n = args.first
95
+ @remote_timeout = {:index => n, :show => n, :create => n, :update => n, :destroy => n} if n.is_a?(Numeric)
96
+ end
97
+ @remote_timeout
98
+ end
99
+
87
100
  def fetch_with(local_key, options={})
88
101
  @local_attribute_routes.merge!(local_key => options[:path])
89
102
  end
@@ -140,12 +153,27 @@ module Remotable
140
153
  def instantiate(*args)
141
154
  record = super
142
155
  if record.expired? && !record.nosync?
143
- record.pull_remote_data!
144
- record = nil if record.destroyed?
156
+ begin
157
+ Remotable.logger.debug "[remotable:#{name.underscore}:instantiate](#{record.fetch_value.inspect}) expired #{record.expires_at}"
158
+ record.pull_remote_data!
159
+ record = nil if record.destroyed?
160
+ rescue Remotable::TimeoutError
161
+ report_ignored_timeout_error($!)
162
+ rescue Remotable::ServiceUnavailableError
163
+ report_ignored_503_error($!)
164
+ end
145
165
  end
146
166
  record
147
167
  end
148
168
 
169
+ def report_ignored_timeout_error(error)
170
+ Remotable.logger.error "[remotable:#{name.underscore}:instantiate] #{error.message}"
171
+ end
172
+
173
+ def report_ignored_503_error(error)
174
+ Remotable.logger.error "[remotable:#{name.underscore}:instantiate] #{error.message}"
175
+ end
176
+
149
177
 
150
178
 
151
179
  def respond_to?(method_sym, include_private=false)
@@ -163,7 +191,7 @@ module Remotable
163
191
  raise ArgumentError, "#{method_sym} was called with #{values.length} but #{local_attributes.length} was expected"
164
192
  end
165
193
 
166
- local_resource = ((0...local_attributes.length).inject(scoped) do |scope, i|
194
+ local_resource = ((0...local_attributes.length).inject(self) do |scope, i|
167
195
  scope.where(local_attributes[i] => values[i])
168
196
  end).first || fetch_by(method_details[:remote_key], *values)
169
197
 
@@ -215,9 +243,21 @@ module Remotable
215
243
  remote_resource && new_from_remote(remote_resource)
216
244
  end
217
245
 
218
- # Looks the resource up remotely;
219
- # Returns the remote resource.
220
246
  def find_remote_resource_by(remote_attr, *values)
247
+ invoke_remote_model_find_by(remote_attr, *values)
248
+ end
249
+
250
+ def find_remote_resource_for_local_by(local_resource, remote_attr, *values)
251
+ if remote_model.respond_to?(:find_by_for_local)
252
+ invoke_remote_model_find_by_for_local(local_resource, remote_attr, *values)
253
+ else
254
+ invoke_remote_model_find_by(remote_attr, *values)
255
+ end
256
+ end
257
+
258
+ def invoke_remote_model_find_by(remote_attr, *values)
259
+ remote_set_timeout :show
260
+
221
261
  find_by = remote_model.method(:find_by)
222
262
  case find_by.arity
223
263
  when 1; find_by.call(remote_path_for(remote_attr, *values))
@@ -227,6 +267,20 @@ module Remotable
227
267
  end
228
268
  end
229
269
 
270
+ def invoke_remote_model_find_by_for_local(local_resource, remote_attr, *values)
271
+ remote_set_timeout :show
272
+
273
+ find_by_for_local = remote_model.method(:find_by_for_local)
274
+ case find_by_for_local.arity
275
+ when 2; find_by_for_local.call(local_resource, remote_path_for(remote_attr, *values))
276
+ when 3; find_by_for_local.call(local_resource, remote_attr, *values)
277
+ else
278
+ raise InvalidRemoteModel, "#{remote_model}.find_by_for_local should take either 2 or 3 parameters"
279
+ end
280
+ end
281
+
282
+
283
+
230
284
  def remote_path_for(remote_key, *values)
231
285
  route = route_for(remote_key)
232
286
  local_key = self.local_key(remote_key)
@@ -260,12 +314,13 @@ module Remotable
260
314
  end
261
315
 
262
316
  def find_by_remote_query(remote_method_name, *args)
263
- remote_resources = remote_model.send(remote_method_name, *args)
317
+ remote_set_timeout :index
318
+ remote_resources = Array.wrap(remote_model.send(remote_method_name, *args))
264
319
  map_remote_resources_to_local(remote_resources)
265
320
  end
266
321
 
267
322
  def map_remote_resources_to_local(remote_resources)
268
- return [] if remote_resources.empty?
323
+ return [] if remote_resources.nil? || remote_resources.empty?
269
324
 
270
325
  local_resources = nosync { fetch_corresponding_local_resources(remote_resources).to_a }
271
326
 
@@ -279,7 +334,7 @@ module Remotable
279
334
  local_resource = local_resources.detect { |local_resource|
280
335
  Array.wrap(remote_key).all? { |remote_attr|
281
336
  local_attr = local_attribute_name(remote_attr)
282
- local_resource.send(local_attr) == remote_resource.send(remote_attr)
337
+ local_resource.send(local_attr) == remote_resource[remote_attr]
283
338
  }
284
339
  }
285
340
 
@@ -298,7 +353,7 @@ module Remotable
298
353
  def fetch_corresponding_local_resources(remote_resources)
299
354
  conditions = Array.wrap(remote_key).each_with_object({}) do |remote_attr, query|
300
355
  local_attr = local_attribute_name(remote_attr)
301
- query[local_attr] = remote_resources.map(&remote_attr)
356
+ query[local_attr] = remote_resources.map { |resource| resource[remote_attr] }
302
357
  end
303
358
 
304
359
  where(conditions)
@@ -306,6 +361,12 @@ module Remotable
306
361
 
307
362
 
308
363
 
364
+ def remote_set_timeout(mode)
365
+ remote_model.timeout = remote_timeout[mode] if remote_model.respond_to?(:timeout=)
366
+ end
367
+
368
+
369
+
309
370
  private
310
371
 
311
372
  def default_remote_attributes
@@ -339,6 +400,7 @@ module Remotable
339
400
  :local_attribute_names,
340
401
  :local_attribute_name,
341
402
  :expires_after,
403
+ :remote_set_timeout,
342
404
  :to => "self.class"
343
405
 
344
406
  def expired?
@@ -371,8 +433,6 @@ module Remotable
371
433
 
372
434
 
373
435
 
374
- private
375
-
376
436
  def fetch_value
377
437
  if local_key.is_a?(Array)
378
438
  local_key.map(&method(:send))
@@ -381,16 +441,21 @@ module Remotable
381
441
  end
382
442
  end
383
443
 
444
+
445
+
446
+ private
447
+
384
448
  def fetch_remote_resource
385
449
  fetch_value && find_remote_resource_by(remote_key, fetch_value)
386
450
  end
387
451
 
388
452
  def find_remote_resource_by(remote_key, fetch_value)
389
- if remote_model.respond_to?(:find_by_for_local)
390
- remote_model.find_by_for_local(self, remote_key, fetch_value)
391
- else
392
- self.class.find_remote_resource_by(remote_key, fetch_value)
453
+ result = nil
454
+ ms = Benchmark.ms do
455
+ result = self.class.find_remote_resource_for_local_by(self, remote_key, fetch_value)
393
456
  end
457
+ Remotable.logger.info "[remotable:#{self.class.name.underscore}:find_remote_resource_by](#{fetch_value.inspect}) %.1fms" % [ ms ]
458
+ result
394
459
  end
395
460
 
396
461
  def merge_remote_data!(remote_resource)
@@ -401,6 +466,7 @@ module Remotable
401
466
  end
402
467
 
403
468
  def remote_model_has_been_destroyed!
469
+ Remotable.logger.info "[remotable:#{self.class.name.underscore}:remote_model_has_been_destroyed!](#{fetch_value.inspect})"
404
470
  nosync { destroy }
405
471
  end
406
472
 
@@ -409,6 +475,8 @@ module Remotable
409
475
  def update_remote_resource
410
476
  if any_remote_changes? && remote_resource
411
477
  merge_local_data(remote_resource, true)
478
+
479
+ remote_set_timeout :update
412
480
  if remote_resource.save
413
481
  merge_remote_data!(remote_resource)
414
482
  else
@@ -422,6 +490,7 @@ module Remotable
422
490
  @remote_resource = remote_model.new_resource
423
491
  merge_local_data(@remote_resource)
424
492
 
493
+ remote_set_timeout :create
425
494
  if @remote_resource.save
426
495
 
427
496
  # This line is especially crucial if the primary key
@@ -436,6 +505,8 @@ module Remotable
436
505
 
437
506
  def destroy_remote_resource
438
507
  return nil unless remote_resource
508
+
509
+ remote_set_timeout :destroy
439
510
  if remote_resource.destroy
440
511
  true
441
512
  else
@@ -467,6 +538,7 @@ module Remotable
467
538
 
468
539
 
469
540
  def merge_remote_errors(errors)
541
+ Remotable.logger.debug "[remotable:#{self.class.name.underscore}:merge_remote_errors](#{fetch_value.inspect}) #{errors.inspect}"
470
542
  errors.each do |attribute, messages|
471
543
  Array.wrap(messages).each do |message|
472
544
  self.errors.add(local_attribute_name(attribute), message)
@@ -477,8 +549,10 @@ module Remotable
477
549
 
478
550
  def merge_remote_data(remote_resource)
479
551
  remote_attribute_map.each do |remote_attr, local_attr|
480
- if remote_resource.respond_to?(remote_attr)
481
- send("#{local_attr}=", remote_resource.send(remote_attr))
552
+ if remote_resource.key?(remote_attr)
553
+ remote_value = remote_resource[remote_attr]
554
+ Remotable.logger.debug "[remotable:#{self.class.name.underscore}:merge_remote_data](#{fetch_value.inspect}) local.#{local_attr} = #{remote_value.inspect}"
555
+ send("#{local_attr}=", remote_value)
482
556
  end
483
557
  end
484
558
  self
@@ -487,7 +561,9 @@ module Remotable
487
561
  def merge_local_data(remote_resource, changes_only=false)
488
562
  remote_attribute_map.each do |remote_attr, local_attr|
489
563
  if !changes_only || local_attribute_changed?(local_attr)
490
- remote_resource.send("#{remote_attr}=", send(local_attr))
564
+ local_value = send(local_attr)
565
+ Remotable.logger.debug "[remotable:#{self.class.name.underscore}:merge_local_data](#{fetch_value.inspect}) remote.#{remote_attr} = #{local_value.inspect}"
566
+ remote_resource[remote_attr] = local_value
491
567
  end
492
568
  end
493
569
  self
@@ -99,6 +99,20 @@ end
99
99
  ActiveResource::Base.send(:include, ActiveResourceFixes)
100
100
 
101
101
 
102
+
103
+ module ActiveResourceJsonFormatFixes
104
+
105
+ def decode(json)
106
+ return {} if json.blank? # <-- insert this line. json will be nil if response is 304
107
+ super
108
+ end
109
+
110
+ end
111
+
112
+ ActiveResource::Formats::JsonFormat.extend ActiveResourceJsonFormatFixes
113
+
114
+
115
+
102
116
  # ActiveResource expects that errors will be an array of string
103
117
  # However, this is not what Rails Responders are inclined to return.
104
118
 
@@ -8,16 +8,50 @@ module Remotable
8
8
  module ActiveResource
9
9
  extend ActiveSupport::Concern
10
10
 
11
+
12
+
13
+ def key?(attribute)
14
+ attributes.key?(attribute.to_s)
15
+ end
16
+
17
+ def [](attribute)
18
+ attributes[attribute.to_s]
19
+ end
20
+
21
+ def []=(attribute, value)
22
+ attributes[attribute.to_s] = value
23
+ end
24
+
25
+
26
+
11
27
  module ClassMethods
12
28
 
29
+ IF_MODIFIED_SINCE = "If-Modified-Since".freeze
30
+
31
+
13
32
 
14
33
  def new_resource
15
34
  new
16
35
  end
17
36
 
18
37
 
19
- def find_by!(path)
20
- find(:one, :from => expanded_path_for(path))
38
+
39
+ # This is always invoked by instance#fetch_remote_resource.
40
+ # It expects to find a remote counterpart for a local resource.
41
+ # It should always return a NullRemote object that doesn't
42
+ # alter the behavior of a normal model at all.
43
+ def find_by_for_local(local_record, path)
44
+ had_previous_value = headers.key?(IF_MODIFIED_SINCE)
45
+ previous_value = headers[IF_MODIFIED_SINCE]
46
+
47
+ headers[IF_MODIFIED_SINCE] = Remotable.http_format_time(local_record.updated_at) if local_record.respond_to?(:updated_at)
48
+ find_by(path)
49
+ ensure
50
+ if had_previous_value
51
+ headers[IF_MODIFIED_SINCE] = previous_value
52
+ else
53
+ headers.delete(IF_MODIFIED_SINCE)
54
+ end
21
55
  end
22
56
 
23
57
  def find_by(path)
@@ -26,6 +60,19 @@ module Remotable
26
60
  nil
27
61
  end
28
62
 
63
+ def find_by!(path)
64
+ expanded_path = expanded_path_for(path)
65
+ Remotable.logger.info "[remotable:#{name.underscore}] GET #{expanded_path} (timeout: #{timeout})"
66
+ find(:one, :from => expanded_path)
67
+ rescue ::ActiveResource::TimeoutError
68
+ $!.extend Remotable::TimeoutError
69
+ raise
70
+ rescue ::ActiveResource::ServerError
71
+ $!.extend Remotable::ServiceUnavailableError if $!.response.code == 503
72
+ raise
73
+ end
74
+
75
+
29
76
 
30
77
  def expanded_path_for(path)
31
78
  if relative_path?(path)
@@ -36,14 +83,13 @@ module Remotable
36
83
  end
37
84
 
38
85
 
39
- private
40
86
 
87
+ private
41
88
 
42
89
  def relative_path?(path)
43
90
  !(path.start_with?("/") || path["://"])
44
91
  end
45
92
 
46
-
47
93
  end
48
94
  end
49
95
  end
@@ -0,0 +1,5 @@
1
+ module Remotable
2
+ module Error; end
3
+ module TimeoutError; include Error; end
4
+ module ServiceUnavailableError; include Error; end
5
+ end
@@ -0,0 +1,37 @@
1
+ module Remotable
2
+ class LoggerWrapper
3
+
4
+ def initialize(logger)
5
+ @logger = logger
6
+ end
7
+
8
+ attr_reader :logger
9
+
10
+ def debug(*args)
11
+ logger.debug(*args) if log? :debug
12
+ end
13
+
14
+ def info(*args)
15
+ logger.info(*args) if log? :info
16
+ end
17
+
18
+ def warn(*args)
19
+ logger.warn(*args) if log? :warn
20
+ end
21
+
22
+ def error(*args)
23
+ logger.error(*args) if log? :error
24
+ end
25
+
26
+ private
27
+
28
+ LEVELS = [:debug, :info, :warn, :error].freeze
29
+
30
+ def log?(value)
31
+ level = LEVELS.index(Remotable.log_level)
32
+ value = LEVELS.index(value)
33
+ value >= level
34
+ end
35
+
36
+ end
37
+ end
@@ -6,16 +6,20 @@ module Remotable
6
6
  self.nosync = true
7
7
  end
8
8
 
9
- def nosync
10
- value = @nosync
11
- nosync!
9
+ def nosync(new_value=true)
10
+ old_value = @nosync
11
+ self.nosync = new_value
12
12
  yield
13
13
  ensure
14
- self.nosync = value
14
+ @nosync = old_value
15
15
  end
16
16
 
17
17
  def nosync=(val)
18
- @nosync = (val == true)
18
+ @nosync = val
19
+ end
20
+
21
+ def nosync_value?
22
+ !@nosync.nil?
19
23
  end
20
24
 
21
25
  def nosync?
@@ -27,13 +27,16 @@ module Remotable
27
27
  end
28
28
 
29
29
 
30
+
30
31
  # NullRemote needs to receive setter messages and
31
32
  # swallow them. It doesn't need to respond to getter
32
33
  # messages since it has nothing to say.
33
- def method_missing(method_name, *args)
34
- super unless method_name.to_s =~ /=$/
34
+ def []=(attribute, value)
35
35
  end
36
36
 
37
+ def key?(attribute)
38
+ false
39
+ end
37
40
 
38
41
  def save
39
42
  true
@@ -1,3 +1,3 @@
1
1
  module Remotable
2
- VERSION = "0.2.4"
2
+ VERSION = "0.2.5.beta1"
3
3
  end
data/lib/remotable.rb CHANGED
@@ -3,6 +3,8 @@ require "remotable/nosync"
3
3
  require "remotable/null_remote"
4
4
  require "remotable/validate_models"
5
5
  require "remotable/with_remote_model_proxy"
6
+ require "remotable/errors"
7
+ require "remotable/logger_wrapper"
6
8
 
7
9
 
8
10
  # Remotable keeps a locally-stored ActiveRecord
@@ -42,6 +44,16 @@ module Remotable
42
44
  # but turn it off in production.
43
45
  self.validate_models = true
44
46
 
47
+ # Logger
48
+ def self.logger; @logger ||= LoggerWrapper.new(FakeLogger.new); end
49
+ def self.logger=(logger); @logger = LoggerWrapper.new(logger); end
50
+
51
+ class << self
52
+ attr_accessor :log_level
53
+ Remotable.log_level = :debug
54
+ end
55
+
56
+
45
57
 
46
58
  # == remote_model( model [optional] )
47
59
  #
@@ -112,6 +124,24 @@ module Remotable
112
124
 
113
125
  class InvalidRemoteModel < ArgumentError; end
114
126
 
127
+ class FakeLogger
128
+
129
+ def write(s)
130
+ puts s
131
+ end
132
+
133
+ alias :debug :write
134
+ alias :info :write
135
+ alias :warn :write
136
+ alias :error :write
137
+
138
+ end
139
+
140
+
141
+ def self.http_format_time(time)
142
+ time.utc.strftime("%a, %e %b %Y %H:%M:%S %Z")
143
+ end
144
+
115
145
 
116
146
 
117
147
  private
data/remotable.gemspec CHANGED
@@ -17,11 +17,10 @@ Gem::Specification.new do |s|
17
17
  s.add_dependency "activerecord"
18
18
  s.add_dependency "activesupport"
19
19
 
20
- s.add_development_dependency "rails"
21
- s.add_development_dependency "minitest"
20
+ s.add_development_dependency "rails", ">= 3.1.0", "< 5.0.0"
22
21
  s.add_development_dependency "turn"
23
22
  s.add_development_dependency "pry"
24
- s.add_development_dependency "factory_girl"
23
+ s.add_development_dependency "factory_girl", "~> 2.0.4"
25
24
  s.add_development_dependency "sqlite3"
26
25
  s.add_development_dependency "active_resource_simulator"
27
26
  s.add_development_dependency "simplecov"
@@ -14,6 +14,7 @@ class ActiveResourceTest < ActiveSupport::TestCase
14
14
 
15
15
 
16
16
 
17
+
17
18
  # ========================================================================= #
18
19
  # Finding #
19
20
  # ========================================================================= #
@@ -117,6 +118,7 @@ class ActiveResourceTest < ActiveSupport::TestCase
117
118
 
118
119
 
119
120
 
121
+
120
122
  # ========================================================================= #
121
123
  # Expiration #
122
124
  # ========================================================================= #
@@ -146,13 +148,40 @@ class ActiveResourceTest < ActiveSupport::TestCase
146
148
  :id => tenant.remote_id,
147
149
  :slug => tenant.slug,
148
150
  :church_name => unexpected_name
149
- })
151
+ }, :headers => if_modified_since(tenant))
150
152
 
151
153
  tenant = Tenant.find_by_remote_id(tenant.remote_id)
152
154
  assert_equal unexpected_name, tenant.name
153
155
  end
154
156
  end
155
157
 
158
+ test "should treat a 304 response as no changes" do
159
+ tenant = Factory(:tenant, :expires_at => 1.year.ago)
160
+
161
+ RemoteTenant.run_simulation do |s|
162
+ s.show(tenant.remote_id, nil, :status => 304, :headers => if_modified_since(tenant))
163
+
164
+ tenant = Tenant.find_by_remote_id(tenant.remote_id)
165
+ assert tenant.expires_at > Time.now, "Tenant should be considered fresh"
166
+ assert_not_nil tenant.remote_id, "The Remote Tenant's id should not be considered nil just because there was no body in the remote response"
167
+ end
168
+ end
169
+
170
+ test "should ignore a 503 response" do
171
+ expired_at = 1.year.ago
172
+ tenant = Factory(:tenant, :expires_at => expired_at)
173
+
174
+ RemoteTenant.run_simulation do |s|
175
+ s.show(tenant.remote_id, nil, :status => 503, :headers => if_modified_since(tenant))
176
+
177
+ assert_nothing_raised do
178
+ tenant = Tenant.find_by_remote_id(tenant.remote_id)
179
+ end
180
+ assert_equal expired_at, tenant.expires_at, "Tenant's expiration date should not have changed"
181
+ end
182
+ end
183
+
184
+
156
185
 
157
186
 
158
187
  # ========================================================================= #
@@ -168,7 +197,7 @@ class ActiveResourceTest < ActiveSupport::TestCase
168
197
  :id => tenant.remote_id,
169
198
  :slug => "totally-wonky",
170
199
  :church_name => tenant.name
171
- })
200
+ }, :headers => if_modified_since(tenant))
172
201
 
173
202
  tenant.nosync = false
174
203
  tenant.name = "Totally Wonky"
@@ -191,7 +220,7 @@ class ActiveResourceTest < ActiveSupport::TestCase
191
220
  :id => tenant.remote_id,
192
221
  :slug => tenant.slug,
193
222
  :church_name => tenant.name
194
- })
223
+ }, :headers => if_modified_since(tenant))
195
224
  s.update(tenant.remote_id, :status => 422, :body => {
196
225
  :errors => {:church_name => ["is already taken"]}
197
226
  })
@@ -275,6 +304,7 @@ class ActiveResourceTest < ActiveSupport::TestCase
275
304
 
276
305
 
277
306
 
307
+
278
308
  # ========================================================================= #
279
309
  # Destroying #
280
310
  # ========================================================================= #
@@ -287,7 +317,7 @@ class ActiveResourceTest < ActiveSupport::TestCase
287
317
  :id => tenant.remote_id,
288
318
  :slug => tenant.slug,
289
319
  :church_name => tenant.name
290
- })
320
+ }, :headers => if_modified_since(tenant))
291
321
 
292
322
  # Throws an error if save is not called on the remote resource
293
323
  mock(tenant.remote_resource).destroy { true }
@@ -305,7 +335,7 @@ class ActiveResourceTest < ActiveSupport::TestCase
305
335
  :id => tenant.remote_id,
306
336
  :slug => tenant.slug,
307
337
  :church_name => tenant.name
308
- })
338
+ }, :headers => if_modified_since(tenant))
309
339
 
310
340
  s.destroy(tenant.remote_id,
311
341
  :body => { :errors => { :base => ["nope"] } },
@@ -323,9 +353,9 @@ class ActiveResourceTest < ActiveSupport::TestCase
323
353
 
324
354
  assert_difference "Tenant.count", -1 do
325
355
  RemoteTenant.run_simulation do |s|
326
- s.show(tenant.remote_id, nil, :status => 404)
356
+ s.show(tenant.remote_id, nil, :status => 404, :headers => if_modified_since(tenant))
327
357
 
328
- tenant = Tenant.find_by_remote_id(tenant.remote_id)
358
+ tenant = Tenant.where(:remote_id => tenant.remote_id).first
329
359
  assert_equal nil, tenant
330
360
  end
331
361
  end
@@ -333,6 +363,7 @@ class ActiveResourceTest < ActiveSupport::TestCase
333
363
 
334
364
 
335
365
 
366
+
336
367
  # ========================================================================= #
337
368
  # Listing #
338
369
  # ========================================================================= #
@@ -365,4 +396,44 @@ class ActiveResourceTest < ActiveSupport::TestCase
365
396
 
366
397
 
367
398
 
399
+ # ========================================================================= #
400
+ # Timeouts #
401
+ # ========================================================================= #
402
+
403
+ test "should raise a Remotable::TimeoutError when a timeout occurs" do
404
+ assert_raise Remotable::TimeoutError do
405
+ stub(Tenant.remote_model).find do |*args|
406
+ raise ActiveResource::TimeoutError, "it timed out"
407
+ end
408
+
409
+ Tenant.find_by_remote_id(15)
410
+ end
411
+ end
412
+
413
+ test "should ignore a Remotable::TimeoutError when instantiating a record" do
414
+ tenant = Factory(:tenant, :expires_at => 1.year.ago)
415
+
416
+ assert_nothing_raised do
417
+ stub(Tenant.remote_model).find do |*args|
418
+ raise ActiveResource::TimeoutError, "it timed out"
419
+ end
420
+
421
+ tenant = Tenant.find_by_remote_id(tenant.remote_id)
422
+ assert_not_nil tenant
423
+ end
424
+ end
425
+
426
+
427
+
428
+
429
+ private
430
+
431
+
432
+
433
+ def if_modified_since(record)
434
+ {"If-Modified-Since" => Remotable.http_format_time(record.updated_at)}
435
+ end
436
+
437
+
438
+
368
439
  end
data/test/nosync_test.rb CHANGED
@@ -25,6 +25,30 @@ class NoSyncTest < ActiveSupport::TestCase
25
25
  end
26
26
  end
27
27
 
28
+ test "nosync? should be false if syncing is resumed temporarily on a model that prevents it by default" do
29
+ Tenant.nosync!
30
+ assert_equal true, Tenant.new.nosync?
31
+ Tenant.nosync(false) do
32
+ assert_equal false, Tenant.new.nosync?
33
+ end
34
+ assert_equal true, Tenant.new.nosync?
35
+ end
36
+
37
+ test "nosync? should take the value further up the chain if a model's value is temporarily cleared" do
38
+ assert_not_nil Tenant.remote_model
39
+
40
+ Remotable.nosync!
41
+ Tenant.nosync(false) do
42
+ Tenant.nosync(nil) do
43
+ assert_equal false, Tenant.nosync_value?
44
+ assert_equal true, Tenant.nosync?
45
+ end
46
+ assert_equal true, Tenant.nosync_value?
47
+ assert_equal false, Tenant.nosync?
48
+ end
49
+ assert_equal true, Tenant.nosync?
50
+ end
51
+
28
52
 
29
53
 
30
54
  # ========================================================================= #
@@ -28,11 +28,10 @@ end
28
28
  class BespokeResource
29
29
 
30
30
  def initialize(attributes={})
31
- self.slug = attributes[:slug]
32
- self.name = attributes[:name]
31
+ @attributes = attributes
33
32
  end
34
33
 
35
- attr_accessor :slug, :name
34
+ delegate :key?, :[], :[]=, :to => :@attributes
36
35
 
37
36
  def save
38
37
  true
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: remotable
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.4
4
+ version: 0.2.5.beta1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Lail
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-03-18 00:00:00.000000000 Z
11
+ date: 2014-02-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activeresource
@@ -58,28 +58,20 @@ dependencies:
58
58
  requirements:
59
59
  - - '>='
60
60
  - !ruby/object:Gem::Version
61
- version: '0'
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - '>='
61
+ version: 3.1.0
62
+ - - <
67
63
  - !ruby/object:Gem::Version
68
- version: '0'
69
- - !ruby/object:Gem::Dependency
70
- name: minitest
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - '>='
74
- - !ruby/object:Gem::Version
75
- version: '0'
64
+ version: 5.0.0
76
65
  type: :development
77
66
  prerelease: false
78
67
  version_requirements: !ruby/object:Gem::Requirement
79
68
  requirements:
80
69
  - - '>='
81
70
  - !ruby/object:Gem::Version
82
- version: '0'
71
+ version: 3.1.0
72
+ - - <
73
+ - !ruby/object:Gem::Version
74
+ version: 5.0.0
83
75
  - !ruby/object:Gem::Dependency
84
76
  name: turn
85
77
  requirement: !ruby/object:Gem::Requirement
@@ -112,16 +104,16 @@ dependencies:
112
104
  name: factory_girl
113
105
  requirement: !ruby/object:Gem::Requirement
114
106
  requirements:
115
- - - '>='
107
+ - - ~>
116
108
  - !ruby/object:Gem::Version
117
- version: '0'
109
+ version: 2.0.4
118
110
  type: :development
119
111
  prerelease: false
120
112
  version_requirements: !ruby/object:Gem::Requirement
121
113
  requirements:
122
- - - '>='
114
+ - - ~>
123
115
  - !ruby/object:Gem::Version
124
- version: '0'
116
+ version: 2.0.4
125
117
  - !ruby/object:Gem::Dependency
126
118
  name: sqlite3
127
119
  requirement: !ruby/object:Gem::Requirement
@@ -215,6 +207,8 @@ files:
215
207
  - lib/remotable/core_ext/enumerable.rb
216
208
  - lib/remotable/core_ext/object.rb
217
209
  - lib/remotable/core_ext/uri.rb
210
+ - lib/remotable/errors.rb
211
+ - lib/remotable/logger_wrapper.rb
218
212
  - lib/remotable/nosync.rb
219
213
  - lib/remotable/null_remote.rb
220
214
  - lib/remotable/validate_models.rb
@@ -247,12 +241,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
247
241
  version: '0'
248
242
  required_rubygems_version: !ruby/object:Gem::Requirement
249
243
  requirements:
250
- - - '>='
244
+ - - '>'
251
245
  - !ruby/object:Gem::Version
252
- version: '0'
246
+ version: 1.3.1
253
247
  requirements: []
254
248
  rubyforge_project: remotable
255
- rubygems_version: 2.0.0
249
+ rubygems_version: 2.2.1
256
250
  signing_key:
257
251
  specification_version: 4
258
252
  summary: Binds an ActiveRecord model to a remote resource and keeps the two synchronized