api_resource 0.6.21 → 0.6.22
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.yardopts +1 -0
- data/Gemfile.lock +1 -1
- data/LICENSE.txt +22 -0
- data/README.md +16 -9
- data/docs/Attributes.md +64 -0
- data/docs/Caching.md +45 -0
- data/docs/GettingStarted.md +149 -0
- data/docs/Relationships.md +136 -0
- data/docs/ResourceDefinition.md +80 -0
- data/docs/Retrieval.md +279 -0
- data/docs/Serialization.md +56 -0
- data/lib/api_resource/associations/has_many_remote_object_proxy.rb +2 -2
- data/lib/api_resource/attributes.rb +16 -4
- data/lib/api_resource/base.rb +98 -19
- data/lib/api_resource/conditions/abstract_condition.rb +241 -129
- data/lib/api_resource/conditions/include_condition.rb +7 -1
- data/lib/api_resource/conditions/pagination_condition.rb +37 -0
- data/lib/api_resource/conditions/where_condition.rb +19 -0
- data/lib/api_resource/conditions.rb +18 -2
- data/lib/api_resource/connection.rb +27 -13
- data/lib/api_resource/exceptions.rb +11 -11
- data/lib/api_resource/finders/abstract_finder.rb +176 -95
- data/lib/api_resource/finders/multi_object_association_finder.rb +10 -9
- data/lib/api_resource/finders/resource_finder.rb +59 -49
- data/lib/api_resource/finders/single_finder.rb +5 -6
- data/lib/api_resource/finders/single_object_association_finder.rb +52 -51
- data/lib/api_resource/finders.rb +1 -1
- data/lib/api_resource/formats/file_upload_format.rb +75 -0
- data/lib/api_resource/formats.rb +4 -1
- data/lib/api_resource/response.rb +108 -0
- data/lib/api_resource/scopes.rb +62 -5
- data/lib/api_resource/serializer.rb +1 -1
- data/lib/api_resource/typecasters/boolean_typecaster.rb +1 -0
- data/lib/api_resource/typecasters/integer_typecaster.rb +1 -0
- data/lib/api_resource/typecasters/time_typecaster.rb +12 -4
- data/lib/api_resource/version.rb +1 -1
- data/lib/api_resource.rb +1 -0
- data/spec/lib/associations/has_one_remote_object_proxy_spec.rb +4 -4
- data/spec/lib/associations_spec.rb +3 -3
- data/spec/lib/attributes_spec.rb +16 -1
- data/spec/lib/base_spec.rb +121 -39
- data/spec/lib/conditions/{abstract_conditions_spec.rb → abstract_condition_spec.rb} +23 -11
- data/spec/lib/conditions/pagination_condition_spec.rb +88 -0
- data/spec/lib/finders/multi_object_association_finder_spec.rb +55 -27
- data/spec/lib/finders/resource_finder_spec.rb +26 -2
- data/spec/lib/finders/single_object_association_finder_spec.rb +14 -6
- data/spec/lib/finders_spec.rb +81 -81
- data/spec/lib/observing_spec.rb +3 -4
- data/spec/lib/response_spec.rb +18 -0
- data/spec/lib/scopes_spec.rb +25 -1
- data/spec/lib/typecasters/boolean_typecaster_spec.rb +1 -1
- data/spec/lib/typecasters/integer_typecaster_spec.rb +1 -1
- data/spec/lib/typecasters/time_typecaster_spec.rb +6 -0
- data/spec/support/files/bg-awesome.jpg +0 -0
- data/spec/support/mocks/test_resource_mocks.rb +26 -16
- data/spec/support/requests/test_resource_requests.rb +27 -23
- metadata +24 -4
@@ -0,0 +1,108 @@
|
|
1
|
+
module ApiResource
|
2
|
+
class Response
|
3
|
+
|
4
|
+
attr_accessor :body
|
5
|
+
attr_reader :headers
|
6
|
+
|
7
|
+
# explicit delegate for things we define
|
8
|
+
delegate(
|
9
|
+
:as_json,
|
10
|
+
:blank?,
|
11
|
+
:inspect,
|
12
|
+
:to_s,
|
13
|
+
to: :body
|
14
|
+
)
|
15
|
+
|
16
|
+
#
|
17
|
+
# Constructor
|
18
|
+
#
|
19
|
+
# @param response [HttpClient::Response]
|
20
|
+
# @param format [ApiResource::Format] Decoder
|
21
|
+
#
|
22
|
+
def initialize(response)
|
23
|
+
@body = self.parse_body(response)
|
24
|
+
@headers = response.try(:headers) || {}
|
25
|
+
end
|
26
|
+
|
27
|
+
#
|
28
|
+
# We typically treat the response as just a wrapper for an
|
29
|
+
# Array or a Hash and use is_a? to determine what kind of response
|
30
|
+
# we have. So to keep that consistent, #is_a? returns true if the
|
31
|
+
# body is a descendant of the class as well
|
32
|
+
#
|
33
|
+
# @param klass [Class]
|
34
|
+
#
|
35
|
+
# @return [Boolean]
|
36
|
+
def is_a?(klass)
|
37
|
+
super || @body.is_a?(klass)
|
38
|
+
end
|
39
|
+
|
40
|
+
#
|
41
|
+
# Implementation of marshal_dump
|
42
|
+
#
|
43
|
+
# @return [Array<Hash>] The data to dump
|
44
|
+
def marshal_dump
|
45
|
+
[@body, @headers]
|
46
|
+
end
|
47
|
+
|
48
|
+
#
|
49
|
+
# Implementation of marshal load
|
50
|
+
# @param args [Array] Array of dumped data
|
51
|
+
#
|
52
|
+
# @return [Response] New instance
|
53
|
+
def marshal_load(args)
|
54
|
+
@body, @headers = args
|
55
|
+
self
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
#
|
60
|
+
# Return ourself cloned with the body wrapped as an array
|
61
|
+
#
|
62
|
+
# @example
|
63
|
+
# resp = ApiResource::Response.new(response, format)
|
64
|
+
# resp.body # => {'a' => 'b'}
|
65
|
+
#
|
66
|
+
# array = Array.wrap(resp)
|
67
|
+
# array.body # => [{'a' => 'b'}]
|
68
|
+
#
|
69
|
+
# array.response == resp.response # true!
|
70
|
+
#
|
71
|
+
# @return [ApiResource::Response]
|
72
|
+
def to_ary
|
73
|
+
klone = self.dup
|
74
|
+
klone.body = Array.wrap(self.body)
|
75
|
+
klone
|
76
|
+
end
|
77
|
+
|
78
|
+
protected
|
79
|
+
|
80
|
+
#
|
81
|
+
# Proxy method missing to the body
|
82
|
+
#
|
83
|
+
# @param meth [Symbol] Method called
|
84
|
+
# @param *args [Array<Mixed>] Args passed
|
85
|
+
# @param &block [Proc] Block passed
|
86
|
+
#
|
87
|
+
# @return [Mixed]
|
88
|
+
def method_missing(meth, *args, &block)
|
89
|
+
@body.__send__(meth, *args, &block)
|
90
|
+
end
|
91
|
+
|
92
|
+
#
|
93
|
+
# Handle parsing of the body. Returns a blank Hash if
|
94
|
+
# no body is present
|
95
|
+
#
|
96
|
+
# @param response [HttpClient::Response]
|
97
|
+
#
|
98
|
+
# @return [Hash, Array<Hash>] Parsed response
|
99
|
+
def parse_body(response)
|
100
|
+
if response.try(:body).present?
|
101
|
+
return ApiResource.format.decode(response.body)
|
102
|
+
else
|
103
|
+
return {}
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
end
|
data/lib/api_resource/scopes.rb
CHANGED
@@ -6,6 +6,7 @@ module ApiResource
|
|
6
6
|
module ClassMethods
|
7
7
|
# TODO: calling these methods should force loading of the resource definition
|
8
8
|
def scopes
|
9
|
+
self.reload_resource_definition
|
9
10
|
return self.related_objects[:scopes]
|
10
11
|
end
|
11
12
|
|
@@ -68,7 +69,7 @@ module ApiResource
|
|
68
69
|
end
|
69
70
|
end
|
70
71
|
|
71
|
-
#
|
72
|
+
#
|
72
73
|
# Apply scopes from params based on our resource
|
73
74
|
# definition
|
74
75
|
#
|
@@ -90,20 +91,53 @@ module ApiResource
|
|
90
91
|
return base
|
91
92
|
end
|
92
93
|
|
94
|
+
#
|
95
|
+
# Add our dynamic scopes based on a set of params
|
96
|
+
#
|
97
|
+
# @param params [Hash] User-supplied params
|
98
|
+
# @param base [ApiResource::Conditions::AbstractCondition, Class] Base
|
99
|
+
# Scope
|
100
|
+
#
|
101
|
+
# @return [ApiResource::Conditions::AbstractCondition] [description]
|
93
102
|
def add_dynamic_scopes(params, base)
|
94
103
|
self.dynamic_scopes.each_pair do |name, args|
|
95
|
-
|
104
|
+
# make sure we have all required arguments
|
105
|
+
next unless self.check_required_scope_args(args, params[name])
|
106
|
+
|
107
|
+
# the args we will apply
|
96
108
|
caller_args = []
|
97
|
-
|
98
|
-
|
99
|
-
|
109
|
+
|
110
|
+
# iterate through our args and add them to an array to send to our
|
111
|
+
# scope
|
112
|
+
args.keys.each do |subkey|
|
113
|
+
# we only apply things that are present or explicitly false
|
114
|
+
if val = self.get_scope_arg_value(subkey, params[name][subkey])
|
115
|
+
caller_args << val
|
100
116
|
end
|
101
117
|
end
|
118
|
+
# call our scope with the supplied args
|
102
119
|
base = base.send(name, *caller_args)
|
103
120
|
end
|
104
121
|
return base
|
105
122
|
end
|
106
123
|
|
124
|
+
#
|
125
|
+
# Check if we have supplied all of the necessary
|
126
|
+
# @param scope [Hash] [Scope Definition
|
127
|
+
# @param params [Hash] [Supplied params]
|
128
|
+
#
|
129
|
+
# @return [Boolean] Validity
|
130
|
+
def check_required_scope_args(scope, params)
|
131
|
+
# make sure we have a hash and it has values
|
132
|
+
return false unless params.is_a?(Hash) && params.present?
|
133
|
+
# find required values
|
134
|
+
required = scope.select{ |k,v| v.to_sym == :req }.keys
|
135
|
+
# make sure we have all of the required values, we allow false
|
136
|
+
required.all? { |key|
|
137
|
+
params[key].present? || params[key] == false
|
138
|
+
}
|
139
|
+
end
|
140
|
+
|
107
141
|
#
|
108
142
|
# Wrapper method to define all scopes from the resource definition
|
109
143
|
#
|
@@ -117,10 +151,33 @@ module ApiResource
|
|
117
151
|
true
|
118
152
|
end
|
119
153
|
|
154
|
+
#
|
155
|
+
# Scopes that require arguments
|
156
|
+
#
|
157
|
+
# @return [Hash]
|
120
158
|
def dynamic_scopes
|
121
159
|
self.scopes.select { |name, args| args.present? }
|
122
160
|
end
|
123
161
|
|
162
|
+
#
|
163
|
+
# Get the parsed/formatted arguments to send to the server
|
164
|
+
#
|
165
|
+
# @param key [String, Symbol] Key name for the scope value
|
166
|
+
# @param value [String, Integer, Symbol] Value for the scope
|
167
|
+
#
|
168
|
+
# @return [String, Integer, Symbol, Date] Parsed/formatted value
|
169
|
+
def get_scope_arg_value(key, value)
|
170
|
+
# cast to string to avoid incorred blank? behavior for us
|
171
|
+
return "false" if value == false
|
172
|
+
# if we havea date field, try to parse, falling back to the original
|
173
|
+
# value
|
174
|
+
if key.to_s =~ /date/
|
175
|
+
value = Date.parse(value) rescue value
|
176
|
+
end
|
177
|
+
# return the final value
|
178
|
+
value
|
179
|
+
end
|
180
|
+
|
124
181
|
def static_scopes
|
125
182
|
self.scopes.select { |name, args| args.blank? }.keys
|
126
183
|
end
|
@@ -15,13 +15,17 @@ module ApiResource
|
|
15
15
|
|
16
16
|
if value =~ ApiResource::Typecast::ISO_DATETIME
|
17
17
|
micro = ($7.to_f * 1_000_000).to_i
|
18
|
-
return self.new_time($1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, micro)
|
18
|
+
return self.new_time(false, $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, micro)
|
19
19
|
end
|
20
20
|
|
21
21
|
time_info = Date._parse(value)
|
22
22
|
time_info[:micro] = ((time_info[:sec_fraction].to_f % 1) * 1_000_000).to_i
|
23
23
|
|
24
|
-
|
24
|
+
if time_info[:zone].present?
|
25
|
+
new_time(true, *time_info.values_at(:year, :mon, :mday, :hour, :min, :sec, :zone))
|
26
|
+
else
|
27
|
+
new_time(false, *time_info.values_at(:year, :mon, :mday, :hour, :min, :sec, :micro))
|
28
|
+
end
|
25
29
|
|
26
30
|
end
|
27
31
|
|
@@ -31,10 +35,14 @@ module ApiResource
|
|
31
35
|
|
32
36
|
protected
|
33
37
|
|
34
|
-
def self.new_time(*args)
|
38
|
+
def self.new_time(use_zone, *args)
|
35
39
|
year = args.first
|
36
40
|
return nil if year.nil? || year == 0
|
37
|
-
|
41
|
+
if use_zone
|
42
|
+
Time.new(*args).utc rescue nil
|
43
|
+
else
|
44
|
+
Time.utc(*args) rescue nil
|
45
|
+
end
|
38
46
|
end
|
39
47
|
|
40
48
|
end
|
data/lib/api_resource/version.rb
CHANGED
data/lib/api_resource.rb
CHANGED
@@ -2,7 +2,7 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
module ApiResource
|
4
4
|
module Associations
|
5
|
-
|
5
|
+
|
6
6
|
describe HasOneRemoteObjectProxy do
|
7
7
|
|
8
8
|
before(:all) do
|
@@ -10,12 +10,12 @@ module ApiResource
|
|
10
10
|
end
|
11
11
|
|
12
12
|
context "#load" do
|
13
|
-
|
13
|
+
|
14
14
|
it "correctly loads data from an endpoint that returns
|
15
15
|
a single record" do
|
16
|
-
|
16
|
+
|
17
17
|
tr = TestResource.new(
|
18
|
-
:has_one_object => {:service_uri => "/has_one_objects/1.json"}
|
18
|
+
:has_one_object => {:service_uri => "/has_one_objects/1.json"}
|
19
19
|
)
|
20
20
|
tr.has_one_object.internal_object.should be_instance_of(
|
21
21
|
HasOneObject
|
@@ -604,10 +604,10 @@ describe "Associations" do
|
|
604
604
|
end
|
605
605
|
|
606
606
|
it "should be able to chain scopes" do
|
607
|
-
scp = TestResource.active.
|
607
|
+
scp = TestResource.active.boolean(20, 1)
|
608
608
|
scp.should be_a Conditions::ScopeCondition
|
609
609
|
scp.to_query.should eql(
|
610
|
-
"active=true&
|
610
|
+
"active=true&boolean[a]=20&boolean[b]=1"
|
611
611
|
)
|
612
612
|
end
|
613
613
|
|
@@ -789,7 +789,7 @@ describe "Associations" do
|
|
789
789
|
end
|
790
790
|
end
|
791
791
|
context "Belongs To" do
|
792
|
-
before(:all) do
|
792
|
+
before(:all) do
|
793
793
|
TestAR.class_eval do
|
794
794
|
belongs_to_remote :test_resource
|
795
795
|
end
|
data/spec/lib/attributes_spec.rb
CHANGED
@@ -70,7 +70,7 @@ describe "Attributes" do
|
|
70
70
|
|
71
71
|
it "should be able to determine scopes when the class loads" do
|
72
72
|
tst = TestResource.new
|
73
|
-
tst.scope?(:
|
73
|
+
tst.scope?(:boolean).should be_true
|
74
74
|
tst.scope?(:active).should be_true
|
75
75
|
end
|
76
76
|
|
@@ -104,6 +104,21 @@ describe "Attributes" do
|
|
104
104
|
|
105
105
|
tst.attr5.should eql("attr5")
|
106
106
|
end
|
107
|
+
|
108
|
+
context '#password' do
|
109
|
+
|
110
|
+
it 'is able to define password even though it is a
|
111
|
+
class attribute' do
|
112
|
+
|
113
|
+
TestResource.define_attributes :password
|
114
|
+
|
115
|
+
resource = TestResource.new(password: '123456')
|
116
|
+
expect(resource.password).to eql('123456')
|
117
|
+
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
121
|
+
|
107
122
|
end
|
108
123
|
|
109
124
|
it "should define methods for testing for reading and writing known attributes" do
|
data/spec/lib/base_spec.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require 'spec_helper'
|
2
2
|
require 'json'
|
3
3
|
|
4
4
|
include ApiResource
|
@@ -93,6 +93,8 @@ describe "Base" do
|
|
93
93
|
self.prefix = "/path/to/nested/:id/"
|
94
94
|
end
|
95
95
|
|
96
|
+
ChildResource = Class.new(PrefixResource)
|
97
|
+
|
96
98
|
end
|
97
99
|
|
98
100
|
|
@@ -108,6 +110,43 @@ describe "Base" do
|
|
108
110
|
)
|
109
111
|
end
|
110
112
|
|
113
|
+
it "appends its type in the query string if it is not a direct
|
114
|
+
descendant of ApiResource::Base" do
|
115
|
+
|
116
|
+
expect(ChildResource.new_element_path).to eql(
|
117
|
+
"/path/to/project/prefix_resources/new.json?type=ChildResource"
|
118
|
+
)
|
119
|
+
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
123
|
+
|
124
|
+
context "#id" do
|
125
|
+
|
126
|
+
it 'should have a blank id on initialize' do
|
127
|
+
expect(TestResource.new.id).to be_nil
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
131
|
+
|
132
|
+
context '#new_record?' do
|
133
|
+
|
134
|
+
it 'is a new record on initialize' do
|
135
|
+
expect(TestResource.new).to be_new_record
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
139
|
+
|
140
|
+
context '#to_key' do
|
141
|
+
|
142
|
+
it 'is the primary key of the record' do
|
143
|
+
expect(TestResource.find(3).to_key).to eq([3])
|
144
|
+
end
|
145
|
+
|
146
|
+
it 'is nil if the record is not persisted' do
|
147
|
+
expect(TestResource.new.to_key).to be_nil
|
148
|
+
end
|
149
|
+
|
111
150
|
end
|
112
151
|
|
113
152
|
context "Prefixes" do
|
@@ -127,11 +166,9 @@ describe "Base" do
|
|
127
166
|
|
128
167
|
TestResource.connection.expects(:post).with(
|
129
168
|
"/belongs_to_objects/22/test_resources.json",
|
130
|
-
{
|
131
|
-
test_resource:
|
132
|
-
|
133
|
-
}
|
134
|
-
},
|
169
|
+
has_entries({
|
170
|
+
test_resource: is_a(Hash)
|
171
|
+
}),
|
135
172
|
TestResource.headers
|
136
173
|
)
|
137
174
|
|
@@ -172,6 +209,53 @@ describe "Base" do
|
|
172
209
|
|
173
210
|
end
|
174
211
|
|
212
|
+
context "Saving with file upload" do
|
213
|
+
|
214
|
+
before(:all) do
|
215
|
+
TestResource.reload_resource_definition
|
216
|
+
end
|
217
|
+
|
218
|
+
after(:all) do
|
219
|
+
TestResource.reload_resource_definition
|
220
|
+
end
|
221
|
+
|
222
|
+
let(:uploaded_file){
|
223
|
+
ActionDispatch::Http::UploadedFile.new(
|
224
|
+
tempfile: File.new(
|
225
|
+
File.expand_path("../../support/files/bg-awesome.jpg", __FILE__)
|
226
|
+
)
|
227
|
+
)
|
228
|
+
}
|
229
|
+
|
230
|
+
context "#save" do
|
231
|
+
|
232
|
+
it 'sends the file to HTTPClient' do
|
233
|
+
|
234
|
+
TestResource.define_attributes :image_file
|
235
|
+
|
236
|
+
TestResource.connection.expects(:request)
|
237
|
+
.with(
|
238
|
+
:post,
|
239
|
+
instance_of(String),
|
240
|
+
has_entries(
|
241
|
+
test_resource: has_entries(
|
242
|
+
'image_file' => instance_of(File)
|
243
|
+
)
|
244
|
+
),
|
245
|
+
instance_of(Hash)
|
246
|
+
)
|
247
|
+
|
248
|
+
test_resource = TestResource.new(
|
249
|
+
image_file: uploaded_file
|
250
|
+
)
|
251
|
+
test_resource.save
|
252
|
+
|
253
|
+
end
|
254
|
+
|
255
|
+
end
|
256
|
+
|
257
|
+
end
|
258
|
+
|
175
259
|
|
176
260
|
describe "Loading data from a hash" do
|
177
261
|
|
@@ -445,19 +529,23 @@ describe "Base" do
|
|
445
529
|
context("Override create to return the json") do
|
446
530
|
|
447
531
|
|
448
|
-
it "should be able to include associations when saving if
|
532
|
+
it "should be able to include associations when saving if
|
533
|
+
they are specified" do
|
449
534
|
ApiResource::Connection.any_instance.expects(:post).with(
|
450
535
|
"/test_resources.json",
|
451
|
-
{
|
452
|
-
test_resource: {
|
453
|
-
'
|
454
|
-
|
455
|
-
|
456
|
-
},
|
536
|
+
has_entries({
|
537
|
+
test_resource: has_entries({
|
538
|
+
'has_many_objects' => [{}]
|
539
|
+
})
|
540
|
+
}),
|
457
541
|
TestResource.headers
|
458
542
|
)
|
459
543
|
|
460
|
-
tr = TestResource.build(
|
544
|
+
tr = TestResource.build(
|
545
|
+
name: "Ethan",
|
546
|
+
age: 20,
|
547
|
+
has_many_objects: [HasManyObject.new]
|
548
|
+
)
|
461
549
|
tr.save
|
462
550
|
end
|
463
551
|
|
@@ -467,7 +555,8 @@ describe "Base" do
|
|
467
555
|
"/test_resources.json",
|
468
556
|
{
|
469
557
|
test_resource: {
|
470
|
-
'name' => 'Ethan'
|
558
|
+
'name' => 'Ethan',
|
559
|
+
'roles' => []
|
471
560
|
}
|
472
561
|
},
|
473
562
|
TestResource.headers
|
@@ -477,13 +566,15 @@ describe "Base" do
|
|
477
566
|
tr.save
|
478
567
|
end
|
479
568
|
|
480
|
-
it "should include false attributes when creating
|
569
|
+
it "should include false and empty attributes when creating
|
570
|
+
by default" do
|
481
571
|
ApiResource::Connection.any_instance.expects(:post).with(
|
482
572
|
"/test_resources.json",
|
483
573
|
{
|
484
574
|
test_resource: {
|
485
575
|
'name' => 'Ethan',
|
486
|
-
'is_active' => false
|
576
|
+
'is_active' => false,
|
577
|
+
'roles' => []
|
487
578
|
}
|
488
579
|
},
|
489
580
|
TestResource.headers
|
@@ -500,6 +591,7 @@ describe "Base" do
|
|
500
591
|
{
|
501
592
|
test_resource: {
|
502
593
|
'name' => 'Ethan',
|
594
|
+
'roles' => [],
|
503
595
|
'has_one_object' => {
|
504
596
|
'size' => 'large'
|
505
597
|
}
|
@@ -514,15 +606,15 @@ describe "Base" do
|
|
514
606
|
end
|
515
607
|
|
516
608
|
|
517
|
-
it "should include nil attributes if they are passed in through
|
609
|
+
it "should include nil attributes if they are passed in through
|
610
|
+
the include_extras" do
|
518
611
|
ApiResource::Connection.any_instance.expects(:post).with(
|
519
612
|
"/test_resources.json",
|
520
|
-
{
|
521
|
-
test_resource: {
|
522
|
-
'name' => 'Ethan',
|
613
|
+
has_entries({
|
614
|
+
test_resource: has_entries({
|
523
615
|
'age' => nil
|
524
|
-
}
|
525
|
-
},
|
616
|
+
})
|
617
|
+
}),
|
526
618
|
TestResource.headers
|
527
619
|
)
|
528
620
|
|
@@ -904,7 +996,7 @@ describe "Base" do
|
|
904
996
|
|
905
997
|
begin
|
906
998
|
initial = ApiResource::Base.ttl
|
907
|
-
ApiResource::Base.ttl =
|
999
|
+
ApiResource::Base.ttl = 50
|
908
1000
|
if defined?(Rails)
|
909
1001
|
Object.send(:remove_const, :Rails)
|
910
1002
|
end
|
@@ -914,23 +1006,13 @@ describe "Base" do
|
|
914
1006
|
end
|
915
1007
|
|
916
1008
|
end
|
917
|
-
|
918
|
-
|
919
|
-
|
920
|
-
|
921
|
-
TestResource.find(
|
1009
|
+
# @todo This doesn't exactly test the cache, but stubbing is really
|
1010
|
+
# difficult when everything uses the cache
|
1011
|
+
it 'should work with the real cache' do
|
1012
|
+
tr = TestResource.find(1, expires_in: 200)
|
1013
|
+
expect(tr).to eql(TestResource.find(1, expires_in: 200))
|
922
1014
|
end
|
923
1015
|
|
924
|
-
it "should find with expires_in and cache" do
|
925
|
-
ApiResource.cache.expects(:fetch)
|
926
|
-
.with(anything, expires_in: 10.0)
|
927
|
-
.returns({id: 2, name: "d"})
|
928
|
-
|
929
|
-
res = TestResource.find("adfa", expires_in: 10)
|
930
|
-
|
931
|
-
ApiResource::Base.ttl.should eql(1)
|
932
|
-
res.id.to_i.should eql(2)
|
933
|
-
end
|
934
1016
|
end
|
935
1017
|
|
936
1018
|
end
|
@@ -17,7 +17,7 @@ describe "Conditions" do
|
|
17
17
|
|
18
18
|
obj3 = obj.paginate
|
19
19
|
|
20
|
-
obj3.to_query.should eql("active=true&
|
20
|
+
obj3.to_query.should eql("active=true&page=1&per_page=10")
|
21
21
|
obj3.should_not be_eager_load
|
22
22
|
obj3.should_not be_blank_conditions
|
23
23
|
# Make sure that it clones
|
@@ -61,25 +61,37 @@ 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
|
-
|
65
|
-
|
66
|
-
|
64
|
+
resource_finder_mock = mock(load: [1], first: 1)
|
65
|
+
|
66
|
+
ApiResource::Finders::ResourceFinder.expects(:new)
|
67
|
+
.with(TestResource, obj)
|
68
|
+
.returns(resource_finder_mock)
|
69
|
+
|
70
|
+
obj.internal_object.should eql(resource_finder_mock)
|
71
|
+
obj.all.should eql(resource_finder_mock)
|
67
72
|
obj.first.should eql(1)
|
68
73
|
end
|
69
74
|
|
70
|
-
it "should proxy calls to enumerable and array methods to the loaded
|
75
|
+
it "should proxy calls to enumerable and array methods to the loaded
|
76
|
+
object" do
|
77
|
+
|
71
78
|
obj = TestResource.includes(:has_many_objects)
|
72
79
|
|
73
|
-
|
80
|
+
# this just needs to call each
|
81
|
+
resource_finder_mock = mock(load: true, each: true)
|
82
|
+
|
83
|
+
ApiResource::Finders::ResourceFinder.expects(:new)
|
84
|
+
.with(TestResource, obj)
|
85
|
+
.returns(resource_finder_mock)
|
74
86
|
|
75
|
-
obj.collect{|o| o * 2}.should eql([
|
87
|
+
obj.collect{|o| o * 2}.should eql([])
|
76
88
|
end
|
77
89
|
|
78
90
|
it "should not typecast nil and false to true when creating condition hashes" do
|
79
|
-
obj = TestResource.
|
80
|
-
hsh = obj.to_hash["
|
81
|
-
hsh["
|
82
|
-
hsh.should be_key(:
|
91
|
+
obj = TestResource.boolean(false, nil)
|
92
|
+
hsh = obj.to_hash["boolean"]
|
93
|
+
hsh["a"].should eql(false)
|
94
|
+
hsh.should be_key(:b)
|
83
95
|
end
|
84
96
|
|
85
97
|
end
|