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
data/docs/Retrieval.md
ADDED
@@ -0,0 +1,279 @@
|
|
1
|
+
# Retrieving Records in ApiResource
|
2
|
+
|
3
|
+
|
4
|
+
## Finding by id
|
5
|
+
|
6
|
+
The simplest way to retrieve a record is by its ID
|
7
|
+
|
8
|
+
Resource::Person.find(1)
|
9
|
+
# GET /people/1.json
|
10
|
+
|
11
|
+
## Simple conditions
|
12
|
+
|
13
|
+
To add simple query params, you can just use the `.where` method
|
14
|
+
|
15
|
+
Resource::Person.where(first_name: 'Aaron').all
|
16
|
+
# GET /people.json?first_name=Aaron
|
17
|
+
|
18
|
+
## Scopes
|
19
|
+
|
20
|
+
Scopes must be activated in the Server application and are then read by
|
21
|
+
the client application via the
|
22
|
+
{file:docs/ResourceDefinition.md Resource Definition}.
|
23
|
+
|
24
|
+
### Activating a scope in the Server application
|
25
|
+
|
26
|
+
# app/models/person.rb
|
27
|
+
class Person < ActiveRecord::Base
|
28
|
+
|
29
|
+
include LifebookerCommon::Model::Resource
|
30
|
+
|
31
|
+
scope :birthday_on, -> date {
|
32
|
+
where(birthday: date)
|
33
|
+
}
|
34
|
+
|
35
|
+
scope :aaron, -> {
|
36
|
+
where(name: 'Aaron')
|
37
|
+
}
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
### Using a scope in the Client application
|
42
|
+
|
43
|
+
# GET /people.json?birthday_on[date]=2014-01-01
|
44
|
+
@people = Resource::Person.birthday_on(Date.parse('2014-01-01'))
|
45
|
+
|
46
|
+
@people.each do |person|
|
47
|
+
...
|
48
|
+
end
|
49
|
+
|
50
|
+
Scopes can be chained together
|
51
|
+
|
52
|
+
# GET /people.json?birthday_on[date]=2014-01-01&aaron=1
|
53
|
+
@people = Resource::Person
|
54
|
+
.birthday_on(Date.parse('2014-01-01'))
|
55
|
+
.aaron
|
56
|
+
|
57
|
+
@people.each do |person|
|
58
|
+
...
|
59
|
+
end
|
60
|
+
|
61
|
+
### Applying scopes in the Client application
|
62
|
+
|
63
|
+
ApiResource::Base gives us the ability to apply scopes that are valid
|
64
|
+
according to the {file:docs/ResourceDefinition.md Resource Definition}
|
65
|
+
directly from params that are passed in
|
66
|
+
|
67
|
+
# In the Client application
|
68
|
+
class PeopleController < ApplicationController
|
69
|
+
|
70
|
+
def index
|
71
|
+
@people = Resource::Person.add_scopes(params)
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
This reads the params supplied to the controller to see if there are any
|
77
|
+
valid scopes and then applies them. These scopes follow the same rules
|
78
|
+
as the ones that are sent to the server
|
79
|
+
|
80
|
+
#### Sequence
|
81
|
+
|
82
|
+
1. Client supplies params to our client app
|
83
|
+
|
84
|
+
# curl http://clienthost/people.html?birthday_on[date]=2014-01-01
|
85
|
+
|
86
|
+
1. Client controller parses params, passes them to {ApiResource.add_scopes}
|
87
|
+
and creates a ScopeCondition to generate params to the server
|
88
|
+
|
89
|
+
1. Resource::Person makes a request to the Server application. This request
|
90
|
+
has the same parameters as what was passed in to the client application
|
91
|
+
assuming those parameters were valid for a scope
|
92
|
+
|
93
|
+
# GET http://serverhost/people.json?birthday_on[date]=2014-01-01
|
94
|
+
|
95
|
+
1. Server application returns the data as JSON and Resource::Person
|
96
|
+
instantiates its records for use in the Client application
|
97
|
+
|
98
|
+
### Automatically generated scopes
|
99
|
+
|
100
|
+
`api_resource_server` and {ApiResource::Base} automatically provide several
|
101
|
+
scopes for all descendants of ActiveRecord::Base and ApiResource::Base
|
102
|
+
respectively
|
103
|
+
|
104
|
+
#### IDs
|
105
|
+
|
106
|
+
You can supply an array of ids to a resource to scope by only resources in
|
107
|
+
that set of ids
|
108
|
+
|
109
|
+
# in the Client application
|
110
|
+
Resource::Person.add_scopes(ids: [1, 2, 3])
|
111
|
+
# => GET /people.json?ids[]=1&ids[]=2&ids[]=3
|
112
|
+
|
113
|
+
#### Type
|
114
|
+
|
115
|
+
In order to limit your scope to a subclass of a class that users Single Table
|
116
|
+
Inheritance, you can supply the type
|
117
|
+
|
118
|
+
# in the Server application
|
119
|
+
class Carpenter < Person
|
120
|
+
end
|
121
|
+
|
122
|
+
# in the Client application
|
123
|
+
Resource::Person.type('Carpenter')
|
124
|
+
# => GET /people.json?type=Carpenter
|
125
|
+
|
126
|
+
# back in the Server application Person.add_scopes
|
127
|
+
# converts
|
128
|
+
|
129
|
+
Person.all
|
130
|
+
|
131
|
+
# into
|
132
|
+
|
133
|
+
Carpenter.all
|
134
|
+
|
135
|
+
#### Page and PerPage
|
136
|
+
|
137
|
+
In order to paginate, you can pass page and per_page scopes into your
|
138
|
+
ApiResource::Base subclass
|
139
|
+
|
140
|
+
Resource::Person.per_page(20).page(1)
|
141
|
+
|
142
|
+
# or
|
143
|
+
|
144
|
+
Resource::Person.add_scopes(per_page: 20, page: 1)
|
145
|
+
|
146
|
+
# either of these produce
|
147
|
+
GET /people.json?page=1&per_page=20
|
148
|
+
|
149
|
+
In the Server application `add_scopes` will take care of applying
|
150
|
+
the pagination using its integration with
|
151
|
+
{https://github.com/mislav/will_paginate will_paginate}
|
152
|
+
|
153
|
+
In addition, the Server application will include a header denoting
|
154
|
+
the number of records in the set so that
|
155
|
+
{https://github.com/mislav/will_paginate will_paginate} can
|
156
|
+
render its page helpers on the client side
|
157
|
+
|
158
|
+
*Note* that in order to enable this feature the Server application
|
159
|
+
*must* use `respond_to`
|
160
|
+
|
161
|
+
# in a controller in the Client application
|
162
|
+
@people = Resource::Person.add_scopes(
|
163
|
+
params.merge(page: 2, per_page: 20)
|
164
|
+
)
|
165
|
+
|
166
|
+
# in a view in the Client application
|
167
|
+
- @people.each do |person|
|
168
|
+
%tr
|
169
|
+
%td ...
|
170
|
+
# renders the pagination links
|
171
|
+
= will_paginate(@people)
|
172
|
+
|
173
|
+
### Types of scopes
|
174
|
+
|
175
|
+
1. Static scopes
|
176
|
+
|
177
|
+
# Server code
|
178
|
+
scope :x, where(x: 'Val')
|
179
|
+
OR
|
180
|
+
scope :x, -> { where(x: 'Val') }
|
181
|
+
|
182
|
+
# Produces request from client
|
183
|
+
Resource::Person.x
|
184
|
+
# ?x=1
|
185
|
+
|
186
|
+
1. Scopes with required parameters
|
187
|
+
|
188
|
+
# Server code
|
189
|
+
scope :birthday_between, -> start, end {
|
190
|
+
where("birthday_between ? AND ?", start, end)
|
191
|
+
}
|
192
|
+
|
193
|
+
# Produces request from the client
|
194
|
+
@people = Resource::Person.birthday_between(
|
195
|
+
start: Date.today,
|
196
|
+
end: Date.tomorrow
|
197
|
+
)
|
198
|
+
# ?birthday_between[start]=2014-01-01&birthday_between[end]=2014-01-02
|
199
|
+
|
200
|
+
1. Scopes with varargs
|
201
|
+
|
202
|
+
# server code
|
203
|
+
scope :first_name, -> *names {
|
204
|
+
where(first_name: names)
|
205
|
+
}
|
206
|
+
|
207
|
+
# Produces request from the client
|
208
|
+
@people = Resource::Person.first_name('Bill', 'Sue')
|
209
|
+
# ?first_name[]=Bill&first_name[]=Sue
|
210
|
+
|
211
|
+
## Including associated data
|
212
|
+
|
213
|
+
Oftentimes we will need data from two resources, which would produce n+1 API
|
214
|
+
calls unless we sideload our data
|
215
|
+
|
216
|
+
### Concept
|
217
|
+
|
218
|
+
If, for example we loaded a list of 50 People, but needed their State data we
|
219
|
+
might have to make 50 requests to the State API endpoint
|
220
|
+
|
221
|
+
### Server application
|
222
|
+
|
223
|
+
# in app/models/person.rb
|
224
|
+
class Person < ActiveRecord::Base
|
225
|
+
|
226
|
+
belongs_to :state
|
227
|
+
|
228
|
+
# Attributes
|
229
|
+
#
|
230
|
+
# :first_name, :last_name, :state_id
|
231
|
+
|
232
|
+
end
|
233
|
+
|
234
|
+
# in app/models/state.rb
|
235
|
+
class State < ActiveRecord::Base
|
236
|
+
# Attributes
|
237
|
+
#
|
238
|
+
# :name
|
239
|
+
end
|
240
|
+
|
241
|
+
# in app/controllers/people_controller.rb
|
242
|
+
def index
|
243
|
+
respond_with(Person.add_scopes(params))
|
244
|
+
end
|
245
|
+
|
246
|
+
# in app/controllers/states_controller.rb
|
247
|
+
def index
|
248
|
+
respond_with(State.add_scopes(params))
|
249
|
+
end
|
250
|
+
|
251
|
+
### Client application
|
252
|
+
|
253
|
+
# in lib/resource/state.rb
|
254
|
+
module Resource
|
255
|
+
class State < ApiResource::Base
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
# in lib/resource/person.rb
|
260
|
+
module Resource
|
261
|
+
class Person < ApiResource::Base
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
# in a controller
|
266
|
+
@people = Resource::Person.includes(:state)
|
267
|
+
|
268
|
+
# this results in
|
269
|
+
#
|
270
|
+
# GET /people.json
|
271
|
+
#
|
272
|
+
# and
|
273
|
+
#
|
274
|
+
# GET /states.json?ids[]=person1.state_id&ids[]=person2.state_id ...
|
275
|
+
|
276
|
+
|
277
|
+
{ApiResource::Base} knows how to include data from any association where
|
278
|
+
ids are embedded in the response of the base object and the association
|
279
|
+
is present in the {file:docs/ResourceDefinition.md Resource Definition}
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# Serialization in ApiResource
|
2
|
+
|
3
|
+
## Sending Data to Server (JSON)
|
4
|
+
|
5
|
+
Data is Serialized using singular resource name as a key, and the attributes as the value.
|
6
|
+
|
7
|
+
# POST /people.json
|
8
|
+
|
9
|
+
# POST body
|
10
|
+
{
|
11
|
+
person: {
|
12
|
+
first_name: "Aaron",
|
13
|
+
last_name: "Burr",
|
14
|
+
birthdate: "1755-02-05"
|
15
|
+
}
|
16
|
+
}
|
17
|
+
|
18
|
+
## Retrieving Data From Server
|
19
|
+
|
20
|
+
ApiResource expects resources to be at the root of the JSON or XML document
|
21
|
+
returned
|
22
|
+
|
23
|
+
# GET /people/1.json
|
24
|
+
|
25
|
+
# Response
|
26
|
+
{
|
27
|
+
first_name: 'Aaron',
|
28
|
+
last_name: 'Burr',
|
29
|
+
birthdate: '1756-02-06'
|
30
|
+
}
|
31
|
+
|
32
|
+
# GET /people.json
|
33
|
+
|
34
|
+
# Response
|
35
|
+
[
|
36
|
+
{
|
37
|
+
first_name: 'Aaron',
|
38
|
+
last_name: 'Burr',
|
39
|
+
birthdate: '1756-02-06'
|
40
|
+
}
|
41
|
+
]
|
42
|
+
|
43
|
+
## Setting the Serializer Format
|
44
|
+
|
45
|
+
ApiResource ships with two major Formats: {ApiResource::Formats::JsonFormat}
|
46
|
+
and {ApiResource::Formats::XmlFormat}
|
47
|
+
|
48
|
+
The default format is `:json`
|
49
|
+
|
50
|
+
You can configure your format:
|
51
|
+
|
52
|
+
ApiResource::Base.format = ApiResource::Formats::JsonFormat
|
53
|
+
|
54
|
+
# OR
|
55
|
+
|
56
|
+
ApiResource::Base.format = :xml
|
@@ -7,7 +7,7 @@ module ApiResource
|
|
7
7
|
id_method_name = self.foreign_key_name(assoc_name)
|
8
8
|
|
9
9
|
klass.api_resource_generated_methods.module_eval <<-EOE, __FILE__, __LINE__ + 1
|
10
|
-
|
10
|
+
|
11
11
|
def #{id_method_name}
|
12
12
|
@attributes_cache[:#{id_method_name}] ||= begin
|
13
13
|
# check our attributes first, then go to the remote
|
@@ -43,7 +43,7 @@ module ApiResource
|
|
43
43
|
:ids => associated_ids
|
44
44
|
)
|
45
45
|
# next try for a foreign key e.g. /objects.json?owner_id=1
|
46
|
-
elsif self.owner.try(:id).
|
46
|
+
elsif self.owner.try(:id).to_i > 0
|
47
47
|
self.remote_path = self.klass.collection_path(
|
48
48
|
self.owner.class.to_s.foreign_key => self.owner.id
|
49
49
|
)
|
@@ -136,8 +136,8 @@ module ApiResource
|
|
136
136
|
# Adds the attribute into some internal data structures but does
|
137
137
|
# not define any methods for it
|
138
138
|
#
|
139
|
-
# @param arg [Array] A 1 or 2 element array holding an
|
140
|
-
# optionally a type for that attribute
|
139
|
+
# @param arg [Array] A 1 or 2 element array holding an
|
140
|
+
# attribute name and optionally a type for that attribute
|
141
141
|
# @param access_level [Symbol] Either :protected or :public based on
|
142
142
|
# the access level for this attribute
|
143
143
|
#
|
@@ -542,9 +542,21 @@ module ApiResource
|
|
542
542
|
|
543
543
|
def method_missing(sym, *args, &block)
|
544
544
|
sym = sym.to_sym
|
545
|
-
|
545
|
+
|
546
|
+
# Maybe the resource definition sucks...
|
547
|
+
if self.class.resource_definition_is_invalid?
|
548
|
+
self.class.reload_resource_definition
|
549
|
+
end
|
550
|
+
|
551
|
+
# If we don't respond by now...
|
552
|
+
if self.respond_to?(sym)
|
553
|
+
return self.send(sym, *args, &block)
|
554
|
+
elsif @attributes.keys.symbolize_array.include?(sym)
|
555
|
+
# Try returning the attributes from the attributes hash
|
546
556
|
return @attributes[sym]
|
547
557
|
end
|
558
|
+
|
559
|
+
# Fall back to class method_missing
|
548
560
|
super
|
549
561
|
end
|
550
562
|
|
@@ -680,4 +692,4 @@ module ApiResource
|
|
680
692
|
|
681
693
|
end
|
682
694
|
|
683
|
-
end
|
695
|
+
end
|
data/lib/api_resource/base.rb
CHANGED
@@ -31,22 +31,31 @@ module ApiResource
|
|
31
31
|
# => 7) Write documentation
|
32
32
|
# => 8) Write Examples
|
33
33
|
|
34
|
-
class_attribute
|
35
|
-
:
|
34
|
+
class_attribute(
|
35
|
+
:site,
|
36
|
+
:proxy,
|
37
|
+
:user,
|
38
|
+
:password,
|
39
|
+
:auth_type,
|
40
|
+
:format,
|
41
|
+
:timeout,
|
42
|
+
:open_timeout,
|
43
|
+
:ssl_options,
|
44
|
+
:token,
|
45
|
+
:ttl,
|
46
|
+
{ instance_writer: false, instance_reader: false }
|
47
|
+
)
|
48
|
+
self.format = ApiResource::Formats::JsonFormat
|
36
49
|
|
37
|
-
class_attribute
|
50
|
+
class_attribute(:include_root_in_json)
|
38
51
|
self.include_root_in_json = true
|
39
52
|
|
40
|
-
class_attribute
|
53
|
+
class_attribute(:include_nil_attributes_on_create)
|
41
54
|
self.include_nil_attributes_on_create = false
|
42
55
|
|
43
|
-
class_attribute
|
56
|
+
class_attribute(:include_all_attributes_on_update)
|
44
57
|
self.include_nil_attributes_on_create = false
|
45
58
|
|
46
|
-
class_attribute :format
|
47
|
-
self.format = ApiResource::Formats::JsonFormat
|
48
|
-
|
49
|
-
|
50
59
|
delegate :logger, to: ApiResource
|
51
60
|
|
52
61
|
class << self
|
@@ -197,12 +206,12 @@ module ApiResource
|
|
197
206
|
# backwards compatibility
|
198
207
|
alias_method :reload_class_attributes, :reload_resource_definition
|
199
208
|
|
200
|
-
#
|
209
|
+
#
|
201
210
|
# Mutex so that multiple Threads don't try to load the resource
|
202
211
|
# definition at the same time
|
203
|
-
#
|
212
|
+
#
|
204
213
|
# @return [Mutex]
|
205
|
-
def resource_definition_mutex
|
214
|
+
def resource_definition_mutex
|
206
215
|
@resource_definition_mutex ||= Mutex.new
|
207
216
|
end
|
208
217
|
|
@@ -365,11 +374,15 @@ module ApiResource
|
|
365
374
|
|
366
375
|
# path to find
|
367
376
|
def new_element_path(prefix_options = {})
|
368
|
-
File.join(
|
377
|
+
url = File.join(
|
369
378
|
self.prefix(prefix_options),
|
370
379
|
self.collection_name,
|
371
380
|
"new.#{format.extension}"
|
372
381
|
)
|
382
|
+
if self.superclass != ApiResource::Base && self.name.present?
|
383
|
+
url = "#{url}?type=#{self.name.demodulize}"
|
384
|
+
end
|
385
|
+
return url
|
373
386
|
end
|
374
387
|
|
375
388
|
def collection_path(prefix_options = {}, query_options = nil)
|
@@ -541,6 +554,14 @@ module ApiResource
|
|
541
554
|
self.class.instantiate_record(self.attributes)
|
542
555
|
end
|
543
556
|
|
557
|
+
#
|
558
|
+
# Implementation of to_key for use in Rails forms
|
559
|
+
#
|
560
|
+
# @return [Array<Fixnum>,nil] Array wrapped id or nil
|
561
|
+
def to_key
|
562
|
+
[self.id] if self.id.present?
|
563
|
+
end
|
564
|
+
|
544
565
|
def update_attributes(attrs)
|
545
566
|
self.attributes = attrs
|
546
567
|
self.save
|
@@ -697,9 +718,14 @@ module ApiResource
|
|
697
718
|
path = self.collection_path
|
698
719
|
body = self.setup_create_call(*args)
|
699
720
|
headers = self.class.headers
|
700
|
-
|
701
|
-
|
702
|
-
|
721
|
+
|
722
|
+
# this checks to see if we have uploaded a file
|
723
|
+
# and sets headers accordingly
|
724
|
+
self.with_uploaded_file_format do
|
725
|
+
# make the post call
|
726
|
+
connection.post(path, body, headers).tap do |response|
|
727
|
+
load_attributes_from_response(response)
|
728
|
+
end
|
703
729
|
end
|
704
730
|
end
|
705
731
|
|
@@ -724,9 +750,14 @@ module ApiResource
|
|
724
750
|
path = self.element_path(self.id)
|
725
751
|
body = self.setup_update_call(*args)
|
726
752
|
headers = self.class.headers
|
727
|
-
|
728
|
-
|
729
|
-
|
753
|
+
|
754
|
+
# this checks to see if we have uploaded a file
|
755
|
+
# and sets headers accordingly
|
756
|
+
self.with_uploaded_file_format do
|
757
|
+
# We can just ignore the response
|
758
|
+
connection.put(path, body, headers).tap do |response|
|
759
|
+
load_attributes_from_response(response)
|
760
|
+
end
|
730
761
|
end
|
731
762
|
end
|
732
763
|
|
@@ -765,6 +796,54 @@ module ApiResource
|
|
765
796
|
return data
|
766
797
|
end
|
767
798
|
|
799
|
+
protected
|
800
|
+
|
801
|
+
#
|
802
|
+
# Are any of our attributes of type File?
|
803
|
+
#
|
804
|
+
# @return [Boolean]
|
805
|
+
def has_file_attribute?
|
806
|
+
self.attributes.values.any?{|val|
|
807
|
+
HTTP::Message.file?(val) ||
|
808
|
+
val.is_a?(ActionDispatch::Http::UploadedFile)
|
809
|
+
}
|
810
|
+
end
|
811
|
+
|
812
|
+
#
|
813
|
+
# Run a block with a given format
|
814
|
+
#
|
815
|
+
# @param new_format [Module] Format module
|
816
|
+
# @param &block [Proc] Block to run
|
817
|
+
#
|
818
|
+
# @return [Mixed]
|
819
|
+
def with_format(new_format, &block)
|
820
|
+
|
821
|
+
old_format = self.class.format
|
822
|
+
|
823
|
+
begin
|
824
|
+
self.class.format = new_format
|
825
|
+
yield
|
826
|
+
ensure
|
827
|
+
self.class.format = old_format
|
828
|
+
end
|
829
|
+
end
|
830
|
+
|
831
|
+
#
|
832
|
+
# Wrapper to add the FileUpload format
|
833
|
+
#
|
834
|
+
# @param &block [Proc] Block to run
|
835
|
+
#
|
836
|
+
# @return [Mixed]
|
837
|
+
def with_uploaded_file_format(&block)
|
838
|
+
if self.has_file_attribute?
|
839
|
+
self.with_format(Formats::FileUploadFormat) do
|
840
|
+
yield
|
841
|
+
end
|
842
|
+
else
|
843
|
+
yield
|
844
|
+
end
|
845
|
+
end
|
846
|
+
|
768
847
|
private
|
769
848
|
|
770
849
|
def split_options(options = {})
|