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,143 @@
|
|
|
1
|
+
module Eluvia
|
|
2
|
+
module ActiveModel
|
|
3
|
+
module ObjectAttribute
|
|
4
|
+
extend ::ActiveSupport::Concern
|
|
5
|
+
include Eluvia::ActiveModel::Attributes
|
|
6
|
+
|
|
7
|
+
class_methods do
|
|
8
|
+
|
|
9
|
+
# *********************************************************************
|
|
10
|
+
# Clean and transform value methods
|
|
11
|
+
# *********************************************************************
|
|
12
|
+
|
|
13
|
+
def clean_object_value(value, attr_name)
|
|
14
|
+
return nil if value.nil?
|
|
15
|
+
attr_spec = self.find_object_attr(attr_name)
|
|
16
|
+
raise Eluvia::Errors::StandardError.new("Cannot clean value of unknow attribute `#{attr_name}`.") unless attr_spec
|
|
17
|
+
raise Eluvia::Errors::StandardError.new("Invalid object value format: #{value.inspect}") unless value.is_a?(::Hash)
|
|
18
|
+
|
|
19
|
+
# Convert all keys to symbols for easier access
|
|
20
|
+
value = value.symbolize_keys
|
|
21
|
+
|
|
22
|
+
# Iterate fields and extract values
|
|
23
|
+
cleaned_value = {}
|
|
24
|
+
attr_spec[:fields].each do |field|
|
|
25
|
+
key = field.keys.first.to_sym
|
|
26
|
+
cleaned_value[key] = value.key?(key) ? value[key] : nil
|
|
27
|
+
end
|
|
28
|
+
cleaned_value
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# *********************************************************************
|
|
32
|
+
# Attribute store methods
|
|
33
|
+
# *********************************************************************
|
|
34
|
+
|
|
35
|
+
def _object_attrs
|
|
36
|
+
@object_attrs ||= {}
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def object_attrs
|
|
40
|
+
_object_attrs.values
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def object_attr_names
|
|
44
|
+
object_attrs.map { |a| a[:attr_name] }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def has_object_attr?(attr_name)
|
|
48
|
+
_object_attrs.key?(attr_name.to_sym)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def find_object_attr(attr_name)
|
|
52
|
+
_object_attrs.fetch(attr_name.to_sym, nil)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# *********************************************************************
|
|
56
|
+
# Attribute definition methods for object
|
|
57
|
+
# *********************************************************************
|
|
58
|
+
|
|
59
|
+
# Add a new "object" attribute to this model
|
|
60
|
+
#
|
|
61
|
+
# @param attr_name [Symbol] Attribute name
|
|
62
|
+
# @param fields [Array<Symbol,String,Hash>] Underlying attribute names or mapping
|
|
63
|
+
# @param declare [Boolean] Whether to declare the underlying attribute
|
|
64
|
+
def object_attr(attr_name, fields, declare: false)
|
|
65
|
+
attr_name = attr_name.to_sym
|
|
66
|
+
|
|
67
|
+
# Declare underlying attribute
|
|
68
|
+
if declare
|
|
69
|
+
any_attr attr_name
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Check fields input
|
|
73
|
+
raise Eluvia::Errors::StandardError.new('Please specify object fields as an array.') unless fields.is_a?(::Array)
|
|
74
|
+
fields = fields.map do |field|
|
|
75
|
+
if field.is_a?(::Hash)
|
|
76
|
+
key = field.keys.first.to_sym
|
|
77
|
+
underlying_attr_name = field.values.first.to_sym
|
|
78
|
+
elsif field.is_a?(::Symbol) || field.is_a?(::String)
|
|
79
|
+
key = underlying_attr_name = field.to_sym
|
|
80
|
+
end
|
|
81
|
+
{ key => underlying_attr_name }
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Register metadata
|
|
85
|
+
self._object_attrs[attr_name] = {
|
|
86
|
+
attr_name: attr_name,
|
|
87
|
+
fields: fields
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
# *******************************************************************
|
|
91
|
+
# Dynamically defined instance methods for object
|
|
92
|
+
# *******************************************************************
|
|
93
|
+
|
|
94
|
+
# Set method
|
|
95
|
+
define_method(:"#{attr_name}=") do |value|
|
|
96
|
+
|
|
97
|
+
# Convert string to object, we expect JSON string here
|
|
98
|
+
if value.is_a?(::String)
|
|
99
|
+
if !value.blank?
|
|
100
|
+
value = JSON.parse(value) rescue nil
|
|
101
|
+
else
|
|
102
|
+
value = nil
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Check input
|
|
107
|
+
raise Eluvia::Errors::BadRequest.new("Invalid value for object attribute `#{attr_name}`, expecting Hash or nil.") unless value.nil? || value.is_a?(::Hash)
|
|
108
|
+
|
|
109
|
+
# Handle blank value
|
|
110
|
+
if value.blank?
|
|
111
|
+
return
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Set values to underlying attributes if defined as fields
|
|
115
|
+
symbolized_value = value.symbolize_keys
|
|
116
|
+
fields.each do |field|
|
|
117
|
+
key = field.keys.first.to_sym
|
|
118
|
+
underlying_attr_name = field.values.first.to_sym
|
|
119
|
+
if symbolized_value.key?(key)
|
|
120
|
+
self.send(:"#{underlying_attr_name}=", symbolized_value[key])
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Get method
|
|
126
|
+
define_method(attr_name) do
|
|
127
|
+
|
|
128
|
+
# Set values of underlying attributes if defined as fields
|
|
129
|
+
cleaned_value = {}
|
|
130
|
+
fields.each do |field|
|
|
131
|
+
key = field.keys.first.to_sym
|
|
132
|
+
underlying_attr_name = field.values.first.to_sym
|
|
133
|
+
cleaned_value[key] = self.send(underlying_attr_name)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
cleaned_value
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
module Eluvia
|
|
2
|
+
module ActiveModel
|
|
3
|
+
module RangeAttribute
|
|
4
|
+
extend ::ActiveSupport::Concern
|
|
5
|
+
include Eluvia::ActiveModel::ObjectAttribute
|
|
6
|
+
|
|
7
|
+
class_methods do
|
|
8
|
+
|
|
9
|
+
# *********************************************************************
|
|
10
|
+
# Clean and transform value methods
|
|
11
|
+
# *********************************************************************
|
|
12
|
+
|
|
13
|
+
def clean_range_value(value, attr_name)
|
|
14
|
+
clean_object_value(value, attr_name)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# *********************************************************************
|
|
18
|
+
# Attribute store methods
|
|
19
|
+
# *********************************************************************
|
|
20
|
+
|
|
21
|
+
def _range_attrs
|
|
22
|
+
@range_attrs ||= {}
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def range_attrs
|
|
26
|
+
_range_attrs.values
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def range_attr_names
|
|
30
|
+
range_attrs.map { |a| a[:attr_name] }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def has_range_attr?(attr_name)
|
|
34
|
+
_range_attrs.key?(attr_name.to_sym)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def find_range_attr(attr_name)
|
|
38
|
+
_range_attrs.fetch(attr_name.to_sym, nil)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# *********************************************************************
|
|
42
|
+
# Attribute definition methods for range
|
|
43
|
+
# *********************************************************************
|
|
44
|
+
|
|
45
|
+
# Add a new "range" attribute to this model
|
|
46
|
+
#
|
|
47
|
+
# @param attr_name [Symbol] Attribute name
|
|
48
|
+
# @param lower_attr_name [Symbol] Lower bound attribute name
|
|
49
|
+
# @param upper_attr_name [Symbol] Upper bound attribute name
|
|
50
|
+
# @param declare [Boolean] Whether to declare the underlying attribute as any
|
|
51
|
+
def range_attr(attr_name, lower_attr_name, upper_attr_name, declare: false)
|
|
52
|
+
attr_name = attr_name.to_sym
|
|
53
|
+
lower_attr_name = lower_attr_name.to_sym
|
|
54
|
+
upper_attr_name = upper_attr_name.to_sym
|
|
55
|
+
|
|
56
|
+
# Range is an object with lower and upper attributes
|
|
57
|
+
object_attr(attr_name,
|
|
58
|
+
[
|
|
59
|
+
{ lower: lower_attr_name },
|
|
60
|
+
{ upper: upper_attr_name }
|
|
61
|
+
], declare: declare)
|
|
62
|
+
|
|
63
|
+
# Register metadata
|
|
64
|
+
self._range_attrs[attr_name] = {
|
|
65
|
+
attr_name: attr_name,
|
|
66
|
+
lower_attr_name: lower_attr_name,
|
|
67
|
+
upper_attr_name: upper_attr_name
|
|
68
|
+
}
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
module Eluvia
|
|
2
|
+
module ActiveRecord
|
|
3
|
+
module FileArrayAttribute
|
|
4
|
+
extend ::ActiveSupport::Concern
|
|
5
|
+
include Eluvia::ActiveModel::ArrayAttribute
|
|
6
|
+
|
|
7
|
+
class_methods do
|
|
8
|
+
|
|
9
|
+
# *********************************************************************
|
|
10
|
+
# Attribute definition methods for file array
|
|
11
|
+
# *********************************************************************
|
|
12
|
+
|
|
13
|
+
# Add a new "file array" attribute to this model
|
|
14
|
+
#
|
|
15
|
+
# @param attr_name [Symbol] Attribute name
|
|
16
|
+
# @param underlying_attachments [Symbol, nil] Name of the attribute holding underlying Active Storage attachments
|
|
17
|
+
# @param underlying_filter [Proc, nil] Optional filter to be applied to scope underlying attachments
|
|
18
|
+
# @param nature [Symbol] Nature of file attribute (:file, :image, etc.)
|
|
19
|
+
# @param declare [Boolean] Whether to declare the underlying attribute
|
|
20
|
+
def file_array_attr(attr_name, underlying_attachments: nil, underlying_filter: nil, nature: :file, declare: false)
|
|
21
|
+
attr_name = attr_name.to_sym
|
|
22
|
+
nature = nature.to_sym
|
|
23
|
+
|
|
24
|
+
# Define it using "has many" attribute
|
|
25
|
+
if declare
|
|
26
|
+
has_many_attr(attr_name, nature == :image ? 'Eluvia::ImageWrapper' : 'Eluvia::FileWrapper')
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# *******************************************************************
|
|
30
|
+
# Dynamically defined instance methods for file array
|
|
31
|
+
# *******************************************************************
|
|
32
|
+
|
|
33
|
+
if underlying_attachments
|
|
34
|
+
|
|
35
|
+
# Set method
|
|
36
|
+
define_method(:"#{attr_name}=") do |new_values|
|
|
37
|
+
|
|
38
|
+
# Check input
|
|
39
|
+
raise Eluvia::Errors::BadRequest.new("Invalid value for attribute `#{attr_name}`, expecting Array.") unless new_values.is_a?(::Array)
|
|
40
|
+
|
|
41
|
+
# Current values
|
|
42
|
+
current_values = self.send(attr_name)
|
|
43
|
+
current_ids = current_values.map(&:id)
|
|
44
|
+
new_ids = []
|
|
45
|
+
|
|
46
|
+
# Create missing attachments
|
|
47
|
+
new_values.each do |value|
|
|
48
|
+
# Normalize and check value
|
|
49
|
+
if value.is_a?(::Hash)
|
|
50
|
+
if nature == :image
|
|
51
|
+
value = Eluvia::ImageWrapper.map(value)
|
|
52
|
+
else
|
|
53
|
+
value = Eluvia::FileWrapper.map(value)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
raise Eluvia::Errors::BadRequest.new("Invalid value for attribute `#{attr_name}[]`, expecting Eluvia::FileWrapper or Eluvia::ImageWrapper.") unless value.is_a?(Eluvia::FileWrapper) || value.is_a?(Eluvia::ImageWrapper)
|
|
57
|
+
raise Eluvia::Errors::BadRequest.new("Invalid value for attribute `#{attr_name}[].file`, can't be blank.") if value.file.blank?
|
|
58
|
+
|
|
59
|
+
# Keep track of new ids
|
|
60
|
+
new_ids << value.id if value.id
|
|
61
|
+
|
|
62
|
+
# Retrieve filename and content
|
|
63
|
+
filename = value.file&.filename
|
|
64
|
+
content = value.file&.content
|
|
65
|
+
|
|
66
|
+
# Sanitize filename
|
|
67
|
+
filename = ActiveStorage::Filename.new(filename).sanitized
|
|
68
|
+
|
|
69
|
+
# Create or update attachment only if both filename and content are passed (this means that user uploaded a new file)
|
|
70
|
+
if !filename.blank? && !content.blank?
|
|
71
|
+
|
|
72
|
+
# Delete the existing attachment
|
|
73
|
+
if value.id && current_ids.include?(value.id)
|
|
74
|
+
attachment_record = self.send("#{underlying_attachments}_attachments").find(value.id)
|
|
75
|
+
attachment_record.purge_later
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Create a new attachment
|
|
79
|
+
self.send(underlying_attachments).attach(io: StringIO.new(Base64.decode64(content)), filename: filename)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Delete no longer wanted attachments
|
|
85
|
+
current_values.each do |value|
|
|
86
|
+
unless new_ids.include?(value.id)
|
|
87
|
+
attachment_record = self.send("#{underlying_attachments}_attachments").find(value.id)
|
|
88
|
+
attachment_record.purge_later
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Get method
|
|
94
|
+
define_method(attr_name) do
|
|
95
|
+
# must be sorted in memory and not in SQL in order to support prefetch via "includes"
|
|
96
|
+
attachments_records = self.send("#{underlying_attachments}_attachments").to_a.sort_by(&:created_at)
|
|
97
|
+
if underlying_filter && underlying_filter.respond_to?(:call)
|
|
98
|
+
attachments_records = attachments_records.select { |attachment_record| underlying_filter.call(attachment_record) }
|
|
99
|
+
end
|
|
100
|
+
attachments_records.map do |attachment|
|
|
101
|
+
if nature == :image
|
|
102
|
+
Eluvia::ImageWrapper.new(id: attachment.id, file: Eluvia::File.from_active_storage_attachment(attachment, skip_check: true))
|
|
103
|
+
else
|
|
104
|
+
Eluvia::FileWrapper.new(id: attachment.id, file: Eluvia::File.from_active_storage_attachment(attachment, skip_check: true))
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
module Eluvia
|
|
2
|
+
module ActiveRecord
|
|
3
|
+
module FileAttribute
|
|
4
|
+
extend ::ActiveSupport::Concern
|
|
5
|
+
include Eluvia::ActiveModel::Attributes
|
|
6
|
+
|
|
7
|
+
class_methods do
|
|
8
|
+
|
|
9
|
+
# *********************************************************************
|
|
10
|
+
# Attribute store methods
|
|
11
|
+
# *********************************************************************
|
|
12
|
+
|
|
13
|
+
def _file_attrs
|
|
14
|
+
@file_attrs ||= {}
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def file_attrs
|
|
18
|
+
_file_attrs.values
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def file_attr_names
|
|
22
|
+
file_attrs.map { |a| a[:attr_name] }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def has_file_attr?(attr_name)
|
|
26
|
+
_file_attrs.key?(attr_name.to_sym)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def find_file_attr(attr_name)
|
|
30
|
+
_file_attrs.fetch(attr_name.to_sym, nil)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# *********************************************************************
|
|
34
|
+
# Attribute definition methods for file
|
|
35
|
+
# *********************************************************************
|
|
36
|
+
|
|
37
|
+
# Add a new "file" attribute to this model
|
|
38
|
+
#
|
|
39
|
+
# @param attr_name [Symbol] Attribute name
|
|
40
|
+
# @param underlying_attachment [Symbol, nil] Name of the attribute holding underlying Active Storage attachment
|
|
41
|
+
# @param nature [Symbol] Nature of file attribute (:file, :image, etc.)
|
|
42
|
+
# @param declare [Boolean] Whether to declare the underlying attribute
|
|
43
|
+
def file_attr(attr_name, underlying_attachment: nil, nature: :file, declare: false)
|
|
44
|
+
attr_name = attr_name.to_sym
|
|
45
|
+
nature = nature.to_sym
|
|
46
|
+
|
|
47
|
+
# Declare underlying "has one" attribute
|
|
48
|
+
if declare
|
|
49
|
+
has_one_attr(attr_name, 'Eluvia::File')
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Register metadata
|
|
53
|
+
self._file_attrs[attr_name] = {
|
|
54
|
+
attr_name: attr_name,
|
|
55
|
+
underlying_attachment: underlying_attachment,
|
|
56
|
+
nature: nature
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
# *******************************************************************
|
|
60
|
+
# Dynamically defined instance methods for file
|
|
61
|
+
# *******************************************************************
|
|
62
|
+
|
|
63
|
+
if underlying_attachment
|
|
64
|
+
|
|
65
|
+
# Set method
|
|
66
|
+
define_method(:"#{attr_name}=") do |value|
|
|
67
|
+
# Delete the existing attachment if requested
|
|
68
|
+
if value.nil?
|
|
69
|
+
attachment_record = self.send(underlying_attachment)
|
|
70
|
+
attachment_record.purge_later if attachment_record.attached?
|
|
71
|
+
return
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Normalize and check value
|
|
75
|
+
value = Eluvia::File.map(value) if value.is_a?(::Hash)
|
|
76
|
+
raise Eluvia::Errors::BadRequest.new("Invalid value for file attribute `#{attr_name}`, expecting Eluvia::File.") unless value.is_a?(Eluvia::File)
|
|
77
|
+
|
|
78
|
+
# Retrieve and check filename - for a chunked/direct upload the frontend only sends
|
|
79
|
+
# `upload_key` (see habarico's `uploadFileChunked`), not a separate filename; the key
|
|
80
|
+
# itself already encodes the original filename as its last path segment (providers
|
|
81
|
+
# build it as `<tmp_prefix><uuid>/<filename>`), so fall back to that instead of
|
|
82
|
+
# requiring it to be sent twice.
|
|
83
|
+
filename = value.filename
|
|
84
|
+
filename = ::File.basename(value.upload_key) if filename.blank? && value.upload_key.present?
|
|
85
|
+
raise Eluvia::Errors::BadRequest.new("Expected `filename` in the attribute `#{attr_name}`.") if filename.blank?
|
|
86
|
+
|
|
87
|
+
# Sanitize filename
|
|
88
|
+
filename = ActiveStorage::Filename.new(filename).sanitized
|
|
89
|
+
|
|
90
|
+
# Retrieve the underlying attribute
|
|
91
|
+
attachment_record = self.send(underlying_attachment)
|
|
92
|
+
|
|
93
|
+
if value.upload_key.present?
|
|
94
|
+
# Finalize a previously uploaded (chunked/direct) temp object via the registered finalizer
|
|
95
|
+
finalizer = Eluvia::Uploads.finalizer
|
|
96
|
+
raise Eluvia::Errors::StandardError.new("No `Eluvia::Uploads.finalizer` registered, cannot finalize upload for attribute `#{attr_name}`.") unless finalizer.respond_to?(:call)
|
|
97
|
+
finalizer.call(attachment_record, value.upload_key, filename)
|
|
98
|
+
else
|
|
99
|
+
# Attach base64 encoded content directly
|
|
100
|
+
content = value.content
|
|
101
|
+
raise Eluvia::Errors::BadRequest.new("Expected `filename` and `content` in the attribute `#{attr_name}`.") if content.blank?
|
|
102
|
+
attachment_record.attach(
|
|
103
|
+
io: StringIO.new(Base64.decode64(content)),
|
|
104
|
+
filename: filename,
|
|
105
|
+
)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Get method
|
|
110
|
+
define_method(attr_name) do
|
|
111
|
+
# Read value from underlying attribute
|
|
112
|
+
attachment = self.send(underlying_attachment)
|
|
113
|
+
|
|
114
|
+
# Handle blank value or not attached
|
|
115
|
+
return nil unless attachment&.attached?
|
|
116
|
+
|
|
117
|
+
# Convert to standard format
|
|
118
|
+
Eluvia::File.from_active_storage_attachment(attachment, skip_check: true)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
else
|
|
122
|
+
|
|
123
|
+
# Set method
|
|
124
|
+
define_method(:"#{attr_name}=") do |value|
|
|
125
|
+
# Normalize value
|
|
126
|
+
value = Eluvia::File.map(value) if value.is_a?(::Hash)
|
|
127
|
+
|
|
128
|
+
# Check input
|
|
129
|
+
raise Eluvia::Errors::BadRequest.new("Invalid value for file attribute `#{attr_name}`, expecting Eluvia::File.") unless value.is_a?(Eluvia::File)
|
|
130
|
+
|
|
131
|
+
# Store to the underlying attribute
|
|
132
|
+
write_attribute(attr_name, value)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Get method
|
|
136
|
+
define_method(attr_name) do
|
|
137
|
+
# Read value from underlying attribute
|
|
138
|
+
value = read_attribute(attr_name)
|
|
139
|
+
|
|
140
|
+
# Handle blank value
|
|
141
|
+
return nil if value.blank?
|
|
142
|
+
|
|
143
|
+
# Normalize value
|
|
144
|
+
value = Eluvia::File.map(value) if value.is_a?(::Hash)
|
|
145
|
+
|
|
146
|
+
# Check output
|
|
147
|
+
raise Eluvia::Errors::StandardError.new("Invalid value for object attribute `#{attr_name}`, expecting Eluvia::File.") unless value.is_a?(Eluvia::File)
|
|
148
|
+
|
|
149
|
+
value
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|