remotable 0.1.2 → 0.2.0
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/README.mdown +47 -7
- data/lib/remotable.rb +99 -3
- data/lib/remotable/active_record_extender.rb +162 -68
- data/lib/remotable/active_resource_fixes.rb +15 -0
- data/lib/remotable/adapters/active_resource.rb +8 -19
- data/lib/remotable/core_ext.rb +2 -1
- data/lib/remotable/core_ext/object.rb +27 -0
- data/lib/remotable/nosync.rb +0 -1
- data/lib/remotable/validate_models.rb +23 -0
- data/lib/remotable/version.rb +1 -1
- data/lib/remotable/with_remote_model_proxy.rb +18 -0
- data/remotable.gemspec +3 -0
- data/test/active_resource_test.rb +332 -0
- data/test/bespoke_test.rb +197 -0
- data/test/factories/tenants.rb +8 -0
- data/test/remotable_test.rb +48 -256
- data/test/support/active_resource.rb +14 -12
- data/test/support/bespoke.rb +57 -0
- data/test/support/schema.rb +1 -0
- data/test/test_helper.rb +13 -0
- data/test/understanding_test.rb +16 -0
- metadata +53 -9
@@ -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
|
-
|
15
|
-
|
16
|
-
|
17
|
-
:route_for,
|
18
|
-
:to => :local_model
|
19
|
-
|
14
|
+
def new_resource
|
15
|
+
new
|
16
|
+
end
|
20
17
|
|
21
18
|
|
22
|
-
def find_by!(
|
23
|
-
find(:one, :from =>
|
19
|
+
def find_by!(path)
|
20
|
+
find(:one, :from => expanded_path_for(path))
|
24
21
|
end
|
25
22
|
|
26
|
-
def find_by(
|
27
|
-
find_by!(
|
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
|
data/lib/remotable/core_ext.rb
CHANGED
@@ -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)
|
data/lib/remotable/nosync.rb
CHANGED
@@ -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
|
data/lib/remotable/version.rb
CHANGED
@@ -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
|
data/remotable.gemspec
CHANGED
@@ -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
|