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
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
module Eluvia
|
|
2
|
+
module ActiveModel
|
|
3
|
+
module Attributes
|
|
4
|
+
extend ::ActiveSupport::Concern
|
|
5
|
+
include ::ActiveModel::Model
|
|
6
|
+
|
|
7
|
+
SIMPLE_TYPES = %i[any string uuid date datetime integer float boolean].freeze
|
|
8
|
+
COMPLEX_TYPES = %i[has_one has_many].freeze
|
|
9
|
+
|
|
10
|
+
def initialize(params = {})
|
|
11
|
+
super(params)
|
|
12
|
+
if params
|
|
13
|
+
self.class.attrs(type: SIMPLE_TYPES).each do |attr_spec|
|
|
14
|
+
attr_name = attr_spec[:attr_name]
|
|
15
|
+
underlying_attr_name = attr_spec[:underlying_attr_name] || attr_name
|
|
16
|
+
|
|
17
|
+
# Get value from input params
|
|
18
|
+
key = nil
|
|
19
|
+
if params.key?(attr_name)
|
|
20
|
+
key = attr_name
|
|
21
|
+
elsif params.key?(attr_name.to_s)
|
|
22
|
+
key = attr_name.to_s
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Clean, transform and assign value only if key defined in params
|
|
26
|
+
if key
|
|
27
|
+
value = params[key]
|
|
28
|
+
self.send(:"#{attr_name}=", self.class.clean_value(value, attr_name))
|
|
29
|
+
self.send(:"#{underlying_attr_name}=", self.class.clean_and_transform_value(value, attr_name)) if underlying_attr_name != attr_name
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def new_record?
|
|
36
|
+
if self.respond_to?(:id)
|
|
37
|
+
self.id.blank?
|
|
38
|
+
else
|
|
39
|
+
true
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def write_attribute(attr_name, value)
|
|
44
|
+
if self.class.has_attr?(attr_name)
|
|
45
|
+
instance_variable_set("@#{attr_name}", value)
|
|
46
|
+
else
|
|
47
|
+
super(attr_name, value)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def read_attribute(attr_name)
|
|
52
|
+
if self.class.has_attr?(attr_name)
|
|
53
|
+
instance_variable_get("@#{attr_name}")
|
|
54
|
+
else
|
|
55
|
+
super(attr_name)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
class_methods do
|
|
60
|
+
|
|
61
|
+
# *********************************************************************
|
|
62
|
+
# Clean and transform value methods
|
|
63
|
+
# *********************************************************************
|
|
64
|
+
|
|
65
|
+
# Clean value of a specific type or attribute.
|
|
66
|
+
def clean_value(value, attr_name)
|
|
67
|
+
return nil if value.nil?
|
|
68
|
+
attr_spec = self.find_attr(attr_name)
|
|
69
|
+
raise Eluvia::Errors::StandardError.new("Cannot clean value of unknow attribute `#{attr_name}`.") unless attr_spec
|
|
70
|
+
self.send(:"clean_#{attr_spec[:type]}_value", value)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def clean_and_transform_value(value, attr_name)
|
|
74
|
+
return nil if value.nil?
|
|
75
|
+
attr_spec = self.find_attr(attr_name)
|
|
76
|
+
raise Eluvia::Errors::StandardError.new("Cannot transform value of unknow attribute `#{attr_name}`.") unless attr_spec
|
|
77
|
+
value_cleaned = self.send(:"clean_#{attr_spec[:type]}_value", value)
|
|
78
|
+
if attr_spec[:transform] && attr_spec[:transform].respond_to?(:call)
|
|
79
|
+
attr_spec[:transform].call(value_cleaned)
|
|
80
|
+
else
|
|
81
|
+
value_cleaned
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Clean value of type `any`.
|
|
86
|
+
def clean_any_value(value)
|
|
87
|
+
value
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Clean value of type `string`.
|
|
91
|
+
def clean_string_value(value)
|
|
92
|
+
value.nil? ? nil : value.to_s
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Clean value of type `uuid`.
|
|
96
|
+
def clean_uuid_value(value)
|
|
97
|
+
value.nil? ? nil : value.to_s
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Clean value of type `date`.
|
|
101
|
+
def clean_date_value(value)
|
|
102
|
+
if value.is_a?(::String)
|
|
103
|
+
return ::Date.parse(value) rescue nil
|
|
104
|
+
elsif value.is_a?(::Date)
|
|
105
|
+
return value
|
|
106
|
+
elsif value.is_a?(::Time)
|
|
107
|
+
return value.to_date
|
|
108
|
+
elsif value.is_a?(::DateTime)
|
|
109
|
+
return value.to_date
|
|
110
|
+
end
|
|
111
|
+
nil
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Clean value of type `datetime`.
|
|
115
|
+
def clean_datetime_value(value)
|
|
116
|
+
if value.is_a?(::String)
|
|
117
|
+
return ::DateTime.parse(value) rescue nil
|
|
118
|
+
elsif value.is_a?(::Date)
|
|
119
|
+
return value.to_datetime
|
|
120
|
+
elsif value.is_a?(::Time)
|
|
121
|
+
return value.to_datetime
|
|
122
|
+
elsif value.is_a?(::DateTime)
|
|
123
|
+
return value
|
|
124
|
+
end
|
|
125
|
+
nil
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Clean value of type `integer`.
|
|
129
|
+
def clean_integer_value(value)
|
|
130
|
+
value.nil? ? nil : value.to_i
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Clean value of type `float`.
|
|
134
|
+
def clean_float_value(value)
|
|
135
|
+
value.nil? ? nil : value.to_f
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Clean value of type `boolean`.
|
|
139
|
+
def clean_boolean_value(value)
|
|
140
|
+
::ActiveModel::Type::Boolean.new.cast(value)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# *********************************************************************
|
|
144
|
+
# Attribute store methods
|
|
145
|
+
# *********************************************************************
|
|
146
|
+
|
|
147
|
+
def _attrs
|
|
148
|
+
@attrs ||= {}
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def attrs(type: nil)
|
|
152
|
+
result = _attrs.values
|
|
153
|
+
return result unless type
|
|
154
|
+
if type.is_a?(::Array)
|
|
155
|
+
result.select { |a| type.include?(a[:type]) }
|
|
156
|
+
else
|
|
157
|
+
result.select { |a| a[:type] == type }
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def attr_names(type: nil)
|
|
162
|
+
attrs(type: type).map { |a| a[:attr_name] }
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def has_attr?(attr_name, type: nil)
|
|
166
|
+
find_attr(attr_name, type: type).present?
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def find_attr(attr_name, type: nil)
|
|
170
|
+
result = _attrs.fetch(attr_name.to_sym, nil)
|
|
171
|
+
return nil unless result
|
|
172
|
+
return result unless type
|
|
173
|
+
if type.is_a?(::Array)
|
|
174
|
+
return nil unless type.include?(result[:type])
|
|
175
|
+
else
|
|
176
|
+
return nil unless result[:type] == type
|
|
177
|
+
end
|
|
178
|
+
result
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def simple_attrs
|
|
182
|
+
attrs(type: SIMPLE_TYPES)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def simple_attr_names
|
|
186
|
+
attr_names(type: SIMPLE_TYPES)
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def has_simple_attr?(attr_name)
|
|
190
|
+
has_attr?(attr_name, type: SIMPLE_TYPES)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def find_simple_attr(attr_name)
|
|
194
|
+
find_attr(attr_name, type: SIMPLE_TYPES)
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def complex_attrs
|
|
198
|
+
attrs(type: COMPLEX_TYPES)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def complex_attr_names
|
|
202
|
+
attr_names(type: COMPLEX_TYPES)
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def has_complex_attr?(attr_name)
|
|
206
|
+
has_attr?(attr_name, type: COMPLEX_TYPES)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def find_complex_attr(attr_name)
|
|
210
|
+
find_attr(attr_name, type: COMPLEX_TYPES)
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# *********************************************************************
|
|
214
|
+
# Polymorphic type declaration
|
|
215
|
+
# *********************************************************************
|
|
216
|
+
|
|
217
|
+
# Declares which `type` values this class handles during polymorphic mapping.
|
|
218
|
+
# Works together with the `default:` option on `has_one_attr` / `has_many_attr`.
|
|
219
|
+
#
|
|
220
|
+
# @param types [Array<Symbol, String>] one or more type values this class owns
|
|
221
|
+
#
|
|
222
|
+
# @example
|
|
223
|
+
# class FieldDescriptor
|
|
224
|
+
# include Eluvia::ActiveModel::Attributes
|
|
225
|
+
# intended_for_types :field
|
|
226
|
+
# end
|
|
227
|
+
def intended_for_types(*types)
|
|
228
|
+
return @_intended_for_types || [] if types.empty?
|
|
229
|
+
@_intended_for_types = types.map(&:to_s)
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# *********************************************************************
|
|
233
|
+
# Mapping methods
|
|
234
|
+
# *********************************************************************
|
|
235
|
+
|
|
236
|
+
def slice(data)
|
|
237
|
+
data.slice(*self.simple_attr_names)
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def _resolve_class_name(item_value, class_name, default_class_name = nil)
|
|
241
|
+
return class_name unless class_name.is_a?(::Array)
|
|
242
|
+
|
|
243
|
+
type_value = item_value.is_a?(::Hash) ? (item_value[:type] || item_value['type']).to_s : nil
|
|
244
|
+
|
|
245
|
+
class_name.each do |candidate|
|
|
246
|
+
klass = candidate.constantize
|
|
247
|
+
return candidate if klass.intended_for_types.include?(type_value)
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
default_class_name || raise(Eluvia::Errors::StandardError.new("No class in #{class_name.inspect} handles type '#{type_value}' and no default was specified."))
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def _map_value(value, class_name)
|
|
254
|
+
if class_name == 'String'
|
|
255
|
+
clean_string_value(value)
|
|
256
|
+
elsif class_name == 'Uuid'
|
|
257
|
+
clean_uuid_value(value)
|
|
258
|
+
elsif class_name == 'Date'
|
|
259
|
+
clean_date_value(value)
|
|
260
|
+
elsif class_name == 'DateTime'
|
|
261
|
+
clean_datetime_value(value)
|
|
262
|
+
elsif class_name == 'Integer'
|
|
263
|
+
clean_integer_value(value)
|
|
264
|
+
elsif class_name == 'Float'
|
|
265
|
+
clean_float_value(value)
|
|
266
|
+
elsif class_name == 'Boolean'
|
|
267
|
+
clean_boolean_value(value)
|
|
268
|
+
else
|
|
269
|
+
class_name.constantize.map(value)
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def map(data)
|
|
274
|
+
return nil if data.blank?
|
|
275
|
+
return nil unless data.is_a?(::Hash)
|
|
276
|
+
data = data.with_indifferent_access
|
|
277
|
+
object = self.new(self.slice(data))
|
|
278
|
+
self.attrs(type: :has_one).each do |attr_spec|
|
|
279
|
+
attr_name = attr_spec[:attr_name]
|
|
280
|
+
class_name = attr_spec[:class_name]
|
|
281
|
+
polymorphic_default = attr_spec[:polymorphic_default]
|
|
282
|
+
item_value = data[attr_name]
|
|
283
|
+
next if item_value.nil?
|
|
284
|
+
resolved = _resolve_class_name(item_value, class_name, polymorphic_default)
|
|
285
|
+
object.send(:"#{attr_name}=", resolved.constantize.map(item_value))
|
|
286
|
+
end
|
|
287
|
+
self.attrs(type: :has_many).each do |attr_spec|
|
|
288
|
+
attr_name = attr_spec[:attr_name]
|
|
289
|
+
class_name = attr_spec[:class_name]
|
|
290
|
+
underlying_type = attr_spec[:underlying_type]
|
|
291
|
+
polymorphic_default = attr_spec[:polymorphic_default]
|
|
292
|
+
if underlying_type == :hash
|
|
293
|
+
combined_value = {}
|
|
294
|
+
if data[attr_name].is_a?(::Hash)
|
|
295
|
+
data[attr_name].each do |item_key, item_value|
|
|
296
|
+
combined_value[item_key] = _map_value(item_value, _resolve_class_name(item_value, class_name, polymorphic_default))
|
|
297
|
+
end
|
|
298
|
+
end
|
|
299
|
+
else
|
|
300
|
+
combined_value = []
|
|
301
|
+
if data[attr_name].is_a?(::Array)
|
|
302
|
+
data[attr_name].each do |item_value|
|
|
303
|
+
combined_value << _map_value(item_value, _resolve_class_name(item_value, class_name, polymorphic_default))
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
end
|
|
307
|
+
object.send(:"#{attr_name}=", combined_value)
|
|
308
|
+
end
|
|
309
|
+
object
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
# *********************************************************************
|
|
313
|
+
# Attribute definition methods
|
|
314
|
+
# *********************************************************************
|
|
315
|
+
|
|
316
|
+
def simple_attr(attr_name, type, transform: nil, underlying_attr_name: nil, default: nil)
|
|
317
|
+
attr_name = attr_name.to_sym
|
|
318
|
+
type = type.to_sym
|
|
319
|
+
type = :any unless SIMPLE_TYPES.include?(type)
|
|
320
|
+
underlying_attr_name = underlying_attr_name.to_sym if underlying_attr_name
|
|
321
|
+
|
|
322
|
+
# Define attribute accessors
|
|
323
|
+
attr_accessor attr_name
|
|
324
|
+
|
|
325
|
+
attr_accessor underlying_attr_name if underlying_attr_name && underlying_attr_name != attr_name
|
|
326
|
+
|
|
327
|
+
# Register metadata
|
|
328
|
+
_attrs[attr_name] = {
|
|
329
|
+
attr_name: attr_name,
|
|
330
|
+
type: type,
|
|
331
|
+
transform: transform,
|
|
332
|
+
underlying_attr_name: underlying_attr_name,
|
|
333
|
+
default: default
|
|
334
|
+
}
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
def any_attr(attr_name, **args)
|
|
338
|
+
simple_attr(attr_name, :any, **args)
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
def string_attr(attr_name, **args)
|
|
342
|
+
simple_attr(attr_name, :string, **args)
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
def uuid_attr(attr_name, **args)
|
|
346
|
+
simple_attr(attr_name, :uuid, **args)
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
def date_attr(attr_name, **args)
|
|
350
|
+
simple_attr(attr_name, :date, **args)
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
def datetime_attr(attr_name, **args)
|
|
354
|
+
simple_attr(attr_name, :datetime, **args)
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
def integer_attr(attr_name, **args)
|
|
358
|
+
simple_attr(attr_name, :integer, **args)
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
def float_attr(attr_name, **args)
|
|
362
|
+
simple_attr(attr_name, :float, **args)
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
def boolean_attr(attr_name, **args)
|
|
366
|
+
simple_attr(attr_name, :boolean, **args)
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
# Add a new "has one" attribute
|
|
370
|
+
#
|
|
371
|
+
# @param attr_name [Symbol] attribute name
|
|
372
|
+
# @param class_name [String, Array<String>] name of the class (or ordered list of candidate
|
|
373
|
+
# class names) of the related object. When an Array, class resolution calls
|
|
374
|
+
# `handles_type?(type_value)` on each candidate; the first match wins.
|
|
375
|
+
# @param default [String, nil] class name to use when no candidate matches. Required when
|
|
376
|
+
# class_name is an Array; raises an error on no-match if omitted.
|
|
377
|
+
def has_one_attr(attr_name, class_name, default: nil)
|
|
378
|
+
attr_name = attr_name.to_sym
|
|
379
|
+
|
|
380
|
+
attr_accessor attr_name
|
|
381
|
+
|
|
382
|
+
_attrs[attr_name] = {
|
|
383
|
+
attr_name: attr_name,
|
|
384
|
+
type: :has_one,
|
|
385
|
+
class_name: class_name,
|
|
386
|
+
polymorphic_default: default
|
|
387
|
+
}
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
# Add a new "has many" attribute
|
|
391
|
+
#
|
|
392
|
+
# @param attr_name [Symbol] attribute name
|
|
393
|
+
# @param class_name [String, Array<String>] name of the class (or ordered list of candidate
|
|
394
|
+
# class names) of the related objects. When an Array, class resolution calls
|
|
395
|
+
# `handles_type?(type_value)` on each candidate; the first match wins.
|
|
396
|
+
# @param underlying_type [Symbol] :array or :hash
|
|
397
|
+
# @param default [String, nil] class name to use when no candidate matches. Required when
|
|
398
|
+
# class_name is an Array; raises an error on no-match if omitted.
|
|
399
|
+
def has_many_attr(attr_name, class_name, underlying_type: :array, default: nil)
|
|
400
|
+
attr_name = attr_name.to_sym
|
|
401
|
+
|
|
402
|
+
attr_accessor attr_name
|
|
403
|
+
|
|
404
|
+
_attrs[attr_name] = {
|
|
405
|
+
attr_name: attr_name,
|
|
406
|
+
type: :has_many,
|
|
407
|
+
class_name: class_name,
|
|
408
|
+
underlying_type: underlying_type,
|
|
409
|
+
polymorphic_default: default
|
|
410
|
+
}
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
end
|
|
416
|
+
end
|
|
417
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
module Eluvia
|
|
2
|
+
module ActiveModel
|
|
3
|
+
module EnumArrayAttribute
|
|
4
|
+
extend ::ActiveSupport::Concern
|
|
5
|
+
include Eluvia::ActiveModel::ArrayAttribute
|
|
6
|
+
include Eluvia::ActiveModel::EnumAttribute
|
|
7
|
+
|
|
8
|
+
class_methods do
|
|
9
|
+
|
|
10
|
+
# *********************************************************************
|
|
11
|
+
# Attribute definition methods for enum array
|
|
12
|
+
# *********************************************************************
|
|
13
|
+
|
|
14
|
+
def enum_array_column(new_column, choices, options = {})
|
|
15
|
+
enum_array_attr(new_column, choices, **options)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Add a new "enum array" attribute to this model
|
|
19
|
+
def enum_array_attr(attr_name, choices, **options)
|
|
20
|
+
attr_name = attr_name.to_sym
|
|
21
|
+
|
|
22
|
+
# Define it using array and enum
|
|
23
|
+
array_attr(attr_name, **options) # -> this will define the setter and getter for array
|
|
24
|
+
enum_attr(attr_name, choices) # -> this will define available_xxx_values and other static functions
|
|
25
|
+
|
|
26
|
+
# *******************************************************************
|
|
27
|
+
# Dynamically defined instance methods for enum array
|
|
28
|
+
# *******************************************************************
|
|
29
|
+
|
|
30
|
+
# Get choices objects
|
|
31
|
+
define_method(:"#{attr_name}_objs") do
|
|
32
|
+
values = self.send(attr_name)
|
|
33
|
+
return nil if values.blank?
|
|
34
|
+
result = []
|
|
35
|
+
values.each do |value|
|
|
36
|
+
obj = self.class.find_enum_attr(attr_name)&.fetch(value.to_s, nil)
|
|
37
|
+
result << obj if obj
|
|
38
|
+
end
|
|
39
|
+
if result.empty?
|
|
40
|
+
nil
|
|
41
|
+
else
|
|
42
|
+
result
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Get labels
|
|
47
|
+
define_method(:"#{attr_name}_labels") do
|
|
48
|
+
self.send(:"#{attr_name}_objs")&.map { |obj| obj[:label] }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Get colors
|
|
52
|
+
define_method(:"#{attr_name}_colors") do
|
|
53
|
+
self.send(:"#{attr_name}_objs")&.map { |obj| obj[:color] }
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
module Eluvia
|
|
2
|
+
module ActiveModel
|
|
3
|
+
module EnumAttribute
|
|
4
|
+
extend ::ActiveSupport::Concern
|
|
5
|
+
include Eluvia::ActiveModel::Attributes
|
|
6
|
+
|
|
7
|
+
class_methods do
|
|
8
|
+
|
|
9
|
+
# *********************************************************************
|
|
10
|
+
# Attribute store methods
|
|
11
|
+
# *********************************************************************
|
|
12
|
+
|
|
13
|
+
def _enum_attrs
|
|
14
|
+
@enum_attrs ||= {}
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def enum_attrs
|
|
18
|
+
_enum_attrs.values
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def enum_attr_names
|
|
22
|
+
enum_attrs.map { |a| a[:attr_name] }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def has_enum_attr?(attr_name)
|
|
26
|
+
_enum_attrs.key?(attr_name.to_sym)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def find_enum_attr(attr_name)
|
|
30
|
+
_enum_attrs.fetch(attr_name.to_sym, nil)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# *********************************************************************
|
|
34
|
+
# Attribute definition methods
|
|
35
|
+
# *********************************************************************
|
|
36
|
+
|
|
37
|
+
# Backward compatibility
|
|
38
|
+
def enum_column(attr_name, choices, options = {})
|
|
39
|
+
enum_attr(attr_name, choices, **options)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Adds new "enum" attribute to this model
|
|
43
|
+
#
|
|
44
|
+
# @param attr_name [Symbol] Attribute name
|
|
45
|
+
# @param choices [Array<Hash, Object>] Array of choice definitions or values
|
|
46
|
+
# @param default [Object] Default value
|
|
47
|
+
# @param declare [Boolean] Whether to declare the underlying attribute as string
|
|
48
|
+
def enum_attr(attr_name, choices, default: nil, declare: false)
|
|
49
|
+
attr_name = attr_name.to_sym
|
|
50
|
+
|
|
51
|
+
# Declare underlying attribute
|
|
52
|
+
if declare
|
|
53
|
+
string_attr attr_name
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Input check
|
|
57
|
+
raise Eluvia::Errors::StandardError.new('Please specify enum choices as an array.') unless choices.is_a?(::Array)
|
|
58
|
+
|
|
59
|
+
# Prepare and fill out the internal structure
|
|
60
|
+
_enum_attrs[attr_name] = {}
|
|
61
|
+
choices.each do |choice|
|
|
62
|
+
|
|
63
|
+
# Value check
|
|
64
|
+
if choice.is_a?(::Hash)
|
|
65
|
+
# OK
|
|
66
|
+
else
|
|
67
|
+
choice = { value: choice }
|
|
68
|
+
end
|
|
69
|
+
raise Eluvia::Errors::StandardError.new('Enum definition cannot be empty.') if choice[:value].nil? || choice[:value].to_s.empty?
|
|
70
|
+
|
|
71
|
+
# Identify underlying type
|
|
72
|
+
if choice[:value].is_a?(::Integer)
|
|
73
|
+
choice[:underlying_type] = :integer
|
|
74
|
+
elsif choice[:value].is_a?(::TrueClass) || choice[:value].is_a?(::FalseClass)
|
|
75
|
+
choice[:underlying_type] = :boolean
|
|
76
|
+
else
|
|
77
|
+
choice[:underlying_type] = :string
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Retype value to string
|
|
81
|
+
choice[:value] = choice[:value].to_s
|
|
82
|
+
|
|
83
|
+
# Label
|
|
84
|
+
unless choice[:label]
|
|
85
|
+
choice[:label] = I18n.t("#{i18n_scope}.attributes.#{model_name.i18n_key}.#{attr_name}_values.#{"#{choice[:underlying_type]}_" if choice[:underlying_type] != :string}#{choice[:value]}", default: choice[:value])
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Color
|
|
89
|
+
unless choice[:color]
|
|
90
|
+
choice[:color] = I18n.t("#{i18n_scope}.attributes.#{model_name.i18n_key}.#{attr_name}_colors.#{"#{choice[:underlying_type]}_" if choice[:underlying_type] != :string}#{choice[:value]}", default: 'inherit')
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Store
|
|
94
|
+
_enum_attrs[attr_name][choice[:value]] = choice
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# *******************************************************************
|
|
98
|
+
# Dynamically defined instance methods
|
|
99
|
+
# *******************************************************************
|
|
100
|
+
|
|
101
|
+
# Define new attribute for choice object
|
|
102
|
+
obj_attr_name = :"#{attr_name}_obj"
|
|
103
|
+
any_attr(obj_attr_name)
|
|
104
|
+
|
|
105
|
+
# Get a choice object
|
|
106
|
+
define_method(obj_attr_name) do
|
|
107
|
+
value = self.send(attr_name)
|
|
108
|
+
self.class.find_enum_attr(attr_name)&.fetch(value.to_s, nil)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Define new attribute for label
|
|
112
|
+
label_attr_name = :"#{attr_name}_label"
|
|
113
|
+
string_attr(label_attr_name)
|
|
114
|
+
|
|
115
|
+
# Get label
|
|
116
|
+
define_method(label_attr_name) do
|
|
117
|
+
value = self.send(attr_name)
|
|
118
|
+
self.class.find_enum_attr(attr_name)&.fetch(value.to_s, nil)&.fetch(:label, nil)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Define new attribute for color
|
|
122
|
+
color_attr_name = :"#{attr_name}_color"
|
|
123
|
+
string_attr(color_attr_name)
|
|
124
|
+
|
|
125
|
+
# Get color
|
|
126
|
+
define_method(color_attr_name) do
|
|
127
|
+
value = self.send(attr_name)
|
|
128
|
+
self.class.find_enum_attr(attr_name)&.fetch(value.to_s, nil)&.fetch(:color, nil)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# *******************************************************************
|
|
132
|
+
# Dynamically defined class methods
|
|
133
|
+
# *******************************************************************
|
|
134
|
+
|
|
135
|
+
# Get all choice objects
|
|
136
|
+
define_singleton_method(:"available_#{attr_name.to_s.pluralize}") do
|
|
137
|
+
find_enum_attr(attr_name)&.values
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Get all values
|
|
141
|
+
define_singleton_method(:"available_#{attr_name}_values") do
|
|
142
|
+
find_enum_attr(attr_name)&.values&.map { |item| item[:value].to_sym }
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Get label - static method
|
|
146
|
+
define_singleton_method(:"#{attr_name}_label") do |value|
|
|
147
|
+
find_enum_attr(attr_name)&.fetch(value.to_s, nil)&.fetch(:label, nil)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Get color - static method
|
|
151
|
+
define_singleton_method(:"#{attr_name}_color") do |value|
|
|
152
|
+
find_enum_attr(attr_name)&.fetch(value.to_s, nil)&.fetch(:color, nil)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# *******************************************************************
|
|
156
|
+
# Other hooks
|
|
157
|
+
# *******************************************************************
|
|
158
|
+
|
|
159
|
+
# Default value
|
|
160
|
+
if default
|
|
161
|
+
before_validation do
|
|
162
|
+
if self.send(attr_name).nil?
|
|
163
|
+
self.send("#{attr_name}=", default.to_s)
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|