remotable 0.2.4 → 0.2.5.beta1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 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