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
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 = {})
|