pragma-decorator 1.3.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +10 -13
- data/Gemfile +2 -0
- data/README.md +282 -3
- data/lib/pragma/decorator.rb +3 -2
- data/lib/pragma/decorator/association.rb +55 -59
- data/lib/pragma/decorator/association/binding.rb +4 -74
- data/lib/pragma/decorator/association/errors.rb +30 -0
- data/lib/pragma/decorator/association/reflection.rb +14 -17
- data/lib/pragma/decorator/base.rb +1 -30
- data/lib/pragma/decorator/collection.rb +28 -0
- data/lib/pragma/decorator/pagination.rb +60 -0
- data/lib/pragma/decorator/timestamp.rb +1 -1
- data/lib/pragma/decorator/version.rb +1 -1
- data/pragma-decorator.gemspec +2 -3
- metadata +16 -19
- data/doc/01-basic-usage.md +0 -42
- data/doc/02-object-types.md +0 -47
- data/doc/03-associations.md +0 -91
- data/doc/04-timestamps.md +0 -33
- data/lib/pragma/decorator/association/inconsistent_type_error.rb +0 -27
- data/lib/pragma/decorator/association/unexpandable_error.rb +0 -19
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4cc969bd13e27d434cc079786db43b29ae6b6154
|
4
|
+
data.tar.gz: cfefba30c1aab7b6d53db48031df9e991159537e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 26574df0b3a1807c18fe1c156feadf6ea37947fcb044f1587d4de0319782af2164cb5a7c591a99bb954d187b0ec6c6e5c499150eba89be2ab04c8b36c00a6763
|
7
|
+
data.tar.gz: d502113406896fd913100c0ededa0242ead3fc8fa1d1cdd7545d2bff0ed15fe9d8a227beac6f3177f22189354223c9bada76a62f02f188b4119b3549bab0c51d
|
data/.rubocop.yml
CHANGED
@@ -15,6 +15,7 @@ AllCops:
|
|
15
15
|
- 'config/**/*'
|
16
16
|
- '**/Rakefile'
|
17
17
|
- '**/Gemfile'
|
18
|
+
- 'pragma-decorator.gemspec'
|
18
19
|
|
19
20
|
RSpec/DescribeClass:
|
20
21
|
Exclude:
|
@@ -24,26 +25,26 @@ Style/BlockDelimiters:
|
|
24
25
|
Exclude:
|
25
26
|
- 'spec/**/*'
|
26
27
|
|
27
|
-
|
28
|
+
Layout/AlignParameters:
|
28
29
|
EnforcedStyle: with_fixed_indentation
|
29
30
|
|
30
|
-
|
31
|
+
Layout/ClosingParenthesisIndentation:
|
31
32
|
Enabled: false
|
32
33
|
|
33
34
|
Metrics/LineLength:
|
34
35
|
Max: 100
|
35
36
|
AllowURI: true
|
36
37
|
|
37
|
-
|
38
|
+
Layout/FirstParameterIndentation:
|
38
39
|
Enabled: false
|
39
40
|
|
40
|
-
|
41
|
+
Layout/MultilineMethodCallIndentation:
|
41
42
|
EnforcedStyle: indented
|
42
43
|
|
43
|
-
|
44
|
+
Layout/IndentArray:
|
44
45
|
EnforcedStyle: consistent
|
45
46
|
|
46
|
-
|
47
|
+
Layout/IndentHash:
|
47
48
|
EnforcedStyle: consistent
|
48
49
|
|
49
50
|
Style/SignalException:
|
@@ -68,7 +69,7 @@ RSpec/NamedSubject:
|
|
68
69
|
RSpec/ExampleLength:
|
69
70
|
Enabled: false
|
70
71
|
|
71
|
-
|
72
|
+
Layout/MultilineMethodCallBraceLayout:
|
72
73
|
Enabled: false
|
73
74
|
|
74
75
|
Metrics/MethodLength:
|
@@ -86,12 +87,8 @@ Metrics/CyclomaticComplexity:
|
|
86
87
|
Metrics/BlockLength:
|
87
88
|
Enabled: false
|
88
89
|
|
89
|
-
Metrics/ClassLength:
|
90
|
-
Enabled: false
|
91
|
-
|
92
90
|
Style/GuardClause:
|
93
91
|
Enabled: false
|
94
92
|
|
95
|
-
|
96
|
-
|
97
|
-
- 'pragma-decorator.gemspec'
|
93
|
+
Capybara/FeatureMethods:
|
94
|
+
Enabled: false
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -7,8 +7,8 @@
|
|
7
7
|
|
8
8
|
Decorators are a way to easily convert your API resources to JSON with minimum hassle.
|
9
9
|
|
10
|
-
They are built on top of [ROAR](https://github.com/apotonick/roar)
|
11
|
-
for rendering collections,
|
10
|
+
They are built on top of [ROAR](https://github.com/apotonick/roar). We provide some useful helpers
|
11
|
+
for rendering collections, expanding associations and much more.
|
12
12
|
|
13
13
|
## Installation
|
14
14
|
|
@@ -32,7 +32,286 @@ $ gem install pragma-decorator
|
|
32
32
|
|
33
33
|
## Usage
|
34
34
|
|
35
|
-
|
35
|
+
Creating a decorator is as simple as inheriting from `Pragma::Decorator::Base`:
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
module API
|
39
|
+
module V1
|
40
|
+
module User
|
41
|
+
module Decorator
|
42
|
+
class Instance < Pragma::Decorator::Base
|
43
|
+
property :id
|
44
|
+
property :email
|
45
|
+
property :full_name
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
```
|
52
|
+
|
53
|
+
Just instantiate the decorator by passing it an object to decorate, then call `#to_hash` or
|
54
|
+
`#to_json`:
|
55
|
+
|
56
|
+
```ruby
|
57
|
+
decorator = API::V1::User::Decorator::Instance.new(user)
|
58
|
+
decorator.to_json
|
59
|
+
```
|
60
|
+
|
61
|
+
This will produce the following JSON:
|
62
|
+
|
63
|
+
```json
|
64
|
+
{
|
65
|
+
"id": 1,
|
66
|
+
"email": "jdoe@example.com",
|
67
|
+
"full_name": "John Doe"
|
68
|
+
}
|
69
|
+
```
|
70
|
+
|
71
|
+
Since Pragma::Decorator is built on top of [ROAR](https://github.com/apotonick/roar) (which, in
|
72
|
+
turn, is built on top of [Representable](https://github.com/apotonick/representable)), you should
|
73
|
+
consult their documentation for the basic usage of decorators; the rest of this section only covers
|
74
|
+
the features provided specifically by Pragma::Decorator.
|
75
|
+
|
76
|
+
### Object Types
|
77
|
+
|
78
|
+
It is recommended that decorators expose the type of the decorated object. You can achieve this
|
79
|
+
with the `Type` mixin:
|
80
|
+
|
81
|
+
```ruby
|
82
|
+
module API
|
83
|
+
module V1
|
84
|
+
module User
|
85
|
+
module Decorator
|
86
|
+
class Instance < Pragma::Decorator::Base
|
87
|
+
feature Pragma::Decorator::Type
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
```
|
94
|
+
|
95
|
+
This would result in the following representation:
|
96
|
+
|
97
|
+
```json
|
98
|
+
{
|
99
|
+
"type": "user",
|
100
|
+
"...": "...""
|
101
|
+
}
|
102
|
+
```
|
103
|
+
|
104
|
+
You can also set a custom type name (just make sure to use it consistently!):
|
105
|
+
|
106
|
+
```ruby
|
107
|
+
module API
|
108
|
+
module V1
|
109
|
+
module User
|
110
|
+
module Decorator
|
111
|
+
class Instance < Pragma::Decorator::Base
|
112
|
+
def type
|
113
|
+
:custom_type
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
```
|
121
|
+
|
122
|
+
Note: `array` is already overridden with the more language-agnostic `list`.
|
123
|
+
|
124
|
+
### Timestamps
|
125
|
+
|
126
|
+
[UNIX time](https://en.wikipedia.org/wiki/Unix_time) is your safest bet when rendering/parsing
|
127
|
+
timestamps in your API, as it doesn't require a timezone indicator (the timezone is always UTC).
|
128
|
+
|
129
|
+
You can use the `Timestamp` mixin for converting `Time` instances to UNIX times:
|
130
|
+
|
131
|
+
```ruby
|
132
|
+
module API
|
133
|
+
module V1
|
134
|
+
module User
|
135
|
+
module Decorator
|
136
|
+
class Instance < Pragma::Decorator::Base
|
137
|
+
feature Pragma::Decorator::Timestamp
|
138
|
+
|
139
|
+
timestamp :created_at
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
```
|
146
|
+
|
147
|
+
This will render a user like this:
|
148
|
+
|
149
|
+
```json
|
150
|
+
{
|
151
|
+
"type": "user",
|
152
|
+
"created_at": 1480287994
|
153
|
+
}
|
154
|
+
```
|
155
|
+
|
156
|
+
The `#timestamp` method supports all the options supported by `#property` (except for `:as`).
|
157
|
+
|
158
|
+
### Associations
|
159
|
+
|
160
|
+
`Pragma::Decorator::Association` allows you to define associations in your decorator (currently,
|
161
|
+
only `belongs_to`/`has_one` associations are supported):
|
162
|
+
|
163
|
+
```ruby
|
164
|
+
module API
|
165
|
+
module V1
|
166
|
+
module Invoice
|
167
|
+
module Decorator
|
168
|
+
class Instance < Pragma::Decorator::Base
|
169
|
+
feature Pragma::Decorator::Association
|
170
|
+
|
171
|
+
belongs_to :customer, decorator: API::V1::Customer::Decorator
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
```
|
178
|
+
|
179
|
+
Rendering an invoice will now create the following representation:
|
180
|
+
|
181
|
+
```json
|
182
|
+
{
|
183
|
+
"customer": 19
|
184
|
+
}
|
185
|
+
```
|
186
|
+
|
187
|
+
You can pass `expand[]=customer` as a request parameter and have the `customer` property expanded
|
188
|
+
into a full object!
|
189
|
+
|
190
|
+
```json
|
191
|
+
{
|
192
|
+
"customer": {
|
193
|
+
"id": 19,
|
194
|
+
"...": "..."
|
195
|
+
}
|
196
|
+
}
|
197
|
+
```
|
198
|
+
|
199
|
+
This also works for nested associations. For instance, if the customer decorator had a `company`
|
200
|
+
association, you could pass `expand[]=customer&expand[]=customer.company` to get the company
|
201
|
+
expanded too.
|
202
|
+
|
203
|
+
Note that you will have to pass the associations to expand as a user option when rendering:
|
204
|
+
|
205
|
+
```ruby
|
206
|
+
decorator = API::V1::Invoice::Decorator::Instance.new(invoice)
|
207
|
+
decorator.to_json(user_options: {
|
208
|
+
expand: ['customer', 'customer.company', 'customer.company.contact']
|
209
|
+
})
|
210
|
+
```
|
211
|
+
|
212
|
+
Needless to say, this is done automatically for you when you use all components together through
|
213
|
+
the [pragma](https://github.com/pragmarb/pragma) gem! :)
|
214
|
+
|
215
|
+
### Collection
|
216
|
+
|
217
|
+
`Pragma::Decorator::Collection` wraps collections in a `data` property so that you can include
|
218
|
+
metadata about them:
|
219
|
+
|
220
|
+
```ruby
|
221
|
+
module API
|
222
|
+
module V1
|
223
|
+
module Invoice
|
224
|
+
module Decorator
|
225
|
+
class Collection < Pragma::Decorator::Base
|
226
|
+
feature Pragma::Decorator::Collection
|
227
|
+
decorate_with Instance # specify the instance decorator
|
228
|
+
|
229
|
+
property :total_cents, exec_context: :decorator
|
230
|
+
|
231
|
+
def total_cents
|
232
|
+
represented.sum(:total_cents)
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
```
|
240
|
+
|
241
|
+
You can now do this:
|
242
|
+
|
243
|
+
```ruby
|
244
|
+
API::V1::Invoice::Decorator::Collection.new(Invoice.all).to_json
|
245
|
+
```
|
246
|
+
|
247
|
+
Which will produce the following JSON:
|
248
|
+
|
249
|
+
```json
|
250
|
+
{
|
251
|
+
"data": [{
|
252
|
+
"id": 1,
|
253
|
+
"total_cents": 1500,
|
254
|
+
}, {
|
255
|
+
"id": 2,
|
256
|
+
"total_cents": 3000,
|
257
|
+
}],
|
258
|
+
"total_cents": 4500
|
259
|
+
}
|
260
|
+
```
|
261
|
+
|
262
|
+
This is very useful, for instance, when you have a paginated collection, but want to include data
|
263
|
+
about the entire collection (not just the current page) in the response.
|
264
|
+
|
265
|
+
### Pagination
|
266
|
+
|
267
|
+
Speaking of pagination, you can use `Pragma::Decorator::Pagination` in combination with
|
268
|
+
`Collection` to include pagination data in your response:
|
269
|
+
|
270
|
+
```ruby
|
271
|
+
module API
|
272
|
+
module V1
|
273
|
+
module Invoice
|
274
|
+
module Decorator
|
275
|
+
class Collection < Pragma::Decorator::Base
|
276
|
+
feature Pragma::Decorator::Collection
|
277
|
+
feature Pragma::Decorator::Pagination
|
278
|
+
|
279
|
+
decorate_with Instance
|
280
|
+
end
|
281
|
+
end
|
282
|
+
end
|
283
|
+
end
|
284
|
+
end
|
285
|
+
```
|
286
|
+
|
287
|
+
Now, you can run this code:
|
288
|
+
|
289
|
+
```ruby
|
290
|
+
API::V1::Invoice::Decorator::Collection.new(Invoice.all).to_json
|
291
|
+
```
|
292
|
+
|
293
|
+
Which will produce the following JSON:
|
294
|
+
|
295
|
+
```json
|
296
|
+
{
|
297
|
+
"data": [{
|
298
|
+
"id": 1,
|
299
|
+
"...": "...",
|
300
|
+
}, {
|
301
|
+
"id": 2,
|
302
|
+
"...": "...",
|
303
|
+
}],
|
304
|
+
"total_entries": 2,
|
305
|
+
"per_page": 30,
|
306
|
+
"total_pages": 1,
|
307
|
+
"previous_page": null,
|
308
|
+
"current_page": 1,
|
309
|
+
"next_page": null
|
310
|
+
}
|
311
|
+
```
|
312
|
+
|
313
|
+
It works with both [will_paginate](https://github.com/mislav/will_paginate) and
|
314
|
+
[Kaminari](https://github.com/kaminari/kaminari)!
|
36
315
|
|
37
316
|
## Contributing
|
38
317
|
|
data/lib/pragma/decorator.rb
CHANGED
@@ -7,10 +7,11 @@ require 'pragma/decorator/base'
|
|
7
7
|
require 'pragma/decorator/association'
|
8
8
|
require 'pragma/decorator/association/reflection'
|
9
9
|
require 'pragma/decorator/association/binding'
|
10
|
-
require 'pragma/decorator/association/
|
11
|
-
require 'pragma/decorator/association/inconsistent_type_error'
|
10
|
+
require 'pragma/decorator/association/errors'
|
12
11
|
require 'pragma/decorator/timestamp'
|
13
12
|
require 'pragma/decorator/type'
|
13
|
+
require 'pragma/decorator/collection'
|
14
|
+
require 'pragma/decorator/pagination'
|
14
15
|
|
15
16
|
module Pragma
|
16
17
|
# Represent your API resources in JSON with minimum hassle.
|
@@ -2,88 +2,84 @@
|
|
2
2
|
|
3
3
|
module Pragma
|
4
4
|
module Decorator
|
5
|
-
# Adds association expansion to decorators.
|
6
|
-
#
|
7
|
-
# @author Alessandro Desantis
|
8
5
|
module Association
|
9
6
|
def self.included(klass)
|
10
7
|
klass.extend ClassMethods
|
8
|
+
klass.include InstanceMethods
|
9
|
+
end
|
11
10
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
def self.associations
|
16
|
-
@associations
|
17
|
-
end
|
11
|
+
module ClassMethods # rubocop:disable Style/Documentation
|
12
|
+
def associations
|
13
|
+
@associations ||= {}
|
18
14
|
end
|
19
|
-
end
|
20
15
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
def initialize(*)
|
25
|
-
super
|
16
|
+
def belongs_to(property_name, options = {})
|
17
|
+
define_association :belongs_to, property_name, options
|
18
|
+
end
|
26
19
|
|
27
|
-
|
28
|
-
|
29
|
-
@association_bindings[property] = Binding.new(reflection: reflection, decorator: self)
|
20
|
+
def has_one(property_name, options = {}) # rubocop:disable Naming/PredicateName
|
21
|
+
define_association :has_one, property_name, options
|
30
22
|
end
|
31
|
-
end
|
32
23
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
# @param property [Symbol] the property containing the associated object
|
39
|
-
# @param options [Hash] the options of the association
|
40
|
-
def belongs_to(property, options = {})
|
41
|
-
define_association :belongs_to, property, options
|
24
|
+
private
|
25
|
+
|
26
|
+
def define_association(type, property_name, options = {})
|
27
|
+
create_association_definition(type, property_name, options)
|
28
|
+
create_association_property(type, property_name, options)
|
42
29
|
end
|
43
30
|
|
44
|
-
|
45
|
-
|
46
|
-
# See {Association::Reflection#initialize} for the list of available options.
|
47
|
-
#
|
48
|
-
# @param property [Symbol] the property containing the associated object
|
49
|
-
# @param options [Hash] the options of the association
|
50
|
-
def has_one(property, options = {}) # rubocop:disable Style/PredicateName
|
51
|
-
define_association :has_one, property, options
|
31
|
+
def create_association_definition(type, property_name, options)
|
32
|
+
associations[property_name.to_sym] = Reflection.new(type, property_name, options)
|
52
33
|
end
|
53
34
|
|
54
|
-
|
35
|
+
def create_association_property(_type, property_name, options)
|
36
|
+
property_options = options.dup.tap { |po| po.delete(:decorator) }.merge(
|
37
|
+
exec_context: :decorator,
|
38
|
+
as: property_name,
|
39
|
+
getter: (lambda do |decorator:, user_options:, **_args|
|
40
|
+
Binding.new(
|
41
|
+
reflection: decorator.class.associations[property_name],
|
42
|
+
decorator: decorator
|
43
|
+
).render(user_options[:expand])
|
44
|
+
end)
|
45
|
+
)
|
55
46
|
|
56
|
-
|
57
|
-
create_association_definition(type, property, options)
|
58
|
-
create_association_getter(property)
|
59
|
-
create_association_property(property)
|
47
|
+
property("_#{property_name}_association", property_options)
|
60
48
|
end
|
49
|
+
end
|
61
50
|
|
62
|
-
|
63
|
-
|
51
|
+
module InstanceMethods
|
52
|
+
def validate_expansion(expand)
|
53
|
+
check_parent_associations_are_expanded(expand)
|
54
|
+
check_expanded_associations_exist(expand)
|
64
55
|
end
|
65
56
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
57
|
+
private
|
58
|
+
|
59
|
+
def check_parent_associations_are_expanded(expand)
|
60
|
+
expand = normalize_expand(expand)
|
61
|
+
|
62
|
+
expand.each do |property|
|
63
|
+
next unless property.include?('.')
|
72
64
|
|
73
|
-
|
65
|
+
parent_path = property.split('.')[0..-2].join('.')
|
66
|
+
next if expand.include?(parent_path)
|
67
|
+
|
68
|
+
fail Association::UnexpandedAssociationParent.new(property, parent_path)
|
69
|
+
end
|
74
70
|
end
|
75
71
|
|
76
|
-
def
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
opts[:render_nil] = @associations[property_name].options[:render_nil]
|
83
|
-
end
|
72
|
+
def check_expanded_associations_exist(expand)
|
73
|
+
expand = normalize_expand(expand)
|
74
|
+
|
75
|
+
expand.each do |property|
|
76
|
+
next if self.class.associations.key?(property.to_sym) || property.include?('.')
|
77
|
+
fail Association::AssociationNotFound, property
|
84
78
|
end
|
79
|
+
end
|
85
80
|
|
86
|
-
|
81
|
+
def normalize_expand(expand)
|
82
|
+
[expand].flatten.map(&:to_s).reject(&:blank?)
|
87
83
|
end
|
88
84
|
end
|
89
85
|
end
|
@@ -21,8 +21,6 @@ module Pragma
|
|
21
21
|
def initialize(reflection:, decorator:)
|
22
22
|
@reflection = reflection
|
23
23
|
@decorator = decorator
|
24
|
-
|
25
|
-
check_type_consistency
|
26
24
|
end
|
27
25
|
|
28
26
|
# Returns the associated object.
|
@@ -31,52 +29,18 @@ module Pragma
|
|
31
29
|
def associated_object
|
32
30
|
case reflection.options[:exec_context]
|
33
31
|
when :decorated
|
34
|
-
|
32
|
+
decorator.decorated.send(reflection.property)
|
35
33
|
when :decorator
|
36
|
-
decorator.
|
34
|
+
decorator.send(reflection.property)
|
37
35
|
end
|
38
36
|
end
|
39
37
|
|
40
|
-
# Returns whether the association belongs to the model.
|
41
|
-
#
|
42
|
-
# @return [Boolean]
|
43
|
-
def model_context?
|
44
|
-
reflection.options[:exec_context].to_sym == :decorated
|
45
|
-
end
|
46
|
-
|
47
|
-
# Returns whether the association belongs to the decorator.
|
48
|
-
#
|
49
|
-
# @return [Boolean]
|
50
|
-
def decorator_context?
|
51
|
-
reflection.options[:exec_context].to_sym == :decorator
|
52
|
-
end
|
53
|
-
|
54
38
|
# Returns the unexpanded value for the associated object (i.e. its +id+ property).
|
55
39
|
#
|
56
40
|
# @return [String]
|
57
41
|
def unexpanded_value
|
58
|
-
|
59
|
-
|
60
|
-
end
|
61
|
-
|
62
|
-
case reflection.type
|
63
|
-
when :belongs_to
|
64
|
-
model.public_send(model_reflection.foreign_key)
|
65
|
-
when :has_one
|
66
|
-
if model.association(reflection.property).loaded?
|
67
|
-
return associated_object&.public_send(associated_object.class.primary_key)
|
68
|
-
end
|
69
|
-
|
70
|
-
pk = model.public_send(model_reflection.active_record_primary_key)
|
71
|
-
|
72
|
-
model_reflection
|
73
|
-
.klass
|
74
|
-
.where(model_reflection.foreign_key => pk)
|
75
|
-
.pluck(model_reflection.klass.primary_key)
|
76
|
-
.first
|
77
|
-
else
|
78
|
-
associated_object&.public_send(associated_object.class.primary_key)
|
79
|
-
end
|
42
|
+
return unless associated_object
|
43
|
+
associated_object.id
|
80
44
|
end
|
81
45
|
|
82
46
|
# Returns the expanded value for the associated object.
|
@@ -92,11 +56,7 @@ module Pragma
|
|
92
56
|
# @param expand [Array<String>] the associations to expand
|
93
57
|
#
|
94
58
|
# @return [Hash]
|
95
|
-
#
|
96
|
-
# @raise [UnexpandableError] if the association is not expandable
|
97
59
|
def expanded_value(expand)
|
98
|
-
fail UnexpandableError, reflection unless reflection.expandable?
|
99
|
-
|
100
60
|
return unless associated_object
|
101
61
|
|
102
62
|
options = {
|
@@ -153,36 +113,6 @@ module Pragma
|
|
153
113
|
reflection.options[:decorator]
|
154
114
|
end
|
155
115
|
end
|
156
|
-
|
157
|
-
def model
|
158
|
-
decorator.decorated
|
159
|
-
end
|
160
|
-
|
161
|
-
def model_reflection
|
162
|
-
# rubocop:disable Metrics/LineLength
|
163
|
-
@model_reflection ||= if Object.const_defined?('ActiveRecord') && model.is_a?(ActiveRecord::Base)
|
164
|
-
model.class.reflect_on_association(reflection.property)
|
165
|
-
end
|
166
|
-
# rubocop:enable Metrics/LineLength
|
167
|
-
end
|
168
|
-
|
169
|
-
def model_association_type
|
170
|
-
return unless model_reflection
|
171
|
-
|
172
|
-
if Object.const_defined?('ActiveRecord') && model.is_a?(ActiveRecord::Base)
|
173
|
-
model_reflection.macro
|
174
|
-
end
|
175
|
-
end
|
176
|
-
|
177
|
-
def check_type_consistency
|
178
|
-
return if !model_association_type || model_association_type == reflection.type
|
179
|
-
|
180
|
-
fail InconsistentTypeError.new(
|
181
|
-
decorator: decorator,
|
182
|
-
reflection: reflection,
|
183
|
-
model_type: model_association_type
|
184
|
-
)
|
185
|
-
end
|
186
116
|
end
|
187
117
|
end
|
188
118
|
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pragma
|
4
|
+
module Decorator
|
5
|
+
module Association
|
6
|
+
class ExpansionError < StandardError
|
7
|
+
end
|
8
|
+
|
9
|
+
class AssociationNotFound < ExpansionError
|
10
|
+
attr_reader :property
|
11
|
+
|
12
|
+
def initialize(property)
|
13
|
+
@property = property
|
14
|
+
super "The '#{property}' association is not defined."
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class UnexpandedAssociationParent < ExpansionError
|
19
|
+
attr_reader :child, :parent
|
20
|
+
|
21
|
+
def initialize(child, parent)
|
22
|
+
@child = child
|
23
|
+
@parent = parent
|
24
|
+
|
25
|
+
super "The '#{child}' association is expanded, but its parent '#{parent}' is not."
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -23,7 +23,6 @@ module Pragma
|
|
23
23
|
# @param property [Symbol] the property holding the associated object
|
24
24
|
# @param options [Hash] additional options
|
25
25
|
#
|
26
|
-
# @option options [Boolean] :expandable (`false`) whether the association is expandable
|
27
26
|
# @option options [Class|Proc] :decorator the decorator to use for the associated object
|
28
27
|
# or a callable that will return the decorator class (or +nil+ to skip decoration)
|
29
28
|
# @option options [Boolean] :render_nil (`true`) whether to render a +nil+ association
|
@@ -38,33 +37,31 @@ module Pragma
|
|
38
37
|
validate_options
|
39
38
|
end
|
40
39
|
|
41
|
-
# Returns whether the association is expandable.
|
42
|
-
#
|
43
|
-
# @return [Boolean]
|
44
|
-
def expandable?
|
45
|
-
options[:expandable]
|
46
|
-
end
|
47
|
-
|
48
40
|
private
|
49
41
|
|
50
42
|
def normalize_options
|
51
43
|
@options = {
|
52
|
-
|
53
|
-
|
54
|
-
exec_context: :decorated,
|
55
|
-
optimize: true,
|
44
|
+
render_nil: true,
|
45
|
+
exec_context: :decorated
|
56
46
|
}.merge(options).tap do |opts|
|
57
47
|
opts[:exec_context] = opts[:exec_context].to_sym
|
58
48
|
end
|
59
49
|
end
|
60
50
|
|
61
51
|
def validate_options
|
62
|
-
|
52
|
+
unless %i[decorator decorated].include?(options[:exec_context])
|
53
|
+
fail(
|
54
|
+
ArgumentError,
|
55
|
+
"'#{options[:exec_context]}' is not a valid value for :exec_context."
|
56
|
+
)
|
57
|
+
end
|
63
58
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
59
|
+
unless options[:decorator]
|
60
|
+
fail(
|
61
|
+
ArgumentError,
|
62
|
+
'The :decorator option is required.'
|
63
|
+
)
|
64
|
+
end
|
68
65
|
end
|
69
66
|
end
|
70
67
|
end
|
@@ -13,36 +13,7 @@ module Pragma
|
|
13
13
|
class Base < Roar::Decorator
|
14
14
|
feature Roar::JSON
|
15
15
|
|
16
|
-
|
17
|
-
# with.
|
18
|
-
#
|
19
|
-
# This allows accessing the options from property getters and is required by {Association}.
|
20
|
-
#
|
21
|
-
# @param options [Hash]
|
22
|
-
#
|
23
|
-
# @return [Hash]
|
24
|
-
def to_hash(options = {}, *args)
|
25
|
-
@last_options = options
|
26
|
-
super(options, *args)
|
27
|
-
end
|
28
|
-
|
29
|
-
protected
|
30
|
-
|
31
|
-
# Returns the options +#to_hash+ was last run with.
|
32
|
-
#
|
33
|
-
# @return [Hash]
|
34
|
-
def options
|
35
|
-
@last_options
|
36
|
-
end
|
37
|
-
|
38
|
-
# Returns the user options +#to_hash+ was last run with.
|
39
|
-
#
|
40
|
-
# @return [Hash]
|
41
|
-
#
|
42
|
-
# @see #options
|
43
|
-
def user_options
|
44
|
-
@last_options[:user_options] || {}
|
45
|
-
end
|
16
|
+
defaults render_nil: true
|
46
17
|
end
|
47
18
|
end
|
48
19
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pragma
|
4
|
+
module Decorator
|
5
|
+
module Collection
|
6
|
+
def self.included(klass)
|
7
|
+
klass.include InstanceMethods
|
8
|
+
klass.extend ClassMethods
|
9
|
+
|
10
|
+
klass.class_eval do
|
11
|
+
collection :represented, as: :data, exec_context: :decorator
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
module InstanceMethods
|
16
|
+
def type
|
17
|
+
'collection'
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
module ClassMethods
|
22
|
+
def decorate_with(decorator)
|
23
|
+
collection :represented, as: :data, exec_context: :decorator, decorator: decorator
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pragma
|
4
|
+
module Decorator
|
5
|
+
module Pagination
|
6
|
+
module InstanceMethods
|
7
|
+
def current_page
|
8
|
+
represented.current_page.to_i
|
9
|
+
end
|
10
|
+
|
11
|
+
def next_page
|
12
|
+
represented.next_page
|
13
|
+
end
|
14
|
+
|
15
|
+
def per_page
|
16
|
+
per_page_method = if represented.respond_to?(:per_page)
|
17
|
+
:per_page
|
18
|
+
else
|
19
|
+
:limit_value
|
20
|
+
end
|
21
|
+
|
22
|
+
represented.public_send(per_page_method)
|
23
|
+
end
|
24
|
+
|
25
|
+
def previous_page
|
26
|
+
previous_page_method = if represented.respond_to?(:previous_page)
|
27
|
+
:previous_page
|
28
|
+
else
|
29
|
+
:prev_page
|
30
|
+
end
|
31
|
+
|
32
|
+
represented.public_send(previous_page_method)
|
33
|
+
end
|
34
|
+
|
35
|
+
def total_entries
|
36
|
+
total_entries_method = if represented.respond_to?(:total_entries)
|
37
|
+
:total_entries
|
38
|
+
else
|
39
|
+
:total_count
|
40
|
+
end
|
41
|
+
|
42
|
+
represented.public_send(total_entries_method)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.included(klass)
|
47
|
+
klass.include InstanceMethods
|
48
|
+
|
49
|
+
klass.class_eval do
|
50
|
+
property :total_entries, exec_context: :decorator
|
51
|
+
property :per_page, exec_context: :decorator
|
52
|
+
property :total_pages
|
53
|
+
property :previous_page, exec_context: :decorator
|
54
|
+
property :current_page, exec_context: :decorator
|
55
|
+
property :next_page, exec_context: :decorator
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -24,7 +24,7 @@ module Pragma
|
|
24
24
|
|
25
25
|
def create_timestamp_getter(name, options = {})
|
26
26
|
define_method "_#{name}_timestamp" do
|
27
|
-
if options[:exec_context]
|
27
|
+
if options[:exec_context]&.to_sym == :decorator
|
28
28
|
send(name)
|
29
29
|
else
|
30
30
|
decorated.send(name)
|
data/pragma-decorator.gemspec
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
|
2
1
|
# frozen_string_literal: true
|
3
2
|
|
4
3
|
lib = File.expand_path('../lib', __FILE__)
|
@@ -22,13 +21,13 @@ Gem::Specification.new do |spec|
|
|
22
21
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
23
22
|
spec.require_paths = ['lib']
|
24
23
|
|
25
|
-
spec.add_dependency 'multi_json', '~> 1.12'
|
26
24
|
spec.add_dependency 'roar', '~> 1.0'
|
25
|
+
spec.add_dependency 'multi_json', '~> 1.12'
|
27
26
|
|
28
27
|
spec.add_development_dependency 'bundler'
|
29
|
-
spec.add_development_dependency 'coveralls'
|
30
28
|
spec.add_development_dependency 'rake'
|
31
29
|
spec.add_development_dependency 'rspec'
|
32
30
|
spec.add_development_dependency 'rubocop'
|
33
31
|
spec.add_development_dependency 'rubocop-rspec'
|
32
|
+
spec.add_development_dependency 'coveralls'
|
34
33
|
end
|
metadata
CHANGED
@@ -1,43 +1,43 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pragma-decorator
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alessandro Desantis
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2017-09-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: roar
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '1.
|
19
|
+
version: '1.0'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '1.
|
26
|
+
version: '1.0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: multi_json
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '1.
|
33
|
+
version: '1.12'
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '1.
|
40
|
+
version: '1.12'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: bundler
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -53,7 +53,7 @@ dependencies:
|
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
56
|
+
name: rake
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
59
|
- - ">="
|
@@ -67,7 +67,7 @@ dependencies:
|
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
70
|
+
name: rspec
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
73
|
- - ">="
|
@@ -81,7 +81,7 @@ dependencies:
|
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0'
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
|
-
name:
|
84
|
+
name: rubocop
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
86
86
|
requirements:
|
87
87
|
- - ">="
|
@@ -95,7 +95,7 @@ dependencies:
|
|
95
95
|
- !ruby/object:Gem::Version
|
96
96
|
version: '0'
|
97
97
|
- !ruby/object:Gem::Dependency
|
98
|
-
name: rubocop
|
98
|
+
name: rubocop-rspec
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
100
100
|
requirements:
|
101
101
|
- - ">="
|
@@ -109,7 +109,7 @@ dependencies:
|
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: '0'
|
111
111
|
- !ruby/object:Gem::Dependency
|
112
|
-
name:
|
112
|
+
name: coveralls
|
113
113
|
requirement: !ruby/object:Gem::Requirement
|
114
114
|
requirements:
|
115
115
|
- - ">="
|
@@ -139,17 +139,14 @@ files:
|
|
139
139
|
- Rakefile
|
140
140
|
- bin/console
|
141
141
|
- bin/setup
|
142
|
-
- doc/01-basic-usage.md
|
143
|
-
- doc/02-object-types.md
|
144
|
-
- doc/03-associations.md
|
145
|
-
- doc/04-timestamps.md
|
146
142
|
- lib/pragma/decorator.rb
|
147
143
|
- lib/pragma/decorator/association.rb
|
148
144
|
- lib/pragma/decorator/association/binding.rb
|
149
|
-
- lib/pragma/decorator/association/
|
145
|
+
- lib/pragma/decorator/association/errors.rb
|
150
146
|
- lib/pragma/decorator/association/reflection.rb
|
151
|
-
- lib/pragma/decorator/association/unexpandable_error.rb
|
152
147
|
- lib/pragma/decorator/base.rb
|
148
|
+
- lib/pragma/decorator/collection.rb
|
149
|
+
- lib/pragma/decorator/pagination.rb
|
153
150
|
- lib/pragma/decorator/timestamp.rb
|
154
151
|
- lib/pragma/decorator/type.rb
|
155
152
|
- lib/pragma/decorator/version.rb
|
data/doc/01-basic-usage.md
DELETED
@@ -1,42 +0,0 @@
|
|
1
|
-
# Basic usage
|
2
|
-
|
3
|
-
Creating a decorator is as simple as inheriting from `Pragma::Decorator::Base`:
|
4
|
-
|
5
|
-
```ruby
|
6
|
-
module API
|
7
|
-
module V1
|
8
|
-
module User
|
9
|
-
module Decorator
|
10
|
-
class Resource < Pragma::Decorator::Base
|
11
|
-
property :id
|
12
|
-
property :email
|
13
|
-
property :full_name
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
```
|
20
|
-
|
21
|
-
Just instantiate the decorator by passing it an object to decorate, then call `#to_hash` or
|
22
|
-
`#to_json`:
|
23
|
-
|
24
|
-
```ruby
|
25
|
-
decorator = API::V1::User::Decorator::Resource.new(user)
|
26
|
-
decorator.to_json
|
27
|
-
```
|
28
|
-
|
29
|
-
This will produce the following JSON:
|
30
|
-
|
31
|
-
```json
|
32
|
-
{
|
33
|
-
"id": 1,
|
34
|
-
"email": "jdoe@example.com",
|
35
|
-
"full_name": "John Doe"
|
36
|
-
}
|
37
|
-
```
|
38
|
-
|
39
|
-
Since Pragma::Decorator is built on top of [ROAR](https://github.com/apotonick/roar) (which, in
|
40
|
-
turn, is built on top of [Representable](https://github.com/apotonick/representable)), you should
|
41
|
-
consult their documentation for the basic usage of decorators; the rest of this section only covers
|
42
|
-
the features provided specifically by Pragma::Decorator.
|
data/doc/02-object-types.md
DELETED
@@ -1,47 +0,0 @@
|
|
1
|
-
# Object types
|
2
|
-
|
3
|
-
It is recommended that decorators expose the type of the decorated object. You can achieve this
|
4
|
-
with the `Type` mixin:
|
5
|
-
|
6
|
-
```ruby
|
7
|
-
module API
|
8
|
-
module V1
|
9
|
-
module User
|
10
|
-
module Decorator
|
11
|
-
class Resource < Pragma::Decorator::Base
|
12
|
-
feature Pragma::Decorator::Type
|
13
|
-
end
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
18
|
-
```
|
19
|
-
|
20
|
-
This would result in the following representation:
|
21
|
-
|
22
|
-
```json
|
23
|
-
{
|
24
|
-
"type": "user",
|
25
|
-
"...": "...""
|
26
|
-
}
|
27
|
-
```
|
28
|
-
|
29
|
-
You can also set a custom type name (just make sure to use it consistently!):
|
30
|
-
|
31
|
-
```ruby
|
32
|
-
module API
|
33
|
-
module V1
|
34
|
-
module User
|
35
|
-
module Decorator
|
36
|
-
class Resource < Pragma::Decorator::Base
|
37
|
-
def type
|
38
|
-
:custom_type
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
45
|
-
```
|
46
|
-
|
47
|
-
Note: `array` is already overridden with the more language-agnostic `list`.
|
data/doc/03-associations.md
DELETED
@@ -1,91 +0,0 @@
|
|
1
|
-
# Associations
|
2
|
-
|
3
|
-
`Pragma::Decorator::Association` allows you to define associations in your decorator (currently,
|
4
|
-
only `belongs_to`/`has_one` associations are supported):
|
5
|
-
|
6
|
-
```ruby
|
7
|
-
module API
|
8
|
-
module V1
|
9
|
-
module Invoice
|
10
|
-
module Decorator
|
11
|
-
class Resource < Pragma::Decorator::Base
|
12
|
-
feature Pragma::Decorator::Association
|
13
|
-
|
14
|
-
belongs_to :customer
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
20
|
-
```
|
21
|
-
|
22
|
-
Rendering an invoice will now create the following representation:
|
23
|
-
|
24
|
-
```json
|
25
|
-
{
|
26
|
-
"customer": 19
|
27
|
-
}
|
28
|
-
```
|
29
|
-
|
30
|
-
Not impressed? Just wait.
|
31
|
-
|
32
|
-
## Expanding associations
|
33
|
-
|
34
|
-
We also support association expansion through an interface similar to the one provided by the
|
35
|
-
[Stripe API](https://stripe.com/docs/api/curl#expanding_objects). You can define which associations
|
36
|
-
are expandable in the decorator:
|
37
|
-
|
38
|
-
```ruby
|
39
|
-
module API
|
40
|
-
module V1
|
41
|
-
module Invoice
|
42
|
-
module Decorator
|
43
|
-
class Resource < Pragma::Decorator::Base
|
44
|
-
feature Pragma::Decorator::Association
|
45
|
-
|
46
|
-
belongs_to :customer, expandable: true
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
52
|
-
```
|
53
|
-
|
54
|
-
You can now pass `expand[]=customer` as a request parameter and have the `customer` property
|
55
|
-
expanded into a full object!
|
56
|
-
|
57
|
-
```json
|
58
|
-
{
|
59
|
-
"customer": {
|
60
|
-
"id": 19,
|
61
|
-
"...": "..."
|
62
|
-
}
|
63
|
-
}
|
64
|
-
```
|
65
|
-
|
66
|
-
## Nested associations
|
67
|
-
|
68
|
-
This also works for nested associations. For instance, if the customer has a `company` association
|
69
|
-
marked as expandable, you can pass `expand[]=customer&expand[]=customer.company` to get that
|
70
|
-
association expanded too.
|
71
|
-
|
72
|
-
In order for association expansion to work, you will have to pass the associations to expand to the
|
73
|
-
representer as a user option:
|
74
|
-
|
75
|
-
```ruby
|
76
|
-
decorator = API::V1::Invoice::Decorator::Resource.new(invoice)
|
77
|
-
decorator.to_json(user_options: {
|
78
|
-
expand: ['customer', 'customer.company', 'customer.company.contact']
|
79
|
-
})
|
80
|
-
```
|
81
|
-
|
82
|
-
## Accepted options
|
83
|
-
|
84
|
-
Here's a list of options accepted when defining an association:
|
85
|
-
|
86
|
-
Name | Type | Default | Meaning
|
87
|
-
---- | ---- | ------- | -------
|
88
|
-
`expandable` | Boolean | `false` | Whether this association is expandable by consumers. Attempting to expand a non-expandable association will raise a `UnexpandableError`.
|
89
|
-
`decorator` | Class|Proc | - | If provided, decorates the expanded object with this decorator. Otherwise, simply calls `#to_hash` on the object to get a representable hash. If the option is callable, will call it and pass the associated object - a decorator class should be returned, or `nil` to skip decoration.
|
90
|
-
`render_nil` | Boolean | `false` | Whether the property should be rendered at all when it is `nil`.
|
91
|
-
`exec_context` | Symbol | `:decorated` | Whether to call the getter on the decorator (`:decorator`) or the decorated object (`:decorated`).
|
data/doc/04-timestamps.md
DELETED
@@ -1,33 +0,0 @@
|
|
1
|
-
# Timestamps
|
2
|
-
|
3
|
-
[UNIX time](https://en.wikipedia.org/wiki/Unix_time) is your safest bet when rendering/parsing
|
4
|
-
timestamps in your API, as it doesn't require a timezone indicator (the timezone is always UTC).
|
5
|
-
|
6
|
-
You can use the `Timestamp` mixin for converting `Time` instances to UNIX times:
|
7
|
-
|
8
|
-
```ruby
|
9
|
-
module API
|
10
|
-
module V1
|
11
|
-
module User
|
12
|
-
module Decorator
|
13
|
-
class Resource < Pragma::Decorator::Base
|
14
|
-
feature Pragma::Decorator::Timestamp
|
15
|
-
|
16
|
-
timestamp :created_at
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
22
|
-
```
|
23
|
-
|
24
|
-
This will render a user like this:
|
25
|
-
|
26
|
-
```json
|
27
|
-
{
|
28
|
-
"type": "user",
|
29
|
-
"created_at": 1480287994
|
30
|
-
}
|
31
|
-
```
|
32
|
-
|
33
|
-
The `#timestamp` method supports all the options supported by `#property` (except for `:as`).
|
@@ -1,27 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Pragma
|
4
|
-
module Decorator
|
5
|
-
module Association
|
6
|
-
# This error is raised when an association's type is different from its type as reported by
|
7
|
-
# the model's reflection.
|
8
|
-
#
|
9
|
-
# @author Alessandro Desantis
|
10
|
-
class InconsistentTypeError < StandardError
|
11
|
-
# Initializes the error.
|
12
|
-
#
|
13
|
-
# @param decorator [Base] the decorator where the association is defined
|
14
|
-
# @param reflection [Reflection] the reflection of the inconsistent association
|
15
|
-
# @param model_type [Symbol|String] the real type of the association
|
16
|
-
def initialize(decorator:, reflection:, model_type:)
|
17
|
-
message = <<~MSG.tr("\n", ' ')
|
18
|
-
#{decorator.class}: Association #{reflection.property} is defined as #{model_type} on
|
19
|
-
the model, but as #{reflection.type} in the decorator.
|
20
|
-
MSG
|
21
|
-
|
22
|
-
super message
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
@@ -1,19 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Pragma
|
4
|
-
module Decorator
|
5
|
-
module Association
|
6
|
-
# This error is raised when expansion of an unexpandable association is attempted.
|
7
|
-
#
|
8
|
-
# @author Alessandro Desantis
|
9
|
-
class UnexpandableError < StandardError
|
10
|
-
# Initializes the error.
|
11
|
-
#
|
12
|
-
# @param reflection [Reflection] the unexpandable association
|
13
|
-
def initialize(reflection)
|
14
|
-
super "Association '#{reflection.property}' cannot be expanded."
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|