remotable 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,6 +2,21 @@ require "active_resource"
2
2
 
3
3
 
4
4
  module ActiveResourceFixes
5
+
6
+ # ActiveResource hacks method_missing without hacking respond_to?
7
+ # In fact, it responds to any method that ends in an equals sign.
8
+ # It also responds to any method that matches an attribute name.
9
+ def respond_to?(method_symbol, include_private=false)
10
+ method_name = method_symbol.to_s
11
+ if method_name =~ /\w+=/
12
+ true
13
+ elsif attributes.include?(method_name)
14
+ true
15
+ else
16
+ super(method_symbol, include_private)
17
+ end
18
+ end
19
+
5
20
  end
6
21
 
7
22
 
@@ -11,30 +11,23 @@ module Remotable
11
11
  module ClassMethods
12
12
 
13
13
 
14
- attr_accessor :local_model
15
-
16
- delegate :local_attribute_name,
17
- :route_for,
18
- :to => :local_model
19
-
14
+ def new_resource
15
+ new
16
+ end
20
17
 
21
18
 
22
- def find_by!(key, value)
23
- find(:one, :from => path_for(key, value))
19
+ def find_by!(path)
20
+ find(:one, :from => expanded_path_for(path))
24
21
  end
25
22
 
26
- def find_by(key, value)
27
- find_by!(key, value)
23
+ def find_by(path)
24
+ find_by!(path)
28
25
  rescue ::ActiveResource::ResourceNotFound
29
26
  nil
30
27
  end
31
28
 
32
29
 
33
-
34
- def path_for(remote_key, value)
35
- local_key = local_attribute_name(remote_key)
36
- route = route_for(local_key)
37
- path = route.gsub(/:#{local_key}/, value.to_s)
30
+ def expanded_path_for(path)
38
31
  if relative_path?(path)
39
32
  URI.join_url_segments(prefix, collection_name, "#{path}.#{format.extension}")
40
33
  else
@@ -43,19 +36,15 @@ module Remotable
43
36
  end
44
37
 
45
38
 
46
-
47
39
  private
48
40
 
49
41
 
50
-
51
42
  def relative_path?(path)
52
43
  !(path.start_with?("/") || path["://"])
53
44
  end
54
45
 
55
46
 
56
-
57
47
  end
58
-
59
48
  end
60
49
  end
61
50
  end
@@ -1 +1,2 @@
1
- require "remotable/core_ext/enumerable"
1
+ require "remotable/core_ext/enumerable"
2
+ require "remotable/core_ext/object"
@@ -0,0 +1,27 @@
1
+ module Remotable
2
+ module CoreExt
3
+ module Object
4
+
5
+
6
+ def respond_to_all?(*methods)
7
+ respond_to = method(:respond_to?)
8
+ methods.flatten.all?(&respond_to)
9
+ end
10
+
11
+ def responds_to(*methods)
12
+ respond_to = method(:respond_to?)
13
+ methods.flatten.select(&respond_to)
14
+ end
15
+
16
+ def does_not_respond_to(*methods)
17
+ methods.flatten - self.responds_to(methods)
18
+ end
19
+
20
+
21
+ end
22
+ end
23
+ end
24
+
25
+
26
+ ::Object.extend(Remotable::CoreExt::Object)
27
+ ::Object.send(:include, Remotable::CoreExt::Object)
@@ -1,4 +1,3 @@
1
-
2
1
  module Remotable
3
2
  module Nosync
4
3
 
@@ -0,0 +1,23 @@
1
+ module Remotable
2
+ module ValidateModels
3
+
4
+
5
+ def validate_models=(val)
6
+ @validate_models = (val == true)
7
+ end
8
+
9
+ def validate_models?
10
+ @validate_models == true
11
+ end
12
+
13
+ def without_validation
14
+ value = self.validate_models?
15
+ self.validate_models = false
16
+ yield
17
+ ensure
18
+ self.validate_models = value
19
+ end
20
+
21
+
22
+ end
23
+ end
@@ -1,3 +1,3 @@
1
1
  module Remotable
2
- VERSION = "0.1.2"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -0,0 +1,18 @@
1
+ module Remotable
2
+ class WithRemoteModelProxy
3
+
4
+ def initialize(model, remote_model)
5
+ @model = model
6
+ @remote_model = remote_model
7
+ end
8
+
9
+ delegate :respond_to?, :to => :@model
10
+
11
+ def method_missing(sym, *args, &block)
12
+ @model.with_remote_model(@remote_model) do
13
+ @model.send(sym, *args, &block)
14
+ end
15
+ end
16
+
17
+ end
18
+ end
@@ -18,11 +18,14 @@ Gem::Specification.new do |s|
18
18
  s.add_dependency "activesupport"
19
19
 
20
20
  s.add_development_dependency "rails"
21
+ s.add_development_dependency "minitest"
21
22
  s.add_development_dependency "turn"
22
23
  s.add_development_dependency "factory_girl"
23
24
  s.add_development_dependency "sqlite3"
24
25
  s.add_development_dependency "active_resource_simulator"
25
26
  s.add_development_dependency "simplecov"
27
+ s.add_development_dependency "rr"
28
+ s.add_development_dependency "database_cleaner"
26
29
 
27
30
  s.files = `git ls-files`.split("\n")
28
31
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
@@ -0,0 +1,332 @@
1
+ require "test_helper"
2
+ require "remotable"
3
+ require "support/active_resource"
4
+ require "active_resource_simulator"
5
+ require "rr"
6
+
7
+
8
+ class ActiveResourceTest < ActiveSupport::TestCase
9
+ include RR::Adapters::TestUnit
10
+
11
+ test "should make an absolute path and add the format" do
12
+ assert_equal "/api/accounts/by_slug/value.json", RemoteTenant.expanded_path_for("by_slug/value")
13
+ end
14
+
15
+
16
+
17
+ # ========================================================================= #
18
+ # Finding #
19
+ # ========================================================================= #
20
+
21
+ test "should be able to find resources by different attributes" do
22
+ new_tenant_slug = "not_found"
23
+
24
+ assert_equal 0, Tenant.where(:slug => new_tenant_slug).count,
25
+ "There's not supposed to be a Tenant with the slug #{new_tenant_slug}."
26
+
27
+ assert_difference "Tenant.count", +1 do
28
+ RemoteTenant.run_simulation do |s|
29
+ s.show(nil, {
30
+ :id => 46,
31
+ :slug => new_tenant_slug,
32
+ :church_name => "Not Found"
33
+ }, :path => "/api/accounts/by_slug/#{new_tenant_slug}.json")
34
+
35
+ new_tenant = Tenant.find_by_slug(new_tenant_slug)
36
+ assert_not_nil new_tenant, "A remote tenant was not found with the slug #{new_tenant_slug.inspect}"
37
+ end
38
+ end
39
+ end
40
+
41
+ test "should be able to find resources with a composite key" do
42
+ group_id = 5
43
+ slug = "not_found"
44
+
45
+ assert_equal 0, RemoteWithCompositeKey.where(:group_id => group_id, :slug => slug).count,
46
+ "There's not supposed to be a Tenant with the group_id #{group_id} and the slug #{slug}."
47
+
48
+ assert_difference "RemoteWithCompositeKey.count", +1 do
49
+ RemoteTenant.run_simulation do |s|
50
+ s.show(nil, {
51
+ :id => 46,
52
+ :group_id => group_id,
53
+ :slug => slug,
54
+ :church_name => "Not Found"
55
+ }, :path => "/api/accounts/groups/#{group_id}/tenants/#{slug}.json")
56
+
57
+ new_tenant = RemoteWithCompositeKey.find_by_group_id_and_slug(group_id, slug)
58
+ assert_not_nil new_tenant, "A remote tenant was not found with the group_id #{group_id} and the slug #{slug}."
59
+ end
60
+ end
61
+ end
62
+
63
+ test "should be able to find resources with the bang method" do
64
+ new_tenant_slug = "not_found2"
65
+
66
+ assert_equal 0, Tenant.where(:slug => new_tenant_slug).count,
67
+ "There's not supposed to be a Tenant with the slug #{new_tenant_slug}."
68
+
69
+ assert_difference "Tenant.count", +1 do
70
+ RemoteTenant.run_simulation do |s|
71
+ s.show(nil, {
72
+ :id => 46,
73
+ :slug => new_tenant_slug,
74
+ :church_name => "Not Found"
75
+ }, :path => "/api/accounts/by_slug/#{new_tenant_slug}.json")
76
+
77
+ new_tenant = Tenant.find_by_slug!(new_tenant_slug)
78
+ assert_not_nil new_tenant, "A remote tenant was not found with the slug #{new_tenant_slug.inspect}"
79
+ end
80
+ end
81
+ end
82
+
83
+ test "if a resource is neither local nor remote, raise an exception with the bang method" do
84
+ new_tenant_slug = "not_found3"
85
+
86
+ assert_equal 0, Tenant.where(:slug => new_tenant_slug).count,
87
+ "There's not supposed to be a Tenant with the slug #{new_tenant_slug}."
88
+
89
+ RemoteTenant.run_simulation do |s|
90
+ s.show(nil, nil, :status => 404, :path => "/api/accounts/by_slug/#{new_tenant_slug}.json")
91
+
92
+ assert_raises ActiveRecord::RecordNotFound do
93
+ Tenant.find_by_slug!(new_tenant_slug)
94
+ end
95
+ end
96
+ end
97
+
98
+ test "should be able to find resources by different attributes and specify a path" do
99
+ new_tenant_name = "JohnnyG"
100
+
101
+ assert_equal 0, Tenant.where(:name => new_tenant_name).count,
102
+ "There's not supposed to be a Tenant with the name #{new_tenant_name}."
103
+
104
+ assert_difference "Tenant.count", +1 do
105
+ RemoteTenant.run_simulation do |s|
106
+ s.show(nil, {
107
+ :id => 46,
108
+ :slug => "not_found",
109
+ :church_name => new_tenant_name
110
+ }, :path => "/api/accounts/by_nombre/#{new_tenant_name}.json")
111
+
112
+ new_tenant = Tenant.find_by_name(new_tenant_name)
113
+ assert_not_nil new_tenant, "A remote tenant was not found with the name #{new_tenant_name.inspect}"
114
+ end
115
+ end
116
+ end
117
+
118
+
119
+
120
+ # ========================================================================= #
121
+ # Expiration #
122
+ # ========================================================================= #
123
+
124
+ test "should not fetch a remote record when a local record is not expired" do
125
+ tenant = Factory(:tenant, :expires_at => 100.years.from_now)
126
+ unexpected_name = "Totally Wonky"
127
+
128
+ RemoteTenant.run_simulation do |s|
129
+ s.show(tenant.remote_id, {
130
+ :id => tenant.remote_id,
131
+ :slug => tenant.slug,
132
+ :church_name => unexpected_name
133
+ })
134
+
135
+ tenant = Tenant.find_by_remote_id(tenant.remote_id)
136
+ assert_not_equal unexpected_name, tenant.name
137
+ end
138
+ end
139
+
140
+ test "should fetch a remote record when a local record is expired" do
141
+ tenant = Factory(:tenant, :expires_at => 1.year.ago)
142
+ unexpected_name = "Totally Wonky"
143
+
144
+ RemoteTenant.run_simulation do |s|
145
+ s.show(tenant.remote_id, {
146
+ :id => tenant.remote_id,
147
+ :slug => tenant.slug,
148
+ :church_name => unexpected_name
149
+ })
150
+
151
+ tenant = Tenant.find_by_remote_id(tenant.remote_id)
152
+ assert_equal unexpected_name, tenant.name
153
+ end
154
+ end
155
+
156
+
157
+
158
+ # ========================================================================= #
159
+ # Updating #
160
+ # ========================================================================= #
161
+
162
+ test "should update a record remotely when updating one locally" do
163
+ tenant = Factory(:tenant)
164
+ new_name = "Totally Wonky"
165
+
166
+ RemoteTenant.run_simulation do |s|
167
+ s.show(tenant.remote_id, {
168
+ :id => tenant.remote_id,
169
+ :slug => tenant.slug,
170
+ :church_name => tenant.name
171
+ })
172
+
173
+ tenant.nosync = false
174
+ tenant.name = "Totally Wonky"
175
+ assert_equal true, tenant.any_remote_changes?
176
+
177
+ # Throws an error if save is not called on the remote resource
178
+ mock(tenant.remote_resource).save { true }
179
+
180
+ tenant.save!
181
+ end
182
+ end
183
+
184
+ test "should fail to update a record locally when failing to update one remotely" do
185
+ tenant = Factory(:tenant)
186
+ new_name = "Totally Wonky"
187
+
188
+ RemoteTenant.run_simulation do |s|
189
+ s.show(tenant.remote_id, {
190
+ :id => tenant.remote_id,
191
+ :slug => tenant.slug,
192
+ :church_name => tenant.name
193
+ })
194
+ s.update(tenant.remote_id, :status => 422, :body => {
195
+ :errors => {:church_name => ["is already taken"]}
196
+ })
197
+
198
+ tenant.nosync = false
199
+ tenant.name = new_name
200
+ assert_raises(ActiveRecord::RecordInvalid) do
201
+ tenant.save!
202
+ end
203
+ assert_equal ["is already taken"], tenant.errors[:name]
204
+ end
205
+ end
206
+
207
+
208
+
209
+
210
+ # ========================================================================= #
211
+ # Creating #
212
+ # ========================================================================= #
213
+
214
+ test "should create a record remotely when creating one locally" do
215
+ tenant = Tenant.new({
216
+ :slug => "brand_new",
217
+ :name => "Brand New"
218
+ })
219
+
220
+ RemoteTenant.run_simulation do |s|
221
+ s.create({
222
+ :id => 143,
223
+ :slug => tenant.slug,
224
+ :church_name => tenant.name
225
+ })
226
+
227
+ tenant.save!
228
+
229
+ assert_equal true, tenant.remote_resource.persisted?
230
+ end
231
+ end
232
+
233
+ test "should fail to create a record locally when failing to create one remotely" do
234
+ tenant = Tenant.new({
235
+ :slug => "brand_new",
236
+ :name => "Brand New"
237
+ })
238
+
239
+ RemoteTenant.run_simulation do |s|
240
+ s.create({
241
+ :errors => {
242
+ :what => ["ever"],
243
+ :church_name => ["is already taken"]}
244
+ }, :status => 422)
245
+
246
+ assert_raises(ActiveRecord::RecordInvalid) do
247
+ tenant.save!
248
+ end
249
+
250
+ assert_equal ["is already taken"], tenant.errors[:name]
251
+ end
252
+ end
253
+
254
+ test "should create a record locally when fetching a new remote resource" do
255
+ new_tenant_id = 17
256
+
257
+ assert_equal 0, Tenant.where(:remote_id => new_tenant_id).count,
258
+ "There's not supposed to be a Tenant with the id #{new_tenant_id}."
259
+
260
+ assert_difference "Tenant.count", +1 do
261
+ RemoteTenant.run_simulation do |s|
262
+ s.show(new_tenant_id, {
263
+ :id => new_tenant_id,
264
+ :slug => "not_found",
265
+ :church_name => "Not Found"
266
+ })
267
+
268
+ new_tenant = Tenant.find_by_remote_id(new_tenant_id)
269
+ assert_not_nil new_tenant, "A remote tenant was not found with the id #{new_tenant_id.inspect}"
270
+ end
271
+ end
272
+ end
273
+
274
+
275
+
276
+ # ========================================================================= #
277
+ # Destroying #
278
+ # ========================================================================= #
279
+
280
+ test "should destroy a record remotely when destroying one locally" do
281
+ tenant = Factory(:tenant)
282
+
283
+ RemoteTenant.run_simulation do |s|
284
+ s.show(tenant.remote_id, {
285
+ :id => tenant.remote_id,
286
+ :slug => tenant.slug,
287
+ :church_name => tenant.name
288
+ })
289
+
290
+ # Throws an error if save is not called on the remote resource
291
+ mock(tenant.remote_resource).destroy { true }
292
+
293
+ tenant.nosync = false
294
+ tenant.destroy
295
+ end
296
+ end
297
+
298
+ test "should fail to destroy a record locally when failing to destroy one remotely" do
299
+ tenant = Factory(:tenant)
300
+
301
+ RemoteTenant.run_simulation do |s|
302
+ s.show(tenant.remote_id, {
303
+ :id => tenant.remote_id,
304
+ :slug => tenant.slug,
305
+ :church_name => tenant.name
306
+ })
307
+
308
+ s.destroy(tenant.remote_id, :status => 500)
309
+
310
+ tenant.nosync = false
311
+ assert_raises(ActiveResource::ServerError) do
312
+ tenant.destroy
313
+ end
314
+ end
315
+ end
316
+
317
+ test "should delete a local record when a remote record has been deleted" do
318
+ tenant = Factory(:tenant, :expires_at => 1.year.ago)
319
+
320
+ assert_difference "Tenant.count", -1 do
321
+ RemoteTenant.run_simulation do |s|
322
+ s.show(tenant.remote_id, nil, :status => 404)
323
+
324
+ tenant = Tenant.find_by_remote_id(tenant.remote_id)
325
+ assert_equal nil, tenant
326
+ end
327
+ end
328
+ end
329
+
330
+
331
+
332
+ end