eluvia-base 3.40.0 → 3.42.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 +21 -0
- data/README.md +37 -85
- data/lib/eluvia/active_record/file_array_attribute.rb +118 -45
- data/lib/eluvia/active_record/file_attribute.rb +2 -5
- data/lib/eluvia/base/config.rb +5 -7
- data/lib/eluvia/handlers/pagination_handler.rb +11 -6
- data/lib/eluvia/uploads.rb +2 -2
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 69890341f7733c2669578108d2e8dec34c753a9cd9dcfc2ff1125d1ca710492a
|
|
4
|
+
data.tar.gz: f59a4a2012c88fa70479feb651857458b8ad9f9580904d340478fa26c51c429b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c7f8f95b0d3b6948862cb21080001e1f35e891a4cb01dfa92c847b33145054de9cbc7f67be561f3ffd450e6e51d40b14568949f5e2dc7c1bcc860d52ded15135
|
|
7
|
+
data.tar.gz: 263c3e8f9a6769695aebade49c3bea01562571b6ea6e104ef6fddb9812d9c6f7b188b82a863f453a4a3bc44e00545a19e64f42ca23d1ad5f656a9cab32b9d4f9
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,27 @@ 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.42.0] — 2026-10-28
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- `Eluvia::PaginationHandler#set_pagination`: `fallback_order_by` no longer overrides the
|
|
12
|
+
direction of a field that the client's `order_by` (or `default_order_by`) already orders by; it
|
|
13
|
+
only appends the fields not ordered by yet. This changes behavior: with the default
|
|
14
|
+
`fallback_order_by: 'created_at:asc'`, `?order_by=created_at:desc` now really sorts descending
|
|
15
|
+
(before it was silently turned into ascending), and `?order_by=id:desc` with
|
|
16
|
+
`fallback_order_by: 'id:asc'` keeps `id` descending (HAB-1064).
|
|
17
|
+
|
|
18
|
+
## [3.41.0] — 2026-10-27
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
- `Eluvia::ActiveRecord::FileArrayAttribute` registers each `file_array_attr` like `file_attr`
|
|
22
|
+
does: `file_array_attrs`, `file_array_attr_names`, `has_file_array_attr?` and
|
|
23
|
+
`find_file_array_attr` return its `underlying_attachments`, `underlying_filter`, `nature` and
|
|
24
|
+
`append_only` (HAB-991).
|
|
25
|
+
- `append_only:` option on `file_array_attr`: the setter takes a list of flat
|
|
26
|
+
`{filename, content}` files, attaches each one with content, never removes an existing
|
|
27
|
+
attachment, and ignores `nil`. Requires `underlying_attachments` (HAB-991).
|
|
28
|
+
|
|
8
29
|
## [3.40.0] — 2026-09-23
|
|
9
30
|
|
|
10
31
|
### Added
|
data/README.md
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
# Eluvia Base
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
Ruby on Rails customizations and helpers implementing the Eluvia
|
|
4
|
+
[API communication standard](https://sparktech.myjetbrains.com/youtrack/articles/BASE-A-4/Communication-interface-FE-BE)
|
|
5
|
+
and common backend behavior:
|
|
6
6
|
|
|
7
|
-
It applies to both client and server sides:
|
|
8
7
|
- Pagination and ordering is handled for the API requests (server side).
|
|
9
8
|
- Extended params parsing is implemented for the API requests (server side).
|
|
10
9
|
- Extended filtering is handled for the API requests (server side).
|
|
@@ -45,8 +44,7 @@ For full list of configuration options, see `lib/eluvia/base/config.rb` file.
|
|
|
45
44
|
|
|
46
45
|
### 2.1. Application controller setup
|
|
47
46
|
|
|
48
|
-
|
|
49
|
-
main `ApplicationController`:
|
|
47
|
+
Include the handlers in your `ApplicationController`:
|
|
50
48
|
|
|
51
49
|
```ruby
|
|
52
50
|
|
|
@@ -61,78 +59,39 @@ end
|
|
|
61
59
|
|
|
62
60
|
### 2.2. Pagination
|
|
63
61
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
easily used in Active Record and Kaminari interfaces.
|
|
62
|
+
`set_pagination` reads the `limit`, `offset` and `order_by` query params and sets `@limit`, `@page`, `@padding` and
|
|
63
|
+
`@order_by` for Active Record and Kaminari:
|
|
67
64
|
|
|
68
65
|
```ruby
|
|
69
|
-
|
|
70
66
|
class TestRecordsController < ApplicationController
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
before_action -> { set_pagination(20, 0, 'created_at:desc') }, only: [:index]
|
|
74
|
-
|
|
75
|
-
# ...
|
|
67
|
+
before_action -> { set_pagination(default_order_by: 'created_at:desc', fallback_order_by: 'id:asc') }, only: [:index]
|
|
76
68
|
|
|
77
69
|
def index
|
|
78
70
|
@test_records = TestRecord.all.order(@order_by).page(@page).per(@limit).padding(@padding)
|
|
79
71
|
end
|
|
80
|
-
|
|
81
|
-
# ...
|
|
82
|
-
end
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
**BREAKING CHANGE:** the `limit` param is now capped. Requests with a `limit` above the configured ceiling are
|
|
86
|
-
rejected with a `422 Unprocessable Entity` (error detail on the `limit` field) instead of being silently accepted.
|
|
87
|
-
A negative or non-numeric `limit` no longer falls through to Kaminari — it now also raises the same `422`. Only a
|
|
88
|
-
missing or blank `limit` param still falls back to `default_limit`, unchanged. `limit=0` is treated as a deliberate
|
|
89
|
-
meta-only request (see below) rather than an error, mirroring `ListPagination` in eluvia-base-django.
|
|
90
|
-
|
|
91
|
-
The ceiling defaults to `Eluvia::Base::Config.max_page_limit` (`1000` by default, `nil` disables the check):
|
|
92
|
-
|
|
93
|
-
```ruby
|
|
94
|
-
Eluvia::Base::Config.max_page_limit = 500
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
It can be overridden per controller by overriding `pagination_max_limit`, or per call via the `max_limit:` keyword
|
|
98
|
-
(an explicit `max_limit: nil` removes the ceiling for that call only, regardless of `pagination_max_limit`):
|
|
99
|
-
|
|
100
|
-
```ruby
|
|
101
|
-
class TestRecordsController < ApplicationController
|
|
102
|
-
def pagination_max_limit
|
|
103
|
-
50
|
|
104
|
-
end
|
|
105
72
|
end
|
|
106
73
|
```
|
|
107
74
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
Pass `allow_meta_only_limit: false` (e.g. for a search-backed resource, mirroring
|
|
131
|
-
`AnySearchCursorBasedPagination` in eluvia-base-django) to reject `limit=0` like any other invalid value instead:
|
|
132
|
-
|
|
133
|
-
```ruby
|
|
134
|
-
before_action -> { set_pagination(default_limit: 20, default_offset: 0, allow_meta_only_limit: false) }, only: [:index]
|
|
135
|
-
```
|
|
75
|
+
| Keyword | Default | Meaning |
|
|
76
|
+
|-------------------------|------------------------------------|---------------------------------------------------------------|
|
|
77
|
+
| `default_limit` | `20` | `limit` when the param is missing or blank |
|
|
78
|
+
| `default_offset` | `0` | `offset` when the param is missing |
|
|
79
|
+
| `max_limit` | `pagination_max_limit` | upper bound for `limit`, `nil` for none |
|
|
80
|
+
| `allow_meta_only_limit` | `true` | accept `limit=0` |
|
|
81
|
+
| `disable_errors` | `Config.disable_pagination_errors` | use `default_limit` instead of raising 422 on invalid `limit` |
|
|
82
|
+
| `default_order_by` | `'created_at:asc'` | ordering when `order_by` is missing or blank |
|
|
83
|
+
| `force_order_by` | `false` | ignore the `order_by` param, always use `default_order_by` |
|
|
84
|
+
| `fallback_order_by` | `'created_at:asc'` | tiebreaker appended to the ordering |
|
|
85
|
+
|
|
86
|
+
**Limit.** `limit` must be a non-negative integer not above the ceiling, otherwise the request fails with
|
|
87
|
+
`422 Unprocessable Entity` on `limit`. The ceiling is `Eluvia::Base::Config.max_page_limit` (default `1000`, `nil`
|
|
88
|
+
for none); a controller can override the `pagination_max_limit` method to set its own. `limit=0` returns only the
|
|
89
|
+
pagination metadata, so skip the query when `@limit.zero?`.
|
|
90
|
+
|
|
91
|
+
**Ordering.** `order_by` is a comma-separated list of `field:asc` or `field:desc` entries; anything else fails with
|
|
92
|
+
`422`. `fallback_order_by` only appends fields the ordering does not contain yet, so it never changes the requested
|
|
93
|
+
direction; set it to the primary key to keep pages stable when ordered values are equal. For example,
|
|
94
|
+
`?order_by=name:asc,id:desc` with `fallback_order_by: 'id:asc'` yields `{ 'name' => :asc, 'id' => :desc }`.
|
|
136
95
|
|
|
137
96
|
### 2.3. Params parser
|
|
138
97
|
|
|
@@ -195,8 +154,7 @@ For the full list of features, see `Eluvia::EluviaIntegration` interface.
|
|
|
195
154
|
|
|
196
155
|
### 2.5. Raising error
|
|
197
156
|
|
|
198
|
-
|
|
199
|
-
formatted as standardized error response.
|
|
157
|
+
An `Eluvia::Errors::XXX` exception raised anywhere in the code is rendered as a standardized error response.
|
|
200
158
|
|
|
201
159
|
For example, this exception:
|
|
202
160
|
|
|
@@ -256,12 +214,10 @@ For the full list of available exceptions, see content of `lib/eluvia/errors` fo
|
|
|
256
214
|
|
|
257
215
|
### 2.6. Chunked/direct upload finalization
|
|
258
216
|
|
|
259
|
-
`Eluvia::File`
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
ActiveStorage attachment. This lets a separate library (e.g. a chunked upload provider) implement the
|
|
264
|
-
actual storage logic (S3 `copy_object`, tus, ...) without eluvia-base depending on it.
|
|
217
|
+
An `Eluvia::File` assigned to a `file_attr` field may carry an `upload_key` instead of base64 `content`. The key
|
|
218
|
+
identifies an already uploaded (chunked/direct) temp object, and the setter passes it to `Eluvia::Uploads.finalizer`,
|
|
219
|
+
which attaches it to the ActiveStorage record. The storage logic (S3 `copy_object`, tus, ...) thus lives in the
|
|
220
|
+
application or another library, not in this gem. `file_array_attr` fields reject `upload_key` with `400 Bad Request`.
|
|
265
221
|
|
|
266
222
|
Register a finalizer, typically from an initializer:
|
|
267
223
|
|
|
@@ -273,8 +229,7 @@ Eluvia::Uploads.finalizer = ->(attachment_record, upload_key, filename) do
|
|
|
273
229
|
end
|
|
274
230
|
```
|
|
275
231
|
|
|
276
|
-
|
|
277
|
-
`Eluvia::Errors::StandardError` is raised.
|
|
232
|
+
Assigning a file with `upload_key` while no finalizer is registered raises `Eluvia::Errors::StandardError`.
|
|
278
233
|
|
|
279
234
|
### 2.7. ENV-driven scope restriction
|
|
280
235
|
|
|
@@ -324,13 +279,10 @@ are NULL-safe: a row whose value is `NULL` is kept.
|
|
|
324
279
|
|
|
325
280
|
### 2.8. Translations
|
|
326
281
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
`
|
|
330
|
-
|
|
331
|
-
`sk`, `uk`. These are appended to `I18n.load_path` automatically when the gem loads; a host application only
|
|
332
|
-
needs to add the relevant locale to its own `I18n.available_locales` and set `I18n.locale` as usual. A host app's
|
|
333
|
-
own `config/locales/*.yml` takes precedence over these if it defines the same key.
|
|
282
|
+
Validation error messages of this gem go through `I18n.t` with an English default, so no locale setup is needed.
|
|
283
|
+
Translations for `cs`, `de`, `en`, `es`, `fr`, `it`, `ja`, `nl`, `pl`, `pt`, `ro`, `sk` and `uk` (under
|
|
284
|
+
`eluvia.errors.*`) are added to `I18n.load_path` when the gem loads. The application's own `config/locales/*.yml`
|
|
285
|
+
takes precedence for the same key.
|
|
334
286
|
|
|
335
287
|
## 3. Development
|
|
336
288
|
|
|
@@ -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
|
|
@@ -75,11 +75,8 @@ module Eluvia
|
|
|
75
75
|
value = Eluvia::File.map(value) if value.is_a?(::Hash)
|
|
76
76
|
raise Eluvia::Errors::BadRequest.new("Invalid value for file attribute `#{attr_name}`, expecting Eluvia::File.") unless value.is_a?(Eluvia::File)
|
|
77
77
|
|
|
78
|
-
# Retrieve and check filename -
|
|
79
|
-
#
|
|
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.
|
|
78
|
+
# Retrieve and check filename - a chunked/direct upload may send only `upload_key`,
|
|
79
|
+
# whose last path segment is the original filename (`<tmp_prefix><uuid>/<filename>`)
|
|
83
80
|
filename = value.filename
|
|
84
81
|
filename = ::File.basename(value.upload_key) if filename.blank? && value.upload_key.present?
|
|
85
82
|
raise Eluvia::Errors::BadRequest.new("Expected `filename` in the attribute `#{attr_name}`.") if filename.blank?
|
data/lib/eluvia/base/config.rb
CHANGED
|
@@ -40,16 +40,14 @@ module Eluvia
|
|
|
40
40
|
mattr_accessor :search_use_unaccent
|
|
41
41
|
@@search_use_unaccent = true
|
|
42
42
|
|
|
43
|
-
# Upper bound for the `limit`
|
|
44
|
-
#
|
|
45
|
-
# disables the check. Mirrors `MAX_PAGE_LIMIT` in eluvia-base-django. (default: 1000)
|
|
43
|
+
# Upper bound for the `limit` param of `set_pagination`; a higher `limit` is rejected with 422.
|
|
44
|
+
# `nil` disables the check. (default: 1000)
|
|
46
45
|
mattr_accessor :max_page_limit
|
|
47
46
|
@@max_page_limit = 1000
|
|
48
47
|
|
|
49
|
-
# Whether an invalid `limit` param (non-numeric,
|
|
50
|
-
# without `allow_meta_only_limit:`)
|
|
51
|
-
#
|
|
52
|
-
# `LimitResolutionMixin` in eluvia-base-django. (default: false)
|
|
48
|
+
# Whether `set_pagination` uses `default_limit` for an invalid `limit` param (non-numeric,
|
|
49
|
+
# negative, above `max_page_limit`, or zero without `allow_meta_only_limit:`) instead of
|
|
50
|
+
# rejecting it with 422. (default: false)
|
|
53
51
|
mattr_accessor :disable_pagination_errors
|
|
54
52
|
@@disable_pagination_errors = false
|
|
55
53
|
|
|
@@ -44,10 +44,18 @@ module Eluvia
|
|
|
44
44
|
# A blank `order_by` is treated as "not supplied" (falls through to defaults/fallback)
|
|
45
45
|
# rather than as a literal empty ordering, since a Kaminari scope always needs some order.
|
|
46
46
|
order_by = !force_order_by && user_order_by.present? ? user_order_by : default_order_by
|
|
47
|
-
order_by = "#{order_by},#{fallback_order_by}"
|
|
48
47
|
|
|
48
|
+
# Within `order_by` a repeated field takes its last direction; `fallback_order_by` only
|
|
49
|
+
# appends fields not already ordered by, so it never overrides the requested direction.
|
|
49
50
|
@order_by = {}
|
|
50
|
-
order_by
|
|
51
|
+
parse_order_by(order_by) { |field_name, direction| @order_by[field_name] = direction }
|
|
52
|
+
parse_order_by(fallback_order_by) { |field_name, direction| @order_by[field_name] ||= direction }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def parse_order_by(order_by)
|
|
58
|
+
order_by.to_s.split(',').each do |entry|
|
|
51
59
|
entry = entry.strip
|
|
52
60
|
# `-1` keeps a trailing empty segment (e.g. `name:`) instead of Ruby's default of
|
|
53
61
|
# dropping it, so it is caught below as an invalid direction rather than as a bare field.
|
|
@@ -68,13 +76,10 @@ module Eluvia
|
|
|
68
76
|
})
|
|
69
77
|
end
|
|
70
78
|
|
|
71
|
-
|
|
79
|
+
yield field_name, direction.to_sym
|
|
72
80
|
end
|
|
73
|
-
|
|
74
81
|
end
|
|
75
82
|
|
|
76
|
-
private
|
|
77
|
-
|
|
78
83
|
def resolve_pagination_limit(default_limit:, max_limit:, disable_errors:, allow_meta_only_limit:)
|
|
79
84
|
return default_limit unless params[:limit].present?
|
|
80
85
|
|
data/lib/eluvia/uploads.rb
CHANGED
|
@@ -2,8 +2,8 @@ module Eluvia
|
|
|
2
2
|
module Uploads
|
|
3
3
|
|
|
4
4
|
# Extension point for finalizing chunked/direct uploads (identified by `Eluvia::File#upload_key`)
|
|
5
|
-
# into the underlying ActiveStorage attachment.
|
|
6
|
-
#
|
|
5
|
+
# into the underlying ActiveStorage attachment. Unset by default, so the gem does not depend on
|
|
6
|
+
# any storage provider (e.g. aws-sdk-s3).
|
|
7
7
|
#
|
|
8
8
|
# Consumers register a callable of the form:
|
|
9
9
|
# ->(attachment_record, upload_key, filename) { ... }
|
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.42.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-28 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rest-client
|