api_resource 0.2.1
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/.document +5 -0
- data/.rspec +3 -0
- data/Gemfile +29 -0
- data/Gemfile.lock +152 -0
- data/Guardfile +22 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +19 -0
- data/Rakefile +49 -0
- data/VERSION +1 -0
- data/api_resource.gemspec +154 -0
- data/lib/api_resource.rb +129 -0
- data/lib/api_resource/association_activation.rb +19 -0
- data/lib/api_resource/associations.rb +169 -0
- data/lib/api_resource/associations/association_proxy.rb +115 -0
- data/lib/api_resource/associations/belongs_to_remote_object_proxy.rb +16 -0
- data/lib/api_resource/associations/dynamic_resource_scope.rb +23 -0
- data/lib/api_resource/associations/has_many_remote_object_proxy.rb +16 -0
- data/lib/api_resource/associations/has_one_remote_object_proxy.rb +24 -0
- data/lib/api_resource/associations/multi_argument_resource_scope.rb +15 -0
- data/lib/api_resource/associations/multi_object_proxy.rb +73 -0
- data/lib/api_resource/associations/related_object_hash.rb +12 -0
- data/lib/api_resource/associations/relation_scope.rb +30 -0
- data/lib/api_resource/associations/resource_scope.rb +34 -0
- data/lib/api_resource/associations/scope.rb +107 -0
- data/lib/api_resource/associations/single_object_proxy.rb +81 -0
- data/lib/api_resource/attributes.rb +162 -0
- data/lib/api_resource/base.rb +587 -0
- data/lib/api_resource/callbacks.rb +49 -0
- data/lib/api_resource/connection.rb +171 -0
- data/lib/api_resource/core_extensions.rb +7 -0
- data/lib/api_resource/custom_methods.rb +119 -0
- data/lib/api_resource/exceptions.rb +87 -0
- data/lib/api_resource/formats.rb +14 -0
- data/lib/api_resource/formats/json_format.rb +25 -0
- data/lib/api_resource/formats/xml_format.rb +36 -0
- data/lib/api_resource/local.rb +12 -0
- data/lib/api_resource/log_subscriber.rb +15 -0
- data/lib/api_resource/mocks.rb +269 -0
- data/lib/api_resource/model_errors.rb +86 -0
- data/lib/api_resource/observing.rb +29 -0
- data/lib/api_resource/railtie.rb +22 -0
- data/lib/api_resource/scopes.rb +45 -0
- data/spec/lib/associations_spec.rb +656 -0
- data/spec/lib/attributes_spec.rb +121 -0
- data/spec/lib/base_spec.rb +504 -0
- data/spec/lib/callbacks_spec.rb +68 -0
- data/spec/lib/connection_spec.rb +76 -0
- data/spec/lib/local_spec.rb +20 -0
- data/spec/lib/mocks_spec.rb +28 -0
- data/spec/lib/model_errors_spec.rb +29 -0
- data/spec/spec_helper.rb +36 -0
- data/spec/support/mocks/association_mocks.rb +46 -0
- data/spec/support/mocks/error_resource_mocks.rb +21 -0
- data/spec/support/mocks/test_resource_mocks.rb +43 -0
- data/spec/support/requests/association_requests.rb +14 -0
- data/spec/support/requests/error_resource_requests.rb +25 -0
- data/spec/support/requests/test_resource_requests.rb +31 -0
- data/spec/support/test_resource.rb +64 -0
- metadata +334 -0
@@ -0,0 +1,29 @@
|
|
1
|
+
module ApiResource
|
2
|
+
|
3
|
+
module Observing
|
4
|
+
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
include ActiveModel::Observing
|
7
|
+
|
8
|
+
# Redefine these methods to
|
9
|
+
included do
|
10
|
+
%w( create save update destroy ).each do |method|
|
11
|
+
alias_method_chain method, :observers
|
12
|
+
end
|
13
|
+
|
14
|
+
module InstanceMethods
|
15
|
+
%w( create save update destroy ).each do |method|
|
16
|
+
module_eval <<-EOE, __FILE__, __LINE__ + 1
|
17
|
+
def #{method}_with_observers(*args)
|
18
|
+
notify_observers(:before_#method)
|
19
|
+
if result = #{method}_without_observers(*args)
|
20
|
+
notify_observers(:after_#{method})
|
21
|
+
end
|
22
|
+
return result
|
23
|
+
end
|
24
|
+
EOE
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'api_resource'
|
2
|
+
require 'rails'
|
3
|
+
|
4
|
+
module ApiResource
|
5
|
+
|
6
|
+
class Railtie < ::Rails::Railtie
|
7
|
+
|
8
|
+
config.api_resource = ActiveSupport::OrderedOptions.new
|
9
|
+
|
10
|
+
initializer "api_resource.set_configs" do |app|
|
11
|
+
app.config.api_resource.each do |k,v|
|
12
|
+
ApiResource::Base.send "#{k}=", v
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
initializer do
|
17
|
+
ApiResource::Associations.activate_active_record if defined?(ActiveRecord)
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module ApiResource
|
2
|
+
module Scopes
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
|
6
|
+
module ClassMethods
|
7
|
+
def scopes
|
8
|
+
return self.related_objects[:scope]
|
9
|
+
end
|
10
|
+
|
11
|
+
def scope(name, hsh)
|
12
|
+
raise ArgumentError, "Expecting an attributes hash given #{hsh.inspect}" unless hsh.is_a?(Hash)
|
13
|
+
self.related_objects[:scope][name.to_sym] = hsh
|
14
|
+
# we also need to define a class method for each scope
|
15
|
+
self.instance_eval <<-EOE, __FILE__, __LINE__ + 1
|
16
|
+
def #{name}(*args)
|
17
|
+
return #{ApiResource::Associations::ResourceScope.class_factory(hsh)}.new(self, :#{name}, *args)
|
18
|
+
end
|
19
|
+
EOE
|
20
|
+
end
|
21
|
+
|
22
|
+
def scope?(name)
|
23
|
+
self.related_objects[:scope][name.to_sym].present?
|
24
|
+
end
|
25
|
+
|
26
|
+
def scope_attributes(name)
|
27
|
+
raise "No such scope #{name}" unless self.scope?(name)
|
28
|
+
self.related_objects[:scope][name.to_sym]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
module InstanceMethods
|
32
|
+
def scopes
|
33
|
+
return self.class.scopes
|
34
|
+
end
|
35
|
+
|
36
|
+
def scope?(name)
|
37
|
+
return self.class.scope?(name)
|
38
|
+
end
|
39
|
+
|
40
|
+
def scope_attributes(name)
|
41
|
+
return self.class.scope_attributes(name)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,656 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
include ApiResource
|
4
|
+
|
5
|
+
describe "Associations" do
|
6
|
+
|
7
|
+
after(:all) do
|
8
|
+
TestResource.reload_class_attributes
|
9
|
+
end
|
10
|
+
|
11
|
+
context "creating and testing for associations of various types" do
|
12
|
+
|
13
|
+
it "should be able to give a list of all associations" do
|
14
|
+
AllAssociations = Class.new(ApiResource::Base)
|
15
|
+
AllAssociations.class_eval do
|
16
|
+
has_many :has_many_objects
|
17
|
+
belongs_to :belongs_to_object
|
18
|
+
has_one :has_one_object
|
19
|
+
end
|
20
|
+
AllAssociations.association_names.sort.should eql [:has_many_objects, :belongs_to_object, :has_one_object].sort
|
21
|
+
AllAssociations.new.association_names.sort.should eql [:has_many_objects, :belongs_to_object, :has_one_object].sort
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should be be able to define an asociation using a method named after that association type" do
|
25
|
+
TestResource.has_many :has_many_objects
|
26
|
+
TestResource.has_many?(:has_many_objects).should be_true
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should be able to define associations with different class names" do
|
30
|
+
TestResource.has_many :test_name, :class_name => :has_many_objects
|
31
|
+
TestResource.has_many?(:test_name).should be_true
|
32
|
+
TestResource.has_many_class_name(:test_name).should eql("HasManyObject")
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should be able to define multiple associations at the same time" do
|
36
|
+
TestResource.has_many :has_many_objects, :other_has_many_objects
|
37
|
+
TestResource.has_many?(:has_many_objects).should be_true
|
38
|
+
TestResource.has_many?(:other_has_many_objects).should be_true
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should be able to tell if something is an association via the association? method" do
|
42
|
+
TestResource.belongs_to :belongs_to_object
|
43
|
+
TestResource.association?(:belongs_to_object).should be_true
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should be able to get the class name of an association via the association_class_name method" do
|
47
|
+
TestResource.belongs_to :belongs_to_object
|
48
|
+
TestResource.association_class_name(:belongs_to_object).should eql("BelongsToObject")
|
49
|
+
TestResource.belongs_to :strange_name, :class_name => :belongs_to_object
|
50
|
+
TestResource.association_class_name(:strange_name).should eql("BelongsToObject")
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should only define relationships for the given class - they should not cascade" do
|
54
|
+
TestResource.belongs_to :belongs_to_object
|
55
|
+
AnotherTestResource.association?(:belongs_to_object).should_not be true
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should have its relationship cascade when sub-classed" do
|
59
|
+
TestResource.belongs_to :belongs_to_object
|
60
|
+
ChildTestResource.association?(:belongs_to_object).should be true
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should have its relationship cascade when sub-classed after the relationship is defined" do
|
64
|
+
TestResource.belongs_to :belongs_to_object
|
65
|
+
class ChildTestResource2 < TestResource; end
|
66
|
+
ChildTestResource2.association?(:belongs_to_object).should be true
|
67
|
+
end
|
68
|
+
|
69
|
+
context "Determining associated classes with a namespace" do
|
70
|
+
|
71
|
+
it "should be able to find classes for associations that exist in the same module without a namespace" do
|
72
|
+
TestMod::TestClass.belongs_to :test_association
|
73
|
+
TestMod::TestClass.association_class_name(:test_association).should eql("TestMod::TestAssociation")
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should be return a regular class name for a class defined at the root level" do
|
77
|
+
TestMod::TestClass.belongs_to :belongs_to_object
|
78
|
+
TestMod::TestClass.association_class_name(:belongs_to_object).should eql("BelongsToObject")
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should work for a class name specified with a namespace module" do
|
82
|
+
TestMod::TestClass.belongs_to :nonsense, :class_name => "TestMod::TestAssociation"
|
83
|
+
TestMod::TestClass.association_class_name(:nonsense).should eql("TestMod::TestAssociation")
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should work for nested module as well" do
|
87
|
+
TestMod::InnerMod::InnerClass.belongs_to :test_association
|
88
|
+
TestMod::InnerMod::InnerClass.association_class_name(:test_association).should eql("TestMod::TestAssociation")
|
89
|
+
end
|
90
|
+
|
91
|
+
it "should prefer to find classes within similar modules to ones in the root namespace" do
|
92
|
+
TestMod::InnerMod::InnerClass.belongs_to :test_resource
|
93
|
+
TestMod::InnerMod::InnerClass.association_class_name(:test_resource).should eql("TestMod::TestResource")
|
94
|
+
end
|
95
|
+
|
96
|
+
it "should be able to override into the root namespace by prefixing with ::" do
|
97
|
+
TestMod::InnerMod::InnerClass.belongs_to :test_resource, :class_name => "::TestResource"
|
98
|
+
TestMod::InnerMod::InnerClass.association_class_name(:test_resource).should eql("::TestResource")
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
|
103
|
+
|
104
|
+
end
|
105
|
+
|
106
|
+
context "Remote Definitions" do
|
107
|
+
|
108
|
+
before(:all) do
|
109
|
+
TestResource.reload_class_attributes
|
110
|
+
end
|
111
|
+
|
112
|
+
it "should be able define an association remotely" do
|
113
|
+
TestResource.belongs_to?(:belongs_to_object).should be true
|
114
|
+
TestResource.new.belongs_to_object.klass.should eql BelongsToObject
|
115
|
+
end
|
116
|
+
|
117
|
+
it "should be able define an association remotely" do
|
118
|
+
TestResource.belongs_to?(:custom_name).should be true
|
119
|
+
TestResource.new.custom_name.klass.should eql BelongsToObject
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
123
|
+
|
124
|
+
|
125
|
+
context "creating and testing for scopes" do
|
126
|
+
|
127
|
+
it "should be able to define scopes which require class names" do
|
128
|
+
lambda {
|
129
|
+
TestResource.scope :test_scope
|
130
|
+
}.should raise_error
|
131
|
+
TestResource.scope :test_scope, {:has_many_objects => "test"}
|
132
|
+
end
|
133
|
+
|
134
|
+
it "should be able to test if a scope exists" do
|
135
|
+
TestResource.scope :test_scope, {:item => "test"}
|
136
|
+
TestResource.scope?(:test_scope).should be_true
|
137
|
+
TestResource.scope_attributes(:test_scope).should eql({"item" => "test"})
|
138
|
+
end
|
139
|
+
|
140
|
+
end
|
141
|
+
|
142
|
+
context "testing for scopes and associations on an instance" do
|
143
|
+
|
144
|
+
it "should be able to define associations on a class and test for them on an instance" do
|
145
|
+
TestResource.has_many :has_many_objects, :class_name => :other_has_many_objects
|
146
|
+
tst = TestResource.new
|
147
|
+
tst.has_many?(:has_many_objects).should be_true
|
148
|
+
tst.has_many_class_name(:has_many_objects).should eql("OtherHasManyObject")
|
149
|
+
end
|
150
|
+
|
151
|
+
it "should be able to define scopes on a class and test for them on an instance" do
|
152
|
+
TestResource.scope :has_many_objects, {:item => "test"}
|
153
|
+
tst = TestResource.new
|
154
|
+
tst.scope?(:has_many_objects).should be_true
|
155
|
+
tst.scope_attributes(:has_many_objects).should eql({"item" => "test"})
|
156
|
+
end
|
157
|
+
|
158
|
+
end
|
159
|
+
|
160
|
+
describe "Single Object Associations" do
|
161
|
+
|
162
|
+
before(:all) do
|
163
|
+
TestResource.reload_class_attributes
|
164
|
+
end
|
165
|
+
|
166
|
+
after(:all) do
|
167
|
+
TestResource.reload_class_attributes
|
168
|
+
end
|
169
|
+
|
170
|
+
it "should return nil if its internal object is nil" do
|
171
|
+
ap = Associations::SingleObjectProxy.new("TestResource", {})
|
172
|
+
ap.instance_variable_set(:@internal_object, nil)
|
173
|
+
ap.blank?.should be_true
|
174
|
+
end
|
175
|
+
|
176
|
+
it "should not throw an error on serializable hash if its internal object is nil" do
|
177
|
+
ap = Associations::SingleObjectProxy.new("TestResource", {})
|
178
|
+
ap.instance_variable_set(:@internal_object, nil)
|
179
|
+
lambda {ap.serializable_hash}.should_not raise_error
|
180
|
+
end
|
181
|
+
|
182
|
+
it "should be able to create a SingleObjectProxy around a blank hash" do
|
183
|
+
ap = Associations::SingleObjectProxy.new("TestResource", {})
|
184
|
+
ap.remote_path.should be_blank
|
185
|
+
end
|
186
|
+
|
187
|
+
it "should be able to extract a service uri from the contents hash" do
|
188
|
+
ap = Associations::SingleObjectProxy.new("TestResource",{:service_uri => "/path"})
|
189
|
+
ap.remote_path.should eql("/path")
|
190
|
+
end
|
191
|
+
|
192
|
+
it "should be able to recognize the attributes of an object and not make them scopes" do
|
193
|
+
TestResource.define_attributes :test
|
194
|
+
ap = Associations::SingleObjectProxy.new("TestResource",{:service_uri => "/path", :test => "testval"})
|
195
|
+
ap.scope?("test").should be_false
|
196
|
+
ap.remote_path.should eql("/path")
|
197
|
+
end
|
198
|
+
|
199
|
+
it "should make all attributes except the service uri into scopes given the scopes_only option" do
|
200
|
+
ap = Associations::SingleObjectProxy.new("TestResource",{:service_uri => "/path", :test_scope => {"testval" => true}, :scopes_only => true})
|
201
|
+
ap.scope?("test_scope").should be_true
|
202
|
+
ap.remote_path.should eql("/path")
|
203
|
+
end
|
204
|
+
|
205
|
+
it "should pass the attributes that are not scopes and make them attributes of the object" do
|
206
|
+
TestResource.define_attributes :test
|
207
|
+
ap = Associations::SingleObjectProxy.new("TestResource",{:service_uri => "/path", :test => "testval"})
|
208
|
+
ap.internal_object.attributes.keys.should include("test")
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
describe "Multi Object Associations" do
|
213
|
+
|
214
|
+
before(:all) do
|
215
|
+
TestResource.related_objects[:scope].clear
|
216
|
+
end
|
217
|
+
|
218
|
+
after(:each) do
|
219
|
+
Associations::MultiObjectProxy.remote_path_element = :service_uri
|
220
|
+
end
|
221
|
+
|
222
|
+
describe "Loading settings" do
|
223
|
+
|
224
|
+
context "Loading array contents" do
|
225
|
+
|
226
|
+
it "should be able to load a blank array" do
|
227
|
+
ap = Associations::MultiObjectProxy.new("TestResource",[])
|
228
|
+
ap.remote_path.should be_nil
|
229
|
+
ap.scopes.keys.should eql([])
|
230
|
+
end
|
231
|
+
|
232
|
+
it "should be able to recognize a settings hash if it has a service_uri" do
|
233
|
+
ap = Associations::MultiObjectProxy.new("TestResource",[{:service_uri => "/route"}])
|
234
|
+
ap.remote_path.should eql("/route")
|
235
|
+
end
|
236
|
+
|
237
|
+
it "should be able to recognize a settings hash if it has a 'service_uri' with another preset name" do
|
238
|
+
Associations::MultiObjectProxy.remote_path_element = :the_element
|
239
|
+
ap = Associations::MultiObjectProxy.new("TestResource",[{:the_element => "/route"}])
|
240
|
+
ap.remote_path.should eql("/route")
|
241
|
+
end
|
242
|
+
|
243
|
+
end
|
244
|
+
|
245
|
+
context "Loading hash contents" do
|
246
|
+
it "should not be able to load a hash without a 'service_uri'" do
|
247
|
+
lambda {
|
248
|
+
Associations::MultiObjectProxy.new("TestResource", {:hi => 3})
|
249
|
+
}.should raise_error
|
250
|
+
end
|
251
|
+
|
252
|
+
it "should be able to recognize settings from a hash" do
|
253
|
+
ap = Associations::MultiObjectProxy.new("TestResource", {:service_uri => "/route"})
|
254
|
+
ap.remote_path.should eql("/route")
|
255
|
+
end
|
256
|
+
|
257
|
+
it "should recognize settings with differing 'service_uri' names" do
|
258
|
+
Associations::MultiObjectProxy.remote_path_element = :the_element
|
259
|
+
ap = Associations::MultiObjectProxy.new("TestResource",{:the_element => "/route"})
|
260
|
+
ap.remote_path.should eql("/route")
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
context "Defining scopes" do
|
265
|
+
|
266
|
+
it "should define scopes based on the other keys in a settings hash" do
|
267
|
+
ap = Associations::MultiObjectProxy.new("TestResource", [{:service_uri => "/route", :scope1 => {"scope1" => true}, :scope2 => {"scope2" => true}}])
|
268
|
+
[:scope1, :scope2].each{|s| ap.scopes.include?(s).should be_true }
|
269
|
+
end
|
270
|
+
|
271
|
+
it "should identify known scopes based on the scopes defined on the object it is a proxy to" do
|
272
|
+
TestResource.scope :class_scope, "class_scope" => "true"
|
273
|
+
ap = Associations::MultiObjectProxy.new("TestResource", [{:service_uri => "/route", :scope1 => {"scope1" => true}, :scope2 => {"scope2" => true}}])
|
274
|
+
[:scope1, :scope2, :class_scope].each{|s| ap.scopes.include?(s).should be_true}
|
275
|
+
end
|
276
|
+
|
277
|
+
it "scopes in the response should shadow class defined scopes" do
|
278
|
+
TestResource.scope :scope1, "scope1" => "true"
|
279
|
+
ap = Associations::MultiObjectProxy.new("TestResource", [{:service_uri => "/route", :scope1 => {"scope1" => true}, :scope2 => {"scope2" => true}}])
|
280
|
+
ap.scopes[:scope1].should eql({"scope1" => true})
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
end
|
285
|
+
|
286
|
+
describe "Selecting scopes" do
|
287
|
+
|
288
|
+
before(:all) do
|
289
|
+
ScopeResource.class_eval do
|
290
|
+
scope :one, :one => true
|
291
|
+
scope :two, :two => "test"
|
292
|
+
scope :three, :three => ["id"]
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
|
297
|
+
it "should be able to query scopes on the current model" do
|
298
|
+
|
299
|
+
ScopeResource.one.to_query.should eql "one=true"
|
300
|
+
ScopeResource.two("test").to_query.should eql "two=test"
|
301
|
+
ScopeResource.three(1,2,3).to_query.should eql "three[]=1&three[]=2&three[]=3"
|
302
|
+
|
303
|
+
ScopeResource.one.two("testing").three([2]).to_query.should eql "one=true&three[]=2&two=testing"
|
304
|
+
end
|
305
|
+
|
306
|
+
|
307
|
+
it "should be able to change scopes" do
|
308
|
+
ap = Associations::MultiObjectProxy.new("TestResource", [{:service_uri => "/route", :scope1 => {"scope1" => true}, :scope2 => {"scope2" => true}}])
|
309
|
+
ap.scope1.should be_a(Associations::RelationScope)
|
310
|
+
ap.scope1.current_scope.scope1_scope?.should be_true
|
311
|
+
end
|
312
|
+
|
313
|
+
it "should be able to chain scope calls together" do
|
314
|
+
ap = Associations::MultiObjectProxy.new("TestResource", [{:service_uri => "/route", :scope1 => {"scope1" => true}, :scope2 => {"scope2" => true}}])
|
315
|
+
ap.scope1.scope2.current_scope.scope1_and_scope2_scope?.should be_true
|
316
|
+
ap.scope1.scope2.to_query.should eql("scope1=true&scope2=true")
|
317
|
+
end
|
318
|
+
|
319
|
+
it "should support scopes that contain underscores" do
|
320
|
+
ap = Associations::MultiObjectProxy.new("TestResource", [{:service_uri => "/route", :scope_1 => {"scope_1" => true}, :scope_2 => {"scope_2" => true}}])
|
321
|
+
ap.scope_1.scope_2.current_scope.scope_1_and_scope_2_scope?.should be_true
|
322
|
+
end
|
323
|
+
|
324
|
+
it "should be able to return the current query string" do
|
325
|
+
ap = Associations::MultiObjectProxy.new("TestResource", [{:service_uri => "/route", :scope_1 => {"scope_1" => true}, :scope_2 => {"scope_2" => true}}])
|
326
|
+
ap.scope_1.scope_2.to_query.should eql("scope_1=true&scope_2=true")
|
327
|
+
end
|
328
|
+
|
329
|
+
it "should be able to substitute values into the scope query strings by passing a hash to the methods" do
|
330
|
+
ap = Associations::MultiObjectProxy.new("TestResource", [{:service_uri => "/route", :scope_1 => {"scope_1" => true, :test_sub => false}, :scope_2 => {"scope_2" => true}}])
|
331
|
+
obj = ap.scope_1(:test_sub => true).scope_2
|
332
|
+
obj.to_query.should eql("scope_1=true&scope_2=true&test_sub=true")
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
|
337
|
+
end
|
338
|
+
|
339
|
+
describe "Loading and Caching loaded data" do
|
340
|
+
|
341
|
+
context "Single Object" do
|
342
|
+
|
343
|
+
before(:all) do
|
344
|
+
TestResource.reload_class_attributes
|
345
|
+
end
|
346
|
+
|
347
|
+
it "should be able to force load an object" do
|
348
|
+
ap = Associations::SingleObjectProxy.new("TestResource",{:service_uri => '/single_object_association', :active => {:active => true}, :scopes_only => true})
|
349
|
+
ap.loaded.should be_blank
|
350
|
+
name = ap.name
|
351
|
+
name.should_not be_blank
|
352
|
+
ap.loaded.should_not be_blank
|
353
|
+
# Make sure it isn't reloaded
|
354
|
+
ap.name.should eql(name)
|
355
|
+
end
|
356
|
+
|
357
|
+
it "should be able to load a scope" do
|
358
|
+
ap = Associations::SingleObjectProxy.new("TestResource",{:service_uri => '/single_object_association', :active => {:active => true}, :scopes_only => true})
|
359
|
+
ap.internal_object.active.should be_false
|
360
|
+
ap.times_loaded.should eql(1)
|
361
|
+
ap.active.should be_a Associations::RelationScope
|
362
|
+
ap.active.name.should_not be_blank
|
363
|
+
ap.times_loaded.should eql(2)
|
364
|
+
ap.active.internal_object.active.should be_true
|
365
|
+
# another check that the resource wasn't reloaded
|
366
|
+
ap.times_loaded.should eql(2)
|
367
|
+
end
|
368
|
+
|
369
|
+
it "should be able to load a chain of scopes" do
|
370
|
+
ap = Associations::SingleObjectProxy.new("TestResource",{:service_uri => '/single_object_association', :active => {:active => true}, :with_birthday => {:birthday => true}, :scopes_only => true})
|
371
|
+
first = ap.active.with_birthday.id
|
372
|
+
ap.with_birthday.active.id.should eql(first)
|
373
|
+
ap.times_loaded.should eql(1)
|
374
|
+
ap.active.with_birthday.birthday.should_not be_blank
|
375
|
+
end
|
376
|
+
|
377
|
+
it "should be able to load a chain of scopes with substitution" do
|
378
|
+
ap = Associations::SingleObjectProxy.new("TestResource",{:service_uri => '/single_object_association', :active => {:active => false}, :with_birthday => {:birthday => true}, :scopes_only => true})
|
379
|
+
item = ap.active(:active => true).internal_object
|
380
|
+
item.active.should be_true
|
381
|
+
ap.times_loaded.should eql(1)
|
382
|
+
# This error and incrementing times_loaded means it tried to load again
|
383
|
+
lambda {
|
384
|
+
ap.active.internal_object
|
385
|
+
}.should raise_error
|
386
|
+
ap.times_loaded.should eql(2)
|
387
|
+
end
|
388
|
+
|
389
|
+
it "should proxy unknown methods to the object loading if it hasn't already" do
|
390
|
+
ap = Associations::SingleObjectProxy.new("TestResource",{:service_uri => '/single_object_association', :active => {:active => false}, :with_birthday => {:birthday => true}, :scopes_only => true})
|
391
|
+
ap.times_loaded.should eql(0)
|
392
|
+
ap.id.should_not be_blank
|
393
|
+
ap.times_loaded.should eql(1)
|
394
|
+
end
|
395
|
+
|
396
|
+
it "should only load each distinct set of scopes once" do
|
397
|
+
ap = Associations::SingleObjectProxy.new("TestResource",{:service_uri => '/single_object_association', :active => {:active => false}, :with_birthday => {:birthday => true}, :scopes_only => true})
|
398
|
+
ap.times_loaded.should eql(0)
|
399
|
+
ap.active(:active => true).with_birthday.internal_object
|
400
|
+
ap.with_birthday.active(:active => true).internal_object
|
401
|
+
ap.times_loaded.should eql(1)
|
402
|
+
end
|
403
|
+
|
404
|
+
it "should be able to clear it's loading cache" do
|
405
|
+
ap = Associations::SingleObjectProxy.new("TestResource",{:service_uri => '/single_object_association', :active => {:active => true}, :with_birthday => {:birthday => true}, :scopes_only => true})
|
406
|
+
ap.active.internal_object
|
407
|
+
ap.times_loaded.should eql(1)
|
408
|
+
ap.reload
|
409
|
+
ap.active.internal_object
|
410
|
+
ap.times_loaded.should eql(1)
|
411
|
+
end
|
412
|
+
|
413
|
+
end
|
414
|
+
|
415
|
+
context "Multi Object" do
|
416
|
+
|
417
|
+
it "should be able to load 'all'" do
|
418
|
+
ap = Associations::MultiObjectProxy.new("TestResource",{:service_uri => '/multi_object_association', :active => {:active => false}, :inactive => {:active => false}, :with_birthday => {:birthday => true}})
|
419
|
+
results = ap.all
|
420
|
+
results.size.should eql(5)
|
421
|
+
results.first.active.should be_false
|
422
|
+
end
|
423
|
+
|
424
|
+
it "should be able to load a scope" do
|
425
|
+
ap = Associations::MultiObjectProxy.new("TestResource",{:service_uri => '/multi_object_association', :active => {:active => false}, :inactive => {:active => false}, :with_birthday => {:birthday => true}})
|
426
|
+
results = ap.active.internal_object
|
427
|
+
results.size.should eql(5)
|
428
|
+
results.first.active.should be_true
|
429
|
+
end
|
430
|
+
|
431
|
+
it "should be able to load a chain of scopes" do
|
432
|
+
ap = Associations::MultiObjectProxy.new("TestResource",{:service_uri => '/multi_object_association', :active => {:active => true}, :inactive => {:active => false}, :with_birthday => {:birthday => true}})
|
433
|
+
results = ap.active.with_birthday.internal_object
|
434
|
+
results.first.active.should be_true
|
435
|
+
results.first.birthday.should_not be_blank
|
436
|
+
end
|
437
|
+
|
438
|
+
it "should be able to load a chain of scopes with substitution" do
|
439
|
+
ap = Associations::MultiObjectProxy.new("TestResource",{:service_uri => '/multi_object_association', :active => {:active => true}, :inactive => {:active => false}, :with_birthday => {:birthday => true}})
|
440
|
+
results = ap.inactive(:active => true).with_birthday.internal_object
|
441
|
+
results.first.active.should be_true
|
442
|
+
results.first.birthday.should_not be_blank
|
443
|
+
end
|
444
|
+
|
445
|
+
it "should proxy unknown methods to the object array loading if it hasn't already" do
|
446
|
+
ap = Associations::MultiObjectProxy.new("TestResource",{:service_uri => '/multi_object_association', :active => {:active => true}, :inactive => {:active => false}, :with_birthday => {:birthday => true}})
|
447
|
+
ap.first.should be_a TestResource
|
448
|
+
ap.active.first.should be_a TestResource
|
449
|
+
ap.times_loaded.should eql(2)
|
450
|
+
end
|
451
|
+
|
452
|
+
it "should only load each distinct set of scopes once" do
|
453
|
+
ap = Associations::MultiObjectProxy.new("TestResource",{:service_uri => '/multi_object_association', :active => {:active => true}, :inactive => {:active => false}, :with_birthday => {:birthday => true}})
|
454
|
+
ap.first
|
455
|
+
ap.active.first
|
456
|
+
ap.times_loaded.should eql(2)
|
457
|
+
ap.active.first
|
458
|
+
ap.times_loaded.should eql(2)
|
459
|
+
ap.active.with_birthday.first
|
460
|
+
ap.with_birthday.active.first
|
461
|
+
ap.times_loaded.should eql(3)
|
462
|
+
end
|
463
|
+
|
464
|
+
it "should be able to clear it's loading cache" do
|
465
|
+
ap = Associations::MultiObjectProxy.new("TestResource",{:service_uri => '/multi_object_association', :active => {:active => true}, :inactive => {:active => false}, :with_birthday => {:birthday => true}})
|
466
|
+
ap.active.first
|
467
|
+
ap.times_loaded.should eql(1)
|
468
|
+
ap.reload
|
469
|
+
ap.active.first
|
470
|
+
ap.times_loaded.should eql(1)
|
471
|
+
end
|
472
|
+
|
473
|
+
it "should be enumerable" do
|
474
|
+
ap = Associations::MultiObjectProxy.new("TestResource",{:service_uri => '/multi_object_association', :active => {:active => true}, :inactive => {:active => false}, :with_birthday => {:birthday => true}})
|
475
|
+
ap.each do |tr|
|
476
|
+
tr.name.should_not be_blank
|
477
|
+
end
|
478
|
+
end
|
479
|
+
|
480
|
+
end
|
481
|
+
|
482
|
+
context "Scopes" do
|
483
|
+
|
484
|
+
before(:all) do
|
485
|
+
TestResource.reload_class_attributes
|
486
|
+
end
|
487
|
+
|
488
|
+
it "should define class methods for the known scopes" do
|
489
|
+
TestResource.scopes.each do |key, _|
|
490
|
+
TestResource.should respond_to key
|
491
|
+
end
|
492
|
+
end
|
493
|
+
|
494
|
+
it "should return a ResourceScope when calling any scope on a class" do
|
495
|
+
TestResource.send(TestResource.scopes.first.first.to_sym).should be_a Associations::ResourceScope
|
496
|
+
end
|
497
|
+
|
498
|
+
it "should be able to chain scopes" do
|
499
|
+
scp = TestResource.active.paginate
|
500
|
+
scp.should be_a Associations::ResourceScope
|
501
|
+
scp.to_query.should eql("active=true¤t_page=current_page&paginate=true&per_page=per_page")
|
502
|
+
end
|
503
|
+
|
504
|
+
it "should load when calling all" do
|
505
|
+
TestResource.active.should respond_to :all
|
506
|
+
TestResource.active.should respond_to :internal_object
|
507
|
+
results = TestResource.active.all
|
508
|
+
results.should be_a Array
|
509
|
+
results.size.should eql(5)
|
510
|
+
results.each do |res|
|
511
|
+
res.should be_a TestResource
|
512
|
+
end
|
513
|
+
end
|
514
|
+
|
515
|
+
it "should load when calling an enumerable method or an array method" do
|
516
|
+
TestResource.active.each do |result|
|
517
|
+
result.should be_a TestResource
|
518
|
+
end
|
519
|
+
end
|
520
|
+
end
|
521
|
+
|
522
|
+
context "Assigning Data" do
|
523
|
+
context "Single Object Association" do
|
524
|
+
before(:all) do
|
525
|
+
TestResource.has_one(:has_one_object)
|
526
|
+
TestResource.belongs_to(:belongs_to_object)
|
527
|
+
end
|
528
|
+
after(:all) do
|
529
|
+
TestResource.reload_class_attributes
|
530
|
+
end
|
531
|
+
|
532
|
+
it "should assign associations to the correct type on initialization" do
|
533
|
+
tr = TestResource.new(:has_one_object => {:name => "Dan"}, :belongs_to_object => {:name => "Dan"})
|
534
|
+
|
535
|
+
tr.has_one_object.internal_object.should be_instance_of HasOneObject
|
536
|
+
tr.belongs_to_object.internal_object.should be_instance_of BelongsToObject
|
537
|
+
|
538
|
+
end
|
539
|
+
|
540
|
+
it "should assign associations to the correct type when setting attributes directly" do
|
541
|
+
tr = TestResource.new()
|
542
|
+
tr.has_one_object = {:name => "Dan"}
|
543
|
+
tr.belongs_to_object = {:name => "Dan"}
|
544
|
+
|
545
|
+
tr.has_one_object.internal_object.should be_instance_of HasOneObject
|
546
|
+
tr.belongs_to_object.internal_object.should be_instance_of BelongsToObject
|
547
|
+
end
|
548
|
+
|
549
|
+
|
550
|
+
end
|
551
|
+
|
552
|
+
context "Single Object Association" do
|
553
|
+
before(:all) do
|
554
|
+
TestResource.has_many(:has_many_objects)
|
555
|
+
end
|
556
|
+
after(:all) do
|
557
|
+
TestResource.reload_class_attributes
|
558
|
+
end
|
559
|
+
|
560
|
+
it "should assign associations to the correct type on initialization" do
|
561
|
+
tr = TestResource.new(:has_many_objects => [{:name => "Dan"}])
|
562
|
+
tr.has_many_objects.internal_object.first.should be_instance_of HasManyObject
|
563
|
+
|
564
|
+
end
|
565
|
+
|
566
|
+
it "should assign associations to the correct type when setting attributes directly" do
|
567
|
+
tr = TestResource.new()
|
568
|
+
tr.has_many_objects = [{:name => "Dan"}]
|
569
|
+
tr.has_many_objects.internal_object.first.should be_instance_of HasManyObject
|
570
|
+
end
|
571
|
+
|
572
|
+
|
573
|
+
end
|
574
|
+
|
575
|
+
context "ActiveModel" do
|
576
|
+
before(:all) do
|
577
|
+
require 'active_record'
|
578
|
+
ActiveRecord::Base.establish_connection({"adapter" => "sqlite3", "database" => "/tmp/api_resource_test_db.sqlite"})
|
579
|
+
ActiveRecord::Base.connection.create_table(:test_ars, :force => true) do |t|
|
580
|
+
t.integer(:test_resource_id)
|
581
|
+
end
|
582
|
+
ApiResource::Associations.activate_active_record
|
583
|
+
TestAR = Class.new(ActiveRecord::Base)
|
584
|
+
TestAR.class_eval do
|
585
|
+
belongs_to_remote :my_favorite_thing, :class_name => "TestClassYay"
|
586
|
+
end
|
587
|
+
end
|
588
|
+
it "should define remote association types for AR" do
|
589
|
+
[:has_many_remote, :belongs_to_remote, :has_one_remote].each do |assoc|
|
590
|
+
ActiveRecord::Base.singleton_methods.should include assoc
|
591
|
+
end
|
592
|
+
end
|
593
|
+
it "should add remote associations to related objects" do
|
594
|
+
TestAR.related_objects.should eql({"has_many_remote"=>{}, "belongs_to_remote"=>{"my_favorite_thing"=>"TestClassYay"}, "has_one_remote"=>{}, "scope"=>{}})
|
595
|
+
end
|
596
|
+
context "Not Overriding Scopes" do
|
597
|
+
it "should not override scopes, which would raise an error with lambda-style scopes" do
|
598
|
+
lambda {
|
599
|
+
TestAR.class_eval do
|
600
|
+
scope :my_favorite_scope, lambda {
|
601
|
+
joins(:my_test)
|
602
|
+
}
|
603
|
+
end
|
604
|
+
}.should_not raise_error
|
605
|
+
end
|
606
|
+
end
|
607
|
+
context "Belongs To" do
|
608
|
+
|
609
|
+
before(:all) do
|
610
|
+
TestAR.class_eval do
|
611
|
+
belongs_to_remote :test_resource
|
612
|
+
end
|
613
|
+
end
|
614
|
+
|
615
|
+
it "should attempt to load a single remote object for a belongs_to relationship" do
|
616
|
+
tar = TestAR.new
|
617
|
+
tar.stubs(:test_resource_id).returns(1)
|
618
|
+
TestResource.connection.expects(:get).with("/test_resources/1.json").once.returns({"name" => "testing"})
|
619
|
+
# load the test resource
|
620
|
+
tar.test_resource.name.should eql "testing"
|
621
|
+
end
|
622
|
+
|
623
|
+
end
|
624
|
+
context "Has One" do
|
625
|
+
before(:all) do
|
626
|
+
TestAR.class_eval do
|
627
|
+
has_one_remote :test_resource
|
628
|
+
end
|
629
|
+
end
|
630
|
+
it "should attempt to load a single remote object for a has_one relationship" do
|
631
|
+
tar = TestAR.new
|
632
|
+
tar.stubs(:id).returns(1)
|
633
|
+
TestResource.connection.expects(:get).with("/test_resources.json?test_ar_id=1").once.returns([{"name" => "testing"}])
|
634
|
+
# load the test resource
|
635
|
+
tar.test_resource.name.should eql "testing"
|
636
|
+
end
|
637
|
+
end
|
638
|
+
context "Has Many" do
|
639
|
+
before(:all) do
|
640
|
+
TestAR.class_eval do
|
641
|
+
has_many_remote :has_many_objects
|
642
|
+
end
|
643
|
+
end
|
644
|
+
it "should attempt to load a collection of remote objects for a has_many relationship" do
|
645
|
+
tar = TestAR.new
|
646
|
+
tar.stubs(:id).returns(1)
|
647
|
+
HasManyObject.connection.expects(:get).with("/has_many_objects.json?test_ar_id=1").once.returns([{"name" => "testing"}])
|
648
|
+
# load the test resource
|
649
|
+
tar.has_many_objects.first.name.should eql "testing"
|
650
|
+
end
|
651
|
+
|
652
|
+
end
|
653
|
+
end
|
654
|
+
end
|
655
|
+
end
|
656
|
+
end
|