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.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +225 -0
  4. data/lib/eluvia/active_model/amount_attribute.rb +108 -0
  5. data/lib/eluvia/active_model/array_attribute.rb +134 -0
  6. data/lib/eluvia/active_model/attributes.rb +417 -0
  7. data/lib/eluvia/active_model/enum_array_attribute.rb +60 -0
  8. data/lib/eluvia/active_model/enum_attribute.rb +174 -0
  9. data/lib/eluvia/active_model/object_attribute.rb +143 -0
  10. data/lib/eluvia/active_model/range_attribute.rb +74 -0
  11. data/lib/eluvia/active_record/file_array_attribute.rb +116 -0
  12. data/lib/eluvia/active_record/file_attribute.rb +158 -0
  13. data/lib/eluvia/active_record/filtering.rb +680 -0
  14. data/lib/eluvia/active_record/filters_model.rb +58 -0
  15. data/lib/eluvia/active_record/ordering.rb +110 -0
  16. data/lib/eluvia/base/config.rb +38 -0
  17. data/lib/eluvia/base/version.rb +12 -0
  18. data/lib/eluvia/errors/bad_request.rb +17 -0
  19. data/lib/eluvia/errors/forbidden.rb +17 -0
  20. data/lib/eluvia/errors/not_found.rb +17 -0
  21. data/lib/eluvia/errors/service_unavailable.rb +17 -0
  22. data/lib/eluvia/errors/standard_error.rb +33 -0
  23. data/lib/eluvia/errors/unauthorized.rb +17 -0
  24. data/lib/eluvia/errors/unprocessable_entity.rb +26 -0
  25. data/lib/eluvia/fieldset/rest_field.rb +83 -0
  26. data/lib/eluvia/fieldset/rest_fieldset.rb +158 -0
  27. data/lib/eluvia/fieldset.rb +12 -0
  28. data/lib/eluvia/handlers/error_handler.rb +60 -0
  29. data/lib/eluvia/handlers/pagination_handler.rb +25 -0
  30. data/lib/eluvia/handlers/params_handler.rb +43 -0
  31. data/lib/eluvia/helpers/attachment_helper.rb +37 -0
  32. data/lib/eluvia/integrations/eluvia_integration.rb +281 -0
  33. data/lib/eluvia/models/file.rb +44 -0
  34. data/lib/eluvia/models/file_wrapper.rb +38 -0
  35. data/lib/eluvia/models/image_wrapper.rb +38 -0
  36. data/lib/eluvia/serializers/error_serializer.rb +21 -0
  37. data/lib/eluvia/serializers/fieldset_serializer.rb +63 -0
  38. data/lib/eluvia/services/health_service.rb +56 -0
  39. data/lib/eluvia/uploads.rb +13 -0
  40. data/lib/eluvia/utils/hash_and_array.rb +98 -0
  41. data/lib/eluvia/utils/jwt.rb +36 -0
  42. data/lib/eluvia/utils/string.rb +31 -0
  43. data/lib/eluvia/utils/uuid.rb +21 -0
  44. data/lib/eluvia-base.rb +79 -0
  45. 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