api_resource 0.6.18 → 0.6.19
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 +4 -4
- data/Gemfile.lock +4 -8
- data/Guardfile +5 -17
- data/api_resource.gemspec +4 -7
- data/lib/api_resource.rb +20 -19
- data/lib/api_resource/associations.rb +39 -23
- data/lib/api_resource/associations/association_proxy.rb +14 -13
- data/lib/api_resource/attributes.rb +555 -156
- data/lib/api_resource/base.rb +376 -305
- data/lib/api_resource/connection.rb +22 -12
- data/lib/api_resource/finders.rb +17 -18
- data/lib/api_resource/finders/single_finder.rb +1 -1
- data/lib/api_resource/mocks.rb +37 -31
- data/lib/api_resource/scopes.rb +70 -12
- data/lib/api_resource/serializer.rb +264 -0
- data/lib/api_resource/typecast.rb +13 -2
- data/lib/api_resource/typecasters/unknown_typecaster.rb +33 -0
- data/lib/api_resource/version.rb +1 -1
- data/spec/lib/associations/has_many_remote_object_proxy_spec.rb +3 -3
- data/spec/lib/associations_spec.rb +49 -94
- data/spec/lib/attributes_spec.rb +40 -56
- data/spec/lib/base_spec.rb +290 -382
- data/spec/lib/callbacks_spec.rb +6 -6
- data/spec/lib/connection_spec.rb +20 -20
- data/spec/lib/finders_spec.rb +14 -0
- data/spec/lib/mocks_spec.rb +9 -9
- data/spec/lib/prefixes_spec.rb +4 -5
- data/spec/lib/scopes_spec.rb +98 -0
- data/spec/lib/serializer_spec.rb +156 -0
- data/spec/spec_helper.rb +1 -4
- data/spec/support/test_resource.rb +1 -1
- metadata +14 -38
- data/spec/tmp/DIR +0 -0
@@ -0,0 +1,264 @@
|
|
1
|
+
module ApiResource
|
2
|
+
|
3
|
+
#
|
4
|
+
# Handles serialization for a given instance of ApiResource::Base with
|
5
|
+
# a set of options
|
6
|
+
#
|
7
|
+
# @author [dlangevin]
|
8
|
+
#
|
9
|
+
class Serializer
|
10
|
+
|
11
|
+
# @!attribute [r] options
|
12
|
+
# @return [HashWithIndifferentAccess]
|
13
|
+
attr_reader :options
|
14
|
+
|
15
|
+
# @!attribute [r] record
|
16
|
+
# @return [ApiResource::Base]
|
17
|
+
attr_reader :record
|
18
|
+
|
19
|
+
#
|
20
|
+
# Constructor
|
21
|
+
#
|
22
|
+
# @param record [ApiResource::Base] Record to serialize
|
23
|
+
# @param options = {} [Hash] Options supplied
|
24
|
+
#
|
25
|
+
# @option options [Array<Symbol,String>] except (Array<>) Attributes to
|
26
|
+
# explicitly exclude
|
27
|
+
# @option options [Array<Symbol,String] include_associations (Array<>)
|
28
|
+
# Associations to explicitly include
|
29
|
+
# @option options [Array<Symbol,String] include_extras (Array<>)
|
30
|
+
# Attributes to explicitly include
|
31
|
+
# @option options [Boolean] include_id (false) Whether or not to include
|
32
|
+
# the primary key
|
33
|
+
# @option options [Boolean] include_nil_attributes (false) Whether or not
|
34
|
+
# to include attributes that are blank/nil in the serialized hash
|
35
|
+
#
|
36
|
+
def initialize(record, options = {})
|
37
|
+
@record = record
|
38
|
+
@options = options.with_indifferent_access
|
39
|
+
end
|
40
|
+
|
41
|
+
#
|
42
|
+
# Return our serialized object as a Hash
|
43
|
+
#
|
44
|
+
# @return [HashWithIndifferentAccess]
|
45
|
+
def to_hash
|
46
|
+
ret = HashWithIndifferentAccess.new
|
47
|
+
ret.merge!(self.attributes_for_hash)
|
48
|
+
ret.merge!(self.associations_for_hash)
|
49
|
+
ret.merge!(self.add_on_data_for_hash)
|
50
|
+
ret.merge!(self.association_foreign_keys_for_hash)
|
51
|
+
ret
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
protected
|
56
|
+
|
57
|
+
#
|
58
|
+
# Data that is implicitly or explicitly added by options
|
59
|
+
#
|
60
|
+
# @return [Hash] Data to add on
|
61
|
+
def add_on_data_for_hash
|
62
|
+
{}.tap do |ret|
|
63
|
+
# explicit inclusion of the record's id (used in nested updates)
|
64
|
+
if self.options[:include_id] && !self.record.new_record?
|
65
|
+
ret[:id] = self.record.id
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
#
|
71
|
+
# Attribute data to include in the hash - this checks whether or not
|
72
|
+
# to include a given attribute with {#include_attribute?}
|
73
|
+
#
|
74
|
+
# @return [Hash] Data from attributes
|
75
|
+
def attributes_for_hash
|
76
|
+
self.record.attributes.inject({}) do |accum, (key,val)|
|
77
|
+
if self.include_attribute?(key, val)
|
78
|
+
accum.merge(key => val)
|
79
|
+
else
|
80
|
+
accum
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
#
|
86
|
+
# Data that is included due to foreign keys.
|
87
|
+
#
|
88
|
+
# @example
|
89
|
+
# tr = TestResource.new
|
90
|
+
# tr.belongs_to_object = BelongsToObject.find(10)
|
91
|
+
# Serializer.new(tr).to_hash #=> {:belongs_to_object_id => 10}
|
92
|
+
#
|
93
|
+
# @return [Hash] Data from foreign keys
|
94
|
+
def association_foreign_keys_for_hash
|
95
|
+
{}.tap do |ret|
|
96
|
+
self.record.association_names.each do |name|
|
97
|
+
# this is the method name
|
98
|
+
# E.g. :belongs_to_object => :belongs_to_object_id
|
99
|
+
method_name = self.record.class.association_foreign_key_field(name)
|
100
|
+
# make sure we have changes
|
101
|
+
next if self.record.changes[method_name].blank?
|
102
|
+
# make sure we aren't in a prefix method
|
103
|
+
next if self.is_prefix_field?(method_name)
|
104
|
+
# add the changed value
|
105
|
+
ret[method_name] = self.record.send(method_name)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
#
|
111
|
+
# Nested association data for the hash. This checks whether or not
|
112
|
+
# to include a given association with {#include_association?}
|
113
|
+
#
|
114
|
+
# @return [Hash] Data from associations
|
115
|
+
def associations_for_hash
|
116
|
+
self.record.association_names.inject({}) do |accum, assoc_name|
|
117
|
+
if self.include_association?(assoc_name)
|
118
|
+
# get the association
|
119
|
+
assoc = self.record.send(assoc_name)
|
120
|
+
options = self.options.merge(include_id: true)
|
121
|
+
accum.merge(assoc_name => assoc.serializable_hash(options))
|
122
|
+
else
|
123
|
+
accum
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
#
|
129
|
+
# List of all association names that are in our changes set
|
130
|
+
#
|
131
|
+
# @return [Array<Symbol>]
|
132
|
+
def changed_associations
|
133
|
+
@changed_associations ||= begin
|
134
|
+
self.record.changes.keys.symbolize_array.select{ |k|
|
135
|
+
self.record.association?(k)
|
136
|
+
}
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
#
|
141
|
+
# Helper method to check if a blank value should be included
|
142
|
+
# in the response
|
143
|
+
#
|
144
|
+
# @param key [String, Symbol] Attribute name
|
145
|
+
# @param val [Mixed] Value to include
|
146
|
+
#
|
147
|
+
# @return [Boolean] Whether or not to include this key/value pair
|
148
|
+
def check_blank_value(key, val)
|
149
|
+
# if we explicitly want nil attributes
|
150
|
+
return true if self.options[:include_nil_attributes]
|
151
|
+
# or if the attribute has changed to nil
|
152
|
+
return true if self.record.changes[key].present?
|
153
|
+
# make sure our value isn't blank
|
154
|
+
return val.present?
|
155
|
+
end
|
156
|
+
|
157
|
+
#
|
158
|
+
# List of explicitly excluded attributes
|
159
|
+
#
|
160
|
+
# @return [type] [description]
|
161
|
+
def excluded_keys
|
162
|
+
@excluded_keys ||= begin
|
163
|
+
ret = self.options[:except] || []
|
164
|
+
ret.map(&:to_sym)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
#
|
169
|
+
# Should we include this association in our hash?
|
170
|
+
#
|
171
|
+
# @param association [Symbol] Association name to check
|
172
|
+
#
|
173
|
+
# @return [Boolean] Whether or not to include it
|
174
|
+
def include_association?(association)
|
175
|
+
# if we have explicitly requested this association we include it
|
176
|
+
return true if self.included_associations.include?(association)
|
177
|
+
return true if self.changed_associations.include?(association)
|
178
|
+
# explicitly excluded
|
179
|
+
return false if self.excluded_keys.include?(association)
|
180
|
+
return false
|
181
|
+
end
|
182
|
+
|
183
|
+
#
|
184
|
+
# Should we include this attribute?
|
185
|
+
#
|
186
|
+
# @param attribute [String, Symbol] Field name
|
187
|
+
# @param val [Mixed] Field value
|
188
|
+
#
|
189
|
+
# @return [Boolean] Whether or not to include it
|
190
|
+
def include_attribute?(attribute, val)
|
191
|
+
attribute = attribute.to_sym
|
192
|
+
# explicitly included
|
193
|
+
return true if self.included_attributes.include?(attribute)
|
194
|
+
# explicitly excluded
|
195
|
+
return false if self.excluded_keys.include?(attribute)
|
196
|
+
# make sure it's public
|
197
|
+
return false unless self.public_attributes.include?(attribute)
|
198
|
+
# make sure it's not already accounted for in the URL
|
199
|
+
if self.is_prefix_field?(attribute)
|
200
|
+
return false
|
201
|
+
end
|
202
|
+
# check to make sure the value is something we want to send
|
203
|
+
return false unless self.check_blank_value(attribute, val)
|
204
|
+
|
205
|
+
# default to true
|
206
|
+
true
|
207
|
+
end
|
208
|
+
|
209
|
+
#
|
210
|
+
# Associations explicitly included by the caller
|
211
|
+
#
|
212
|
+
# @return [Array<Symbol>]
|
213
|
+
def included_associations
|
214
|
+
@included_associations ||= begin
|
215
|
+
self.options[:include_associations] ||= []
|
216
|
+
self.options[:include_associations].collect(&:to_sym)
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
#
|
221
|
+
# Attributes explicitly included by the caller
|
222
|
+
#
|
223
|
+
# @return [Array<Symbol>]
|
224
|
+
def included_attributes
|
225
|
+
@included_attributes ||= begin
|
226
|
+
ret = self.options[:include_extras] || []
|
227
|
+
ret.map(&:to_sym)
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
#
|
232
|
+
# Whether or not a given attribute is accounted for in the
|
233
|
+
# prefix for this class
|
234
|
+
#
|
235
|
+
# @param attribute [Symbol] Attribute to check
|
236
|
+
#
|
237
|
+
# @example
|
238
|
+
# class TestResource
|
239
|
+
# prefix '/belongs_to_objects/:id/test_resources'
|
240
|
+
# end
|
241
|
+
#
|
242
|
+
# tr = TestResource.new(:belongs_to_object_id => 10)
|
243
|
+
# Serializer.new(tr).to_hash #=> {}
|
244
|
+
#
|
245
|
+
# tr.save #=> makes a call to /belongs_to_objects/10/test_resources
|
246
|
+
#
|
247
|
+
# @return [Boolean]
|
248
|
+
def is_prefix_field?(attribute)
|
249
|
+
self.record.prefix_attribute_names.include?(attribute.to_sym)
|
250
|
+
end
|
251
|
+
|
252
|
+
#
|
253
|
+
# List of public attributes for the record
|
254
|
+
#
|
255
|
+
# @return [Array<Sumbol>]
|
256
|
+
def public_attributes
|
257
|
+
@public_attributes ||= begin
|
258
|
+
self.record.attributes.keys.symbolize_array.reject{ |k|
|
259
|
+
self.record.protected_attribute?(k)
|
260
|
+
}
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
@@ -5,6 +5,7 @@ require 'api_resource/typecasters/integer_typecaster'
|
|
5
5
|
require 'api_resource/typecasters/string_typecaster'
|
6
6
|
require 'api_resource/typecasters/time_typecaster'
|
7
7
|
require 'api_resource/typecasters/array_typecaster'
|
8
|
+
require 'api_resource/typecasters/unknown_typecaster'
|
8
9
|
|
9
10
|
# Apparently need to require the active_support class_attribute
|
10
11
|
require 'active_support/core_ext/class/attribute'
|
@@ -12,6 +13,15 @@ require 'active_support/core_ext/class/attribute'
|
|
12
13
|
|
13
14
|
module ApiResource
|
14
15
|
|
16
|
+
#
|
17
|
+
# Error raised when unable to find a typecaster for a given
|
18
|
+
# attribute
|
19
|
+
#
|
20
|
+
# @author [ejlangev]
|
21
|
+
#
|
22
|
+
class TypecasterNotFound < NoMethodError
|
23
|
+
end
|
24
|
+
|
15
25
|
module Typecast
|
16
26
|
|
17
27
|
extend ActiveSupport::Concern
|
@@ -64,9 +74,11 @@ module ApiResource
|
|
64
74
|
|
65
75
|
def default_typecasters
|
66
76
|
@default_typecasters ||= {
|
77
|
+
:array => ArrayTypecaster,
|
67
78
|
:boolean => BooleanTypecaster,
|
68
79
|
:bool => BooleanTypecaster,
|
69
80
|
:date => DateTypecaster,
|
81
|
+
:datetime => TimeTypecaster,
|
70
82
|
:decimal => FloatTypecaster,
|
71
83
|
:float => FloatTypecaster,
|
72
84
|
:integer => IntegerTypecaster,
|
@@ -74,8 +86,7 @@ module ApiResource
|
|
74
86
|
:string => StringTypecaster,
|
75
87
|
:text => StringTypecaster,
|
76
88
|
:time => TimeTypecaster,
|
77
|
-
:
|
78
|
-
:array => ArrayTypecaster,
|
89
|
+
:unknown => UnknownTypecaster
|
79
90
|
}
|
80
91
|
end
|
81
92
|
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module ApiResource
|
2
|
+
|
3
|
+
module Typecast
|
4
|
+
|
5
|
+
# The unknown typecaster which does not modify the value of
|
6
|
+
# the attribute in either direction. Keeps the interface consistent
|
7
|
+
# for unspecified typecasters
|
8
|
+
#
|
9
|
+
# @author [ejlangev]
|
10
|
+
#
|
11
|
+
module UnknownTypecaster
|
12
|
+
|
13
|
+
#
|
14
|
+
# Just returns what was passed in
|
15
|
+
# @param value [Object] The value to typecast
|
16
|
+
#
|
17
|
+
# @return [Object] An unmodified value
|
18
|
+
def self.from_api(value)
|
19
|
+
return value
|
20
|
+
end
|
21
|
+
|
22
|
+
#
|
23
|
+
# Just returns what was passed in
|
24
|
+
# @param value [Object] The value to typecast
|
25
|
+
#
|
26
|
+
# @return [Object] An unmodified value
|
27
|
+
def self.to_api(value)
|
28
|
+
return value
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/api_resource/version.rb
CHANGED
@@ -2,15 +2,15 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
module ApiResource
|
4
4
|
module Associations
|
5
|
-
|
5
|
+
|
6
6
|
describe HasManyRemoteObjectProxy do
|
7
7
|
|
8
|
-
before(:
|
8
|
+
before(:each) do
|
9
9
|
TestResource.reload_resource_definition
|
10
10
|
end
|
11
11
|
|
12
12
|
context "#<<" do
|
13
|
-
|
13
|
+
|
14
14
|
it "implements the shift operator" do
|
15
15
|
tr = TestResource.new
|
16
16
|
tr.has_many_objects << HasManyObject.new
|
@@ -10,7 +10,7 @@ describe "Associations" do
|
|
10
10
|
|
11
11
|
let(:ap) do
|
12
12
|
res = Associations::SingleObjectProxy.new(
|
13
|
-
"TestResource",
|
13
|
+
"TestResource",
|
14
14
|
HasManyObject.new
|
15
15
|
)
|
16
16
|
res.internal_object = {
|
@@ -130,7 +130,7 @@ describe "Associations" do
|
|
130
130
|
|
131
131
|
it "should be able to override into the root namespace by prefixing with ::" do
|
132
132
|
TestMod::InnerMod::InnerClass.belongs_to :test_resource, :class_name => "::TestResource"
|
133
|
-
TestMod::InnerMod::InnerClass.association_class_name(:test_resource).should eql("::TestResource")
|
133
|
+
TestMod::InnerMod::InnerClass.association_class_name(:test_resource).should eql("::TestResource")
|
134
134
|
end
|
135
135
|
|
136
136
|
end
|
@@ -170,7 +170,7 @@ describe "Associations" do
|
|
170
170
|
TestResource.scope :test_scope, {:item => "test"}
|
171
171
|
TestResource.scope?(:test_scope).should be_true
|
172
172
|
TestResource.scope_attributes(:test_scope).should eql({"item" => "test"})
|
173
|
-
end
|
173
|
+
end
|
174
174
|
|
175
175
|
it "should not propagate scopes from one class to another" do
|
176
176
|
|
@@ -239,7 +239,7 @@ describe "Associations" do
|
|
239
239
|
|
240
240
|
it "should be able to extract a service uri from the contents hash" do
|
241
241
|
ap = Associations::SingleObjectProxy.new(
|
242
|
-
"TestResource",
|
242
|
+
"TestResource",
|
243
243
|
HasManyObject.new
|
244
244
|
)
|
245
245
|
ap.internal_object = {
|
@@ -248,12 +248,12 @@ describe "Associations" do
|
|
248
248
|
ap.remote_path.should eql("/path")
|
249
249
|
end
|
250
250
|
|
251
|
-
it "should be able to recognize the attributes of an object
|
251
|
+
it "should be able to recognize the attributes of an object
|
252
252
|
and not make them scopes" do
|
253
|
-
|
253
|
+
|
254
254
|
TestResource.define_attributes :test
|
255
255
|
ap = Associations::SingleObjectProxy.new(
|
256
|
-
"TestResource",
|
256
|
+
"TestResource",
|
257
257
|
HasManyObject.new
|
258
258
|
)
|
259
259
|
|
@@ -329,7 +329,7 @@ describe "Associations" do
|
|
329
329
|
|
330
330
|
it "should be able to recognize settings from a hash" do
|
331
331
|
ap = Associations::MultiObjectProxy.new(
|
332
|
-
"TestResource",
|
332
|
+
"TestResource",
|
333
333
|
BelongsToObject
|
334
334
|
)
|
335
335
|
ap.internal_object = {:service_uri => "/route"}
|
@@ -338,7 +338,7 @@ describe "Associations" do
|
|
338
338
|
|
339
339
|
it "should be able to recognize settings from a hash as a string" do
|
340
340
|
ap = Associations::MultiObjectProxy.new(
|
341
|
-
"TestResource",
|
341
|
+
"TestResource",
|
342
342
|
BelongsToObject
|
343
343
|
)
|
344
344
|
ap.internal_object = {"service_uri" => "/route"}
|
@@ -348,22 +348,13 @@ describe "Associations" do
|
|
348
348
|
it "should recognize settings with differing 'service_uri' names" do
|
349
349
|
Associations::MultiObjectProxy.remote_path_element = :the_element
|
350
350
|
ap = Associations::MultiObjectProxy.new(
|
351
|
-
"TestResource",
|
351
|
+
"TestResource",
|
352
352
|
BelongsToObject
|
353
353
|
)
|
354
354
|
ap.internal_object = {:the_element => "/route"}
|
355
355
|
ap.remote_path.should eql("/route")
|
356
356
|
end
|
357
357
|
|
358
|
-
it "should include the foreign_key_id when saving" do
|
359
|
-
tr = TestResource.new.tap do |tr|
|
360
|
-
tr.stubs(:id => 123)
|
361
|
-
end
|
362
|
-
tr.has_many_object_ids = [4]
|
363
|
-
hsh = tr.serializable_hash
|
364
|
-
hsh[:has_many_object_ids].should eql([4])
|
365
|
-
end
|
366
|
-
|
367
358
|
it "should handle loading attributes from the remote" do
|
368
359
|
tr = TestResource.instantiate_record({:has_many_object_ids => [3]})
|
369
360
|
tr.has_many_object_ids.should eql([3])
|
@@ -379,51 +370,6 @@ describe "Associations" do
|
|
379
370
|
|
380
371
|
end
|
381
372
|
|
382
|
-
describe "Selecting scopes" do
|
383
|
-
|
384
|
-
before(:all) do
|
385
|
-
ScopeResource.class_eval do
|
386
|
-
scope :no_arg, {}
|
387
|
-
scope :one_arg, {:id => :req}
|
388
|
-
scope :one_array_arg, {:ids => :req}
|
389
|
-
scope :two_args, {:page => :req, :per_page => :req}
|
390
|
-
scope :opt_args, {:arg1 => :opt}
|
391
|
-
scope :var_args, {:ids => :rest}
|
392
|
-
scope :mix_args, {:id => :req, :vararg => :rest}
|
393
|
-
end
|
394
|
-
end
|
395
|
-
|
396
|
-
it "should be able to query scopes on the current model" do
|
397
|
-
ScopeResource.no_arg.to_query.should eql(
|
398
|
-
"no_arg=true"
|
399
|
-
)
|
400
|
-
ScopeResource.one_arg(5).to_query.should eql(
|
401
|
-
"one_arg[id]=5"
|
402
|
-
)
|
403
|
-
ScopeResource.one_array_arg([3, 5]).to_query.should eql(
|
404
|
-
"one_array_arg[ids][]=3&one_array_arg[ids][]=5"
|
405
|
-
)
|
406
|
-
ScopeResource.two_args(1, 20).to_query.should eql(
|
407
|
-
"two_args[page]=1&two_args[per_page]=20"
|
408
|
-
)
|
409
|
-
$DEB = true
|
410
|
-
ScopeResource.opt_args.to_query.should eql(
|
411
|
-
"opt_args=true"
|
412
|
-
)
|
413
|
-
ScopeResource.opt_args(3).to_query.should eql(
|
414
|
-
"opt_args[arg1]=3"
|
415
|
-
)
|
416
|
-
ScopeResource.var_args(1, 2).to_query.should eql(
|
417
|
-
"var_args[ids][]=1&var_args[ids][]=2"
|
418
|
-
)
|
419
|
-
args = ["a", {:opt1 => 1}, {:opt2 => 2}]
|
420
|
-
ScopeResource.mix_args(*args).to_query.should eql(
|
421
|
-
"mix_args[id]=a&mix_args[vararg][][opt1]=1&mix_args[vararg][][opt2]=2"
|
422
|
-
)
|
423
|
-
end
|
424
|
-
end
|
425
|
-
|
426
|
-
|
427
373
|
end
|
428
374
|
|
429
375
|
describe "Loading and Caching loaded data" do
|
@@ -452,7 +398,7 @@ describe "Associations" do
|
|
452
398
|
|
453
399
|
it "should proxy unknown methods to the object loading if it hasn't already" do
|
454
400
|
ap = Associations::SingleObjectProxy.new(
|
455
|
-
"TestResource",
|
401
|
+
"TestResource",
|
456
402
|
HasManyObject.new
|
457
403
|
)
|
458
404
|
ap.internal_object = {
|
@@ -465,7 +411,7 @@ describe "Associations" do
|
|
465
411
|
|
466
412
|
it "should load scopes with caching" do
|
467
413
|
ap = Associations::SingleObjectProxy.new(
|
468
|
-
"TestResource",
|
414
|
+
"TestResource",
|
469
415
|
HasManyObject.new
|
470
416
|
)
|
471
417
|
ap.internal_object = {
|
@@ -481,7 +427,7 @@ describe "Associations" do
|
|
481
427
|
|
482
428
|
it "should check that ttl matches the expiration parameter" do
|
483
429
|
ap = Associations::SingleObjectProxy.new(
|
484
|
-
"TestResource",
|
430
|
+
"TestResource",
|
485
431
|
HasManyObject.new
|
486
432
|
)
|
487
433
|
ap.internal_object = {
|
@@ -492,7 +438,7 @@ describe "Associations" do
|
|
492
438
|
|
493
439
|
it "should cache scopes when caching enabled" do
|
494
440
|
ap = Associations::SingleObjectProxy.new(
|
495
|
-
"TestResource",
|
441
|
+
"TestResource",
|
496
442
|
HasManyObject.new
|
497
443
|
)
|
498
444
|
ap.internal_object = {
|
@@ -503,7 +449,7 @@ describe "Associations" do
|
|
503
449
|
|
504
450
|
it "should be able to clear it's loading cache" do
|
505
451
|
ap = Associations::SingleObjectProxy.new(
|
506
|
-
"TestResource",
|
452
|
+
"TestResource",
|
507
453
|
HasManyObject.new
|
508
454
|
)
|
509
455
|
ap.internal_object = {
|
@@ -523,7 +469,7 @@ describe "Associations" do
|
|
523
469
|
|
524
470
|
it "should be able to reload a single-object association" do
|
525
471
|
ap = Associations::SingleObjectProxy.new(
|
526
|
-
"TestResource",
|
472
|
+
"TestResource",
|
527
473
|
HasManyObject.new
|
528
474
|
)
|
529
475
|
ap.internal_object = {
|
@@ -543,9 +489,9 @@ describe "Associations" do
|
|
543
489
|
end
|
544
490
|
|
545
491
|
it "should be able to reload a multi-object association" do
|
546
|
-
|
492
|
+
|
547
493
|
ap = Associations::MultiObjectProxy.new(
|
548
|
-
"TestResource",
|
494
|
+
"TestResource",
|
549
495
|
BelongsToObject.new
|
550
496
|
)
|
551
497
|
ap.internal_object = {
|
@@ -568,7 +514,7 @@ describe "Associations" do
|
|
568
514
|
|
569
515
|
it "should be able to load 'all'" do
|
570
516
|
ap = Associations::MultiObjectProxy.new(
|
571
|
-
"TestResource",
|
517
|
+
"TestResource",
|
572
518
|
BelongsToObject.new
|
573
519
|
)
|
574
520
|
ap.internal_object = {
|
@@ -581,7 +527,7 @@ describe "Associations" do
|
|
581
527
|
|
582
528
|
it "should be able to load a scope" do
|
583
529
|
ap = Associations::MultiObjectProxy.new(
|
584
|
-
"TestResource",
|
530
|
+
"TestResource",
|
585
531
|
BelongsToObject.new
|
586
532
|
)
|
587
533
|
ap.internal_object = {
|
@@ -595,7 +541,7 @@ describe "Associations" do
|
|
595
541
|
|
596
542
|
it "should be able to load a chain of scopes" do
|
597
543
|
ap = Associations::MultiObjectProxy.new(
|
598
|
-
"TestResource",
|
544
|
+
"TestResource",
|
599
545
|
BelongsToObject.new
|
600
546
|
)
|
601
547
|
ap.internal_object = {
|
@@ -610,7 +556,7 @@ describe "Associations" do
|
|
610
556
|
it "should be able to clear it's loading cache" do
|
611
557
|
|
612
558
|
ap = Associations::MultiObjectProxy.new(
|
613
|
-
"TestResource",
|
559
|
+
"TestResource",
|
614
560
|
BelongsToObject.new
|
615
561
|
)
|
616
562
|
ap.internal_object = {
|
@@ -628,7 +574,7 @@ describe "Associations" do
|
|
628
574
|
|
629
575
|
it "should be enumerable" do
|
630
576
|
ap = Associations::MultiObjectProxy.new(
|
631
|
-
"TestResource",
|
577
|
+
"TestResource",
|
632
578
|
BelongsToObject.new
|
633
579
|
)
|
634
580
|
ap.internal_object = {
|
@@ -698,11 +644,11 @@ describe "Associations" do
|
|
698
644
|
TestResource.reload_class_attributes
|
699
645
|
end
|
700
646
|
|
701
|
-
it "should assign associations to the correct
|
647
|
+
it "should assign associations to the correct
|
702
648
|
type on initialization" do
|
703
|
-
|
649
|
+
|
704
650
|
tr = TestResource.new(
|
705
|
-
:has_one_object => {:color => "Blue"},
|
651
|
+
:has_one_object => {:color => "Blue"},
|
706
652
|
:belongs_to_object => {:zip => "11201"},
|
707
653
|
:has_many_objects => [{:name => "Dan"}]
|
708
654
|
)
|
@@ -728,14 +674,14 @@ describe "Associations" do
|
|
728
674
|
it "should assign associations to the correct type when setting attributes directly" do
|
729
675
|
tr = TestResource.new()
|
730
676
|
tr.has_one_object = {:name => "Dan"}
|
731
|
-
tr.belongs_to_object = {:name => "Dan"}
|
677
|
+
tr.belongs_to_object = {:name => "Dan"}
|
732
678
|
|
733
679
|
tr.has_one_object.internal_object.should be_instance_of HasOneObject
|
734
680
|
tr.belongs_to_object.internal_object.should be_instance_of BelongsToObject
|
735
681
|
end
|
736
682
|
|
737
683
|
it "should be able to reload a single-object association" do
|
738
|
-
|
684
|
+
|
739
685
|
tr = TestResource.new()
|
740
686
|
tr.has_one_object = {:color => "Blue"}
|
741
687
|
|
@@ -766,7 +712,7 @@ describe "Associations" do
|
|
766
712
|
end
|
767
713
|
|
768
714
|
it "should be able to reload a multi-object association" do
|
769
|
-
|
715
|
+
|
770
716
|
# do this to load the resource definition
|
771
717
|
TestResource.reload_resource_definition
|
772
718
|
HasManyObject.reload_resource_definition
|
@@ -778,7 +724,7 @@ describe "Associations" do
|
|
778
724
|
tr.has_many_objects.should be_blank
|
779
725
|
end
|
780
726
|
|
781
|
-
it "should be able to override service_uri for a
|
727
|
+
it "should be able to override service_uri for a
|
782
728
|
multi-object association" do
|
783
729
|
|
784
730
|
tr = TestResource.new
|
@@ -788,7 +734,7 @@ describe "Associations" do
|
|
788
734
|
|
789
735
|
end
|
790
736
|
|
791
|
-
it "should be able to override service_uri for a multi-object
|
737
|
+
it "should be able to override service_uri for a multi-object
|
792
738
|
association when loaded with instantiate_record" do
|
793
739
|
|
794
740
|
tr = TestResource.instantiate_record(
|
@@ -804,17 +750,25 @@ describe "Associations" do
|
|
804
750
|
before(:all) do
|
805
751
|
require 'active_record'
|
806
752
|
db_path = File.expand_path(File.dirname(__FILE__) + "/../tmp/api_resource_test_db.sqlite")
|
807
|
-
ActiveRecord::Base.establish_connection({"adapter" => "sqlite3", "database" => db_path})
|
808
|
-
ActiveRecord::Base.connection.create_table(:test_ars, :force => true) do |t|
|
809
|
-
|
810
|
-
end
|
753
|
+
# ActiveRecord::Base.establish_connection({"adapter" => "sqlite3", "database" => db_path})
|
754
|
+
# ActiveRecord::Base.connection.create_table(:test_ars, :force => true) do |t|
|
755
|
+
# t.integer(:test_resource_id)
|
756
|
+
# end
|
811
757
|
ApiResource::Associations.activate_active_record
|
812
758
|
TestAR = Class.new(ActiveRecord::Base)
|
813
|
-
TestAR.class_eval do
|
759
|
+
TestAR.class_eval do
|
814
760
|
belongs_to_remote :my_favorite_thing, :class_name => "TestClassYay"
|
815
761
|
end
|
816
762
|
HasManyObject.reload_resource_definition
|
817
763
|
end
|
764
|
+
|
765
|
+
before(:each) do
|
766
|
+
TestAR.stubs(:columns).returns([])
|
767
|
+
TestAR.stubs(:connection).returns(
|
768
|
+
:schema_cache => stub(:table_exists => true)
|
769
|
+
)
|
770
|
+
end
|
771
|
+
|
818
772
|
it "should define remote association types for AR" do
|
819
773
|
[:has_many_remote, :belongs_to_remote, :has_one_remote].each do |assoc|
|
820
774
|
ActiveRecord::Base.singleton_methods.should include assoc
|
@@ -835,7 +789,7 @@ describe "Associations" do
|
|
835
789
|
end
|
836
790
|
end
|
837
791
|
context "Belongs To" do
|
838
|
-
before(:all) do
|
792
|
+
before(:all) do
|
839
793
|
TestAR.class_eval do
|
840
794
|
belongs_to_remote :test_resource
|
841
795
|
end
|
@@ -852,11 +806,12 @@ describe "Associations" do
|
|
852
806
|
before(:all) do
|
853
807
|
TestAR.class_eval do
|
854
808
|
has_one_remote :test_resource
|
855
|
-
has_one_remote :other_test_resource,
|
809
|
+
has_one_remote :other_test_resource,
|
856
810
|
:class_name => "TestResource"
|
857
811
|
end
|
858
812
|
end
|
859
813
|
it "should attempt to load a single remote object for a has_one relationship" do
|
814
|
+
|
860
815
|
tar = TestAR.new
|
861
816
|
tar.stubs(:id).returns(1)
|
862
817
|
TestResource.connection.expects(:get)
|
@@ -907,8 +862,8 @@ describe "Associations" do
|
|
907
862
|
end
|
908
863
|
end
|
909
864
|
context "Has Many Through" do
|
910
|
-
before(:all) do
|
911
|
-
TestAR.class_eval do
|
865
|
+
before(:all) do
|
866
|
+
TestAR.class_eval do
|
912
867
|
self.extend ApiResource::Associations::HasManyThroughRemoteObjectProxy
|
913
868
|
has_many :test_throughs
|
914
869
|
has_many_through_remote(:belongs_to_objects, :through => :test_throughs)
|