pragma-decorator 2.0.0 → 2.1.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 -0
- data/CHANGELOG.md +36 -0
- data/README.md +13 -11
- data/lib/pragma/decorator/association/adapter/active_record.rb +142 -0
- data/lib/pragma/decorator/association/adapter/base.rb +88 -0
- data/lib/pragma/decorator/association/adapter/poro.rb +48 -0
- data/lib/pragma/decorator/association/adapter.rb +32 -0
- data/lib/pragma/decorator/association/{binding.rb → bond.rb} +32 -23
- data/lib/pragma/decorator/association/errors.rb +37 -0
- data/lib/pragma/decorator/association/reflection.rb +1 -1
- data/lib/pragma/decorator/association.rb +42 -5
- data/lib/pragma/decorator/base.rb +1 -3
- data/lib/pragma/decorator/collection.rb +38 -3
- data/lib/pragma/decorator/pagination/adapter/base.rb +80 -0
- data/lib/pragma/decorator/pagination/adapter/kaminari.rb +71 -0
- data/lib/pragma/decorator/pagination/adapter/will_paginate.rb +71 -0
- data/lib/pragma/decorator/pagination/adapter.rb +51 -0
- data/lib/pragma/decorator/pagination.rb +73 -24
- data/lib/pragma/decorator/timestamp.rb +11 -3
- data/lib/pragma/decorator/type.rb +43 -12
- data/lib/pragma/decorator/version.rb +1 -1
- data/lib/pragma/decorator.rb +9 -3
- metadata +12 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3112d0727880b4dc23ca921b073f064e2f06067f
|
|
4
|
+
data.tar.gz: fea5ef98f76c7a32c28e461502039f1f1c9a2d32
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ac722386db2bfe735422685fbf81fdecbcf4c79a8c03677df38b84a681b2c435232260dc60f11843504dfca7d772439f42ec3f8c4a8dbbdd76d66c30853fb93b
|
|
7
|
+
data.tar.gz: 444d356b1fbc1277c77a2caa86ee2d4378636f285c574451a4b7a0843f53bd9fe46898b2d599f10d18ea77f830bb60ae8b3e0413104143c72f0b6f3123b21d59
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
|
6
|
+
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [2.1.0]
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Added support for `:as` in `timestamp` properties
|
|
15
|
+
- `user_options` are now forwarded to expanded associations
|
|
16
|
+
- Made associations ORM-independent with the Adapter API
|
|
17
|
+
- Implemented the Type Overrides API
|
|
18
|
+
- Implemented the Pagination Adapter API
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- Changed the `#type` of collections from `collection` to `list`
|
|
23
|
+
- Replaced `feature` with `include` in tests and examples
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
|
|
27
|
+
- Fixed `type` property not returning `list` for instances of `ActiveRecord::Relation`
|
|
28
|
+
- Fixed bugs with the optimization of associations with custom scopes
|
|
29
|
+
|
|
30
|
+
## [2.0.0]
|
|
31
|
+
|
|
32
|
+
First Pragma 2 release.
|
|
33
|
+
|
|
34
|
+
[Unreleased]: https://github.com/pragmarb/pragma-decorator/compare/v2.1.0...HEAD
|
|
35
|
+
[2.1.0]: https://github.com/pragmarb/pragma-decorator/compare/v2.0.0...v2.1.0
|
|
36
|
+
[2.0.0]: https://github.com/pragmarb/pragma-decorator/compare/v1.2.0...v2.0.0
|
data/README.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# Pragma::Decorator
|
|
2
2
|
|
|
3
|
-
[](https://travis-ci.org/pragmarb/pragma-decorator)
|
|
4
|
+
[](https://gemnasium.com/github.com/pragmarb/pragma-decorator)
|
|
5
|
+
[](https://coveralls.io/github/pragmarb/pragma-decorator?branch=master)
|
|
6
|
+
[](https://codeclimate.com/github/pragmarb/pragma-decorator/maintainability)
|
|
7
7
|
|
|
8
8
|
Decorators are a way to easily convert your API resources to JSON with minimum hassle.
|
|
9
9
|
|
|
@@ -84,7 +84,7 @@ module API
|
|
|
84
84
|
module User
|
|
85
85
|
module Decorator
|
|
86
86
|
class Instance < Pragma::Decorator::Base
|
|
87
|
-
|
|
87
|
+
include Pragma::Decorator::Type
|
|
88
88
|
end
|
|
89
89
|
end
|
|
90
90
|
end
|
|
@@ -134,7 +134,7 @@ module API
|
|
|
134
134
|
module User
|
|
135
135
|
module Decorator
|
|
136
136
|
class Instance < Pragma::Decorator::Base
|
|
137
|
-
|
|
137
|
+
include Pragma::Decorator::Timestamp
|
|
138
138
|
|
|
139
139
|
timestamp :created_at
|
|
140
140
|
end
|
|
@@ -153,7 +153,7 @@ This will render a user like this:
|
|
|
153
153
|
}
|
|
154
154
|
```
|
|
155
155
|
|
|
156
|
-
The `#timestamp` method supports all the options supported by `#property
|
|
156
|
+
The `#timestamp` method supports all the options supported by `#property`.
|
|
157
157
|
|
|
158
158
|
### Associations
|
|
159
159
|
|
|
@@ -166,7 +166,7 @@ module API
|
|
|
166
166
|
module Invoice
|
|
167
167
|
module Decorator
|
|
168
168
|
class Instance < Pragma::Decorator::Base
|
|
169
|
-
|
|
169
|
+
include Pragma::Decorator::Association
|
|
170
170
|
|
|
171
171
|
belongs_to :customer, decorator: API::V1::Customer::Decorator
|
|
172
172
|
end
|
|
@@ -212,6 +212,8 @@ decorator.to_json(user_options: {
|
|
|
212
212
|
Needless to say, this is done automatically for you when you use all components together through
|
|
213
213
|
the [pragma](https://github.com/pragmarb/pragma) gem! :)
|
|
214
214
|
|
|
215
|
+
Associations support all the options supported by `#property`.
|
|
216
|
+
|
|
215
217
|
### Collection
|
|
216
218
|
|
|
217
219
|
`Pragma::Decorator::Collection` wraps collections in a `data` property so that you can include
|
|
@@ -223,7 +225,7 @@ module API
|
|
|
223
225
|
module Invoice
|
|
224
226
|
module Decorator
|
|
225
227
|
class Collection < Pragma::Decorator::Base
|
|
226
|
-
|
|
228
|
+
include Pragma::Decorator::Collection
|
|
227
229
|
decorate_with Instance # specify the instance decorator
|
|
228
230
|
|
|
229
231
|
property :total_cents, exec_context: :decorator
|
|
@@ -273,8 +275,8 @@ module API
|
|
|
273
275
|
module Invoice
|
|
274
276
|
module Decorator
|
|
275
277
|
class Collection < Pragma::Decorator::Base
|
|
276
|
-
|
|
277
|
-
|
|
278
|
+
include Pragma::Decorator::Collection
|
|
279
|
+
include Pragma::Decorator::Pagination
|
|
278
280
|
|
|
279
281
|
decorate_with Instance
|
|
280
282
|
end
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pragma
|
|
4
|
+
module Decorator
|
|
5
|
+
module Association
|
|
6
|
+
module Adapter
|
|
7
|
+
# The ActiveRecord association adapter is used in AR environments and tries to minimize the
|
|
8
|
+
# number of SQL queries that are made to retrieve the associated object's data.
|
|
9
|
+
#
|
|
10
|
+
# @api private
|
|
11
|
+
class ActiveRecord < Base
|
|
12
|
+
class << self
|
|
13
|
+
# Returns whether the adapter supports the given model.
|
|
14
|
+
#
|
|
15
|
+
# @param model [Object] the model to check
|
|
16
|
+
#
|
|
17
|
+
# @return [Boolean] whether the object is an instance of +ActiveRecord::Base+
|
|
18
|
+
def supports?(model)
|
|
19
|
+
Object.const_defined?('ActiveRecord::Base') && model.is_a?(ActiveRecord::Base)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Initializes the adapter.
|
|
24
|
+
#
|
|
25
|
+
# @param bond [Bond] the bond to use in the adapter
|
|
26
|
+
#
|
|
27
|
+
# @raise [InconsistentTypeError] when the association's real type is different from the
|
|
28
|
+
# one defined on the decorator ()e.g. decorator defines the association as +belongs_to+,
|
|
29
|
+
# but ActiveRecord reports its type is +has_one+)
|
|
30
|
+
def initialize(bond)
|
|
31
|
+
super
|
|
32
|
+
check_type_consistency
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Returns the primary key of the associated object.
|
|
36
|
+
#
|
|
37
|
+
# If the +exec_context+ of the association is +decorator+, this will simply return early
|
|
38
|
+
# with the value returned by +#id+ on the associated object.
|
|
39
|
+
#
|
|
40
|
+
# If the association is a +belongs_to+, there are three possible scenarios:
|
|
41
|
+
#
|
|
42
|
+
# * the association does not have a custom scope: this will compute the PK by calling
|
|
43
|
+
# the foreign key on the parent model;
|
|
44
|
+
# * the association has a custom scope and it has not been loaded: this will compute
|
|
45
|
+
# the PK by +pluck+ing the PK column of the associated object;
|
|
46
|
+
# * the association has a custom scope and it has been loaded: this will compute
|
|
47
|
+
# the PK by retrieving the PK attribute from the loaded object.
|
|
48
|
+
#
|
|
49
|
+
# If the association is a +has_one+, there are two possible scenarios:
|
|
50
|
+
#
|
|
51
|
+
# * the association has already been loaded: this will compute the PK by retrieving the
|
|
52
|
+
# PK attribute from the loaded object;
|
|
53
|
+
# * the association has not been loaded: this will compute the PK by +pluck+ing the PK
|
|
54
|
+
# column of the associated object;
|
|
55
|
+
#
|
|
56
|
+
# Custom scopes are always respected in both +belongs_to+ and +has_one+.
|
|
57
|
+
#
|
|
58
|
+
# +nil+ values are handled gracefully in all cases.
|
|
59
|
+
#
|
|
60
|
+
# @return [String|Integer|NilClass] the PK of the associated object
|
|
61
|
+
#
|
|
62
|
+
# @todo Allow to specify a different PK attribute when +exec_context+ is +decorator+
|
|
63
|
+
def primary_key
|
|
64
|
+
return associated_object&.id if reflection.options[:exec_context] == :decorator
|
|
65
|
+
|
|
66
|
+
case reflection.type
|
|
67
|
+
when :belongs_to
|
|
68
|
+
compute_belongs_to_fk
|
|
69
|
+
when :has_one
|
|
70
|
+
compute_has_one_fk
|
|
71
|
+
else
|
|
72
|
+
fail "Cannot compute primary key for #{reflection.type} association"
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Returns the expanded associated object.
|
|
77
|
+
#
|
|
78
|
+
# This will simply return the associated object itself, delegating caching to AR.
|
|
79
|
+
#
|
|
80
|
+
# @return [Object] the associated object
|
|
81
|
+
#
|
|
82
|
+
# @todo Ensure the required attributes are present on the associated object
|
|
83
|
+
def full_object
|
|
84
|
+
associated_object
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
private
|
|
88
|
+
|
|
89
|
+
def compute_belongs_to_fk
|
|
90
|
+
if model.association(reflection.property).loaded?
|
|
91
|
+
return associated_object&.public_send(association_reflection.association_primary_key)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
if association_reflection.scope.nil?
|
|
95
|
+
return model.public_send(association_reflection.foreign_key)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
pluck_association_fk do |scope|
|
|
99
|
+
fk = model.public_send(association_reflection.foreign_key)
|
|
100
|
+
scope.where(association_reflection.association_primary_key => fk)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def compute_has_one_fk
|
|
105
|
+
if model.association(reflection.property).loaded?
|
|
106
|
+
return associated_object&.public_send(associated_object.association_primary_key)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
pluck_association_fk do |scope|
|
|
110
|
+
pk = model.public_send(association_reflection.active_record_primary_key)
|
|
111
|
+
scope.where(association_reflection.foreign_key => pk)
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def pluck_association_fk
|
|
116
|
+
scope = association_reflection.klass.all
|
|
117
|
+
|
|
118
|
+
if association_reflection.scope
|
|
119
|
+
scope = association_reflection.instance_eval(&association_reflection.scope)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
yield(scope).pluck(association_reflection.association_primary_key).first
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def association_reflection
|
|
126
|
+
@association_reflection ||= model.class.reflect_on_association(reflection.property)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def check_type_consistency
|
|
130
|
+
return if association_reflection.macro.to_sym == reflection.type.to_sym
|
|
131
|
+
|
|
132
|
+
fail InconsistentTypeError.new(
|
|
133
|
+
decorator: decorator,
|
|
134
|
+
reflection: reflection,
|
|
135
|
+
model_type: association_reflection.macro
|
|
136
|
+
)
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pragma
|
|
4
|
+
module Decorator
|
|
5
|
+
module Association
|
|
6
|
+
module Adapter
|
|
7
|
+
# The base association adapter, defining the interface for the implementations.
|
|
8
|
+
#
|
|
9
|
+
# @abstract Subclass and override {.supports?}, {#primary_key} and {#full_object} to
|
|
10
|
+
# create a new adapter
|
|
11
|
+
#
|
|
12
|
+
# @api private
|
|
13
|
+
class Base
|
|
14
|
+
class << self
|
|
15
|
+
# Returns whether the adapter supports the given model.
|
|
16
|
+
#
|
|
17
|
+
# @param _model [Object] a model
|
|
18
|
+
#
|
|
19
|
+
# @return [Boolean] whether the adpater supports the model
|
|
20
|
+
#
|
|
21
|
+
# @abstract
|
|
22
|
+
def supports?(_model)
|
|
23
|
+
fail NotImplementedError
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# @!attribute [r] bond
|
|
28
|
+
# @return [Bond] the bond this adapter has been instantiated with
|
|
29
|
+
attr_reader :bond
|
|
30
|
+
|
|
31
|
+
# Initializes the adapter.
|
|
32
|
+
#
|
|
33
|
+
# @param bond [Bond] the bond to use
|
|
34
|
+
def initialize(bond)
|
|
35
|
+
@bond = bond
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Returns the primary key of the association represented by the provided bond.
|
|
39
|
+
#
|
|
40
|
+
# @return [String|Integer] the PK
|
|
41
|
+
#
|
|
42
|
+
# @abstract
|
|
43
|
+
def primary_key
|
|
44
|
+
fail NotImplementedError
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Returns the full object of the association represented by the provided bond.
|
|
48
|
+
#
|
|
49
|
+
# @return [Object] the full object
|
|
50
|
+
#
|
|
51
|
+
# @abstract
|
|
52
|
+
def full_object
|
|
53
|
+
fail NotImplementedError
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
protected
|
|
57
|
+
|
|
58
|
+
# This is a convenience method returning the reflection defined on the bond.
|
|
59
|
+
#
|
|
60
|
+
# @return [Reflection] the bond's reflection
|
|
61
|
+
#
|
|
62
|
+
# @see Bond#reflection
|
|
63
|
+
def reflection
|
|
64
|
+
bond.reflection
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# This is a convenience method returning the reflection defined on the bond.
|
|
68
|
+
#
|
|
69
|
+
# @return [Object] the bond's associated object
|
|
70
|
+
#
|
|
71
|
+
# @see Bond#associated_object
|
|
72
|
+
def associated_object
|
|
73
|
+
bond.associated_object
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# This is a convenience method returning the model defined on the bond.
|
|
77
|
+
#
|
|
78
|
+
# @return [Reflection] the bond's model
|
|
79
|
+
#
|
|
80
|
+
# @see Bond#model
|
|
81
|
+
def model
|
|
82
|
+
bond.model
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pragma
|
|
4
|
+
module Decorator
|
|
5
|
+
module Association
|
|
6
|
+
module Adapter
|
|
7
|
+
# This is the fallback adapter that is used when no other adpater is compatible with a
|
|
8
|
+
# model. It simply calls +#id+ on the associated object to get the PK and returns the
|
|
9
|
+
# associated object itself when expanding.
|
|
10
|
+
#
|
|
11
|
+
# @api private
|
|
12
|
+
class Poro < Base
|
|
13
|
+
class << self
|
|
14
|
+
# Returns whether the adapter supports the model.
|
|
15
|
+
#
|
|
16
|
+
# Since this is the default adapter, this always returns +true+.
|
|
17
|
+
#
|
|
18
|
+
# @param _model [Object] the model to check
|
|
19
|
+
#
|
|
20
|
+
# @return [Boolean] always +true+
|
|
21
|
+
def supports?(_model)
|
|
22
|
+
true
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Returns the PK of the associated object.
|
|
27
|
+
#
|
|
28
|
+
# This adapter simply calls +#id+ on the associated object or returns +nil+ if there is
|
|
29
|
+
# no associated object.
|
|
30
|
+
#
|
|
31
|
+
# @return [Integer|String|NilClass] the PK of the associated object
|
|
32
|
+
def primary_key
|
|
33
|
+
associated_object&.id
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Returns the expanded associated object.
|
|
37
|
+
#
|
|
38
|
+
# This adapter simply returns the associated object itself.
|
|
39
|
+
#
|
|
40
|
+
# @return [Object] the associated object
|
|
41
|
+
def full_object
|
|
42
|
+
associated_object
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pragma
|
|
4
|
+
module Decorator
|
|
5
|
+
module Association
|
|
6
|
+
# Adapters make associations ORM-independent by providing support for multiple underlying
|
|
7
|
+
# libraries like ActiveRecord and simple POROs.
|
|
8
|
+
#
|
|
9
|
+
# @api private
|
|
10
|
+
module Adapter
|
|
11
|
+
# The list of supported adapters, in order of priority.
|
|
12
|
+
SUPPORTED_ADAPTERS = [ActiveRecord, Poro].freeze
|
|
13
|
+
|
|
14
|
+
# Loads the adapter for the given association bond.
|
|
15
|
+
#
|
|
16
|
+
# This will try {SUPPORTED_ADAPTERS} in order until it finds an adapter that supports the
|
|
17
|
+
# bond's model. When the adapter is found, it will return a new instance of it.
|
|
18
|
+
#
|
|
19
|
+
# @param bond [Bond] the bond to load the adapter for
|
|
20
|
+
#
|
|
21
|
+
# @return [Adapter::Base]
|
|
22
|
+
#
|
|
23
|
+
# @see Adapter::Base.supports?
|
|
24
|
+
def self.load_for(bond)
|
|
25
|
+
SUPPORTED_ADAPTERS.find do |adapter|
|
|
26
|
+
adapter.supports?(bond.model)
|
|
27
|
+
end.new(bond)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -5,8 +5,8 @@ module Pragma
|
|
|
5
5
|
module Association
|
|
6
6
|
# Links an association definition to a specific decorator instance, allowing to render it.
|
|
7
7
|
#
|
|
8
|
-
# @
|
|
9
|
-
class
|
|
8
|
+
# @api private
|
|
9
|
+
class Bond
|
|
10
10
|
# @!attribute [r] reflection
|
|
11
11
|
# @return [Reflection] the association reflection
|
|
12
12
|
#
|
|
@@ -14,7 +14,7 @@ module Pragma
|
|
|
14
14
|
# @return [Pragma::Decorator::Base] the decorator instance
|
|
15
15
|
attr_reader :reflection, :decorator
|
|
16
16
|
|
|
17
|
-
# Initializes the
|
|
17
|
+
# Initializes the bond.
|
|
18
18
|
#
|
|
19
19
|
# @param reflection [Reflection] the association reflection
|
|
20
20
|
# @param decorator [Pragma::Decorator::Base] the decorator instance
|
|
@@ -29,9 +29,9 @@ module Pragma
|
|
|
29
29
|
def associated_object
|
|
30
30
|
case reflection.options[:exec_context]
|
|
31
31
|
when :decorated
|
|
32
|
-
|
|
32
|
+
model.public_send(reflection.property)
|
|
33
33
|
when :decorator
|
|
34
|
-
decorator.
|
|
34
|
+
decorator.public_send(reflection.property)
|
|
35
35
|
end
|
|
36
36
|
end
|
|
37
37
|
|
|
@@ -39,8 +39,7 @@ module Pragma
|
|
|
39
39
|
#
|
|
40
40
|
# @return [String]
|
|
41
41
|
def unexpanded_value
|
|
42
|
-
|
|
43
|
-
associated_object.id
|
|
42
|
+
adapter.primary_key
|
|
44
43
|
end
|
|
45
44
|
|
|
46
45
|
# Returns the expanded value for the associated object.
|
|
@@ -53,48 +52,58 @@ module Pragma
|
|
|
53
52
|
# In any case, passes all nested associations as the +expand+ user option of the method
|
|
54
53
|
# called.
|
|
55
54
|
#
|
|
56
|
-
# @param
|
|
55
|
+
# @param user_options [Array]
|
|
57
56
|
#
|
|
58
57
|
# @return [Hash]
|
|
59
|
-
def expanded_value(
|
|
60
|
-
|
|
58
|
+
def expanded_value(user_options)
|
|
59
|
+
full_object = adapter.full_object
|
|
60
|
+
return unless full_object
|
|
61
61
|
|
|
62
62
|
options = {
|
|
63
|
-
user_options:
|
|
64
|
-
expand: flatten_expand(expand)
|
|
65
|
-
|
|
63
|
+
user_options: user_options.merge(
|
|
64
|
+
expand: flatten_expand(user_options[:expand])
|
|
65
|
+
)
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
decorator_klass = compute_decorator
|
|
69
69
|
|
|
70
70
|
if decorator_klass
|
|
71
|
-
decorator_klass.new(
|
|
71
|
+
decorator_klass.new(full_object).to_hash(options)
|
|
72
72
|
else
|
|
73
|
-
|
|
73
|
+
full_object.as_json(options)
|
|
74
74
|
end
|
|
75
75
|
end
|
|
76
76
|
|
|
77
77
|
# Renders the unexpanded or expanded associations, depending on the +expand+ user option
|
|
78
78
|
# passed to the decorator.
|
|
79
79
|
#
|
|
80
|
-
# @param
|
|
80
|
+
# @param user_options [Array]
|
|
81
81
|
#
|
|
82
82
|
# @return [Hash|Pragma::Decorator::Base]
|
|
83
|
-
def render(
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
expand ||= []
|
|
87
|
-
|
|
88
|
-
if expand.any? { |value| value.to_s == reflection.property.to_s }
|
|
89
|
-
expanded_value(expand)
|
|
83
|
+
def render(user_options)
|
|
84
|
+
if user_options[:expand]&.any? { |value| value.to_s == reflection.property.to_s }
|
|
85
|
+
expanded_value(user_options)
|
|
90
86
|
else
|
|
91
87
|
unexpanded_value
|
|
92
88
|
end
|
|
93
89
|
end
|
|
94
90
|
|
|
91
|
+
# Returns the model associated to this bond.
|
|
92
|
+
#
|
|
93
|
+
# @return [Object]
|
|
94
|
+
def model
|
|
95
|
+
decorator.decorated
|
|
96
|
+
end
|
|
97
|
+
|
|
95
98
|
private
|
|
96
99
|
|
|
100
|
+
def adapter
|
|
101
|
+
@adapter ||= Adapter.load_for(self)
|
|
102
|
+
end
|
|
103
|
+
|
|
97
104
|
def flatten_expand(expand)
|
|
105
|
+
expand ||= []
|
|
106
|
+
|
|
98
107
|
expected_beginning = "#{reflection.property}."
|
|
99
108
|
|
|
100
109
|
expand.reject { |value| value.to_s == reflection.property.to_s }.map do |value|
|
|
@@ -3,21 +3,38 @@
|
|
|
3
3
|
module Pragma
|
|
4
4
|
module Decorator
|
|
5
5
|
module Association
|
|
6
|
+
# This is a generic class for all errors during association expansion.
|
|
6
7
|
class ExpansionError < StandardError
|
|
7
8
|
end
|
|
8
9
|
|
|
10
|
+
# This is raised when a non-existing association is expanded.
|
|
9
11
|
class AssociationNotFound < ExpansionError
|
|
12
|
+
# @!attribute [r] property
|
|
13
|
+
# @return [String|Sybmol] the property the user tried to expand
|
|
10
14
|
attr_reader :property
|
|
11
15
|
|
|
16
|
+
# Initializes the rror.
|
|
17
|
+
#
|
|
18
|
+
# @param property [String|Symbol] the property the user tried to expand
|
|
12
19
|
def initialize(property)
|
|
13
20
|
@property = property
|
|
14
21
|
super "The '#{property}' association is not defined."
|
|
15
22
|
end
|
|
16
23
|
end
|
|
17
24
|
|
|
25
|
+
# This is raised when the user expanded a nested association without expanding its parent.
|
|
18
26
|
class UnexpandedAssociationParent < ExpansionError
|
|
27
|
+
# @!attribute [r] child
|
|
28
|
+
# @return [String|Symbol] the name of the child association
|
|
29
|
+
#
|
|
30
|
+
# @!attribute [r] parent
|
|
31
|
+
# @return [String|Symbol] the name of the parent association
|
|
19
32
|
attr_reader :child, :parent
|
|
20
33
|
|
|
34
|
+
# Initializes the error.
|
|
35
|
+
#
|
|
36
|
+
# @param child [String|Symbol] the name of the child association
|
|
37
|
+
# @param parent [String|Symbol] the name of the parent association
|
|
21
38
|
def initialize(child, parent)
|
|
22
39
|
@child = child
|
|
23
40
|
@parent = parent
|
|
@@ -25,6 +42,26 @@ module Pragma
|
|
|
25
42
|
super "The '#{child}' association is expanded, but its parent '#{parent}' is not."
|
|
26
43
|
end
|
|
27
44
|
end
|
|
45
|
+
|
|
46
|
+
# This error is raised when an association's type is different from its type as reported by
|
|
47
|
+
# the model's reflection.
|
|
48
|
+
#
|
|
49
|
+
# @author Alessandro Desantis
|
|
50
|
+
class InconsistentTypeError < StandardError
|
|
51
|
+
# Initializes the error.
|
|
52
|
+
#
|
|
53
|
+
# @param decorator [Base] the decorator where the association is defined
|
|
54
|
+
# @param reflection [Reflection] the reflection of the inconsistent association
|
|
55
|
+
# @param model_type [Symbol|String] the real type of the association
|
|
56
|
+
def initialize(decorator:, reflection:, model_type:)
|
|
57
|
+
message = <<~MSG.tr("\n", ' ')
|
|
58
|
+
#{decorator.class}: Association #{reflection.property} is defined as #{model_type} on
|
|
59
|
+
the model, but as #{reflection.type} in the decorator.
|
|
60
|
+
MSG
|
|
61
|
+
|
|
62
|
+
super message
|
|
63
|
+
end
|
|
64
|
+
end
|
|
28
65
|
end
|
|
29
66
|
end
|
|
30
67
|
end
|