api_resource 0.6.21 → 0.6.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.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
|