pragma-decorator 1.3.0 → 2.0.0
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/.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
|