pragma-decorator 2.0.0 → 2.1.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 -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
|
-
[![Build Status](https://
|
4
|
-
[![Dependency Status](https://
|
5
|
-
[![
|
6
|
-
[![
|
3
|
+
[![Build Status](https://travis-ci.org/pragmarb/pragma-decorator.svg?branch=master)](https://travis-ci.org/pragmarb/pragma-decorator)
|
4
|
+
[![Dependency Status](https://gemnasium.com/badges/github.com/pragmarb/pragma-decorator.svg)](https://gemnasium.com/github.com/pragmarb/pragma-decorator)
|
5
|
+
[![Coverage Status](https://coveralls.io/repos/github/pragmarb/pragma-decorator/badge.svg?branch=master)](https://coveralls.io/github/pragmarb/pragma-decorator?branch=master)
|
6
|
+
[![Maintainability](https://api.codeclimate.com/v1/badges/e51e8d7489eb72ab97ba/maintainability)](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
|