eluvia-base 3.37.1
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 +7 -0
- data/LICENSE +21 -0
- data/README.md +225 -0
- data/lib/eluvia/active_model/amount_attribute.rb +108 -0
- data/lib/eluvia/active_model/array_attribute.rb +134 -0
- data/lib/eluvia/active_model/attributes.rb +417 -0
- data/lib/eluvia/active_model/enum_array_attribute.rb +60 -0
- data/lib/eluvia/active_model/enum_attribute.rb +174 -0
- data/lib/eluvia/active_model/object_attribute.rb +143 -0
- data/lib/eluvia/active_model/range_attribute.rb +74 -0
- data/lib/eluvia/active_record/file_array_attribute.rb +116 -0
- data/lib/eluvia/active_record/file_attribute.rb +158 -0
- data/lib/eluvia/active_record/filtering.rb +680 -0
- data/lib/eluvia/active_record/filters_model.rb +58 -0
- data/lib/eluvia/active_record/ordering.rb +110 -0
- data/lib/eluvia/base/config.rb +38 -0
- data/lib/eluvia/base/version.rb +12 -0
- data/lib/eluvia/errors/bad_request.rb +17 -0
- data/lib/eluvia/errors/forbidden.rb +17 -0
- data/lib/eluvia/errors/not_found.rb +17 -0
- data/lib/eluvia/errors/service_unavailable.rb +17 -0
- data/lib/eluvia/errors/standard_error.rb +33 -0
- data/lib/eluvia/errors/unauthorized.rb +17 -0
- data/lib/eluvia/errors/unprocessable_entity.rb +26 -0
- data/lib/eluvia/fieldset/rest_field.rb +83 -0
- data/lib/eluvia/fieldset/rest_fieldset.rb +158 -0
- data/lib/eluvia/fieldset.rb +12 -0
- data/lib/eluvia/handlers/error_handler.rb +60 -0
- data/lib/eluvia/handlers/pagination_handler.rb +25 -0
- data/lib/eluvia/handlers/params_handler.rb +43 -0
- data/lib/eluvia/helpers/attachment_helper.rb +37 -0
- data/lib/eluvia/integrations/eluvia_integration.rb +281 -0
- data/lib/eluvia/models/file.rb +44 -0
- data/lib/eluvia/models/file_wrapper.rb +38 -0
- data/lib/eluvia/models/image_wrapper.rb +38 -0
- data/lib/eluvia/serializers/error_serializer.rb +21 -0
- data/lib/eluvia/serializers/fieldset_serializer.rb +63 -0
- data/lib/eluvia/services/health_service.rb +56 -0
- data/lib/eluvia/uploads.rb +13 -0
- data/lib/eluvia/utils/hash_and_array.rb +98 -0
- data/lib/eluvia/utils/jwt.rb +36 -0
- data/lib/eluvia/utils/string.rb +31 -0
- data/lib/eluvia/utils/uuid.rb +21 -0
- data/lib/eluvia-base.rb +79 -0
- metadata +186 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 6f3de7ff3e455a14c86e7c241ac9456ef39ca93ba40c32fd0a8dfb480986b644
|
|
4
|
+
data.tar.gz: 3ee766edbedf1243cf4036ef90b6e84da1b81585dd9115ed387c802280269850
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 96a8f3748400de99c05d3bc237f5d598de09c07b7366a60336a38051af342bf61a14bd35a96572a704f36f8395213b1288ebd5dd5b78099979d082a0d8016b6d
|
|
7
|
+
data.tar.gz: 175ea58ec0bc03a3f7a3271ab5447a2676d892df10eee44825c0effe924fa2b3029ec7c8bfc3ef253247f984847485f42e9f4cf49ef8bee64fac2cb2683532aa
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022 Eluvia.land s.r.o.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
# Eluvia Base
|
|
2
|
+
|
|
3
|
+
This library provides a set of Ruby on Rails customizations and other helpers in order to meet common Eluvia standards
|
|
4
|
+
for API communication (https://sparktech.myjetbrains.com/youtrack/articles/BASE-A-4/Communication-interface-FE-BE) and
|
|
5
|
+
Eluvia requirements for backend application behavior.
|
|
6
|
+
|
|
7
|
+
It applies to both client and server sides:
|
|
8
|
+
- Pagination and ordering is handled for the API requests (server side).
|
|
9
|
+
- Extended params parsing is implemented for the API requests (server side).
|
|
10
|
+
- Extended filtering is handled for the API requests (server side).
|
|
11
|
+
- Errors are serialized in the API response in a standard way (server side).
|
|
12
|
+
- Integration helpers are implemented to provide easy communication with other Eluvia components (client side).
|
|
13
|
+
|
|
14
|
+
## 1. Installation
|
|
15
|
+
|
|
16
|
+
### 1.1. Gem
|
|
17
|
+
|
|
18
|
+
To install the library to your project, just add this to your `Gemfile`:
|
|
19
|
+
|
|
20
|
+
```ruby
|
|
21
|
+
gem 'eluvia-base'
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
New dependencies should be installed with:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
bundle install
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### 1.2. Initializer
|
|
31
|
+
|
|
32
|
+
Next, create an initializer `config/initializers/eluvia_base.rb` with the configuration:
|
|
33
|
+
|
|
34
|
+
```ruby
|
|
35
|
+
Eluvia::Base.setup do |config|
|
|
36
|
+
config.api_case = ENV.fetch('API_CASE') { '' }
|
|
37
|
+
config.private_api_key = ENV.fetch('PRIVATE_API_KEY') { '' }
|
|
38
|
+
config.public_api_key = ENV.fetch('PUBLIC_API_KEY') { '' }
|
|
39
|
+
end
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
For full list of configuration options, see `lib/eluvia/base/config.rb` file.
|
|
43
|
+
|
|
44
|
+
## 2. Usage
|
|
45
|
+
|
|
46
|
+
### 2.1. Application controller setup
|
|
47
|
+
|
|
48
|
+
In order to use all the implemented functionality, you should integrate all defined handlers to your
|
|
49
|
+
main `ApplicationController`:
|
|
50
|
+
|
|
51
|
+
```ruby
|
|
52
|
+
|
|
53
|
+
class ApplicationController < ActionController::API
|
|
54
|
+
include Eluvia::ErrorHandler
|
|
55
|
+
include Eluvia::PaginationHandler
|
|
56
|
+
include Eluvia::ParamsHandler
|
|
57
|
+
|
|
58
|
+
# ...
|
|
59
|
+
end
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### 2.2. Pagination
|
|
63
|
+
|
|
64
|
+
In order to interpret `limit`, `offset` and `order_by` parameters, you can user `set_pagination` decorator. It will
|
|
65
|
+
transform the input parameters into the `@order_by`, `@page`, `@limit` and `@padding` instance variables which can be
|
|
66
|
+
easily used in Active Record and Kaminari interfaces.
|
|
67
|
+
|
|
68
|
+
```ruby
|
|
69
|
+
|
|
70
|
+
class TestRecordsController < ApplicationController
|
|
71
|
+
# ...
|
|
72
|
+
|
|
73
|
+
before_action -> { set_pagination(20, 0, 'created_at:desc') }, only: [:index]
|
|
74
|
+
|
|
75
|
+
# ...
|
|
76
|
+
|
|
77
|
+
def index
|
|
78
|
+
@test_records = TestRecord.all.order(@order_by).page(@page).per(@limit).padding(@padding)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# ...
|
|
82
|
+
end
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### 2.3. Params parser
|
|
86
|
+
|
|
87
|
+
You can use `parse_json_param` helper to parse JSON without predefined structure. In case you know the input JSON
|
|
88
|
+
structure, you should use standard "strong params" mechanism.
|
|
89
|
+
|
|
90
|
+
```ruby
|
|
91
|
+
|
|
92
|
+
class TestRecordsController < ApplicationController
|
|
93
|
+
# ...
|
|
94
|
+
|
|
95
|
+
def create
|
|
96
|
+
@test_record = TestRecord.create(test_record_params)
|
|
97
|
+
# ...
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# ...
|
|
101
|
+
|
|
102
|
+
def test_record_params
|
|
103
|
+
result = params.permit(
|
|
104
|
+
:param_1,
|
|
105
|
+
:param_2,
|
|
106
|
+
)
|
|
107
|
+
result[:additional_params] = parse_json_param(params[:additional_params])
|
|
108
|
+
result
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# ...
|
|
112
|
+
end
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### 2.4. Eluvia integration
|
|
116
|
+
|
|
117
|
+
Integration class can be implemented with the help of `Eluvia::EluviaIntegration` concern. This concern acts as
|
|
118
|
+
a wrapper over `RestClient` and brings some functionality for JSON parsing, pagination and Eluvia specific headers
|
|
119
|
+
composition.
|
|
120
|
+
|
|
121
|
+
```ruby
|
|
122
|
+
|
|
123
|
+
class TestRecordsIntegration
|
|
124
|
+
include Singleton
|
|
125
|
+
include Eluvia::EluviaIntegration
|
|
126
|
+
|
|
127
|
+
def initialize
|
|
128
|
+
@service_url = '...'
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def get_some_data(session_id)
|
|
132
|
+
parse_get_request(compose_url('some-data'), compose_headers_both(session_id))
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def post_some_data(session_id, data)
|
|
136
|
+
parse_post_request(compose_url('some-data'), compose_headers_both(session_id), data)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
end
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
For the full list of features, see `Eluvia::EluviaIntegration` interface.
|
|
143
|
+
|
|
144
|
+
### 2.5. Raising error
|
|
145
|
+
|
|
146
|
+
You can raise en exception `Eluvia::Errors::XXX` anywhere in the code and this exception will be automatically
|
|
147
|
+
formatted as standardized error response.
|
|
148
|
+
|
|
149
|
+
For example, this exception:
|
|
150
|
+
|
|
151
|
+
```ruby
|
|
152
|
+
|
|
153
|
+
raise Eluvia::Errors::NotFound.new('Test record was not found.')
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
will be formatted into this response:
|
|
157
|
+
|
|
158
|
+
```json
|
|
159
|
+
{
|
|
160
|
+
"errors": [
|
|
161
|
+
{
|
|
162
|
+
"message": "Test record was not found.",
|
|
163
|
+
"error": "Not Found",
|
|
164
|
+
"status": 404,
|
|
165
|
+
"source": null,
|
|
166
|
+
"timestamp": "2022-11-04T17:11:00.969Z"
|
|
167
|
+
}
|
|
168
|
+
]
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
For example, this exception:
|
|
173
|
+
|
|
174
|
+
```ruby
|
|
175
|
+
|
|
176
|
+
raise Eluvia::Errors::UnprocessableEntity.new('param_1' => 'Param 1 must not be blank.',
|
|
177
|
+
'param_2' => 'Param 2 must not be blank.')
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
will be formatted into this response:
|
|
181
|
+
|
|
182
|
+
```json
|
|
183
|
+
{
|
|
184
|
+
"errors": [
|
|
185
|
+
{
|
|
186
|
+
"message": "Param 1 must not be blank.",
|
|
187
|
+
"error": "Unprocessable Entity",
|
|
188
|
+
"status": 422,
|
|
189
|
+
"source": "param_1",
|
|
190
|
+
"timestamp": "2022-11-04T17:11:00.969Z"
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
"message": "Param 2 must not be blank.",
|
|
194
|
+
"error": "Unprocessable Entity",
|
|
195
|
+
"status": 422,
|
|
196
|
+
"source": "param_2",
|
|
197
|
+
"timestamp": "2022-11-04T17:11:00.969Z"
|
|
198
|
+
}
|
|
199
|
+
]
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
For the full list of available exceptions, see content of `lib/eluvia/errors` folder.
|
|
204
|
+
|
|
205
|
+
### 2.6. Chunked/direct upload finalization
|
|
206
|
+
|
|
207
|
+
`Eluvia::File` carries an `upload_key` attribute, an alternative to `content` (base64 encoded file data)
|
|
208
|
+
for `file_attr` fields backed by an underlying ActiveStorage attachment. When `upload_key` is present,
|
|
209
|
+
the setter no longer base64-decodes `content` — instead it delegates to `Eluvia::Uploads.finalizer`, a
|
|
210
|
+
registrable extension point that finalizes a previously uploaded (chunked/direct) temp object into the
|
|
211
|
+
ActiveStorage attachment. This lets a separate library (e.g. a chunked upload provider) implement the
|
|
212
|
+
actual storage logic (S3 `copy_object`, tus, ...) without eluvia-base depending on it.
|
|
213
|
+
|
|
214
|
+
Register a finalizer, typically from an initializer:
|
|
215
|
+
|
|
216
|
+
```ruby
|
|
217
|
+
|
|
218
|
+
Eluvia::Uploads.finalizer = ->(attachment_record, upload_key, filename) do
|
|
219
|
+
# Resolve `upload_key` in your storage backend and attach the resulting blob to `attachment_record`,
|
|
220
|
+
# using `filename` as the final (sanitized) filename.
|
|
221
|
+
end
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
If `upload_key` is present on the assigned `Eluvia::File` but no finalizer is registered, a
|
|
225
|
+
`Eluvia::Errors::StandardError` is raised.
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
module Eluvia
|
|
2
|
+
module ActiveModel
|
|
3
|
+
module AmountAttribute
|
|
4
|
+
extend ::ActiveSupport::Concern
|
|
5
|
+
include Eluvia::ActiveModel::Attributes
|
|
6
|
+
|
|
7
|
+
class_methods do
|
|
8
|
+
|
|
9
|
+
# *********************************************************************
|
|
10
|
+
# Attribute store methods
|
|
11
|
+
# *********************************************************************
|
|
12
|
+
|
|
13
|
+
def _amount_attrs
|
|
14
|
+
@amount_attrs ||= {}
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def amount_attrs
|
|
18
|
+
_amount_attrs.values
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def amount_attr_names
|
|
22
|
+
amount_attrs.map { |a| a[:attr_name] }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def has_amount_attr?(attr_name)
|
|
26
|
+
_amount_attrs.key?(attr_name.to_sym)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def find_amount_attr(attr_name)
|
|
30
|
+
_amount_attrs.fetch(attr_name.to_sym, nil)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# *********************************************************************
|
|
34
|
+
# Transform value methods
|
|
35
|
+
# *********************************************************************
|
|
36
|
+
|
|
37
|
+
def convert_value_from_major_to_minor(value_major, precision: 2)
|
|
38
|
+
if value_major.nil?
|
|
39
|
+
nil
|
|
40
|
+
else
|
|
41
|
+
(value_major.to_f * (10**precision)).round(0).to_i
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def convert_value_from_minor_to_major(value_minor, precision: 2)
|
|
46
|
+
if value_minor.nil?
|
|
47
|
+
nil
|
|
48
|
+
else
|
|
49
|
+
(value_minor.to_f / (10**precision)).round(precision)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# *********************************************************************
|
|
54
|
+
# Attribute definition methods
|
|
55
|
+
# *********************************************************************
|
|
56
|
+
|
|
57
|
+
# Adds a new amount attribute to this model
|
|
58
|
+
#
|
|
59
|
+
# @param attr_name [Symbol] Attribute name
|
|
60
|
+
# @param precision [Integer] Number of decimal places
|
|
61
|
+
# @param declare [Boolean] Whether to declare the underlying attribute as integer
|
|
62
|
+
def amount_attr(attr_name, precision: 2, declare: false)
|
|
63
|
+
if declare
|
|
64
|
+
integer_attr attr_name
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Prepare internal structure
|
|
68
|
+
_amount_attrs[attr_name] = {
|
|
69
|
+
precision: precision,
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
# Define attribute for minor units
|
|
73
|
+
minor_attr_name = :"#{attr_name}_minor"
|
|
74
|
+
float_attr minor_attr_name
|
|
75
|
+
|
|
76
|
+
# Get a value in minor units
|
|
77
|
+
define_method(minor_attr_name) do
|
|
78
|
+
self.send(attr_name)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Set a value in minor units
|
|
82
|
+
define_method(:"#{minor_attr_name}=") do |value_minor|
|
|
83
|
+
self.send(:"#{attr_name}=", value_minor)
|
|
84
|
+
value_minor
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Define attribute for major units
|
|
88
|
+
major_attr_name = :"#{attr_name}_major"
|
|
89
|
+
float_attr major_attr_name
|
|
90
|
+
|
|
91
|
+
# Get a value in major units
|
|
92
|
+
define_method(major_attr_name) do
|
|
93
|
+
self.class.convert_value_from_minor_to_major(self.send(attr_name), precision: precision)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Set a value in major units
|
|
97
|
+
define_method(:"#{major_attr_name}=") do |value_major|
|
|
98
|
+
self.send(:"#{attr_name}=", self.class.convert_value_from_major_to_minor(value_major, precision: precision))
|
|
99
|
+
value_major
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
module Eluvia
|
|
2
|
+
module ActiveModel
|
|
3
|
+
module ArrayAttribute
|
|
4
|
+
extend ::ActiveSupport::Concern
|
|
5
|
+
include Eluvia::ActiveModel::Attributes
|
|
6
|
+
|
|
7
|
+
class_methods do
|
|
8
|
+
|
|
9
|
+
# *********************************************************************
|
|
10
|
+
# Attribute store methods
|
|
11
|
+
# *********************************************************************
|
|
12
|
+
|
|
13
|
+
def _array_attrs
|
|
14
|
+
@array_attrs ||= {}
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def array_attrs
|
|
18
|
+
_array_attrs.values
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def array_attr_names
|
|
22
|
+
array_attrs.map { |a| a[:attr_name] }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def has_array_attr?(attr_name)
|
|
26
|
+
_array_attrs.key?(attr_name.to_sym)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def find_array_attr(attr_name)
|
|
30
|
+
_array_attrs.fetch(attr_name.to_sym, nil)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# *********************************************************************
|
|
34
|
+
# Attribute definition methods for array
|
|
35
|
+
# *********************************************************************
|
|
36
|
+
|
|
37
|
+
def array_column(attr_name, options = {})
|
|
38
|
+
array_attr(attr_name, **options)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Add a new "array" attribute to this model
|
|
42
|
+
#
|
|
43
|
+
# @param attr_name [Symbol] Attribute name
|
|
44
|
+
# @param type [Symbol] Type of the array elements (:any, :string, :uuid, :date, :datetime, :integer, :float, :boolean)
|
|
45
|
+
# @param filter [Proc, nil] Optional filter proc to filter array elements
|
|
46
|
+
# @param transform [Proc, nil] Optional transform proc to transform array elements
|
|
47
|
+
# @param serialize [Boolean] Whether to serialize the array as JSON string in the underlying attribute
|
|
48
|
+
# @param declare [Boolean] Whether to declare the underlying attribute as any
|
|
49
|
+
def array_attr(attr_name, type: :string, filter: nil, transform: nil, serialize: false, declare: false)
|
|
50
|
+
attr_name = attr_name.to_sym
|
|
51
|
+
type = type.to_sym
|
|
52
|
+
|
|
53
|
+
# Declare underlying attribute
|
|
54
|
+
if declare
|
|
55
|
+
any_attr attr_name
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Type check
|
|
59
|
+
raise Eluvia::Errors::StandardError.new("Unknown type `#{type}`.") unless Eluvia::ActiveModel::Attributes::SIMPLE_TYPES.include?(type)
|
|
60
|
+
|
|
61
|
+
# Register metadata
|
|
62
|
+
self._array_attrs[attr_name] = {
|
|
63
|
+
attr_name: attr_name,
|
|
64
|
+
type: type,
|
|
65
|
+
filter: filter,
|
|
66
|
+
transform: transform,
|
|
67
|
+
serialize: serialize
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
# *******************************************************************
|
|
71
|
+
# Dynamically defined instance methods for array
|
|
72
|
+
# *******************************************************************
|
|
73
|
+
|
|
74
|
+
# Set method
|
|
75
|
+
define_method(:"#{attr_name}=") do |value|
|
|
76
|
+
|
|
77
|
+
# Convert string to array, we expect JSON string here
|
|
78
|
+
if value.is_a?(::String)
|
|
79
|
+
if !value.blank?
|
|
80
|
+
value = JSON.parse(value) rescue nil
|
|
81
|
+
else
|
|
82
|
+
value = nil
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Check input
|
|
87
|
+
raise Eluvia::Errors::BadRequest.new("Invalid value for array attribute `#{attr_name}`, expecting Array or nil.") unless value.nil? || value.is_a?(::Array)
|
|
88
|
+
|
|
89
|
+
# Handle blank value
|
|
90
|
+
if value.blank?
|
|
91
|
+
write_attribute(attr_name, nil)
|
|
92
|
+
return
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Clean values according to the specified type
|
|
96
|
+
value = value.map { |item| self.class.send(:"clean_#{type}_value", item) }
|
|
97
|
+
|
|
98
|
+
# Optional value filter
|
|
99
|
+
if filter && filter.respond_to?(:call)
|
|
100
|
+
value = value.select { |item| filter.call(item) }
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Optional value transform
|
|
104
|
+
if transform && transform.respond_to?(:call)
|
|
105
|
+
value = value.map { |item| transform.call(item) }.compact
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Store to the underlying attribute
|
|
109
|
+
write_attribute(attr_name, serialize ? value.to_json : value)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Get method
|
|
113
|
+
define_method(attr_name) do
|
|
114
|
+
# Read value from the underlying attribute and parse it if needed
|
|
115
|
+
value = read_attribute(attr_name)
|
|
116
|
+
value = JSON.parse(value) rescue nil if serialize
|
|
117
|
+
|
|
118
|
+
# Handle blank value
|
|
119
|
+
return nil if value.blank?
|
|
120
|
+
|
|
121
|
+
# Check output
|
|
122
|
+
raise Eluvia::Errors::StandardError.new("Invalid value for array attribute `#{attr_name}`, expecting Array.") unless value.is_a?(::Array)
|
|
123
|
+
|
|
124
|
+
# Clean values according to the specified type
|
|
125
|
+
value = value.map { |item| self.class.send(:"clean_#{type}_value", item) }
|
|
126
|
+
|
|
127
|
+
value
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|