eluvia-base 3.39.0 → 3.41.0
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 +4 -4
- data/CHANGELOG.md +23 -0
- data/README.md +47 -1
- data/lib/eluvia/active_record/env_filterable.rb +110 -0
- data/lib/eluvia/active_record/file_array_attribute.rb +118 -45
- data/lib/eluvia-base.rb +1 -0
- metadata +17 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8d7c48fd8f44bcda78e3e702b68e2d1fc1da1f16c5c5231f9243c1a82c3132ca
|
|
4
|
+
data.tar.gz: 9eba98e2363832aea7e3015cfdc9312b7fb88fccae0d45def95cb34b8606a0ef
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: eb92181c8003d8da9d2647734357877fc8e75aed3ae362ccd8fe91dad0da757f28648fbe1fffd9fcadb367ae97b5884670ae202847a5bf8842730b45b5355438
|
|
7
|
+
data.tar.gz: c6aa908b43d905ba1080d12b5c6c2cbeda73917c00a58ae45fbd3ef3c9ddf293ab87726036945ff27fce510dbac61d4ced691a1341d2997babeaa4c0c2e6b0d6
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,29 @@ All notable changes to this project are documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [3.41.0] — 2026-10-27
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- `Eluvia::ActiveRecord::FileArrayAttribute` registers each `file_array_attr` like `file_attr`
|
|
12
|
+
does: `file_array_attrs`, `file_array_attr_names`, `has_file_array_attr?` and
|
|
13
|
+
`find_file_array_attr` return its `underlying_attachments`, `underlying_filter`, `nature` and
|
|
14
|
+
`append_only` (HAB-991).
|
|
15
|
+
- `append_only:` option on `file_array_attr`: the setter takes a list of flat
|
|
16
|
+
`{filename, content}` files, attaches each one with content, never removes an existing
|
|
17
|
+
attachment, and ignores `nil`. Requires `underlying_attachments` (HAB-991).
|
|
18
|
+
|
|
19
|
+
## [3.40.0] — 2026-09-23
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
- `Eluvia::ActiveRecord::EnvFilterable` — a generic, model-agnostic filter driven by deploy-time
|
|
23
|
+
config (typically a base64-encoded JSON ENV var). `env_filter_fields` declares the column
|
|
24
|
+
whitelist (the only part ever interpolated into SQL; all values stay bound parameters),
|
|
25
|
+
`parse_env_filter_config` decodes the base64 JSON, and `apply_env_filter(scope, config)` applies
|
|
26
|
+
the AND-combined `field`/`operator`/`value` conditions to a scope. Operators: `eq`, `not_eq`,
|
|
27
|
+
`in`, `not_in`, `like`, `not_like` (case-insensitive substring; negative operators are
|
|
28
|
+
NULL-safe). Extracted from the payments service so services need only declare their whitelist
|
|
29
|
+
(VD-2811).
|
|
30
|
+
|
|
8
31
|
## [3.39.0] — 2026-09-18
|
|
9
32
|
|
|
10
33
|
### Added
|
data/README.md
CHANGED
|
@@ -276,7 +276,53 @@ end
|
|
|
276
276
|
If `upload_key` is present on the assigned `Eluvia::File` but no finalizer is registered, a
|
|
277
277
|
`Eluvia::Errors::StandardError` is raised.
|
|
278
278
|
|
|
279
|
-
### 2.7.
|
|
279
|
+
### 2.7. ENV-driven scope restriction
|
|
280
|
+
|
|
281
|
+
`Eluvia::ActiveRecord::EnvFilterable` applies a deploy-time restriction — typically a base64-encoded
|
|
282
|
+
JSON blob in an ENV var — to any scope. It is generic and model-agnostic: a model only declares which
|
|
283
|
+
columns the config may reference, and the config lists `field` / `operator` / `value` conditions that
|
|
284
|
+
are AND-combined. Only declared fields are ever interpolated into SQL (as a column name); every value
|
|
285
|
+
stays a bound parameter, so the config cannot inject SQL. Conditions with an unknown field or operator
|
|
286
|
+
are silently ignored.
|
|
287
|
+
|
|
288
|
+
```ruby
|
|
289
|
+
|
|
290
|
+
class BankTransaction < ApplicationRecord
|
|
291
|
+
include Eluvia::ActiveRecord::EnvFilterable
|
|
292
|
+
|
|
293
|
+
# Only these columns may appear in a filter config.
|
|
294
|
+
env_filter_fields %i[id direction variable_symbol date amount is_hidden]
|
|
295
|
+
end
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
Read and apply the config (e.g. from a Pundit `policy_scope`):
|
|
299
|
+
|
|
300
|
+
```ruby
|
|
301
|
+
|
|
302
|
+
config = BankTransaction.parse_env_filter_config(ENV['ADMIN_BANK_TRANSACTIONS_FILTER_B64'])
|
|
303
|
+
scope = BankTransaction.apply_env_filter(BankTransaction.all, config)
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
`parse_env_filter_config` returns `nil` for a blank/missing ENV var (which disables the filter) and
|
|
307
|
+
raises `ArgumentError` on malformed input, so a broken deploy config fails loudly instead of silently
|
|
308
|
+
letting everything through. The config shape is:
|
|
309
|
+
|
|
310
|
+
```json
|
|
311
|
+
{
|
|
312
|
+
"conditions": [
|
|
313
|
+
{ "field": "direction", "operator": "eq", "value": "in" },
|
|
314
|
+
{ "field": "is_hidden", "operator": "eq", "value": false },
|
|
315
|
+
{ "field": "variable_symbol", "operator": "like", "value": ["12", "34"] }
|
|
316
|
+
]
|
|
317
|
+
}
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
Operators: `eq` / `not_eq` (equality), `in` / `not_in` (membership; value may be a scalar or array),
|
|
321
|
+
`like` / `not_like` (case-insensitive `ILIKE '%…%'` substring match; value may be a scalar or array —
|
|
322
|
+
`like` matches any, `not_like` excludes any). The negative operators (`not_eq`, `not_in`, `not_like`)
|
|
323
|
+
are NULL-safe: a row whose value is `NULL` is kept.
|
|
324
|
+
|
|
325
|
+
### 2.8. Translations
|
|
280
326
|
|
|
281
327
|
All validation error messages raised by this gem (ordering, filtering, fieldset and pagination errors) go through
|
|
282
328
|
`I18n.t` with an English `default:`, so the gem works out of the box without any locale setup. In addition,
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
require 'base64'
|
|
2
|
+
|
|
3
|
+
module Eluvia
|
|
4
|
+
module ActiveRecord
|
|
5
|
+
# Generic, model-agnostic filter driven by deploy-time configuration (typically a base64-encoded
|
|
6
|
+
# JSON blob in an ENV var). The config is a list of `field` / `operator` / `value` conditions,
|
|
7
|
+
# AND-combined, that narrow a scope — e.g. to restrict what an admin interface may ever see.
|
|
8
|
+
#
|
|
9
|
+
# Only fields declared via `env_filter_fields` are ever interpolated into SQL (as a column name);
|
|
10
|
+
# every value is a bound parameter, so the config cannot be used to inject arbitrary SQL. Unknown
|
|
11
|
+
# fields and operators are silently ignored.
|
|
12
|
+
#
|
|
13
|
+
# @example Declaring the whitelist on a model
|
|
14
|
+
# class BankTransaction < ApplicationRecord
|
|
15
|
+
# include Eluvia::ActiveRecord::EnvFilterable
|
|
16
|
+
# env_filter_fields %i[id direction variable_symbol date amount is_hidden]
|
|
17
|
+
# end
|
|
18
|
+
#
|
|
19
|
+
# @example Applying it to a scope (e.g. from a Pundit `policy_scope`)
|
|
20
|
+
# config = BankTransaction.parse_env_filter_config(ENV['ADMIN_BANK_TRANSACTIONS_FILTER_B64'])
|
|
21
|
+
# BankTransaction.apply_env_filter(scope, config)
|
|
22
|
+
module EnvFilterable
|
|
23
|
+
extend ::ActiveSupport::Concern
|
|
24
|
+
|
|
25
|
+
class_methods do
|
|
26
|
+
|
|
27
|
+
# Declares (with arguments) or returns (without arguments) the fields that may appear in an
|
|
28
|
+
# env filter config. Only these column names are ever interpolated into SQL. Names are stored
|
|
29
|
+
# as strings so they compare directly against the config's `field` values.
|
|
30
|
+
#
|
|
31
|
+
# @param field_names [Array<String, Symbol>] whitelisted column names
|
|
32
|
+
# @return [Array<String>] the declared whitelist
|
|
33
|
+
def env_filter_fields(*field_names)
|
|
34
|
+
if field_names.empty?
|
|
35
|
+
@_env_filter_fields ||= []
|
|
36
|
+
else
|
|
37
|
+
@_env_filter_fields = field_names.flatten.map(&:to_s)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Decodes a base64-encoded JSON filter config (typically read straight from an ENV var) into a
|
|
42
|
+
# Hash. Returns `nil` for blank input so a missing ENV var disables the filter. Raises
|
|
43
|
+
# `ArgumentError` on malformed input so a broken deploy-time config fails loudly rather than
|
|
44
|
+
# silently letting everything through.
|
|
45
|
+
#
|
|
46
|
+
# @param encoded [String, nil] base64-encoded JSON, or `nil`/blank
|
|
47
|
+
# @return [Hash, nil]
|
|
48
|
+
def parse_env_filter_config(encoded)
|
|
49
|
+
return nil if encoded.blank?
|
|
50
|
+
|
|
51
|
+
JSON.parse(Base64.strict_decode64(encoded.strip))
|
|
52
|
+
rescue ArgumentError, JSON::ParserError => e
|
|
53
|
+
raise ArgumentError, "invalid env filter config: #{e.message}"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Applies an env filter config to +scope+. Config is a Hash with a `"conditions"` array; each
|
|
57
|
+
# condition has `"field"`, `"operator"` and `"value"`. All conditions are AND-combined. A blank
|
|
58
|
+
# config returns the scope unchanged; conditions with an unknown field or operator are ignored.
|
|
59
|
+
#
|
|
60
|
+
# Operators:
|
|
61
|
+
# eq / not_eq equality (not_eq keeps NULLs)
|
|
62
|
+
# in / not_in membership; value may be a scalar or array (not_in keeps NULLs)
|
|
63
|
+
# like / not_like case-insensitive substring match; value may be a scalar or an array
|
|
64
|
+
# (like → matches any; not_like → excludes any, keeps NULLs)
|
|
65
|
+
#
|
|
66
|
+
# @param scope [ActiveRecord::Relation]
|
|
67
|
+
# @param config [Hash, nil]
|
|
68
|
+
# @return [ActiveRecord::Relation]
|
|
69
|
+
def apply_env_filter(scope, config)
|
|
70
|
+
return scope if config.blank?
|
|
71
|
+
|
|
72
|
+
Array(config['conditions']).each do |cond|
|
|
73
|
+
field = cond['field'].to_s
|
|
74
|
+
op = cond['operator'].to_s
|
|
75
|
+
next unless env_filter_fields.include?(field)
|
|
76
|
+
|
|
77
|
+
col = "#{table_name}.#{field}" # field is whitelisted → safe to interpolate
|
|
78
|
+
val = cond['value']
|
|
79
|
+
|
|
80
|
+
scope =
|
|
81
|
+
case op
|
|
82
|
+
when 'eq'
|
|
83
|
+
scope.where(field => val)
|
|
84
|
+
when 'not_eq'
|
|
85
|
+
scope.where("#{col} IS NULL OR #{col} <> ?", val)
|
|
86
|
+
when 'in'
|
|
87
|
+
scope.where(field => Array(val))
|
|
88
|
+
when 'not_in'
|
|
89
|
+
scope.where("#{col} IS NULL OR #{col} NOT IN (?)", Array(val))
|
|
90
|
+
when 'like'
|
|
91
|
+
Array(val).each_with_index.reduce(scope) do |s, (v, i)|
|
|
92
|
+
clause = scope.where("#{col} ILIKE ('%' || ? || '%')", v)
|
|
93
|
+
i.zero? ? clause : s.or(clause)
|
|
94
|
+
end
|
|
95
|
+
when 'not_like'
|
|
96
|
+
Array(val).reduce(scope) do |s, v|
|
|
97
|
+
s.where("#{col} IS NULL OR #{col} NOT ILIKE ('%' || ? || '%')", v)
|
|
98
|
+
end
|
|
99
|
+
else
|
|
100
|
+
scope
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
scope
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
@@ -6,6 +6,30 @@ module Eluvia
|
|
|
6
6
|
|
|
7
7
|
class_methods do
|
|
8
8
|
|
|
9
|
+
# *********************************************************************
|
|
10
|
+
# Attribute store methods
|
|
11
|
+
# *********************************************************************
|
|
12
|
+
|
|
13
|
+
def _file_array_attrs
|
|
14
|
+
@file_array_attrs ||= {}
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def file_array_attrs
|
|
18
|
+
_file_array_attrs.values
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def file_array_attr_names
|
|
22
|
+
file_array_attrs.map { |a| a[:attr_name] }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def has_file_array_attr?(attr_name)
|
|
26
|
+
_file_array_attrs.key?(attr_name.to_sym)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def find_file_array_attr(attr_name)
|
|
30
|
+
_file_array_attrs.fetch(attr_name.to_sym, nil)
|
|
31
|
+
end
|
|
32
|
+
|
|
9
33
|
# *********************************************************************
|
|
10
34
|
# Attribute definition methods for file array
|
|
11
35
|
# *********************************************************************
|
|
@@ -16,78 +40,101 @@ module Eluvia
|
|
|
16
40
|
# @param underlying_attachments [Symbol, nil] Name of the attribute holding underlying Active Storage attachments
|
|
17
41
|
# @param underlying_filter [Proc, nil] Optional filter to be applied to scope underlying attachments
|
|
18
42
|
# @param nature [Symbol] Nature of file attribute (:file, :image, etc.)
|
|
43
|
+
# @param append_only [Boolean] Whether the setter only attaches new `{filename, content}` files and never removes existing ones
|
|
19
44
|
# @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)
|
|
45
|
+
def file_array_attr(attr_name, underlying_attachments: nil, underlying_filter: nil, nature: :file, append_only: false, declare: false)
|
|
21
46
|
attr_name = attr_name.to_sym
|
|
22
47
|
nature = nature.to_sym
|
|
23
48
|
|
|
49
|
+
raise ArgumentError, "`append_only` file array attribute `#{attr_name}` requires `underlying_attachments`." if append_only && !underlying_attachments
|
|
50
|
+
|
|
24
51
|
# Define it using "has many" attribute
|
|
25
52
|
if declare
|
|
26
53
|
has_many_attr(attr_name, nature == :image ? 'Eluvia::ImageWrapper' : 'Eluvia::FileWrapper')
|
|
27
54
|
end
|
|
28
55
|
|
|
56
|
+
# Register metadata
|
|
57
|
+
self._file_array_attrs[attr_name] = {
|
|
58
|
+
attr_name: attr_name,
|
|
59
|
+
underlying_attachments: underlying_attachments,
|
|
60
|
+
underlying_filter: underlying_filter,
|
|
61
|
+
nature: nature,
|
|
62
|
+
append_only: append_only
|
|
63
|
+
}
|
|
64
|
+
|
|
29
65
|
# *******************************************************************
|
|
30
66
|
# Dynamically defined instance methods for file array
|
|
31
67
|
# *******************************************************************
|
|
32
68
|
|
|
33
69
|
if underlying_attachments
|
|
34
70
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
#
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
71
|
+
if append_only
|
|
72
|
+
|
|
73
|
+
# Set method - attaches each new file, leaves the existing attachments untouched
|
|
74
|
+
define_method(:"#{attr_name}=") do |new_values|
|
|
75
|
+
append_file_array_uploads(attr_name, underlying_attachments, new_values)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
else
|
|
79
|
+
|
|
80
|
+
# Set method
|
|
81
|
+
define_method(:"#{attr_name}=") do |new_values|
|
|
82
|
+
|
|
83
|
+
# Check input
|
|
84
|
+
raise Eluvia::Errors::BadRequest.new("Invalid value for attribute `#{attr_name}`, expecting Array.") unless new_values.is_a?(::Array)
|
|
85
|
+
|
|
86
|
+
# Current values
|
|
87
|
+
current_values = self.send(attr_name)
|
|
88
|
+
current_ids = current_values.map(&:id)
|
|
89
|
+
new_ids = []
|
|
90
|
+
|
|
91
|
+
# Create missing attachments
|
|
92
|
+
new_values.each do |value|
|
|
93
|
+
# Normalize and check value
|
|
94
|
+
if value.is_a?(::Hash)
|
|
95
|
+
if nature == :image
|
|
96
|
+
value = Eluvia::ImageWrapper.map(value)
|
|
97
|
+
else
|
|
98
|
+
value = Eluvia::FileWrapper.map(value)
|
|
99
|
+
end
|
|
54
100
|
end
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
raise Eluvia::Errors::BadRequest.new("Invalid value for attribute `#{attr_name}[].file`, can't be blank.") if value.file.blank?
|
|
101
|
+
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)
|
|
102
|
+
raise Eluvia::Errors::BadRequest.new("Invalid value for attribute `#{attr_name}[].file`, can't be blank.") if value.file.blank?
|
|
58
103
|
|
|
59
|
-
|
|
60
|
-
|
|
104
|
+
# Keep track of new ids
|
|
105
|
+
new_ids << value.id if value.id
|
|
61
106
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
107
|
+
# Retrieve filename and content
|
|
108
|
+
filename = value.file&.filename
|
|
109
|
+
content = value.file&.content
|
|
65
110
|
|
|
66
|
-
|
|
67
|
-
|
|
111
|
+
# Sanitize filename
|
|
112
|
+
filename = ActiveStorage::Filename.new(filename).sanitized
|
|
68
113
|
|
|
69
|
-
|
|
70
|
-
|
|
114
|
+
# Create or update attachment only if both filename and content are passed (this means that user uploaded a new file)
|
|
115
|
+
if !filename.blank? && !content.blank?
|
|
71
116
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
117
|
+
# Delete the existing attachment
|
|
118
|
+
if value.id && current_ids.include?(value.id)
|
|
119
|
+
attachment_record = self.send("#{underlying_attachments}_attachments").find(value.id)
|
|
120
|
+
attachment_record.purge_later
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Create a new attachment
|
|
124
|
+
self.send(underlying_attachments).attach(io: StringIO.new(Base64.decode64(content)), filename: filename)
|
|
76
125
|
end
|
|
77
126
|
|
|
78
|
-
# Create a new attachment
|
|
79
|
-
self.send(underlying_attachments).attach(io: StringIO.new(Base64.decode64(content)), filename: filename)
|
|
80
127
|
end
|
|
81
128
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
attachment_record.purge_later
|
|
129
|
+
# Delete no longer wanted attachments
|
|
130
|
+
current_values.each do |value|
|
|
131
|
+
unless new_ids.include?(value.id)
|
|
132
|
+
attachment_record = self.send("#{underlying_attachments}_attachments").find(value.id)
|
|
133
|
+
attachment_record.purge_later
|
|
134
|
+
end
|
|
89
135
|
end
|
|
90
136
|
end
|
|
137
|
+
|
|
91
138
|
end
|
|
92
139
|
|
|
93
140
|
# Get method
|
|
@@ -111,6 +158,32 @@ module Eluvia
|
|
|
111
158
|
end
|
|
112
159
|
|
|
113
160
|
end
|
|
161
|
+
|
|
162
|
+
private
|
|
163
|
+
|
|
164
|
+
# Attaches each `{filename, content}` upload of an append-only file array attribute
|
|
165
|
+
def append_file_array_uploads(attr_name, underlying_attachments, new_values)
|
|
166
|
+
return if new_values.nil?
|
|
167
|
+
|
|
168
|
+
# Check input
|
|
169
|
+
raise Eluvia::Errors::BadRequest.new("Invalid value for attribute `#{attr_name}`, expecting Array.") unless new_values.is_a?(::Array)
|
|
170
|
+
|
|
171
|
+
new_values.each do |value|
|
|
172
|
+
# Normalize and check value
|
|
173
|
+
value = Eluvia::File.map(value) if value.is_a?(::Hash)
|
|
174
|
+
raise Eluvia::Errors::BadRequest.new("Invalid value for attribute `#{attr_name}[]`, expecting Eluvia::File.") unless value.is_a?(Eluvia::File)
|
|
175
|
+
raise Eluvia::Errors::BadRequest.new("Invalid value for attribute `#{attr_name}[]`, `upload_key` is not supported.") if value.upload_key.present?
|
|
176
|
+
|
|
177
|
+
# An item without content carries no new file
|
|
178
|
+
next if value.content.blank?
|
|
179
|
+
|
|
180
|
+
filename = ActiveStorage::Filename.new(value.filename.to_s).sanitized
|
|
181
|
+
raise Eluvia::Errors::BadRequest.new("Invalid value for attribute `#{attr_name}[].filename`, can't be blank.") if filename.blank?
|
|
182
|
+
|
|
183
|
+
self.send(underlying_attachments).attach(io: StringIO.new(Base64.decode64(value.content)), filename: filename)
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
114
187
|
end
|
|
115
188
|
end
|
|
116
189
|
end
|
data/lib/eluvia-base.rb
CHANGED
|
@@ -20,6 +20,7 @@ require 'eluvia/active_model/object_attribute'
|
|
|
20
20
|
require 'eluvia/active_model/range_attribute' # depends on object_attribute
|
|
21
21
|
|
|
22
22
|
# Active record
|
|
23
|
+
require 'eluvia/active_record/env_filterable'
|
|
23
24
|
require 'eluvia/active_record/file_array_attribute'
|
|
24
25
|
require 'eluvia/active_record/file_attribute'
|
|
25
26
|
require 'eluvia/active_record/filtering'
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: eluvia-base
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.
|
|
4
|
+
version: 3.41.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Matěj Outlý
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-10-27 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rest-client
|
|
@@ -66,6 +66,20 @@ dependencies:
|
|
|
66
66
|
- - "~>"
|
|
67
67
|
- !ruby/object:Gem::Version
|
|
68
68
|
version: '2.19'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: base64
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - "~>"
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '0.2'
|
|
76
|
+
type: :runtime
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - "~>"
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '0.2'
|
|
69
83
|
- !ruby/object:Gem::Dependency
|
|
70
84
|
name: ostruct
|
|
71
85
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -128,6 +142,7 @@ files:
|
|
|
128
142
|
- lib/eluvia/active_model/enum_attribute.rb
|
|
129
143
|
- lib/eluvia/active_model/object_attribute.rb
|
|
130
144
|
- lib/eluvia/active_model/range_attribute.rb
|
|
145
|
+
- lib/eluvia/active_record/env_filterable.rb
|
|
131
146
|
- lib/eluvia/active_record/file_array_attribute.rb
|
|
132
147
|
- lib/eluvia/active_record/file_attribute.rb
|
|
133
148
|
- lib/eluvia/active_record/filtering.rb
|