glib-web 6.10.2 → 6.10.4
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/app/controllers/concerns/glib/json/libs.rb +33 -0
- data/app/helpers/glib/json_ui/view_builder/fields.rb +47 -0
- data/app/jobs/glib/purge_unattached_job.rb +6 -2
- data/app/models/concerns/glib/snapshot_v2.rb +58 -6
- data/app/views/json_ui/garage/test_page/_header.json.jbuilder +1 -0
- data/app/views/json_ui/garage/test_page/fields_change_trigger.json.jbuilder +128 -0
- data/lib/glib/purge_unattached/config.rb +38 -0
- data/lib/glib-web.rb +4 -2
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: af62a06f4071bb5b2779e61b2dfa771d9984d4f2f15bd6ed121fd71a676d7918
|
|
4
|
+
data.tar.gz: f5100fe92e6d2959a4479b82887e0009c0aaea1a2ef419f1ae6205c8fbda427b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b0e19fa8b04dc2a7d44826f0c28eefe5411610d26226180dbd20079af4287177d44967331c56dd1c0dc7ebf0b3f01df08f1a38939958fe58c37a08183e8d10c0
|
|
7
|
+
data.tar.gz: 3c717ab4989d1e67f712d7d118d77cc6e99d136e0b1ed2e4e403a0974d0c364f705d064bad706b5f1bbfeca295333c523708e76f259a78cba969e958f6a494de
|
|
@@ -96,6 +96,18 @@ module Glib::Json::Libs
|
|
|
96
96
|
|
|
97
97
|
json_ui_redirect_to url, status: status
|
|
98
98
|
end
|
|
99
|
+
# Any other request format (e.g. a browser tab opening a protected file link
|
|
100
|
+
# `/temp_blobs/:id/report.pdf`, where the filename sets the format) used to raise
|
|
101
|
+
# ActionController::UnknownFormat -> a bare 406 with an empty body. Mirror the html
|
|
102
|
+
# branch instead: whatever opened the URL (browser tab, iframe, img) is redirected
|
|
103
|
+
# to the target, which the browser follows natively.
|
|
104
|
+
#
|
|
105
|
+
# MUST stay the LAST block. `negotiate_mime` answers `Accept: */*` requests with
|
|
106
|
+
# the FIRST declared format, so an `any` block placed before html would capture
|
|
107
|
+
# ordinary browser page loads and leave request.format as `*/*` for the action.
|
|
108
|
+
format.any do
|
|
109
|
+
redirect_to url
|
|
110
|
+
end
|
|
99
111
|
end
|
|
100
112
|
end
|
|
101
113
|
|
|
@@ -142,6 +154,17 @@ module Glib::Json::Libs
|
|
|
142
154
|
redirect_to sign_in_url, **redirect_options
|
|
143
155
|
end
|
|
144
156
|
end
|
|
157
|
+
# Other non-HTML/JSON formats (a protected file link whose filename sets the
|
|
158
|
+
# request format, an XML export, ...) used to raise UnknownFormat -> a bare 406.
|
|
159
|
+
# Mirrors the csv branch: a clean gate status in test, a redirect in production.
|
|
160
|
+
# MUST stay the LAST block -- see glib_redirect_to.
|
|
161
|
+
format.any do
|
|
162
|
+
if Rails.env.test?
|
|
163
|
+
head :unauthorized
|
|
164
|
+
else
|
|
165
|
+
redirect_to sign_in_url, **redirect_options
|
|
166
|
+
end
|
|
167
|
+
end
|
|
145
168
|
end
|
|
146
169
|
end
|
|
147
170
|
|
|
@@ -185,6 +208,16 @@ module Glib::Json::Libs
|
|
|
185
208
|
redirect_to user_default_url
|
|
186
209
|
end
|
|
187
210
|
end
|
|
211
|
+
# Other non-HTML/JSON formats -- mirrors the csv branch (clean gate status in
|
|
212
|
+
# test, redirect in production); previously raised UnknownFormat -> a bare 406.
|
|
213
|
+
# MUST stay the LAST block -- see glib_redirect_to.
|
|
214
|
+
format.any do
|
|
215
|
+
if Rails.env.test?
|
|
216
|
+
head :forbidden
|
|
217
|
+
else
|
|
218
|
+
redirect_to user_default_url
|
|
219
|
+
end
|
|
220
|
+
end
|
|
188
221
|
end
|
|
189
222
|
end
|
|
190
223
|
|
|
@@ -211,6 +211,51 @@ class Glib::JsonUi::ViewBuilder
|
|
|
211
211
|
end
|
|
212
212
|
end
|
|
213
213
|
|
|
214
|
+
# Timing options for the `onChange` / `onChangeAndLoad` actions of fields
|
|
215
|
+
# whose value changes while the user is still typing.
|
|
216
|
+
#
|
|
217
|
+
# Honoured by the single-line text family (text, number, email, url,
|
|
218
|
+
# password -- all one Vue component) and by textarea. Other fields ignore
|
|
219
|
+
# them: the discrete ones (check, radio, select, chip group, date pickers)
|
|
220
|
+
# commit a value the moment it is picked, and richText/phone/file keep
|
|
221
|
+
# their own reporting. The options are declared on `Text`, so its other
|
|
222
|
+
# subclasses accept them without effect -- same as `leftIcon` and
|
|
223
|
+
# `onTypeStart` there.
|
|
224
|
+
#
|
|
225
|
+
# In both modes the change is also reported when focus leaves the field.
|
|
226
|
+
# Under `input` that only flushes a change already pending in the debounce
|
|
227
|
+
# window -- it cannot produce a second run, because a value that was
|
|
228
|
+
# already reported is not reported again.
|
|
229
|
+
module ChangeTrigger
|
|
230
|
+
def self.included(base)
|
|
231
|
+
# When the `onChange` / `onChangeAndLoad` actions fire.
|
|
232
|
+
#
|
|
233
|
+
# - `input` (default): after the user pauses typing for `changeDelay`.
|
|
234
|
+
# - `blur`: only once focus leaves the field. Use for actions too
|
|
235
|
+
# expensive to run mid-word -- a server round trip, a recalculation
|
|
236
|
+
# that rewrites other fields under the user's cursor.
|
|
237
|
+
#
|
|
238
|
+
# @example Round-trip only once the user is done with the field
|
|
239
|
+
# form.fields_text \
|
|
240
|
+
# name: 'user[promo_code]',
|
|
241
|
+
# label: 'Promo code',
|
|
242
|
+
# changeOn: 'blur',
|
|
243
|
+
# onChange: ->(action) { action.http_get url: verify_promo_path }
|
|
244
|
+
base.enum :changeOn, options: %w[input blur]
|
|
245
|
+
|
|
246
|
+
# Milliseconds of no typing before `changeOn: 'input'` fires.
|
|
247
|
+
# Defaults to 300. Ignored under `changeOn: 'blur'`.
|
|
248
|
+
#
|
|
249
|
+
# @example Slow down a search-as-you-type round trip
|
|
250
|
+
# form.fields_text \
|
|
251
|
+
# name: 'q',
|
|
252
|
+
# label: 'Search',
|
|
253
|
+
# changeDelay: 800,
|
|
254
|
+
# onChange: ->(action) { action.http_get url: search_path }
|
|
255
|
+
base.int :changeDelay
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
|
|
214
259
|
# Basic text input field for single-line text entry.
|
|
215
260
|
#
|
|
216
261
|
# The most common field type, supporting various customizations like
|
|
@@ -228,6 +273,8 @@ class Glib::JsonUi::ViewBuilder
|
|
|
228
273
|
#
|
|
229
274
|
# @see app/views/json_ui/garage/forms/basic.json.jbuilder Garage example
|
|
230
275
|
class Text < AbstractField
|
|
276
|
+
include ChangeTrigger
|
|
277
|
+
|
|
231
278
|
# Maximum number of characters allowed.
|
|
232
279
|
int :maxLength
|
|
233
280
|
|
|
@@ -6,17 +6,21 @@ module Glib
|
|
|
6
6
|
# Opt-in: if the host app has not run the gem's `create_snapshot_blob_references` migration,
|
|
7
7
|
# `SnapshotBlobReference.table_exists?` is false and the exclusion subquery short-circuits —
|
|
8
8
|
# the job then behaves identically to Rails' built-in unattached-blob cleanup, just delayed
|
|
9
|
-
# by 8 hours to give upload flows time
|
|
9
|
+
# by `Glib::PurgeUnattached::Config.min_age` (8 hours by default) to give upload flows time
|
|
10
|
+
# to attach.
|
|
10
11
|
#
|
|
11
12
|
# Schedule from the host app (e.g. via sidekiq-cron):
|
|
12
13
|
# Glib::PurgeUnattachedJob.perform_later
|
|
14
|
+
#
|
|
15
|
+
# Adjust the grace window from the host app (e.g. for a slower upload flow):
|
|
16
|
+
# Glib::PurgeUnattached::Config.min_age = 10.hours
|
|
13
17
|
class PurgeUnattachedJob < ApplicationJob
|
|
14
18
|
queue_as :cleanup
|
|
15
19
|
|
|
16
20
|
def perform(*_args)
|
|
17
21
|
scope = ActiveStorage::Blob
|
|
18
22
|
.unattached
|
|
19
|
-
.where('active_storage_blobs.created_at < ?',
|
|
23
|
+
.where('active_storage_blobs.created_at < ?', Glib::PurgeUnattached::Config.min_age.ago)
|
|
20
24
|
|
|
21
25
|
if SnapshotBlobReference.table_exists?
|
|
22
26
|
scope = scope.where.not(id: SnapshotBlobReference.select(:blob_id))
|
|
@@ -10,6 +10,27 @@ require 'hashdiff'
|
|
|
10
10
|
# 2. Nil has_one safe-nav in check_snapshot_changed.
|
|
11
11
|
# 3. has_one wrapping in diff_associations (V2 only tested with has_many in mapping-web).
|
|
12
12
|
# 4. ActiveStorage in diff_associations (convert Attached::One/Many to attachment records).
|
|
13
|
+
# 5. same_as_before? derives from the computed diff instead of the instance-local
|
|
14
|
+
# `snapshot_changed` flag (mapping-web's flag-gating silently dropped a target's
|
|
15
|
+
# snapshot when taken after the owner's -- see "Snapshot ordering" below, issue #481),
|
|
16
|
+
# with the flag re-entering as a NEGATIVE signal for first snapshots only (see
|
|
17
|
+
# `same_as_before?` for why an empty association baseline cannot decide those).
|
|
18
|
+
#
|
|
19
|
+
# ## Snapshot ordering: owner vs association targets
|
|
20
|
+
#
|
|
21
|
+
# Owner and association-target snapshots may be taken in ANY order within a request.
|
|
22
|
+
# `same_as_before?` decides from persisted state, so a target snapshotted after its owner
|
|
23
|
+
# (through the replaced association target) still records its version.
|
|
24
|
+
#
|
|
25
|
+
# The one residual ordering caveat: `diff` compares against the instance's IN-MEMORY
|
|
26
|
+
# attributes, so unsaved changes live only on the instance that holds them. If a target is
|
|
27
|
+
# modified in memory without saving, snapshot it via a local captured BEFORE the owner's
|
|
28
|
+
# `glib_create_snapshot!` (whose `with_lock` reload replaces the association targets) --
|
|
29
|
+
# reading the target back through the association snapshots the persisted state instead.
|
|
30
|
+
#
|
|
31
|
+
# `snapshot_changed` / `attributes_before_save` remain writable instance accessors for
|
|
32
|
+
# call-site use; `snapshot_changed` is consulted by the skip decision only as a NEGATIVE
|
|
33
|
+
# signal on first snapshots (see `same_as_before?`).
|
|
13
34
|
#
|
|
14
35
|
# To get all records of a model, you can use the following query:
|
|
15
36
|
# MyModel.joins(:snapshots).distinct
|
|
@@ -42,7 +63,16 @@ module Glib
|
|
|
42
63
|
# Only set snapshot_changed if not already set (handles case where before_save
|
|
43
64
|
# is called multiple times - e.g., when associated records are saved before parent)
|
|
44
65
|
if snapshot_changed.nil?
|
|
45
|
-
|
|
66
|
+
# For a save with no pending change `attributes_in_database` is empty -- an
|
|
67
|
+
# unchanged persisted record has no database-side delta -- which would make a
|
|
68
|
+
# first snapshot diff every attribute as an addition. Capture the full
|
|
69
|
+
# persisted state instead, so an unchanged save yields an empty item diff and
|
|
70
|
+
# the nil-guard keeps that baseline stable for later changes on the same
|
|
71
|
+
# instance. New records keep `attributes_in_database` (their all-nil
|
|
72
|
+
# originals): that non-empty baseline is exactly why a create's version 1
|
|
73
|
+
# records the full initial state. `unless` keeps the accessor nil, never
|
|
74
|
+
# `false`, for call-site nil-safety.
|
|
75
|
+
self.attributes_before_save = attributes_in_database.presence || (attributes unless new_record?)
|
|
46
76
|
self.snapshot_changed = check_snapshot_changed
|
|
47
77
|
end
|
|
48
78
|
end
|
|
@@ -106,8 +136,11 @@ module Glib
|
|
|
106
136
|
metadata: {} # Keep for potential future use, but main data in columns
|
|
107
137
|
}
|
|
108
138
|
|
|
109
|
-
# Don't create version if same as before
|
|
110
|
-
|
|
139
|
+
# Don't create version if same as before. Reuses `calculated_diff` instead of
|
|
140
|
+
# recomputing it inside `same_as_before?`. `version == 1` (no previous
|
|
141
|
+
# version exists) tells `same_as_before?` this is a FIRST snapshot, where the
|
|
142
|
+
# association baseline is empty and the flag carries decision weight.
|
|
143
|
+
unless same_as_before?(calculated_diff, first_snapshot: version == 1)
|
|
111
144
|
# No nil guard: active_snapshot's `create_snapshot!` raises on failure and
|
|
112
145
|
# always returns the snapshot.
|
|
113
146
|
snapshot = create_snapshot!(**snapshot_obj)
|
|
@@ -166,10 +199,29 @@ module Glib
|
|
|
166
199
|
snapshots.order(version: :desc).first
|
|
167
200
|
end
|
|
168
201
|
|
|
169
|
-
|
|
170
|
-
|
|
202
|
+
# Decides whether `glib_create_snapshot!` should skip the version write. Derived from
|
|
203
|
+
# the computed diff (persisted state), not from the instance-local `snapshot_changed`
|
|
204
|
+
# flag: the flag dies with its instance, and an owner's snapshot replaces association
|
|
205
|
+
# targets with fresh instances (`with_lock`'s reload clears the association cache, then
|
|
206
|
+
# the diff walk re-reads the targets), so flag-gating used to silently drop a target's
|
|
207
|
+
# follow-up snapshot taken through the association (issue #481).
|
|
208
|
+
#
|
|
209
|
+
# The one place the flag still decides: a FIRST snapshot whose association diff is
|
|
210
|
+
# meaningless. With no previous version, `fetch_snapshot_items(nil)` returns `{}`
|
|
211
|
+
# for associations, so every pre-existing child row reads as `+` and the diff alone
|
|
212
|
+
# can never prove a no-op — any never-snapshotted record with children would mint a
|
|
213
|
+
# version on every snapshot call (observed as spurious "Edited" audit entries on
|
|
214
|
+
# routine re-saves). There the flag is consulted as a NEGATIVE signal:
|
|
215
|
+
# - `false` — this instance crossed the before_save boundary and
|
|
216
|
+
# `check_snapshot_changed` found no item or child change, and the item diff is
|
|
217
|
+
# net-zero against `attributes_before_save` => skip;
|
|
218
|
+
# - `nil` — no save boundary on this instance (an owner-triggered snapshot of a
|
|
219
|
+
# replaced association target, issue #481) => mint;
|
|
220
|
+
# - `true` — a change was detected at the save boundary; falls through to the
|
|
221
|
+
# diff-based decision (a reverted change still no-ops).
|
|
222
|
+
def same_as_before?(computed_diff = diff, first_snapshot: false)
|
|
223
|
+
return true if first_snapshot && snapshot_changed == false && computed_diff['item'].blank?
|
|
171
224
|
|
|
172
|
-
computed_diff = diff
|
|
173
225
|
item_unchanged = computed_diff['item'].blank?
|
|
174
226
|
assoc_diff = computed_diff['associations']
|
|
175
227
|
associations_unchanged = assoc_diff.nil? || assoc_diff.values.all?(&:blank?)
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
json.title 'Test Page (Fields Change Trigger)'
|
|
2
|
+
|
|
3
|
+
page = json_ui_page json
|
|
4
|
+
|
|
5
|
+
render 'json_ui/garage/test_page/header', json: json, page: page
|
|
6
|
+
|
|
7
|
+
page.body(
|
|
8
|
+
childViews: ->(body) do
|
|
9
|
+
|
|
10
|
+
body.panels_responsive(
|
|
11
|
+
padding: glib_json_padding_body,
|
|
12
|
+
childViews: ->(res) do
|
|
13
|
+
res.h2 text: 'Fields Change Trigger'
|
|
14
|
+
res.spacer height: 8
|
|
15
|
+
res.label text: 'changeOn / changeDelay control WHEN a typed-input field reports its change to onChange.'
|
|
16
|
+
res.spacer height: 12
|
|
17
|
+
|
|
18
|
+
res.panels_form(
|
|
19
|
+
url: json_ui_garage_url(path: 'forms/generic_post'),
|
|
20
|
+
method: 'post',
|
|
21
|
+
childViews: ->(form) do
|
|
22
|
+
|
|
23
|
+
# ── Default: changeOn 'input', 300ms ──────────────────────────────
|
|
24
|
+
form.h4 text: 'Default (changeOn: input)'
|
|
25
|
+
form.spacer height: 8
|
|
26
|
+
form.label text: 'No options set: reports once the user pauses for the default 300ms.'
|
|
27
|
+
form.spacer height: 8
|
|
28
|
+
|
|
29
|
+
form.fields_text(
|
|
30
|
+
name: 'user[default_value]',
|
|
31
|
+
label: 'Default',
|
|
32
|
+
placeholder: 'default',
|
|
33
|
+
width: 'matchParent',
|
|
34
|
+
onChange: ->(action) do
|
|
35
|
+
action.logics_set(
|
|
36
|
+
targetId: 'status_default',
|
|
37
|
+
conditionalData: { text: { 'printf': ['Default: {0}', { 'var': 'user[default_value]' }] } }
|
|
38
|
+
)
|
|
39
|
+
end
|
|
40
|
+
)
|
|
41
|
+
form.spacer height: 8
|
|
42
|
+
form.label id: 'status_default', text: 'Default: idle'
|
|
43
|
+
|
|
44
|
+
form.spacer height: 12
|
|
45
|
+
form.hr width: 'matchParent'
|
|
46
|
+
form.spacer height: 12
|
|
47
|
+
|
|
48
|
+
# ── changeDelay ───────────────────────────────────────────────────
|
|
49
|
+
form.h4 text: 'changeDelay: 1500'
|
|
50
|
+
form.spacer height: 8
|
|
51
|
+
form.label text: 'Same input mode, longer pause. Leaving the field still reports right away, rather than waiting out the remaining delay.'
|
|
52
|
+
form.spacer height: 8
|
|
53
|
+
|
|
54
|
+
form.fields_text(
|
|
55
|
+
name: 'user[slow]',
|
|
56
|
+
label: 'Slow',
|
|
57
|
+
placeholder: 'slow',
|
|
58
|
+
width: 'matchParent',
|
|
59
|
+
changeDelay: 1500,
|
|
60
|
+
onChange: ->(action) do
|
|
61
|
+
action.logics_set(
|
|
62
|
+
targetId: 'status_slow',
|
|
63
|
+
conditionalData: { text: { 'printf': ['Slow: {0}', { 'var': 'user[slow]' }] } }
|
|
64
|
+
)
|
|
65
|
+
end
|
|
66
|
+
)
|
|
67
|
+
form.spacer height: 8
|
|
68
|
+
form.label id: 'status_slow', text: 'Slow: idle'
|
|
69
|
+
|
|
70
|
+
form.spacer height: 12
|
|
71
|
+
form.hr width: 'matchParent'
|
|
72
|
+
form.spacer height: 12
|
|
73
|
+
|
|
74
|
+
# ── changeOn: 'blur' ──────────────────────────────────────────────
|
|
75
|
+
form.h4 text: "changeOn: 'blur'"
|
|
76
|
+
form.spacer height: 8
|
|
77
|
+
form.label text: 'Stays idle no matter how long the user pauses; reports only once focus leaves.'
|
|
78
|
+
form.spacer height: 8
|
|
79
|
+
|
|
80
|
+
form.fields_text(
|
|
81
|
+
name: 'user[on_blur]',
|
|
82
|
+
label: 'Reports on blur',
|
|
83
|
+
placeholder: 'on blur',
|
|
84
|
+
width: 'matchParent',
|
|
85
|
+
changeOn: 'blur',
|
|
86
|
+
onChange: ->(action) do
|
|
87
|
+
action.logics_set(
|
|
88
|
+
targetId: 'status_on_blur',
|
|
89
|
+
conditionalData: { text: { 'printf': ['Blur: {0}', { 'var': 'user[on_blur]' }] } }
|
|
90
|
+
)
|
|
91
|
+
end
|
|
92
|
+
)
|
|
93
|
+
form.spacer height: 8
|
|
94
|
+
form.label id: 'status_on_blur', text: 'Blur: idle'
|
|
95
|
+
|
|
96
|
+
form.spacer height: 12
|
|
97
|
+
form.hr width: 'matchParent'
|
|
98
|
+
form.spacer height: 12
|
|
99
|
+
|
|
100
|
+
# ── Textarea on blur ──────────────────────────────────────────────
|
|
101
|
+
form.h4 text: "fields_textarea with changeOn: 'blur'"
|
|
102
|
+
form.spacer height: 8
|
|
103
|
+
|
|
104
|
+
form.fields_textarea(
|
|
105
|
+
name: 'user[notes]',
|
|
106
|
+
label: 'Notes',
|
|
107
|
+
placeholder: 'notes',
|
|
108
|
+
width: 'matchParent',
|
|
109
|
+
rows: 3,
|
|
110
|
+
changeOn: 'blur',
|
|
111
|
+
onChange: ->(action) do
|
|
112
|
+
action.logics_set(
|
|
113
|
+
targetId: 'status_notes',
|
|
114
|
+
conditionalData: { text: { 'printf': ['Notes: {0}', { 'var': 'user[notes]' }] } }
|
|
115
|
+
)
|
|
116
|
+
end
|
|
117
|
+
)
|
|
118
|
+
form.spacer height: 8
|
|
119
|
+
form.label id: 'status_notes', text: 'Notes: idle'
|
|
120
|
+
|
|
121
|
+
form.spacer height: 16
|
|
122
|
+
form.fields_submit text: 'Submit'
|
|
123
|
+
end
|
|
124
|
+
)
|
|
125
|
+
end
|
|
126
|
+
)
|
|
127
|
+
end
|
|
128
|
+
)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
require 'active_support/core_ext/numeric/time'
|
|
2
|
+
|
|
3
|
+
module Glib
|
|
4
|
+
module PurgeUnattached
|
|
5
|
+
# Global settings for Glib::PurgeUnattachedJob.
|
|
6
|
+
#
|
|
7
|
+
# `min_age` is how long an unattached ActiveStorage blob must exist before
|
|
8
|
+
# the purge may remove it. The 8-hour default leaves room for multi-step
|
|
9
|
+
# upload flows (direct upload -> form submit -> attach) to finish before
|
|
10
|
+
# their blob becomes eligible; apps with slower flows raise it:
|
|
11
|
+
#
|
|
12
|
+
# # config/initializers/glib.rb
|
|
13
|
+
# Glib::PurgeUnattached::Config.min_age = 10.hours
|
|
14
|
+
#
|
|
15
|
+
# Deliberately NOT a setting on the job class: the job is autoloaded from
|
|
16
|
+
# app/, so an initializer-assigned value would be wiped the first time the
|
|
17
|
+
# class is reloaded. This file lives in lib/ and is required eagerly by
|
|
18
|
+
# glib-web.rb, which is what makes the setting survive reloads.
|
|
19
|
+
class Config
|
|
20
|
+
# The shipped default. Apps opt into a different window via the setter.
|
|
21
|
+
DEFAULT_MIN_AGE = 8.hours
|
|
22
|
+
|
|
23
|
+
@@min_age = DEFAULT_MIN_AGE
|
|
24
|
+
|
|
25
|
+
def self.min_age
|
|
26
|
+
@@min_age
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.min_age=(value)
|
|
30
|
+
unless value.is_a?(ActiveSupport::Duration) && value.positive?
|
|
31
|
+
raise ArgumentError, "Invalid min_age: #{value.inspect}. Expected a positive ActiveSupport::Duration (e.g. 10.hours)."
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
@@min_age = value
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
data/lib/glib-web.rb
CHANGED
|
@@ -6,10 +6,12 @@ if defined?(::Rails)
|
|
|
6
6
|
end
|
|
7
7
|
require 'glib/value'
|
|
8
8
|
# Loaded eagerly (not autoloaded from app/) so an app initializer can set
|
|
9
|
-
# `Glib::JsonUi::Config.name_separator`
|
|
10
|
-
# autoloading during initialization -- and so the
|
|
9
|
+
# `Glib::JsonUi::Config.name_separator` or `Glib::PurgeUnattached::Config.min_age`
|
|
10
|
+
# without tripping Rails' ban on autoloading during initialization -- and so the
|
|
11
|
+
# setting survives a reload.
|
|
11
12
|
require 'glib/json_ui/config'
|
|
12
13
|
require 'glib/json_ui/name'
|
|
14
|
+
require 'glib/purge_unattached/config'
|
|
13
15
|
require 'glib/json_crawler'
|
|
14
16
|
|
|
15
17
|
require 'glib/dynamic_text'
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: glib-web
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 6.10.
|
|
4
|
+
version: 6.10.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- ''
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-09-
|
|
11
|
+
date: 2026-09-19 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activestorage
|
|
@@ -456,6 +456,7 @@ files:
|
|
|
456
456
|
- app/views/json_ui/garage/test_page/duplicate_names.json.jbuilder
|
|
457
457
|
- app/views/json_ui/garage/test_page/fields.json.jbuilder
|
|
458
458
|
- app/views/json_ui/garage/test_page/fields_captcha.json.jbuilder
|
|
459
|
+
- app/views/json_ui/garage/test_page/fields_change_trigger.json.jbuilder
|
|
459
460
|
- app/views/json_ui/garage/test_page/fields_creditCard.json.jbuilder
|
|
460
461
|
- app/views/json_ui/garage/test_page/fields_date_time.json.jbuilder
|
|
461
462
|
- app/views/json_ui/garage/test_page/fields_dynamicSelect.json.jbuilder
|
|
@@ -590,6 +591,7 @@ files:
|
|
|
590
591
|
- lib/glib/last_resort_unit_test.rb
|
|
591
592
|
- lib/glib/mailer_tester.rb
|
|
592
593
|
- lib/glib/non_http_integration_test.rb
|
|
594
|
+
- lib/glib/purge_unattached/config.rb
|
|
593
595
|
- lib/glib/rubocop.rb
|
|
594
596
|
- lib/glib/rubocop/cops/json_ui/base_nested_parameter.rb
|
|
595
597
|
- lib/glib/rubocop/cops/json_ui/nested_action_parameter.rb
|