api_resource 0.6.0 → 0.6.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/.gitignore +4 -0
- data/Gemfile.lock +27 -29
- data/README.md +1 -1
- data/api_resource.gemspec +2 -4
- data/lib/api_resource/associations.rb +25 -5
- data/lib/api_resource/associations/has_one_remote_object_proxy.rb +2 -1
- data/lib/api_resource/associations/multi_object_proxy.rb +13 -4
- data/lib/api_resource/associations/single_object_proxy.rb +9 -2
- data/lib/api_resource/base.rb +12 -12
- data/lib/api_resource/conditions.rb +2 -0
- data/lib/api_resource/conditions/abstract_condition.rb +24 -5
- data/lib/api_resource/conditions/association_condition.rb +2 -1
- data/lib/api_resource/conditions/multi_object_association_condition.rb +1 -1
- data/lib/api_resource/conditions/single_object_association_condition.rb +1 -1
- data/lib/api_resource/connection.rb +1 -2
- data/lib/api_resource/finders.rb +41 -49
- data/lib/api_resource/finders/abstract_finder.rb +26 -10
- data/lib/api_resource/finders/multi_object_association_finder.rb +16 -5
- data/lib/api_resource/finders/resource_finder.rb +31 -15
- data/lib/api_resource/finders/single_finder.rb +45 -0
- data/lib/api_resource/finders/single_object_association_finder.rb +14 -4
- data/lib/api_resource/version.rb +1 -1
- data/spec/lib/associations_spec.rb +2 -0
- data/spec/lib/base_spec.rb +3 -0
- data/spec/lib/conditions/abstract_conditions_spec.rb +2 -2
- data/spec/lib/finders/multi_object_association_finder_spec.rb +2 -2
- data/spec/lib/finders/resource_finder_spec.rb +29 -40
- data/spec/lib/finders/single_object_association_finder_spec.rb +2 -2
- data/spec/lib/finders_spec.rb +38 -0
- data/spec/lib/prefixes_spec.rb +5 -8
- metadata +10 -39
data/.gitignore
CHANGED
data/Gemfile.lock
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
api_resource (0.
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
api_resource (0.6.0)
|
|
5
|
+
colorize
|
|
6
|
+
differ
|
|
7
7
|
json
|
|
8
8
|
log4r
|
|
9
9
|
rails
|
|
@@ -12,12 +12,12 @@ PATH
|
|
|
12
12
|
GEM
|
|
13
13
|
remote: https://rubygems.org/
|
|
14
14
|
specs:
|
|
15
|
-
actionmailer (3.2.
|
|
16
|
-
actionpack (= 3.2.
|
|
15
|
+
actionmailer (3.2.10)
|
|
16
|
+
actionpack (= 3.2.10)
|
|
17
17
|
mail (~> 2.4.4)
|
|
18
|
-
actionpack (3.2.
|
|
19
|
-
activemodel (= 3.2.
|
|
20
|
-
activesupport (= 3.2.
|
|
18
|
+
actionpack (3.2.10)
|
|
19
|
+
activemodel (= 3.2.10)
|
|
20
|
+
activesupport (= 3.2.10)
|
|
21
21
|
builder (~> 3.0.0)
|
|
22
22
|
erubis (~> 2.7.0)
|
|
23
23
|
journey (~> 1.0.4)
|
|
@@ -25,18 +25,18 @@ GEM
|
|
|
25
25
|
rack-cache (~> 1.2)
|
|
26
26
|
rack-test (~> 0.6.1)
|
|
27
27
|
sprockets (~> 2.2.1)
|
|
28
|
-
activemodel (3.2.
|
|
29
|
-
activesupport (= 3.2.
|
|
28
|
+
activemodel (3.2.10)
|
|
29
|
+
activesupport (= 3.2.10)
|
|
30
30
|
builder (~> 3.0.0)
|
|
31
|
-
activerecord (3.2.
|
|
32
|
-
activemodel (= 3.2.
|
|
33
|
-
activesupport (= 3.2.
|
|
31
|
+
activerecord (3.2.10)
|
|
32
|
+
activemodel (= 3.2.10)
|
|
33
|
+
activesupport (= 3.2.10)
|
|
34
34
|
arel (~> 3.0.2)
|
|
35
35
|
tzinfo (~> 0.3.29)
|
|
36
|
-
activeresource (3.2.
|
|
37
|
-
activemodel (= 3.2.
|
|
38
|
-
activesupport (= 3.2.
|
|
39
|
-
activesupport (3.2.
|
|
36
|
+
activeresource (3.2.10)
|
|
37
|
+
activemodel (= 3.2.10)
|
|
38
|
+
activesupport (= 3.2.10)
|
|
39
|
+
activesupport (3.2.10)
|
|
40
40
|
i18n (~> 0.6)
|
|
41
41
|
multi_json (~> 1.0)
|
|
42
42
|
arel (3.0.2)
|
|
@@ -106,17 +106,17 @@ GEM
|
|
|
106
106
|
rack
|
|
107
107
|
rack-test (0.6.2)
|
|
108
108
|
rack (>= 1.0)
|
|
109
|
-
rails (3.2.
|
|
110
|
-
actionmailer (= 3.2.
|
|
111
|
-
actionpack (= 3.2.
|
|
112
|
-
activerecord (= 3.2.
|
|
113
|
-
activeresource (= 3.2.
|
|
114
|
-
activesupport (= 3.2.
|
|
109
|
+
rails (3.2.10)
|
|
110
|
+
actionmailer (= 3.2.10)
|
|
111
|
+
actionpack (= 3.2.10)
|
|
112
|
+
activerecord (= 3.2.10)
|
|
113
|
+
activeresource (= 3.2.10)
|
|
114
|
+
activesupport (= 3.2.10)
|
|
115
115
|
bundler (~> 1.0)
|
|
116
|
-
railties (= 3.2.
|
|
117
|
-
railties (3.2.
|
|
118
|
-
actionpack (= 3.2.
|
|
119
|
-
activesupport (= 3.2.
|
|
116
|
+
railties (= 3.2.10)
|
|
117
|
+
railties (3.2.10)
|
|
118
|
+
actionpack (= 3.2.10)
|
|
119
|
+
activesupport (= 3.2.10)
|
|
120
120
|
rack-ssl (~> 1.3.2)
|
|
121
121
|
rake (>= 0.8.7)
|
|
122
122
|
rdoc (~> 3.4)
|
|
@@ -162,8 +162,6 @@ PLATFORMS
|
|
|
162
162
|
|
|
163
163
|
DEPENDENCIES
|
|
164
164
|
api_resource!
|
|
165
|
-
colorize
|
|
166
|
-
differ
|
|
167
165
|
faker
|
|
168
166
|
flay
|
|
169
167
|
flog
|
data/README.md
CHANGED
data/api_resource.gemspec
CHANGED
|
@@ -33,14 +33,12 @@ Gem::Specification.new do |gem|
|
|
|
33
33
|
gem.add_development_dependency "rb-fsevent"
|
|
34
34
|
gem.add_development_dependency "simplecov"
|
|
35
35
|
# stuff that seems like crap
|
|
36
|
-
gem.add_development_dependency "differ"
|
|
37
|
-
gem.add_development_dependency "colorize"
|
|
38
36
|
gem.add_development_dependency "sqlite3"
|
|
39
37
|
|
|
40
38
|
gem.add_dependency "rails"
|
|
41
|
-
gem.add_dependency "activemodel"
|
|
42
|
-
gem.add_dependency "activesupport"
|
|
43
39
|
gem.add_dependency "json"
|
|
44
40
|
gem.add_dependency "rest-client"
|
|
45
41
|
gem.add_dependency "log4r"
|
|
42
|
+
gem.add_dependency "differ"
|
|
43
|
+
gem.add_dependency "colorize"
|
|
46
44
|
end
|
|
@@ -183,10 +183,19 @@ module ApiResource
|
|
|
183
183
|
if self.ancestors.include?(ApiResource::Base)
|
|
184
184
|
define_attribute_method(assoc_name)
|
|
185
185
|
end
|
|
186
|
+
|
|
187
|
+
id_method_name = assoc_name.to_s.singularize + "_id"
|
|
188
|
+
|
|
189
|
+
if assoc_type.to_s == "has_many"
|
|
190
|
+
id_method_name += "s"
|
|
191
|
+
end
|
|
186
192
|
|
|
187
193
|
# TODO: Come up with a better implementation for the foreign key thing
|
|
188
194
|
# implement the rest of the active record methods, refactor this into something
|
|
189
|
-
# a
|
|
195
|
+
# a little bit more sensible
|
|
196
|
+
|
|
197
|
+
# TODO: This should support saving the ids when they are modified and saving anything
|
|
198
|
+
# that is not created, associations need to be their own object
|
|
190
199
|
self.class_eval <<-EOE, __FILE__, __LINE__ + 1
|
|
191
200
|
def #{assoc_name}
|
|
192
201
|
@attributes_cache[:#{assoc_name}] ||= begin
|
|
@@ -200,17 +209,28 @@ module ApiResource
|
|
|
200
209
|
instance
|
|
201
210
|
end
|
|
202
211
|
end
|
|
203
|
-
def #{assoc_name}=(val)
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
212
|
+
def #{assoc_name}=(val, force = true)
|
|
213
|
+
if !force
|
|
214
|
+
#{assoc_name}_will_change!
|
|
215
|
+
elsif self.#{assoc_name}.internal_object != val
|
|
216
|
+
#{assoc_name}_will_change!
|
|
207
217
|
end
|
|
218
|
+
# This should not force a load
|
|
208
219
|
self.#{assoc_name}.internal_object = val
|
|
209
220
|
end
|
|
210
221
|
def #{assoc_name}?
|
|
211
222
|
self.#{assoc_name}.internal_object.present?
|
|
212
223
|
end
|
|
213
224
|
|
|
225
|
+
def #{id_method_name}
|
|
226
|
+
@attributes_cache[:#{id_method_name}] ||=
|
|
227
|
+
@attributes[:#{id_method_name}] || self.#{assoc_name}.collect(&:id)
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def #{id_method_name}=(val)
|
|
231
|
+
@attributes_cache[:#{id_method_name}] = val
|
|
232
|
+
end
|
|
233
|
+
|
|
214
234
|
EOE
|
|
215
235
|
end
|
|
216
236
|
|
|
@@ -32,13 +32,19 @@ module ApiResource
|
|
|
32
32
|
|
|
33
33
|
def internal_object=(contents)
|
|
34
34
|
# if we were passed in a service uri, stop here
|
|
35
|
-
|
|
35
|
+
# but if we have a service uri already then don't overwrite
|
|
36
|
+
unless self.remote_path.present?
|
|
37
|
+
return true if self.set_remote_path(contents)
|
|
38
|
+
end
|
|
36
39
|
|
|
37
40
|
if contents.try(:first).is_a?(self.klass)
|
|
38
|
-
|
|
41
|
+
@loaded = true
|
|
42
|
+
return @internal_object = contents
|
|
39
43
|
elsif contents.instance_of?(self.class)
|
|
44
|
+
@loaded = true
|
|
40
45
|
return @internal_object = contents.internal_object
|
|
41
46
|
elsif contents.is_a?(Array)
|
|
47
|
+
@loaded = true
|
|
42
48
|
return @internal_object = self.klass.instantiate_collection(
|
|
43
49
|
contents
|
|
44
50
|
)
|
|
@@ -70,12 +76,15 @@ module ApiResource
|
|
|
70
76
|
protected
|
|
71
77
|
|
|
72
78
|
def to_condition
|
|
73
|
-
|
|
79
|
+
obj = nil
|
|
80
|
+
obj = self.internal_object if self.loaded?
|
|
81
|
+
ApiResource::Conditions::MultiObjectAssociationCondition.new(self.klass, self.remote_path, obj)
|
|
74
82
|
end
|
|
75
83
|
|
|
76
84
|
def load(opts = {})
|
|
85
|
+
res = self.to_condition.load
|
|
77
86
|
@loaded = true
|
|
78
|
-
|
|
87
|
+
res
|
|
79
88
|
end
|
|
80
89
|
|
|
81
90
|
def set_remote_path(opts)
|
|
@@ -22,8 +22,10 @@ module ApiResource
|
|
|
22
22
|
|
|
23
23
|
def internal_object=(contents)
|
|
24
24
|
if contents.is_a?(self.klass) || contents.nil?
|
|
25
|
+
@loaded = true
|
|
25
26
|
return @internal_object = contents
|
|
26
27
|
elsif contents.is_a?(self.class)
|
|
28
|
+
@loaded = true
|
|
27
29
|
return @internal_object = contents.internal_object
|
|
28
30
|
# a Hash may be attributes and/or a service_uri
|
|
29
31
|
elsif contents.is_a?(Hash)
|
|
@@ -32,6 +34,7 @@ module ApiResource
|
|
|
32
34
|
self.class.remote_path_element.to_sym
|
|
33
35
|
)
|
|
34
36
|
if contents.present?
|
|
37
|
+
@loaded = true
|
|
35
38
|
return @internal_object = self.klass.instantiate_record(contents)
|
|
36
39
|
end
|
|
37
40
|
else
|
|
@@ -58,13 +61,17 @@ module ApiResource
|
|
|
58
61
|
protected
|
|
59
62
|
|
|
60
63
|
def to_condition
|
|
61
|
-
|
|
64
|
+
obj = nil
|
|
65
|
+
obj = self.internal_object if self.loaded?
|
|
66
|
+
ApiResource::Conditions::SingleObjectAssociationCondition.new(self.klass, self.remote_path, obj)
|
|
62
67
|
end
|
|
63
68
|
|
|
64
69
|
# Should make a proper conditions object and call find on it
|
|
70
|
+
# It MUST set loaded to true after calling load
|
|
65
71
|
def load(opts = {})
|
|
72
|
+
res = self.to_condition.load
|
|
66
73
|
@loaded = true
|
|
67
|
-
|
|
74
|
+
res
|
|
68
75
|
end
|
|
69
76
|
|
|
70
77
|
|
data/lib/api_resource/base.rb
CHANGED
|
@@ -341,6 +341,18 @@ module ApiResource
|
|
|
341
341
|
connection.delete(element_path(id, options))
|
|
342
342
|
end
|
|
343
343
|
|
|
344
|
+
# split an option hash into two hashes, one containing the prefix options,
|
|
345
|
+
# and the other containing the leftovers.
|
|
346
|
+
def split_options(options = {})
|
|
347
|
+
prefix_options, query_options = {}, {}
|
|
348
|
+
(options || {}).each do |key, value|
|
|
349
|
+
next if key.blank?
|
|
350
|
+
(prefix_parameters.include?(key.to_sym) ? prefix_options : query_options)[key.to_sym] = value
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
[ prefix_options, query_options ]
|
|
354
|
+
end
|
|
355
|
+
|
|
344
356
|
protected
|
|
345
357
|
def method_missing(meth, *args, &block)
|
|
346
358
|
# make one attempt to load remote attrs
|
|
@@ -372,18 +384,6 @@ module ApiResource
|
|
|
372
384
|
"?#{options.to_query}" unless options.nil? || options.empty?
|
|
373
385
|
end
|
|
374
386
|
|
|
375
|
-
# split an option hash into two hashes, one containing the prefix options,
|
|
376
|
-
# and the other containing the leftovers.
|
|
377
|
-
def split_options(options = {})
|
|
378
|
-
prefix_options, query_options = {}, {}
|
|
379
|
-
(options || {}).each do |key, value|
|
|
380
|
-
next if key.blank?
|
|
381
|
-
(prefix_parameters.include?(key.to_sym) ? prefix_options : query_options)[key.to_sym] = value
|
|
382
|
-
end
|
|
383
|
-
|
|
384
|
-
[ prefix_options, query_options ]
|
|
385
|
-
end
|
|
386
|
-
|
|
387
387
|
def uri_parser
|
|
388
388
|
@uri_parser ||= URI.const_defined?(:Parser) ? URI::Parser.new : URI
|
|
389
389
|
end
|
|
@@ -21,6 +21,8 @@ module ApiResource
|
|
|
21
21
|
@klass = klass
|
|
22
22
|
|
|
23
23
|
@conditions = args.with_indifferent_access
|
|
24
|
+
|
|
25
|
+
@klass.load_resource_definition
|
|
24
26
|
end
|
|
25
27
|
|
|
26
28
|
def each(&block)
|
|
@@ -48,14 +50,31 @@ module ApiResource
|
|
|
48
50
|
end
|
|
49
51
|
|
|
50
52
|
def internal_object
|
|
51
|
-
@internal_object
|
|
52
|
-
|
|
53
|
-
|
|
53
|
+
return @internal_object if @loaded
|
|
54
|
+
@internal_object = self.instantiate_finder.load
|
|
55
|
+
@loaded = true
|
|
56
|
+
@internal_object
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def all(*args)
|
|
60
|
+
if args.blank?
|
|
61
|
+
self.internal_object
|
|
62
|
+
else
|
|
63
|
+
self.find(*([:all] + args))
|
|
54
64
|
end
|
|
55
65
|
end
|
|
56
66
|
|
|
57
|
-
|
|
58
|
-
|
|
67
|
+
# implement find that accepts an optional
|
|
68
|
+
# condition object
|
|
69
|
+
def find(*args)
|
|
70
|
+
self.klass.find(*(args + [self]))
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# TODO: review the hierarchy that makes this necessary
|
|
74
|
+
# consider changing it to alias method
|
|
75
|
+
def load
|
|
76
|
+
self.internal_object
|
|
77
|
+
end
|
|
59
78
|
|
|
60
79
|
def loaded?
|
|
61
80
|
@loaded == true
|
|
@@ -4,11 +4,12 @@ module ApiResource
|
|
|
4
4
|
|
|
5
5
|
class AssociationCondition < AbstractCondition
|
|
6
6
|
|
|
7
|
-
def initialize(klass, service_uri)
|
|
7
|
+
def initialize(klass, service_uri, internal_object= nil)
|
|
8
8
|
super({}, klass)
|
|
9
9
|
|
|
10
10
|
@assocaiton = true
|
|
11
11
|
@remote_path = service_uri
|
|
12
|
+
@internal_object = internal_object
|
|
12
13
|
end
|
|
13
14
|
|
|
14
15
|
end
|
|
@@ -119,8 +119,7 @@ module ApiResource
|
|
|
119
119
|
ActiveSupport::Notifications.instrument("request.api_resource") do |payload|
|
|
120
120
|
|
|
121
121
|
# debug logging
|
|
122
|
-
ApiResource.logger.
|
|
123
|
-
|
|
122
|
+
ApiResource.logger.info("#{method.to_s.upcase} #{site.scheme}://#{site.host}:#{site.port}#{path}")
|
|
124
123
|
payload[:method] = method
|
|
125
124
|
payload[:request_uri] = "#{site.scheme}://#{site.host}:#{site.port}#{path}"
|
|
126
125
|
payload[:result] = http(path).send(method, *arguments)
|
data/lib/api_resource/finders.rb
CHANGED
|
@@ -7,6 +7,7 @@ module ApiResource
|
|
|
7
7
|
|
|
8
8
|
autoload :AbstractFinder
|
|
9
9
|
autoload :ResourceFinder
|
|
10
|
+
autoload :SingleFinder
|
|
10
11
|
autoload :SingleObjectAssociationFinder
|
|
11
12
|
autoload :MultiObjectAssociationFinder
|
|
12
13
|
|
|
@@ -16,22 +17,54 @@ module ApiResource
|
|
|
16
17
|
# It accepts arguments of the form "scope", "options={}"
|
|
17
18
|
# where options can be standard rails options or :expires_in.
|
|
18
19
|
# If :expires_in is set, it caches it for expires_in seconds.
|
|
19
|
-
def find(*arguments)
|
|
20
20
|
|
|
21
|
+
# Need to support the following cases
|
|
22
|
+
# => 1) Klass.find(1)
|
|
23
|
+
# => 2) Klass.find(:all, :params => {a => b})
|
|
24
|
+
# => 3) Klass.find(:first, :params => {a => b})
|
|
25
|
+
# => 4) Klass.includes(:assoc).find(1)
|
|
26
|
+
# => 5) Klass.active.find(1)
|
|
27
|
+
# => 6) Klass.includes(:assoc).find(:all, a => b)
|
|
28
|
+
def find(*arguments)
|
|
21
29
|
# make sure we have class data before loading
|
|
22
30
|
self.load_resource_definition
|
|
23
31
|
|
|
24
32
|
scope = arguments.slice!(0)
|
|
25
33
|
options = arguments.slice!(0) || {}
|
|
26
|
-
|
|
27
|
-
|
|
34
|
+
cond = arguments.slice!(0)
|
|
35
|
+
|
|
36
|
+
# TODO: Make this into a class attribute properly (if it isn't already)
|
|
37
|
+
# this is a little bit of a hack because options can sometimes be a Condition
|
|
38
|
+
expiry = (options.is_a?(Hash) ? options.delete(:expires_in) : nil) || ApiResource::Base.ttl || 0
|
|
28
39
|
ApiResource.with_ttl(expiry.to_f) do
|
|
29
40
|
case scope
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
41
|
+
when :all, :first, :last
|
|
42
|
+
final_cond = ApiResource::Conditions::ScopeCondition.new({}, self)
|
|
43
|
+
# we need new conditions here to take into account options, which could
|
|
44
|
+
# either be a Condition object or a hash
|
|
45
|
+
if options.is_a?(Hash)
|
|
46
|
+
opts = options.with_indifferent_access.delete(:params) || options || {}
|
|
47
|
+
final_cond = ApiResource::Conditions::ScopeCondition.new(opts, self)
|
|
48
|
+
# cond may be nil
|
|
49
|
+
unless cond == nil # THIS MUST BE == NOT nil?
|
|
50
|
+
final_cond = cond.merge!(final_cond)
|
|
51
|
+
end
|
|
52
|
+
elsif options.is_a?(ApiResource::Conditions::AbstractCondition)
|
|
53
|
+
final_cond = options
|
|
54
|
+
end
|
|
55
|
+
# now final cond contains all the conditions we should need to pass to the finder
|
|
56
|
+
fnd = ApiResource::Finders::ResourceFinder.new(self, final_cond)
|
|
57
|
+
fnd.send(scope)
|
|
58
|
+
else
|
|
59
|
+
# in this case scope is the id we want to find, and options should be a condition object or nil
|
|
60
|
+
final_cond = ApiResource::Conditions::ScopeCondition.new({:id => scope}, self)
|
|
61
|
+
if options.is_a?(ApiResource::Conditions::AbstractCondition)
|
|
62
|
+
final_cond = options.merge!(final_cond)
|
|
63
|
+
elsif options.is_a?(Hash)
|
|
64
|
+
opts = options.with_indifferent_access.delete(:params) || options || {}
|
|
65
|
+
final_cond = ApiResource::Conditions::ScopeCondition.new(opts, self).merge!(final_cond)
|
|
66
|
+
end
|
|
67
|
+
ApiResource::Finders::SingleFinder.new(self, final_cond).load
|
|
35
68
|
end
|
|
36
69
|
end
|
|
37
70
|
end
|
|
@@ -75,47 +108,6 @@ module ApiResource
|
|
|
75
108
|
ret
|
|
76
109
|
end
|
|
77
110
|
|
|
78
|
-
private
|
|
79
|
-
|
|
80
|
-
# Find every resource
|
|
81
|
-
def find_every(options)
|
|
82
|
-
begin
|
|
83
|
-
case from = options[:from]
|
|
84
|
-
when Symbol
|
|
85
|
-
instantiate_collection(get(from, options[:params]))
|
|
86
|
-
when String
|
|
87
|
-
path = "#{from}#{query_string(options[:params])}"
|
|
88
|
-
instantiate_collection(connection.get(path, headers) || [])
|
|
89
|
-
else
|
|
90
|
-
prefix_options, query_options = split_options(options[:params])
|
|
91
|
-
path = collection_path(prefix_options, query_options)
|
|
92
|
-
instantiate_collection( (connection.get(path, headers) || []))
|
|
93
|
-
end
|
|
94
|
-
rescue ApiResource::ResourceNotFound
|
|
95
|
-
# Swallowing ResourceNotFound exceptions and return nil - as per
|
|
96
|
-
# ActiveRecord.
|
|
97
|
-
nil
|
|
98
|
-
end
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
# Find a single resource from a one-off URL
|
|
102
|
-
def find_one(options)
|
|
103
|
-
case from = options[:from]
|
|
104
|
-
when Symbol
|
|
105
|
-
instantiate_record(get(from, options[:params]))
|
|
106
|
-
when String
|
|
107
|
-
path = "#{from}#{query_string(options[:params])}"
|
|
108
|
-
instantiate_record(connection.get(path, headers))
|
|
109
|
-
end
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
# Find a single resource from the default URL
|
|
113
|
-
def find_single(scope, options)
|
|
114
|
-
prefix_options, query_options = split_options(options[:params])
|
|
115
|
-
path = element_path(scope, prefix_options, query_options)
|
|
116
|
-
instantiate_record(connection.get(path, headers))
|
|
117
|
-
end
|
|
118
|
-
|
|
119
111
|
end
|
|
120
112
|
end
|
|
121
113
|
|
|
@@ -9,16 +9,17 @@ module ApiResource
|
|
|
9
9
|
attr_reader :found, :internal_object
|
|
10
10
|
|
|
11
11
|
# TODO: Make this list longer since there are for sure more methods to delegate
|
|
12
|
-
delegate :to_s, :inspect, :reload, :present?, :blank?, :size, :to => :internal_object
|
|
12
|
+
delegate :to_s, :inspect, :reload, :present?, :blank?, :size, :count, :to => :internal_object
|
|
13
13
|
|
|
14
14
|
def initialize(klass, condition)
|
|
15
15
|
@klass = klass
|
|
16
16
|
@condition = condition
|
|
17
17
|
@found = false
|
|
18
|
-
|
|
18
|
+
|
|
19
|
+
@klass.load_resource_definition
|
|
19
20
|
end
|
|
20
21
|
|
|
21
|
-
def
|
|
22
|
+
def load
|
|
22
23
|
raise NotImplementedError("Must be defined in a subclass")
|
|
23
24
|
end
|
|
24
25
|
|
|
@@ -27,8 +28,16 @@ module ApiResource
|
|
|
27
28
|
if instance_variable_defined?(:@internal_object)
|
|
28
29
|
return instance_variable_get(:@internal_object)
|
|
29
30
|
end
|
|
30
|
-
# If we haven't tried to load then just call
|
|
31
|
-
self.
|
|
31
|
+
# If we haven't tried to load then just call load
|
|
32
|
+
self.load
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def all(*args)
|
|
36
|
+
if args.blank?
|
|
37
|
+
self.internal_object
|
|
38
|
+
else
|
|
39
|
+
self.klass.send(:all, *args)
|
|
40
|
+
end
|
|
32
41
|
end
|
|
33
42
|
|
|
34
43
|
# proxy unknown methods to the internal_object
|
|
@@ -51,9 +60,10 @@ module ApiResource
|
|
|
51
60
|
id_hash = HashWithIndifferentAccess.new(id_hash)
|
|
52
61
|
# load each individually
|
|
53
62
|
self.condition.included_objects.inject(hsh) do |accum, assoc|
|
|
54
|
-
accum[assoc.to_sym] = self.klass.association_class(assoc).
|
|
55
|
-
:
|
|
56
|
-
|
|
63
|
+
accum[assoc.to_sym] = self.klass.association_class(assoc).all(
|
|
64
|
+
:params => {:ids => id_hash[assoc]}
|
|
65
|
+
)
|
|
66
|
+
accum
|
|
57
67
|
end
|
|
58
68
|
|
|
59
69
|
hsh
|
|
@@ -62,9 +72,15 @@ module ApiResource
|
|
|
62
72
|
def apply_includes(objects, includes)
|
|
63
73
|
Array.wrap(objects).each do |obj|
|
|
64
74
|
includes.each_pair do |assoc, vals|
|
|
65
|
-
ids_to_keep = obj.send(obj.class.association_foreign_key_field(assoc))
|
|
75
|
+
ids_to_keep = Array.wrap(obj.send(obj.class.association_foreign_key_field(assoc)))
|
|
66
76
|
to_keep = vals.select{|elm| ids_to_keep.include?(elm.id)}
|
|
67
|
-
|
|
77
|
+
# if this is a single association take the first
|
|
78
|
+
# TODO: subclass instead of this
|
|
79
|
+
if self.klass.has_many?(assoc)
|
|
80
|
+
obj.send("#{assoc}=", to_keep, false)
|
|
81
|
+
else
|
|
82
|
+
obj.send("#{assoc}=", to_keep.first, false)
|
|
83
|
+
end
|
|
68
84
|
end
|
|
69
85
|
end
|
|
70
86
|
end
|
|
@@ -4,17 +4,28 @@ module ApiResource
|
|
|
4
4
|
|
|
5
5
|
class MultiObjectAssociationFinder < AbstractFinder
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
# If they pass in the internal object just skip the first
|
|
8
|
+
# step and apply the includes
|
|
9
|
+
def initialize(klass, condition, internal_object = nil)
|
|
10
|
+
super(klass, condition)
|
|
11
|
+
|
|
12
|
+
@internal_object = internal_object
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def load
|
|
8
16
|
# otherwise just instantiate the record
|
|
9
17
|
unless self.condition.remote_path
|
|
10
18
|
raise "Tried to load association without a remote path"
|
|
11
19
|
end
|
|
12
20
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
21
|
+
unless @internal_object
|
|
22
|
+
data = self.klass.connection.get(self.build_load_path)
|
|
23
|
+
return [] if data.blank?
|
|
16
24
|
|
|
17
|
-
|
|
25
|
+
@internal_object = self.klass.instantiate_collection(data)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
@loaded = true
|
|
18
29
|
|
|
19
30
|
id_hash = self.condition.included_objects.inject({}) do |accum, assoc|
|
|
20
31
|
accum[assoc] = @internal_object.collect do |obj|
|
|
@@ -6,26 +6,42 @@ module ApiResource
|
|
|
6
6
|
|
|
7
7
|
# this is a little bit simpler, it's always a collection and does
|
|
8
8
|
# not require a remote path
|
|
9
|
-
def
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
9
|
+
def load
|
|
10
|
+
begin
|
|
11
|
+
@loaded = true
|
|
12
|
+
@internal_object = self.klass.connection.get(self.build_load_path)
|
|
13
|
+
return [] if @internal_object.blank?
|
|
14
|
+
|
|
15
|
+
@internal_object = self.klass.instantiate_collection(@internal_object)
|
|
16
|
+
|
|
17
|
+
id_hash = self.condition.included_objects.inject({}) do |accum, assoc|
|
|
18
|
+
accum[assoc] = @internal_object.collect do |obj|
|
|
19
|
+
obj.send(self.klass.association_foreign_key_field(assoc))
|
|
20
|
+
end
|
|
21
|
+
accum[assoc].flatten!
|
|
22
|
+
accum[assoc].uniq!
|
|
23
|
+
accum
|
|
17
24
|
end
|
|
18
|
-
|
|
19
|
-
accum[assoc].uniq!
|
|
20
|
-
accum
|
|
21
|
-
end
|
|
22
|
-
included_objects = self.load_includes(id_hash)
|
|
25
|
+
included_objects = self.load_includes(id_hash)
|
|
23
26
|
|
|
24
|
-
|
|
27
|
+
self.apply_includes(@internal_object, included_objects)
|
|
25
28
|
|
|
26
|
-
|
|
29
|
+
return @internal_object
|
|
30
|
+
rescue ApiResource::ResourceNotFound
|
|
31
|
+
nil
|
|
32
|
+
end
|
|
33
|
+
@internal_object
|
|
27
34
|
end
|
|
28
35
|
|
|
36
|
+
protected
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# Find every resource
|
|
40
|
+
def build_load_path
|
|
41
|
+
prefix_opts, query_opts = self.klass.split_options(self.condition.to_hash)
|
|
42
|
+
self.klass.collection_path(prefix_opts, query_opts)
|
|
43
|
+
end
|
|
44
|
+
|
|
29
45
|
end
|
|
30
46
|
|
|
31
47
|
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
module ApiResource
|
|
2
|
+
|
|
3
|
+
module Finders
|
|
4
|
+
|
|
5
|
+
class SingleFinder < AbstractFinder
|
|
6
|
+
|
|
7
|
+
def load
|
|
8
|
+
data = self.klass.connection.get(self.build_load_path)
|
|
9
|
+
@loaded = true
|
|
10
|
+
return nil if data.blank?
|
|
11
|
+
@internal_object = self.klass.instantiate_record(data)
|
|
12
|
+
# now that the object is loaded, resolve the includes
|
|
13
|
+
id_hash = self.condition.included_objects.inject({}) do |accum, assoc|
|
|
14
|
+
accum[assoc] = Array.wrap(
|
|
15
|
+
@internal_object.send(
|
|
16
|
+
@internal_object.class.association_foreign_key_field(assoc)
|
|
17
|
+
)
|
|
18
|
+
)
|
|
19
|
+
accum
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
included_objects = self.load_includes(id_hash)
|
|
23
|
+
|
|
24
|
+
self.apply_includes(@internal_object, included_objects)
|
|
25
|
+
|
|
26
|
+
return @internal_object
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
protected
|
|
30
|
+
|
|
31
|
+
def build_load_path
|
|
32
|
+
if self.condition.to_hash["id"].nil?
|
|
33
|
+
raise "Invalid evaluation of a SingleFinder without an ID"
|
|
34
|
+
end
|
|
35
|
+
args = self.condition.to_hash
|
|
36
|
+
id = args.delete("id")
|
|
37
|
+
prefix_opts, query_opts = self.klass.split_options(args)
|
|
38
|
+
self.klass.element_path(id, prefix_opts, query_opts)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
end
|
|
@@ -4,18 +4,28 @@ module ApiResource
|
|
|
4
4
|
|
|
5
5
|
class SingleObjectAssociationFinder < AbstractFinder
|
|
6
6
|
|
|
7
|
+
def initialize(klass, condition, internal_object = nil)
|
|
8
|
+
super(klass, condition)
|
|
9
|
+
|
|
10
|
+
@internal_object = internal_object
|
|
11
|
+
end
|
|
12
|
+
|
|
7
13
|
# since it is only a single object we can just load from
|
|
8
14
|
# the service_uri and deal with includes
|
|
9
|
-
def
|
|
15
|
+
def load
|
|
10
16
|
# otherwise just instantiate the record
|
|
11
17
|
unless self.condition.remote_path
|
|
12
18
|
raise "Tried to load association without a remote path"
|
|
13
19
|
end
|
|
14
20
|
|
|
15
|
-
|
|
21
|
+
unless @internal_object
|
|
22
|
+
data = self.klass.connection.get(self.build_load_path)
|
|
23
|
+
|
|
24
|
+
return nil if data.blank?
|
|
25
|
+
@internal_object = self.klass.instantiate_record(data)
|
|
26
|
+
end
|
|
27
|
+
|
|
16
28
|
@loaded = true
|
|
17
|
-
return nil if data.blank?
|
|
18
|
-
@internal_object = self.klass.instantiate_record(data)
|
|
19
29
|
# now that the object is loaded, resolve the includes
|
|
20
30
|
id_hash = self.condition.included_objects.inject({}) do |accum, assoc|
|
|
21
31
|
accum[assoc] = Array.wrap(
|
data/lib/api_resource/version.rb
CHANGED
|
@@ -729,6 +729,7 @@ describe "Associations" do
|
|
|
729
729
|
TestAR.class_eval do
|
|
730
730
|
belongs_to_remote :my_favorite_thing, :class_name => "TestClassYay"
|
|
731
731
|
end
|
|
732
|
+
HasManyObject.reload_resource_definition
|
|
732
733
|
end
|
|
733
734
|
it "should define remote association types for AR" do
|
|
734
735
|
[:has_many_remote, :belongs_to_remote, :has_one_remote].each do |assoc|
|
|
@@ -788,6 +789,7 @@ describe "Associations" do
|
|
|
788
789
|
it "should attempt to load a collection of remote objects for a has_many relationship" do
|
|
789
790
|
tar = TestAR.new
|
|
790
791
|
tar.stubs(:id).returns(1)
|
|
792
|
+
HasManyObject.load_resource_definition
|
|
791
793
|
HasManyObject.connection.expects(:get).with("/has_many_objects.json?test_ar_id=1").once.returns([{"name" => "testing"}])
|
|
792
794
|
# load the test resource
|
|
793
795
|
tar.has_many_objects.first.name.should eql "testing"
|
data/spec/lib/base_spec.rb
CHANGED
|
@@ -7,6 +7,8 @@ describe "Base" do
|
|
|
7
7
|
|
|
8
8
|
before(:each) do
|
|
9
9
|
TestResource.reload_resource_definition
|
|
10
|
+
HasOneObject.reload_resource_definition
|
|
11
|
+
HasManyObject.reload_resource_definition
|
|
10
12
|
end
|
|
11
13
|
|
|
12
14
|
context ".new_element_path" do
|
|
@@ -795,6 +797,7 @@ describe "Base" do
|
|
|
795
797
|
|
|
796
798
|
before(:all) do
|
|
797
799
|
HasOneObject.reload_resource_definition
|
|
800
|
+
HasManyObject.load_resource_definition
|
|
798
801
|
end
|
|
799
802
|
|
|
800
803
|
it "should know if it is persisted" do
|
|
@@ -61,7 +61,7 @@ describe "Conditions" do
|
|
|
61
61
|
it "should create a resource finder when forced to load, and cache the result" do
|
|
62
62
|
obj = TestResource.includes(:has_many_objects)
|
|
63
63
|
|
|
64
|
-
ApiResource::Finders::ResourceFinder.expects(:new).with(TestResource, obj).returns(mock(:
|
|
64
|
+
ApiResource::Finders::ResourceFinder.expects(:new).with(TestResource, obj).returns(mock(:load => [1]))
|
|
65
65
|
obj.internal_object.should eql([1])
|
|
66
66
|
obj.all.should eql([1])
|
|
67
67
|
obj.first.should eql(1)
|
|
@@ -70,7 +70,7 @@ describe "Conditions" do
|
|
|
70
70
|
it "should proxy calls to enumerable and array methods to the loaded object" do
|
|
71
71
|
obj = TestResource.includes(:has_many_objects)
|
|
72
72
|
|
|
73
|
-
ApiResource::Finders::ResourceFinder.expects(:new).with(TestResource, obj).returns(mock(:
|
|
73
|
+
ApiResource::Finders::ResourceFinder.expects(:new).with(TestResource, obj).returns(mock(:load => [1,2]))
|
|
74
74
|
|
|
75
75
|
obj.collect{|o| o * 2}.should eql([2,4])
|
|
76
76
|
end
|
|
@@ -12,7 +12,7 @@ describe "MultiObjectAssociationFinder" do
|
|
|
12
12
|
ApiResource::Finders::MultiObjectAssociationFinder.new(
|
|
13
13
|
TestResource,
|
|
14
14
|
stub(:remote_path => "test_resources", :to_query => "id[]=1&id[]=2", :blank_conditions? => false)
|
|
15
|
-
).
|
|
15
|
+
).load
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
it "should load a has many association properly" do
|
|
@@ -37,7 +37,7 @@ describe "MultiObjectAssociationFinder" do
|
|
|
37
37
|
finder.expects(:load_includes).with(:has_many_objects => [1,2]).returns(5)
|
|
38
38
|
finder.expects(:apply_includes).with([tr], 5).returns(6)
|
|
39
39
|
|
|
40
|
-
finder.
|
|
40
|
+
finder.load.should eql([tr])
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
end
|
|
@@ -4,83 +4,72 @@ describe "ResourceFinder" do
|
|
|
4
4
|
|
|
5
5
|
before(:each) do
|
|
6
6
|
TestResource.reload_resource_definition
|
|
7
|
+
|
|
7
8
|
end
|
|
8
9
|
|
|
9
|
-
it "should
|
|
10
|
-
TestResource.expects(:
|
|
10
|
+
it "should load normally without includes using connection.get" do
|
|
11
|
+
TestResource.connection.expects(:get).with("/test_resources.json")
|
|
11
12
|
|
|
12
13
|
ApiResource::Finders::ResourceFinder.new(
|
|
13
14
|
TestResource,
|
|
14
15
|
mock(:to_hash => {})
|
|
15
|
-
).
|
|
16
|
+
).load
|
|
16
17
|
end
|
|
17
18
|
|
|
18
|
-
it "should pass conditions into the
|
|
19
|
-
TestResource.expects(:
|
|
19
|
+
it "should pass conditions into the connection get method" do
|
|
20
|
+
TestResource.connection.expects(:get).with("/test_resources.json?ids%5B%5D=1&ids%5B%5D=2&ids%5B%5D=3")
|
|
20
21
|
|
|
21
22
|
ApiResource::Finders::ResourceFinder.new(
|
|
22
23
|
TestResource,
|
|
23
|
-
mock(:to_hash => {:
|
|
24
|
-
).
|
|
24
|
+
mock(:to_hash => {:ids => [1,2,3]})
|
|
25
|
+
).load
|
|
25
26
|
end
|
|
26
27
|
|
|
27
28
|
it "should try to load includes if it finds an object" do
|
|
28
|
-
obj_mock =
|
|
29
|
+
obj_mock = {:id => 1}
|
|
29
30
|
|
|
30
|
-
TestResource.expects(:
|
|
31
|
+
TestResource.connection.expects(:get).with("/test_resources.json").returns([obj_mock])
|
|
31
32
|
|
|
32
33
|
obj = ApiResource::Finders::ResourceFinder.new(
|
|
33
34
|
TestResource,
|
|
34
35
|
stub(:to_hash => {}, :eager_load? => false, :included_objects => [])
|
|
35
|
-
).
|
|
36
|
+
).load
|
|
36
37
|
|
|
37
|
-
|
|
38
|
+
# obj.first.id.should eql(1)
|
|
38
39
|
end
|
|
39
40
|
|
|
40
41
|
it "should load and distribute includes among the returned objects" do
|
|
41
|
-
inc_mock =
|
|
42
|
-
inc_mock2 =
|
|
43
|
-
|
|
44
|
-
tr = TestResource.new
|
|
45
|
-
tr.stubs(:id).returns(1)
|
|
46
|
-
tr.stubs(:has_many_object_ids).returns([1,2])
|
|
47
|
-
tr.expects(:has_many_objects=).with([inc_mock, inc_mock2])
|
|
48
|
-
tr.expects(:has_many_objects).returns([inc_mock, inc_mock2])
|
|
42
|
+
inc_mock = {:id => 1}
|
|
43
|
+
inc_mock2 = {:id => 2}
|
|
49
44
|
|
|
50
|
-
TestResource.expects(:
|
|
45
|
+
TestResource.connection.expects(:get)
|
|
46
|
+
.with("/test_resources.json")
|
|
47
|
+
.returns([{:id => 1, :has_many_object_ids => [1,2]}])
|
|
51
48
|
|
|
52
|
-
HasManyObject.expects(:
|
|
49
|
+
HasManyObject.connection.expects(:get)
|
|
50
|
+
.with("/has_many_objects.json?ids%5B%5D=1&ids%5B%5D=2")
|
|
51
|
+
.returns([inc_mock, inc_mock2])
|
|
53
52
|
|
|
54
53
|
obj = ApiResource::Finders::ResourceFinder.new(
|
|
55
54
|
TestResource,
|
|
56
55
|
stub(:to_hash => {}, :eager_load? => true, :included_objects => [:has_many_objects])
|
|
57
|
-
).
|
|
56
|
+
).load
|
|
58
57
|
obj.first.has_many_objects.collect(&:id).should eql([1,2])
|
|
59
58
|
end
|
|
60
59
|
|
|
61
60
|
it "should work with loading for multiple objects" do
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
tr.expects(:has_many_objects).returns([inc_mock])
|
|
70
|
-
|
|
71
|
-
tr2 = TestResource.new
|
|
72
|
-
tr2.stubs(:id).returns(2)
|
|
73
|
-
tr2.stubs(:has_many_object_ids).returns([2])
|
|
74
|
-
tr2.expects(:has_many_objects=).with([inc_mock2]).returns([inc_mock2])
|
|
75
|
-
tr2.expects(:has_many_objects).returns([inc_mock2])
|
|
76
|
-
|
|
77
|
-
TestResource.expects(:find).with(:all, {}).returns([tr, tr2])
|
|
78
|
-
HasManyObject.expects(:find).with(:all, :id => [1,2]).returns([inc_mock2, inc_mock])
|
|
61
|
+
TestResource.connection.expects(:get).with("/test_resources.json").returns([
|
|
62
|
+
{:id => 1, :has_many_object_ids => [1]},
|
|
63
|
+
{:id => 2, :has_many_object_ids => [2]}
|
|
64
|
+
])
|
|
65
|
+
HasManyObject.connection.expects(:get)
|
|
66
|
+
.with("/has_many_objects.json?ids%5B%5D=1&ids%5B%5D=2")
|
|
67
|
+
.returns([{:id => 1}, {:id => 2}])
|
|
79
68
|
|
|
80
69
|
obj = ApiResource::Finders::ResourceFinder.new(
|
|
81
70
|
TestResource,
|
|
82
71
|
stub(:to_hash => {}, :eager_load? => true, :included_objects => [:has_many_objects])
|
|
83
|
-
).
|
|
72
|
+
).load
|
|
84
73
|
|
|
85
74
|
obj.first.has_many_objects.collect(&:id).should eql([1])
|
|
86
75
|
obj.second.has_many_objects.collect(&:id).should eql([2])
|
|
@@ -12,7 +12,7 @@ describe "SingleObjectAssociationFinder" do
|
|
|
12
12
|
ApiResource::Finders::SingleObjectAssociationFinder.new(
|
|
13
13
|
TestResource,
|
|
14
14
|
stub(:remote_path => "test_resources", :to_query => "id[]=1&id[]=2", :blank_conditions? => false)
|
|
15
|
-
).
|
|
15
|
+
).load
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
it "should load a has many association properly" do
|
|
@@ -37,7 +37,7 @@ describe "SingleObjectAssociationFinder" do
|
|
|
37
37
|
finder.expects(:load_includes).with(:has_many_objects => [1,2]).returns(5)
|
|
38
38
|
finder.expects(:apply_includes).with(tr, 5).returns(6)
|
|
39
39
|
|
|
40
|
-
finder.
|
|
40
|
+
finder.load.should eql(tr)
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe ApiResource::Finders do
|
|
4
|
+
|
|
5
|
+
before(:each) do
|
|
6
|
+
TestResource.reload_resource_definition
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
it "should be able to find a single object" do
|
|
10
|
+
TestResource.connection.expects(:get).with("/test_resources/1.json")
|
|
11
|
+
|
|
12
|
+
TestResource.find(1)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it "should be able to find with parameters, params syntax" do
|
|
16
|
+
TestResource.connection.expects(:get).with("/test_resources.json?active=true")
|
|
17
|
+
TestResource.all(:params => {:active => true})
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it "should be able to find with parameters without the params syntax" do
|
|
21
|
+
TestResource.connection.expects(:get).with("/test_resources.json?active=true&passive=false")
|
|
22
|
+
|
|
23
|
+
TestResource.all(:active => true, :passive => false)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it "should be able to chain find on top of a scope" do
|
|
27
|
+
TestResource.connection.expects(:get).with("/test_resources.json?active=true&passive=true")
|
|
28
|
+
TestResource.active.all(:passive => true)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it "should be able to chain find on top of an includes call" do
|
|
32
|
+
TestResource.connection.expects(:get).with("/test_resources/1.json").returns({"id" => 1, "has_many_object_ids" => [1,2]})
|
|
33
|
+
HasManyObject.connection.expects(:get).with("/has_many_objects.json?ids%5B%5D=1&ids%5B%5D=2").returns([])
|
|
34
|
+
|
|
35
|
+
TestResource.includes(:has_many_objects).find(1)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
end
|
data/spec/lib/prefixes_spec.rb
CHANGED
|
@@ -15,8 +15,7 @@ describe "With Prefixes" do
|
|
|
15
15
|
it "should use the prefix to find a single record when given as a param" do
|
|
16
16
|
PrefixModel.connection.expects(:get)
|
|
17
17
|
.with(
|
|
18
|
-
"/foreign/123/prefix_models/456.json"
|
|
19
|
-
instance_of(Hash)
|
|
18
|
+
"/foreign/123/prefix_models/456.json"
|
|
20
19
|
)
|
|
21
20
|
.returns({})
|
|
22
21
|
PrefixModel.find(456, :params => {:foreign_key_id => 123})
|
|
@@ -25,8 +24,7 @@ describe "With Prefixes" do
|
|
|
25
24
|
it "should not use the prefix to find a single record when not given as a param to avoid automatic failure" do
|
|
26
25
|
PrefixModel.connection.expects(:get)
|
|
27
26
|
.with(
|
|
28
|
-
"/prefix_models/456.json"
|
|
29
|
-
instance_of(Hash)
|
|
27
|
+
"/prefix_models/456.json"
|
|
30
28
|
)
|
|
31
29
|
.returns({})
|
|
32
30
|
PrefixModel.find(456)
|
|
@@ -52,8 +50,7 @@ describe "With Prefixes" do
|
|
|
52
50
|
it "should use the prefix to find records" do
|
|
53
51
|
prefix_model.send(:connection).expects(:get)
|
|
54
52
|
.with(
|
|
55
|
-
"/foreign/123/prefix_models.json"
|
|
56
|
-
instance_of(Hash)
|
|
53
|
+
"/foreign/123/prefix_models.json"
|
|
57
54
|
)
|
|
58
55
|
.returns([])
|
|
59
56
|
PrefixModel.first(:params => {:foreign_key_id => 123})
|
|
@@ -62,8 +59,7 @@ describe "With Prefixes" do
|
|
|
62
59
|
it "should not use the prefix to find records when not given as a param to avoid automatic failure" do
|
|
63
60
|
prefix_model.send(:connection).expects(:get)
|
|
64
61
|
.with(
|
|
65
|
-
"/prefix_models.json"
|
|
66
|
-
instance_of(Hash)
|
|
62
|
+
"/prefix_models.json"
|
|
67
63
|
)
|
|
68
64
|
.returns([])
|
|
69
65
|
PrefixModel.first
|
|
@@ -94,6 +90,7 @@ describe "With Prefixes" do
|
|
|
94
90
|
prefix_model.name = "changed name"
|
|
95
91
|
prefix_model.send(:connection).expects(:put)
|
|
96
92
|
.with(
|
|
93
|
+
|
|
97
94
|
"/foreign/123/prefix_models/456.json",
|
|
98
95
|
{"prefix_model" => {"name" => "changed name"}}.to_json,
|
|
99
96
|
instance_of(Hash)
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: api_resource
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.6.
|
|
4
|
+
version: 0.6.1
|
|
5
5
|
prerelease:
|
|
6
6
|
platform: ruby
|
|
7
7
|
authors:
|
|
@@ -11,7 +11,7 @@ authors:
|
|
|
11
11
|
autorequire:
|
|
12
12
|
bindir: bin
|
|
13
13
|
cert_chain: []
|
|
14
|
-
date: 2013-01-
|
|
14
|
+
date: 2013-01-04 00:00:00.000000000 Z
|
|
15
15
|
dependencies:
|
|
16
16
|
- !ruby/object:Gem::Dependency
|
|
17
17
|
name: rake
|
|
@@ -237,38 +237,6 @@ dependencies:
|
|
|
237
237
|
- - ! '>='
|
|
238
238
|
- !ruby/object:Gem::Version
|
|
239
239
|
version: '0'
|
|
240
|
-
- !ruby/object:Gem::Dependency
|
|
241
|
-
name: differ
|
|
242
|
-
requirement: !ruby/object:Gem::Requirement
|
|
243
|
-
none: false
|
|
244
|
-
requirements:
|
|
245
|
-
- - ! '>='
|
|
246
|
-
- !ruby/object:Gem::Version
|
|
247
|
-
version: '0'
|
|
248
|
-
type: :development
|
|
249
|
-
prerelease: false
|
|
250
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
251
|
-
none: false
|
|
252
|
-
requirements:
|
|
253
|
-
- - ! '>='
|
|
254
|
-
- !ruby/object:Gem::Version
|
|
255
|
-
version: '0'
|
|
256
|
-
- !ruby/object:Gem::Dependency
|
|
257
|
-
name: colorize
|
|
258
|
-
requirement: !ruby/object:Gem::Requirement
|
|
259
|
-
none: false
|
|
260
|
-
requirements:
|
|
261
|
-
- - ! '>='
|
|
262
|
-
- !ruby/object:Gem::Version
|
|
263
|
-
version: '0'
|
|
264
|
-
type: :development
|
|
265
|
-
prerelease: false
|
|
266
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
267
|
-
none: false
|
|
268
|
-
requirements:
|
|
269
|
-
- - ! '>='
|
|
270
|
-
- !ruby/object:Gem::Version
|
|
271
|
-
version: '0'
|
|
272
240
|
- !ruby/object:Gem::Dependency
|
|
273
241
|
name: sqlite3
|
|
274
242
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -302,7 +270,7 @@ dependencies:
|
|
|
302
270
|
- !ruby/object:Gem::Version
|
|
303
271
|
version: '0'
|
|
304
272
|
- !ruby/object:Gem::Dependency
|
|
305
|
-
name:
|
|
273
|
+
name: json
|
|
306
274
|
requirement: !ruby/object:Gem::Requirement
|
|
307
275
|
none: false
|
|
308
276
|
requirements:
|
|
@@ -318,7 +286,7 @@ dependencies:
|
|
|
318
286
|
- !ruby/object:Gem::Version
|
|
319
287
|
version: '0'
|
|
320
288
|
- !ruby/object:Gem::Dependency
|
|
321
|
-
name:
|
|
289
|
+
name: rest-client
|
|
322
290
|
requirement: !ruby/object:Gem::Requirement
|
|
323
291
|
none: false
|
|
324
292
|
requirements:
|
|
@@ -334,7 +302,7 @@ dependencies:
|
|
|
334
302
|
- !ruby/object:Gem::Version
|
|
335
303
|
version: '0'
|
|
336
304
|
- !ruby/object:Gem::Dependency
|
|
337
|
-
name:
|
|
305
|
+
name: log4r
|
|
338
306
|
requirement: !ruby/object:Gem::Requirement
|
|
339
307
|
none: false
|
|
340
308
|
requirements:
|
|
@@ -350,7 +318,7 @@ dependencies:
|
|
|
350
318
|
- !ruby/object:Gem::Version
|
|
351
319
|
version: '0'
|
|
352
320
|
- !ruby/object:Gem::Dependency
|
|
353
|
-
name:
|
|
321
|
+
name: differ
|
|
354
322
|
requirement: !ruby/object:Gem::Requirement
|
|
355
323
|
none: false
|
|
356
324
|
requirements:
|
|
@@ -366,7 +334,7 @@ dependencies:
|
|
|
366
334
|
- !ruby/object:Gem::Version
|
|
367
335
|
version: '0'
|
|
368
336
|
- !ruby/object:Gem::Dependency
|
|
369
|
-
name:
|
|
337
|
+
name: colorize
|
|
370
338
|
requirement: !ruby/object:Gem::Requirement
|
|
371
339
|
none: false
|
|
372
340
|
requirements:
|
|
@@ -432,6 +400,7 @@ files:
|
|
|
432
400
|
- lib/api_resource/finders/abstract_finder.rb
|
|
433
401
|
- lib/api_resource/finders/multi_object_association_finder.rb
|
|
434
402
|
- lib/api_resource/finders/resource_finder.rb
|
|
403
|
+
- lib/api_resource/finders/single_finder.rb
|
|
435
404
|
- lib/api_resource/finders/single_object_association_finder.rb
|
|
436
405
|
- lib/api_resource/formats.rb
|
|
437
406
|
- lib/api_resource/formats/json_format.rb
|
|
@@ -464,6 +433,7 @@ files:
|
|
|
464
433
|
- spec/lib/finders/multi_object_association_finder_spec.rb
|
|
465
434
|
- spec/lib/finders/resource_finder_spec.rb
|
|
466
435
|
- spec/lib/finders/single_object_association_finder_spec.rb
|
|
436
|
+
- spec/lib/finders_spec.rb
|
|
467
437
|
- spec/lib/local_spec.rb
|
|
468
438
|
- spec/lib/mocks_spec.rb
|
|
469
439
|
- spec/lib/model_errors_spec.rb
|
|
@@ -523,6 +493,7 @@ test_files:
|
|
|
523
493
|
- spec/lib/finders/multi_object_association_finder_spec.rb
|
|
524
494
|
- spec/lib/finders/resource_finder_spec.rb
|
|
525
495
|
- spec/lib/finders/single_object_association_finder_spec.rb
|
|
496
|
+
- spec/lib/finders_spec.rb
|
|
526
497
|
- spec/lib/local_spec.rb
|
|
527
498
|
- spec/lib/mocks_spec.rb
|
|
528
499
|
- spec/lib/model_errors_spec.rb
|