api_resource 0.6.18 → 0.6.19
Sign up to get free protection for your applications and to get access to all the features.
- 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)
|