concerns_on_rails 1.28.4 → 1.28.6
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 +169 -0
- data/README.md +157 -40
- data/lib/concerns_on_rails/configuration.rb +35 -0
- data/lib/concerns_on_rails/controllers/idempotentable.rb +76 -15
- data/lib/concerns_on_rails/controllers/includable.rb +88 -18
- data/lib/concerns_on_rails/controllers/localizable.rb +58 -5
- data/lib/concerns_on_rails/controllers/respondable.rb +125 -0
- data/lib/concerns_on_rails/controllers/secure_headable.rb +86 -7
- data/lib/concerns_on_rails/controllers/throttleable.rb +106 -10
- data/lib/concerns_on_rails/models/activatable.rb +90 -13
- data/lib/concerns_on_rails/models/addressable.rb +109 -1
- data/lib/concerns_on_rails/models/auditable.rb +49 -5
- data/lib/concerns_on_rails/models/counter_cacheable.rb +94 -17
- data/lib/concerns_on_rails/models/duplicable.rb +50 -7
- data/lib/concerns_on_rails/models/expirable.rb +59 -9
- data/lib/concerns_on_rails/models/hashable.rb +66 -11
- data/lib/concerns_on_rails/models/maskable.rb +83 -1
- data/lib/concerns_on_rails/models/monetizable.rb +36 -6
- data/lib/concerns_on_rails/models/schedulable.rb +82 -1
- data/lib/concerns_on_rails/models/sluggable.rb +18 -0
- data/lib/concerns_on_rails/models/taggable.rb +34 -1
- data/lib/concerns_on_rails/models/tokenizable.rb +121 -29
- data/lib/concerns_on_rails/support/include_tree.rb +74 -0
- data/lib/concerns_on_rails/support/money.rb +29 -0
- data/lib/concerns_on_rails/version.rb +1 -1
- data/lib/concerns_on_rails.rb +1 -0
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d01a41891ae267bab705b85ab63d6555e7057719a218738bd82c858c63febfa9
|
|
4
|
+
data.tar.gz: 3bcaebd299e65af41029baebb90ba1670a3502fde222071bffeeef86a4e1fb7b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 602d50edd8fd84224c73a6ddc09e756bf322f4b3b4e526d34e55774caa88594c54c3ba05d1920005c2152986035b230e2f982e5b6916c2a3a2ec402e1ac2c3c6
|
|
7
|
+
data.tar.gz: 9ab3f9c635ab132b284d673861c3a3a8282f93aa370f6f75cb1890a1205b9e3c924705b52afd7d182a94fb5247fdc38f51e94bcc4d2a0b1c8de39ea53ee03b54
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,174 @@
|
|
|
1
1
|
<!-- CHANGELOG.md -->
|
|
2
2
|
|
|
3
|
+
## 1.28.6 (2026-09-18)
|
|
4
|
+
|
|
5
|
+
Ten feature PRs deepening existing concerns, released as a patch by request: no new
|
|
6
|
+
concerns and no dependency changes, though several add an optional column or option.
|
|
7
|
+
Every PR was reviewed before merge and carries the review's fixes; the `### Changed`
|
|
8
|
+
and `### Fixed` sections below are those fixes, and they are the ones to read — a few
|
|
9
|
+
tighten validation or start raising where the code used to accept bad input silently.
|
|
10
|
+
Eight further PRs were reviewed in the same pass and held back: they carry defects
|
|
11
|
+
that need more than a patch-sized change.
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
- **Models::Maskable**: `masked_attributes` (every declared field masked, keyed like
|
|
15
|
+
`attributes`) and a `masked:` serialization option — `as_json(masked: true)` /
|
|
16
|
+
`to_json(masked: true)` / `serializable_hash(masked: true)` swap the declared fields
|
|
17
|
+
for their masked forms (`masked: [:email]` for a subset; undeclared fields raise).
|
|
18
|
+
Composes with `only:`/`except:`/`methods:`/`include:`; plain serialization is
|
|
19
|
+
unchanged. (#65)
|
|
20
|
+
- **Models::Activatable**: `before_activate` / `after_activate` / `before_deactivate` /
|
|
21
|
+
`after_deactivate` hooks (one transaction with the write; a raising after-hook rolls
|
|
22
|
+
back, a failed update skips it; overriding one moves that direction's batch verb to
|
|
23
|
+
the per-record path) and `activatable_by timestamps:` (`true` → `activated_at` /
|
|
24
|
+
`deactivated_at` stamped on each transition, or a Hash to rename/drop a side). The
|
|
25
|
+
batch fast path stamps too. (#78)
|
|
26
|
+
- **Models::Hashable**: `prefix:` prepends a literal to generated values (Stripe-style
|
|
27
|
+
public IDs such as `ord_k7m3pq9a`; string types only), and `to_param: true` makes the
|
|
28
|
+
hashed field the URL parameter (falls back to the id while blank). Both validated at
|
|
29
|
+
class load. (#70)
|
|
30
|
+
- **Models::Tokenizable**: `tokenizable_by … expires_in:` gives a token a lifetime —
|
|
31
|
+
`<field>_expires_at` is stamped on every generation, `authenticate_by_<field>` refuses
|
|
32
|
+
an expired token, `<field>_expired?` and the `<field>_expired` scope report it. New
|
|
33
|
+
`consume_<field>(value)` (every field) authenticates and revokes in one race-safe step
|
|
34
|
+
for single-use tokens. (#51)
|
|
35
|
+
- **Models::Auditable**: `ConcernsOnRails.setup { |c| c.audit_actor = -> { Current.user&.id } }`
|
|
36
|
+
sets the `"by"` actor once for every audited model that passes no `actor:` (resolved
|
|
37
|
+
per save, `instance_exec`'d on the record); `actor: false` opts a model out, and
|
|
38
|
+
`actor: :method_name` stamps a record method's value. (#50)
|
|
39
|
+
- **Models::CounterCacheable**: `recount_counter_caches!(association, parents:)` repairs
|
|
40
|
+
only the given parents (ids, records or a relation) — locked, zeroed and re-tallied in
|
|
41
|
+
one transaction, other rows untouched — so a post-import fix is O(their children).
|
|
42
|
+
Empty `parents:` is a no-op; the association is required when more than one is
|
|
43
|
+
declared. (#75)
|
|
44
|
+
- **Controllers::Includable**: nested include allow-lists — `includable :author,
|
|
45
|
+
comments: :author` accepts `?include=comments.author` (a path must match every
|
|
46
|
+
segment). `requested_includes(as: :query | :paths | :json)` returns the sanitized
|
|
47
|
+
includes for `includes`/`preload`, JSON:API serializers, or `as_json(include:)`;
|
|
48
|
+
`requested_include_paths` exposes the dotted paths. `default:` loads named paths when
|
|
49
|
+
`?include` is absent (blank opts out); `strategy:` picks
|
|
50
|
+
`includes`/`preload`/`eager_load`. New `Support::IncludeTree`. (#69)
|
|
51
|
+
- **Controllers::Idempotentable**: replays now carry the original response's `Location`,
|
|
52
|
+
`Content-Location`, `ETag`, `Last-Modified` and `Link` headers, so a retried `create`
|
|
53
|
+
still says where the resource lives. `idempotent_actions … headers:` tunes the
|
|
54
|
+
allow-list (`[]` to capture none). Records written before this release replay
|
|
55
|
+
unchanged. (#47)
|
|
56
|
+
- **Controllers::Throttleable**: `throttle_by … if:/unless:` per-request skip conditions
|
|
57
|
+
(Symbol method or callable; both must pass), mirroring Rails 7.2's `rate_limit`. A
|
|
58
|
+
throttled request instruments `rate_limited.concerns_on_rails` (rule, discriminator,
|
|
59
|
+
count/limit/period, reset_at/retry_after, controller, action) through the new public
|
|
60
|
+
`on_rate_limited(rule, result)` hook. (#44)
|
|
61
|
+
- **Controllers::Respondable**: `respondable_by error_format: :problem_details,
|
|
62
|
+
problem_type_base:` makes `render_error` emit RFC 9457 `application/problem+json`
|
|
63
|
+
documents (`type`, `title`, `status`, `detail`, `instance`, plus `code`/`errors`
|
|
64
|
+
extensions). Every concern that renders errors through Respondable follows, so the
|
|
65
|
+
switch is app-wide. The default stays `:envelope`. (#57)
|
|
66
|
+
|
|
67
|
+
### Changed
|
|
68
|
+
- **Controllers::Throttleable**: with several applicable rules the `X-RateLimit-*`
|
|
69
|
+
headers now describe the tightest passing rule (fewest remaining) instead of the last
|
|
70
|
+
one declared, and a tie on remaining is broken by the rule that resets *last* — so the
|
|
71
|
+
advertised `X-RateLimit-Reset` can no longer promise a 60-second wait when an hourly
|
|
72
|
+
rule is the real budget. `result[:discriminator]` is now available to
|
|
73
|
+
`throttled_response`. Note it is raw personal data (an IP or a user id) in the
|
|
74
|
+
instrumentation payload too; override `on_rate_limited` to hash or drop it. (#44)
|
|
75
|
+
- **Controllers::Throttleable**: `if:`/`unless:` conditions are now arity-aware — a
|
|
76
|
+
one-argument callable receives the controller, a zero-arity Proc is still
|
|
77
|
+
`instance_exec`'d. Previously a `->(c) { … }` condition raised `ArgumentError` on
|
|
78
|
+
every request. (#44)
|
|
79
|
+
- **Models::Auditable**: an explicit `actor: nil` now keeps meaning "never record who"
|
|
80
|
+
and no longer falls through to the gem-wide `config.audit_actor`; only omitting
|
|
81
|
+
`actor:` takes the fallback. `config.audit_actor` accepts a non-Proc callable and
|
|
82
|
+
`false` (a synonym for nil), and rejects a lambda that demands arguments at
|
|
83
|
+
configuration time rather than raising inside `before_save`. (#50)
|
|
84
|
+
- **Models::CounterCacheable**: `recount_counter_caches!` now raises `ArgumentError`
|
|
85
|
+
for an association with no declared counter, instead of returning `{}` as a silent
|
|
86
|
+
success; and a scoped repair locks the named parent rows before tallying, so a
|
|
87
|
+
concurrent child write can no longer be lost. (#75)
|
|
88
|
+
- **Controllers::Idempotentable**: response-header capture is case-insensitive, so an
|
|
89
|
+
app that sets `location` rather than `Location` (correct under Rack 3) is captured on
|
|
90
|
+
every supported Rails version, not only 7.1+. `idempotency_scope` is now a documented
|
|
91
|
+
public override point — the default namespace has no per-principal component, so two
|
|
92
|
+
users sharing a client-chosen key can be served each other's cached response. (#47)
|
|
93
|
+
- **Controllers::Respondable**: the problem-details media type is emitted as bare
|
|
94
|
+
`application/problem+json`, with no `charset` parameter — RFC 9457 registers none, and
|
|
95
|
+
strict clients reject the parameterised form. (#57)
|
|
96
|
+
|
|
97
|
+
### Fixed
|
|
98
|
+
- **Models::Activatable**: lifecycle hooks are invoked with `send`, so a `private` hook
|
|
99
|
+
override runs instead of raising `NoMethodError` — which in a batch verb aborted and
|
|
100
|
+
rolled back the whole run. (#78)
|
|
101
|
+
- **Models::Hashable**: the `to_param: true` / Sluggable conflict now raises in *either*
|
|
102
|
+
declaration order. With Hashable declared first, friendly_id's `to_param` used to land
|
|
103
|
+
above Hashable's and silently win, so `to_param: true` did nothing. (#70)
|
|
104
|
+
- **Models::Tokenizable**: `expires_in:` now requires an `ActiveSupport::Duration` or a
|
|
105
|
+
`Numeric`. `expires_in: 2.hours.from_now` was accepted as ~1.8 billion seconds (tokens
|
|
106
|
+
effectively never expired) and `expires_in: "2 hours"` as 2 seconds. A model missing
|
|
107
|
+
both token columns now reports them in one error. (#51)
|
|
108
|
+
- **Controllers::Includable**: `requested_includes(as: :paths)` returns a fresh array on
|
|
109
|
+
the `default:` path; it previously handed back the class attribute itself, so a caller
|
|
110
|
+
mutating the result corrupted every later request in the process. (#69)
|
|
111
|
+
|
|
112
|
+
## 1.28.5 (2026-09-17)
|
|
113
|
+
|
|
114
|
+
Eight feature PRs deepening existing concerns, released as a patch by request:
|
|
115
|
+
no new concerns and no dependency changes, though several add an optional
|
|
116
|
+
column or option. Every PR was reviewed before merge and carries the review's
|
|
117
|
+
fixes; the notes below call out the ones that change a documented behaviour.
|
|
118
|
+
1522 examples, 0 failures.
|
|
119
|
+
|
|
120
|
+
### Added
|
|
121
|
+
- **Models::Schedulable**: `overlapping(from, to)` scope and `overlaps?`
|
|
122
|
+
predicate for booking-clash checks. Open-ended windows (a `NULL` start or
|
|
123
|
+
end), touching intervals, reversed arguments and Range forms behave
|
|
124
|
+
identically in SQL and in Ruby. Affixable like the other scopes. (#58)
|
|
125
|
+
- **Models::Duplicable**: per-call `only:` / `except:` association selection —
|
|
126
|
+
`invoice.duplicate!(except: :line_items)`. The macro's list stays the
|
|
127
|
+
ceiling, so a controller param cannot smuggle in an undeclared association.
|
|
128
|
+
An explicit `nil` counts as passed, not absent, so an empty checkbox list
|
|
129
|
+
copies nothing rather than everything. (#73)
|
|
130
|
+
- **Models::Monetizable**: scope-aware `sum_`, `average_`, `minimum_` and
|
|
131
|
+
`maximum_<name>` aggregates plus their `formatted_` twins, with per-call
|
|
132
|
+
formatting overrides. BigDecimal throughout; a grouped relation returns a
|
|
133
|
+
Hash of converted values instead of raising. (#67)
|
|
134
|
+
- **Models::Taggable**: `tag_counts` — tag to record count in one `GROUP BY`
|
|
135
|
+
query, relation-aware, with `limit:` for the top N. A `select`, `group` or
|
|
136
|
+
`order` on the relation is stripped; a `limit`/`offset` window is honoured by
|
|
137
|
+
resolving it to ids first. (#56)
|
|
138
|
+
- **Models::Expirable**: `before_expire` / `after_expire` lifecycle hooks,
|
|
139
|
+
`expire_in!(duration)` and `clear_expiry!`. The hooks fire when a write
|
|
140
|
+
actually expires the record; a future time only schedules expiry and fires
|
|
141
|
+
nothing, so `after_expire { account.downgrade! }` is safe next to
|
|
142
|
+
`trial.expire_in!(14.days)`. Overriding either hook moves `expire_all` to the
|
|
143
|
+
per-record path. (#54)
|
|
144
|
+
- **Controllers::SecureHeadable**: HSTS, cross-origin (COOP/COEP/CORP) and
|
|
145
|
+
Permissions-Policy presets, plus the `:recommended` and
|
|
146
|
+
`:cross_origin_isolation` bundles. `:recommended` uses `(self)`-scoped
|
|
147
|
+
permission values, so it denies third-party frames without disabling the
|
|
148
|
+
app's own camera, microphone, geolocation or Payment Request. HSTS is skipped
|
|
149
|
+
on a plaintext request (RFC 6797 §7.2) and never overwrites a stricter value
|
|
150
|
+
already on the response. (#55)
|
|
151
|
+
- **Models::Addressable**: `address_fingerprint` (SHA-256 of the normalized
|
|
152
|
+
address), `same_address_as?`, `address_parts_changed?` and a `fingerprint:`
|
|
153
|
+
column option with a `with_address` finder for deduplication reports. The
|
|
154
|
+
column is stamped in `before_save`, after every `before_validation`, so a
|
|
155
|
+
sibling concern rewriting a mapped column cannot desync it. (#79)
|
|
156
|
+
- **Controllers::Localizable**: `Content-Language` response header and
|
|
157
|
+
`Vary: Accept-Language`, both written before the action so a
|
|
158
|
+
`rescue_from`-rendered error carries them. Rails' own `Vary: Accept` is
|
|
159
|
+
preserved rather than suppressed, and `Vary` is advertised only when
|
|
160
|
+
`Accept-Language` can actually change the resolved locale. (#53)
|
|
161
|
+
|
|
162
|
+
### Notes
|
|
163
|
+
- **Addressable** defines `address_changed?` only when the model has no
|
|
164
|
+
`address` column of its own, so it never shadows ActiveModel's dirty
|
|
165
|
+
predicate; `address_parts_changed?` is always available.
|
|
166
|
+
- Existing rows keep a `NULL` address fingerprint until they are re-saved.
|
|
167
|
+
Backfill with `Model.find_each(&:save)`; `update_columns`, `insert_all` and
|
|
168
|
+
`upsert_all` bypass callbacks and leave it stale.
|
|
169
|
+
- The address digest is unkeyed, so treat the column as revealing the address.
|
|
170
|
+
Do not pair it with an `encryptable` address column.
|
|
171
|
+
|
|
3
172
|
## 1.28.4 (2026-09-16)
|
|
4
173
|
|
|
5
174
|
Eleven bug-fix PRs (#91–#101) from the audit of the shipped gem, released as a
|