concerns_on_rails 1.23.0 → 1.24.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 +12 -0
- data/README.md +27 -6
- data/lib/concerns_on_rails/configuration.rb +27 -0
- data/lib/concerns_on_rails/controllers/idempotentable.rb +7 -4
- data/lib/concerns_on_rails/controllers/throttleable.rb +7 -3
- data/lib/concerns_on_rails/legacy_aliases.rb +25 -29
- data/lib/concerns_on_rails/models/activatable.rb +1 -1
- data/lib/concerns_on_rails/models/addressable.rb +1 -1
- data/lib/concerns_on_rails/models/auditable.rb +1 -1
- data/lib/concerns_on_rails/models/counter_cacheable.rb +1 -1
- data/lib/concerns_on_rails/models/encryptable.rb +2 -2
- data/lib/concerns_on_rails/models/expirable.rb +1 -1
- data/lib/concerns_on_rails/models/hashable.rb +2 -1
- data/lib/concerns_on_rails/models/lockable.rb +2 -1
- data/lib/concerns_on_rails/models/monetizable.rb +1 -1
- data/lib/concerns_on_rails/models/publishable.rb +1 -1
- data/lib/concerns_on_rails/models/schedulable.rb +1 -1
- data/lib/concerns_on_rails/models/sequenceable.rb +3 -3
- data/lib/concerns_on_rails/models/sluggable.rb +12 -2
- data/lib/concerns_on_rails/models/soft_deletable.rb +1 -1
- data/lib/concerns_on_rails/models/sortable.rb +11 -2
- data/lib/concerns_on_rails/models/stateable.rb +1 -1
- data/lib/concerns_on_rails/models/storable.rb +1 -1
- data/lib/concerns_on_rails/models/taggable.rb +1 -1
- data/lib/concerns_on_rails/models/tokenizable.rb +1 -1
- data/lib/concerns_on_rails/support/column_guard.rb +23 -4
- data/lib/concerns_on_rails/version.rb +1 -1
- data/lib/concerns_on_rails.rb +95 -67
- metadata +52 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a5361fdba6417a0a46213e0e8e41f8ced13d00f76cb17f102404d1d794a98a39
|
|
4
|
+
data.tar.gz: 0fab62907022084c9b54d1cca52f8b4eeefbecced0a4bc905e4b3a8a55f8f582
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5654215000d983a63260507d6741090add3b7861e200e81ebd180ad7d7de61891c0646c49b666d4b01b179b51ae88d1da73386ffb35bc1cbb24328d8f3238c99
|
|
7
|
+
data.tar.gz: 436b80fa129a4a3922a0a6b7ba4945f90205f1071a4d190875fa26e87458bb39ba483b0f682bff0c64cd3fb09fda79ec15cec776f22b3d085af03d14fe5a2275
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
<!-- CHANGELOG.md -->
|
|
2
2
|
|
|
3
|
+
## 1.24.0 (2026-08-15)
|
|
4
|
+
|
|
5
|
+
A developer-experience wave from the 2026-08-15 usability review: leaner install, lazy loading, teaching errors, and one-initializer store configuration. No behavior changes for configured concerns. 1084 examples, 0 failures.
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
- **Dependencies**: the gem now depends on `actionpack` / `activerecord` / `activesupport` instead of the full `rails` meta-gem (API-only hosts stop pulling Action Cable / Mailbox / Text), and the `acts_as_list` constraint is `>= 0.7.5, < 2` — the old `~> 0.7.5` pin was an unresolvable Bundler conflict for any app already on acts_as_list 1.x. Suite verified against acts_as_list 1.2.6.
|
|
9
|
+
- **Lazy loading**: `require "concerns_on_rails"` no longer eagerly loads all 40 concern/support files — everything is autoloaded on first constant reference, and `friendly_id` / `acts_as_list` load only when Sluggable / Sortable is actually used (a missing gem raises `ConcernsOnRails::MissingDependency`, a `LoadError` subclass whose message names the Gemfile line to add). The pre-1.6 top-level aliases (`ConcernsOnRails::Sluggable`, …) keep working, now resolved lazily via `const_missing`.
|
|
10
|
+
- **Support::ColumnGuard** (all model concerns): the missing-column `ArgumentError` now teaches the fix — it appends a ready-to-paste `bin/rails generate migration ...` command carrying the concern's expected column type (e.g. `AddDeletedAtToArticles deleted_at:datetime`; token/slug columns suggest `string:uniq`, Encryptable blind-index columns `string:index`).
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **`ConcernsOnRails.setup`**: gem-wide configuration entry point (`lib/concerns_on_rails/configuration.rb`). First setting: `config.cache_store` — a store or a Proc (e.g. `-> { Rails.cache }`) used as the fallback store by `Controllers::Throttleable` and `Controllers::Idempotentable`, so one initializer line replaces per-controller `self.throttleable_store = ...` / `self.idempotency_store = ...` wiring. A class-level store still wins; with neither configured the concerns raise exactly as before.
|
|
14
|
+
|
|
3
15
|
## 1.23.0 (2026-07-26)
|
|
4
16
|
|
|
5
17
|
Two new model concerns — the roadmap picks after 1.22's fixes round: the right-to-erasure capability that completes the sensitive-data suite, and the concern-aware deep copy that makes "duplicate this invoice" safe next to unique slugs, tokens, and sequence numbers. 1075 examples, 0 failures.
|
data/README.md
CHANGED
|
@@ -12,7 +12,7 @@ One `include`, one declarative macro — done.
|
|
|
12
12
|
[](https://rubyonrails.org)
|
|
13
13
|
[](#-license)
|
|
14
14
|
|
|
15
|
-
🧩 **
|
|
15
|
+
🧩 **26 model concerns** · 🎮 **16 controller concerns** · 🪶 **lean deps** · ✅ **schema-validated**
|
|
16
16
|
|
|
17
17
|
</div>
|
|
18
18
|
|
|
@@ -96,7 +96,7 @@ Article.published.without_deleted.find("hello-world")
|
|
|
96
96
|
|
|
97
97
|
- **Twenty-six model concerns + sixteen controller concerns**, all production-ready
|
|
98
98
|
- **One include, one macro** — no boilerplate, no glue code
|
|
99
|
-
- **Lean dependencies** — only `acts_as_list` (Sortable) and `friendly_id` (Sluggable); controller concerns have zero extra deps
|
|
99
|
+
- **Lean dependencies** — only `acts_as_list` (Sortable) and `friendly_id` (Sluggable), and both load **lazily**: an app that never includes those concerns never loads them. Depends on `activerecord`/`actionpack`/`activesupport`, not the full `rails` meta-gem; controller concerns have zero extra deps
|
|
100
100
|
- **Schema-validated configuration** — every macro checks that the configured column exists and raises `ArgumentError` early
|
|
101
101
|
- **Composable** — concerns are independent; mix and match per model
|
|
102
102
|
|
|
@@ -107,7 +107,7 @@ Article.published.without_deleted.find("hello-world")
|
|
|
107
107
|
Add to your application's `Gemfile`:
|
|
108
108
|
|
|
109
109
|
```ruby
|
|
110
|
-
gem "concerns_on_rails", "~> 1.
|
|
110
|
+
gem "concerns_on_rails", "~> 1.24"
|
|
111
111
|
```
|
|
112
112
|
|
|
113
113
|
Or pull the latest from GitHub:
|
|
@@ -122,6 +122,27 @@ Then run:
|
|
|
122
122
|
bundle install
|
|
123
123
|
```
|
|
124
124
|
|
|
125
|
+
### Optional: one-initializer configuration
|
|
126
|
+
|
|
127
|
+
Everything works with zero configuration. The store-backed controller concerns
|
|
128
|
+
(`Throttleable`, `Idempotentable`) need a cache store — set it once, gem-wide,
|
|
129
|
+
instead of per controller class:
|
|
130
|
+
|
|
131
|
+
```ruby
|
|
132
|
+
# config/initializers/concerns_on_rails.rb
|
|
133
|
+
ConcernsOnRails.setup do |config|
|
|
134
|
+
config.cache_store = -> { Rails.cache } # fallback for Throttleable / Idempotentable
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Encryptable's key lives in its own config (see the Encryptable section):
|
|
138
|
+
ConcernsOnRails.configure_encryption do |c|
|
|
139
|
+
c.key = -> { Rails.application.credentials.dig(:encryption, :key) }
|
|
140
|
+
end
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
A per-class `self.throttleable_store = ...` / `self.idempotency_store = ...`
|
|
144
|
+
still wins over the gem-wide fallback.
|
|
145
|
+
|
|
125
146
|
---
|
|
126
147
|
|
|
127
148
|
## 🧪 Compatibility
|
|
@@ -1599,7 +1620,7 @@ Fixed-window counter: the key embeds a floored time bucket (`epoch / period`) so
|
|
|
1599
1620
|
|
|
1600
1621
|
**Notes**
|
|
1601
1622
|
- The store MUST support **atomic increment-with-expiry** (`Rails.cache` with `#increment`, or Redis) — a non-atomic store under-counts under concurrency.
|
|
1602
|
-
- There is **no in-process default store** on purpose: the first throttled request raises `ArgumentError` until you set `throttleable_store
|
|
1623
|
+
- There is **no in-process default store** on purpose: the first throttled request raises `ArgumentError` until you set `throttleable_store` (or the gem-wide fallback `ConcernsOnRails.setup { |c| c.cache_store = -> { Rails.cache } }`), so you never silently rate-limit per-process.
|
|
1603
1624
|
- When `Respondable` is included, the 429 body delegates to `render_error` (`code: "rate_limited"`).
|
|
1604
1625
|
- Backports the essentials of Rails 7.2's `rate_limit` (with standardized headers) to Rails 5.0+. For richer rules (fail2ban, allow/deny lists, exponential backoff) reach for [`rack-attack`](https://github.com/rack/rack-attack).
|
|
1605
1626
|
|
|
@@ -1648,7 +1669,7 @@ Per-key lifecycle: claim atomically (`write unless_exist`, TTL `lock_ttl:`) →
|
|
|
1648
1669
|
|
|
1649
1670
|
**Notes**
|
|
1650
1671
|
- Cache keys are scoped per `controller#action` and the client key is SHA256-hashed, so the same key on different endpoints never collides.
|
|
1651
|
-
- There is **no in-process default store** on purpose: the first keyed request raises `ArgumentError` until you set `idempotency_store
|
|
1672
|
+
- There is **no in-process default store** on purpose: the first keyed request raises `ArgumentError` until you set `idempotency_store` (or the gem-wide fallback `ConcernsOnRails.setup { |c| c.cache_store = -> { Rails.cache } }`).
|
|
1652
1673
|
- When `Respondable` is included, the 400/409/422 bodies delegate to `render_error`.
|
|
1653
1674
|
- Declare halting filters (authentication, `Throttleable`) **before** including this concern — a 401/403 rendered by an inner filter would be cached and replayed for the full TTL. Responses rendered by `rescue_from` handlers are never cached.
|
|
1654
1675
|
- Keys must be ≤255 chars with no control characters (the raw key is echoed in `X-Idempotency-Key`); set `lock_ttl:` above the slowest declared action's worst case.
|
|
@@ -1813,7 +1834,7 @@ Both forms reference the same module, so you can freely mix them.
|
|
|
1813
1834
|
bundle install # install dev dependencies
|
|
1814
1835
|
bundle exec rspec # run the test suite
|
|
1815
1836
|
gem build concerns_on_rails.gemspec # build the gem
|
|
1816
|
-
gem install ./concerns_on_rails-1.
|
|
1837
|
+
gem install ./concerns_on_rails-1.24.0.gem # install locally
|
|
1817
1838
|
```
|
|
1818
1839
|
|
|
1819
1840
|
The test suite uses an in-memory SQLite database and a lightweight `FakeController` harness for controller-concern specs — no Rails routes or boot required.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module ConcernsOnRails
|
|
2
|
+
# Gem-wide configuration, set once from an initializer:
|
|
3
|
+
#
|
|
4
|
+
# ConcernsOnRails.setup do |config|
|
|
5
|
+
# config.cache_store = -> { Rails.cache }
|
|
6
|
+
# end
|
|
7
|
+
#
|
|
8
|
+
# `cache_store` is the fallback store consulted by Controllers::Throttleable
|
|
9
|
+
# and Controllers::Idempotentable when the controller class hasn't set its
|
|
10
|
+
# own (`self.throttleable_store = ...` / `self.idempotency_store = ...`
|
|
11
|
+
# still win). A store object or a zero-arg callable — prefer a Proc so
|
|
12
|
+
# `Rails.cache` is read lazily, after the framework has booted. The store
|
|
13
|
+
# contract is unchanged: atomic #increment for throttling, #read /
|
|
14
|
+
# #write(expires_in:, unless_exist:) / #delete for idempotency. There is
|
|
15
|
+
# still no in-process default on purpose — a non-atomic store silently
|
|
16
|
+
# under-counts, so the host must opt in explicitly (just once, here).
|
|
17
|
+
class Configuration
|
|
18
|
+
attr_accessor :cache_store
|
|
19
|
+
|
|
20
|
+
# The fallback store with any callable resolved (per lookup, so a Proc
|
|
21
|
+
# reading Rails.cache follows a swapped-out cache in tests). nil when the
|
|
22
|
+
# host never configured one.
|
|
23
|
+
def resolved_cache_store
|
|
24
|
+
cache_store.respond_to?(:call) ? cache_store.call : cache_store
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -33,8 +33,10 @@ module ConcernsOnRails
|
|
|
33
33
|
# The claim is taken atomically via `write(..., unless_exist: true)`
|
|
34
34
|
# (memcached `add` / Redis `SET NX` through Rails.cache); a store without
|
|
35
35
|
# that atomicity is best-effort under concurrency. There is no in-process
|
|
36
|
-
# default store on purpose — configure one explicitly
|
|
37
|
-
#
|
|
36
|
+
# default store on purpose — configure one explicitly (per controller as
|
|
37
|
+
# above, or once for the whole app via `ConcernsOnRails.setup { |c|
|
|
38
|
+
# c.cache_store = -> { Rails.cache } }`) or the first keyed request raises
|
|
39
|
+
# ArgumentError. Note that responses rendered by
|
|
38
40
|
# `rescue_from` handlers bypass the around filter's success path and are
|
|
39
41
|
# never cached.
|
|
40
42
|
#
|
|
@@ -246,12 +248,13 @@ module ConcernsOnRails
|
|
|
246
248
|
end
|
|
247
249
|
|
|
248
250
|
def idempotency_store!
|
|
249
|
-
store = self.class.idempotency_store
|
|
251
|
+
store = self.class.idempotency_store || ConcernsOnRails.config.resolved_cache_store
|
|
250
252
|
return store if store
|
|
251
253
|
|
|
252
254
|
raise ArgumentError,
|
|
253
255
|
"ConcernsOnRails::Controllers::Idempotentable: no store configured. " \
|
|
254
|
-
"Set `self.idempotency_store = Rails.cache` " \
|
|
256
|
+
"Set `self.idempotency_store = Rails.cache` on the controller, or the gem-wide " \
|
|
257
|
+
"fallback: ConcernsOnRails.setup { |c| c.cache_store = -> { Rails.cache } } " \
|
|
255
258
|
"(must support #read, #write(expires_in:, unless_exist:) and #delete)."
|
|
256
259
|
end
|
|
257
260
|
|
|
@@ -23,7 +23,9 @@ module ConcernsOnRails
|
|
|
23
23
|
# exact. The store MUST support atomic increment-with-expiry (`Rails.cache`
|
|
24
24
|
# with `#increment`, or Redis); a non-atomic store under-counts under
|
|
25
25
|
# concurrency. There is no in-process default store on purpose — configure
|
|
26
|
-
# one explicitly or
|
|
26
|
+
# one explicitly (per controller as above, or once for the whole app via
|
|
27
|
+
# `ConcernsOnRails.setup { |c| c.cache_store = -> { Rails.cache } }`) or
|
|
28
|
+
# the first throttled request raises ArgumentError.
|
|
27
29
|
module Throttleable
|
|
28
30
|
extend ActiveSupport::Concern
|
|
29
31
|
|
|
@@ -161,12 +163,14 @@ module ConcernsOnRails
|
|
|
161
163
|
end
|
|
162
164
|
|
|
163
165
|
def throttle_store!
|
|
164
|
-
store = self.class.throttleable_store
|
|
166
|
+
store = self.class.throttleable_store || ConcernsOnRails.config.resolved_cache_store
|
|
165
167
|
return store if store
|
|
166
168
|
|
|
167
169
|
raise ArgumentError,
|
|
168
170
|
"ConcernsOnRails::Controllers::Throttleable: no store configured. " \
|
|
169
|
-
"Set `self.throttleable_store = Rails.cache`
|
|
171
|
+
"Set `self.throttleable_store = Rails.cache` on the controller, or the gem-wide " \
|
|
172
|
+
"fallback: ConcernsOnRails.setup { |c| c.cache_store = -> { Rails.cache } } " \
|
|
173
|
+
"(must support atomic #increment)."
|
|
170
174
|
end
|
|
171
175
|
|
|
172
176
|
def throttle_action_name
|
|
@@ -1,31 +1,27 @@
|
|
|
1
1
|
module ConcernsOnRails
|
|
2
|
-
# Backwards-compatibility aliases for pre-1.6 module paths
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
CounterCacheable = Models::CounterCacheable
|
|
28
|
-
Encryptable = Models::Encryptable
|
|
29
|
-
Anonymizable = Models::Anonymizable
|
|
30
|
-
Duplicable = Models::Duplicable
|
|
2
|
+
# Backwards-compatibility aliases for pre-1.6 module paths, resolved lazily:
|
|
3
|
+
# eager `Sluggable = Models::Sluggable` assignments would force-load every
|
|
4
|
+
# concern file at boot and defeat the loader's autoload laziness. The first
|
|
5
|
+
# reference to `ConcernsOnRails::<Name>` loads the real module and pins the
|
|
6
|
+
# alias constant, so `include ConcernsOnRails::Sluggable` works unchanged.
|
|
7
|
+
# New code is encouraged to use the namespaced form:
|
|
8
|
+
# `ConcernsOnRails::Models::Sluggable`.
|
|
9
|
+
#
|
|
10
|
+
# Note `Sortable` resolves to Models::Sortable (as it always has) — the
|
|
11
|
+
# controller concern is only reachable as Controllers::Sortable.
|
|
12
|
+
LEGACY_MODEL_ALIASES = %i[
|
|
13
|
+
Sluggable Sortable Publishable SoftDeletable Hashable Schedulable
|
|
14
|
+
Expirable Normalizable Searchable Activatable Tokenizable Stateable
|
|
15
|
+
Addressable Sequenceable Taggable Sanitizable Maskable Monetizable
|
|
16
|
+
Auditable Lockable Aliasable Storable CounterCacheable Encryptable
|
|
17
|
+
Anonymizable Duplicable
|
|
18
|
+
].freeze
|
|
19
|
+
|
|
20
|
+
def self.const_missing(name)
|
|
21
|
+
return super unless LEGACY_MODEL_ALIASES.include?(name)
|
|
22
|
+
|
|
23
|
+
# Idempotent under a concurrent first reference: both threads pin the
|
|
24
|
+
# same module object, and once pinned const_missing never fires again.
|
|
25
|
+
const_set(name, Models.const_get(name))
|
|
26
|
+
end
|
|
31
27
|
end
|
|
@@ -33,7 +33,7 @@ module ConcernsOnRails
|
|
|
33
33
|
|
|
34
34
|
def activatable_by(field = DEFAULT_FIELD, prefix: nil, suffix: nil)
|
|
35
35
|
self.activatable_field = field.to_sym
|
|
36
|
-
ensure_columns!("ConcernsOnRails::Models::Activatable", activatable_field)
|
|
36
|
+
ensure_columns!("ConcernsOnRails::Models::Activatable", activatable_field, types: :boolean)
|
|
37
37
|
|
|
38
38
|
# Affix the scope names so two concerns that each define `.active`
|
|
39
39
|
# (e.g. SoftDeletable / Expirable) can coexist on one model.
|
|
@@ -86,7 +86,7 @@ module ConcernsOnRails
|
|
|
86
86
|
raise ArgumentError, "#{LABEL}: unknown address part(s): #{unknown.join(', ')}" if unknown.any?
|
|
87
87
|
|
|
88
88
|
overrides = mapping.to_h { |part, column| [part.to_sym, column.to_sym] }
|
|
89
|
-
ensure_columns!(LABEL, overrides.values)
|
|
89
|
+
ensure_columns!(LABEL, overrides.values, types: :string)
|
|
90
90
|
DEFAULT_FIELDS.merge(overrides).select { |_part, column| column_names.include?(column.to_s) }
|
|
91
91
|
end
|
|
92
92
|
|
|
@@ -74,7 +74,7 @@ module ConcernsOnRails
|
|
|
74
74
|
self.auditable_actor = actor
|
|
75
75
|
self.auditable_max_entries = max_entries
|
|
76
76
|
self.auditable_max_value_length = max_value_length
|
|
77
|
-
ensure_columns!(LABEL, into, *fields)
|
|
77
|
+
ensure_columns!(LABEL, into, *fields, types: { into => :text })
|
|
78
78
|
auditable_guard_encryptable!(fields)
|
|
79
79
|
|
|
80
80
|
before_save :auditable_capture_changes
|
|
@@ -232,13 +232,13 @@ module ConcernsOnRails
|
|
|
232
232
|
def encryptable(*fields, type: :string, key: nil, blind_index: nil)
|
|
233
233
|
type = type.to_sym
|
|
234
234
|
encryptable_validate!(fields, type, blind_index)
|
|
235
|
-
ensure_columns!(LABEL, *fields)
|
|
235
|
+
ensure_columns!(LABEL, *fields, types: :text)
|
|
236
236
|
|
|
237
237
|
fields.each do |field|
|
|
238
238
|
field = field.to_sym
|
|
239
239
|
encryptable_guard_auditable!(field)
|
|
240
240
|
bi = encryptable_normalize_blind_index(field, blind_index)
|
|
241
|
-
ensure_columns!(LABEL, bi[:column]) if bi
|
|
241
|
+
ensure_columns!(LABEL, bi[:column], types: "string:index") if bi
|
|
242
242
|
self.encryptable_rules = encryptable_rules.merge(field => { type: type, key: key, blind_index: bi })
|
|
243
243
|
attribute field, EncryptedType.new(type: type, key: key)
|
|
244
244
|
encryptable_define_helpers(field)
|
|
@@ -21,7 +21,7 @@ module ConcernsOnRails
|
|
|
21
21
|
# expirable_by :valid_until
|
|
22
22
|
def expirable_by(field = DEFAULT_FIELD, prefix: nil, suffix: nil)
|
|
23
23
|
self.expirable_field = field.to_sym
|
|
24
|
-
ensure_columns!("ConcernsOnRails::Models::Expirable", expirable_field)
|
|
24
|
+
ensure_columns!("ConcernsOnRails::Models::Expirable", expirable_field, types: :datetime)
|
|
25
25
|
define_expirable_scopes(prefix, suffix)
|
|
26
26
|
end
|
|
27
27
|
|
|
@@ -37,7 +37,8 @@ module ConcernsOnRails
|
|
|
37
37
|
self.hashable_alphabet = alphabet
|
|
38
38
|
self.hashable_unique = unique
|
|
39
39
|
|
|
40
|
-
ensure_columns!("ConcernsOnRails::Models::Hashable", hashable_field
|
|
40
|
+
ensure_columns!("ConcernsOnRails::Models::Hashable", hashable_field,
|
|
41
|
+
types: hashable_unique ? "string:uniq" : :string)
|
|
41
42
|
validate_hashable_options!
|
|
42
43
|
before_create :assign_hashable_value
|
|
43
44
|
|
|
@@ -75,7 +75,8 @@ module ConcernsOnRails
|
|
|
75
75
|
self.lockable_locked_at_field = locked_at
|
|
76
76
|
self.lockable_max_attempts = max_attempts
|
|
77
77
|
self.lockable_unlock_in = unlock_in
|
|
78
|
-
ensure_columns!(LABEL, attempts, locked_at
|
|
78
|
+
ensure_columns!(LABEL, attempts, locked_at,
|
|
79
|
+
types: { attempts => :integer, locked_at => :datetime })
|
|
79
80
|
validate_lockable_attempts_column!(attempts)
|
|
80
81
|
define_lockable_scopes(prefix, suffix)
|
|
81
82
|
end
|
|
@@ -47,7 +47,7 @@ module ConcernsOnRails
|
|
|
47
47
|
raise ArgumentError, "ConcernsOnRails::Models::Monetizable: :subunit_to_unit must be a positive integer"
|
|
48
48
|
end
|
|
49
49
|
|
|
50
|
-
ensure_columns!("ConcernsOnRails::Models::Monetizable", fields)
|
|
50
|
+
ensure_columns!("ConcernsOnRails::Models::Monetizable", fields, types: :integer)
|
|
51
51
|
config = { unit: unit, precision: precision, delimiter: delimiter, separator: separator, subunit_to_unit: subunit_to_unit }
|
|
52
52
|
fields.each { |cents_field| define_money_accessors(cents_field.to_sym, as, config) }
|
|
53
53
|
end
|
|
@@ -53,7 +53,7 @@ module ConcernsOnRails
|
|
|
53
53
|
def publishable_by(field = nil, default_scope: false)
|
|
54
54
|
self.publishable_field = field || :published_at
|
|
55
55
|
@publishable_boolean_column = nil
|
|
56
|
-
ensure_columns!("ConcernsOnRails::Models::Publishable", publishable_field)
|
|
56
|
+
ensure_columns!("ConcernsOnRails::Models::Publishable", publishable_field, types: :datetime)
|
|
57
57
|
enable_published_default_scope if default_scope
|
|
58
58
|
end
|
|
59
59
|
|
|
@@ -62,10 +62,10 @@ module ConcernsOnRails
|
|
|
62
62
|
reset = reset.to_sym
|
|
63
63
|
scope_cols = Array(scope).map(&:to_sym)
|
|
64
64
|
|
|
65
|
-
ensure_columns!(NAME, field)
|
|
66
|
-
ensure_columns!(NAME, into) if into
|
|
65
|
+
ensure_columns!(NAME, field, types: :integer)
|
|
66
|
+
ensure_columns!(NAME, into, types: :string) if into
|
|
67
67
|
ensure_columns!(NAME, *scope_cols) unless scope_cols.empty?
|
|
68
|
-
ensure_columns!(NAME, :created_at) unless reset == :never
|
|
68
|
+
ensure_columns!(NAME, :created_at, types: :datetime) unless reset == :never
|
|
69
69
|
validate_sequenceable_options!(reset, template)
|
|
70
70
|
|
|
71
71
|
self.sequenceable_config = sequenceable_config.merge(
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
require "active_support/concern"
|
|
2
2
|
require "concerns_on_rails/support/column_guard"
|
|
3
|
-
|
|
3
|
+
|
|
4
|
+
# Loaded here — with the concern, on first use — rather than at gem boot, so
|
|
5
|
+
# apps that never include Sluggable never load friendly_id.
|
|
6
|
+
begin
|
|
7
|
+
require "friendly_id"
|
|
8
|
+
rescue LoadError
|
|
9
|
+
raise ConcernsOnRails::MissingDependency,
|
|
10
|
+
"ConcernsOnRails::Models::Sluggable requires the friendly_id gem. " \
|
|
11
|
+
'Add `gem "friendly_id", "~> 5.4"` to your Gemfile to use it.'
|
|
12
|
+
end
|
|
4
13
|
|
|
5
14
|
module ConcernsOnRails
|
|
6
15
|
module Models
|
|
@@ -59,7 +68,8 @@ module ConcernsOnRails
|
|
|
59
68
|
# first save with an opaque friendly_id error instead of this
|
|
60
69
|
# concern's clear ArgumentError.
|
|
61
70
|
ensure_columns!("ConcernsOnRails::Models::Sluggable",
|
|
62
|
-
[sluggable_field, friendly_id_config.slug_column, scope].compact
|
|
71
|
+
[sluggable_field, friendly_id_config.slug_column, scope].compact,
|
|
72
|
+
types: { friendly_id_config.slug_column.to_sym => "string:uniq" })
|
|
63
73
|
return unless history || scope || reserved_words || finders
|
|
64
74
|
|
|
65
75
|
reconfigure_friendly_id(history: history, scope: scope,
|
|
@@ -50,7 +50,7 @@ module ConcernsOnRails
|
|
|
50
50
|
self.soft_delete_field = field || :deleted_at
|
|
51
51
|
self.soft_delete_touch = touch
|
|
52
52
|
self.soft_delete_default_scope = default_scope
|
|
53
|
-
ensure_columns!("ConcernsOnRails::Models::SoftDeletable", soft_delete_field)
|
|
53
|
+
ensure_columns!("ConcernsOnRails::Models::SoftDeletable", soft_delete_field, types: :datetime)
|
|
54
54
|
end
|
|
55
55
|
|
|
56
56
|
# Soft-delete every matching record. Returns the Integer count of
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
require "active_support/concern"
|
|
2
2
|
require "concerns_on_rails/support/column_guard"
|
|
3
|
-
|
|
3
|
+
|
|
4
|
+
# Loaded here — with the concern, on first use — rather than at gem boot, so
|
|
5
|
+
# apps that never include Sortable never load acts_as_list.
|
|
6
|
+
begin
|
|
7
|
+
require "acts_as_list"
|
|
8
|
+
rescue LoadError
|
|
9
|
+
raise ConcernsOnRails::MissingDependency,
|
|
10
|
+
"ConcernsOnRails::Models::Sortable requires the acts_as_list gem. " \
|
|
11
|
+
'Add `gem "acts_as_list"` to your Gemfile to use it.'
|
|
12
|
+
end
|
|
4
13
|
|
|
5
14
|
module ConcernsOnRails
|
|
6
15
|
module Models
|
|
@@ -62,7 +71,7 @@ module ConcernsOnRails
|
|
|
62
71
|
self.sortable_field = field
|
|
63
72
|
self.sortable_direction = direction
|
|
64
73
|
|
|
65
|
-
ensure_columns!("ConcernsOnRails::Models::Sortable", sortable_field)
|
|
74
|
+
ensure_columns!("ConcernsOnRails::Models::Sortable", sortable_field, types: :integer)
|
|
66
75
|
|
|
67
76
|
return unless use_acts_as_list
|
|
68
77
|
|
|
@@ -90,7 +90,7 @@ module ConcernsOnRails
|
|
|
90
90
|
self.stateable_transitions = options[:transitions] || {}
|
|
91
91
|
self.stateable_prefix = stateable_affix(options[:prefix])
|
|
92
92
|
self.stateable_suffix = stateable_affix(options[:suffix])
|
|
93
|
-
ensure_columns!(LABEL, stateable_field)
|
|
93
|
+
ensure_columns!(LABEL, stateable_field, types: :string)
|
|
94
94
|
end
|
|
95
95
|
|
|
96
96
|
# `true` => use the field name; a string/symbol => use it literally; else none.
|
|
@@ -104,7 +104,7 @@ module ConcernsOnRails
|
|
|
104
104
|
# named `prefix`/`suffix`. See the module docs.
|
|
105
105
|
def storable_by(column, keys = {}, prefix: nil, suffix: nil, **kw_keys)
|
|
106
106
|
column = column.to_sym
|
|
107
|
-
ensure_columns!(LABEL, column)
|
|
107
|
+
ensure_columns!(LABEL, column, types: :text)
|
|
108
108
|
|
|
109
109
|
prepared = storable_merge_key_specs(keys, kw_keys).map do |key, raw_spec|
|
|
110
110
|
key = key.to_sym
|
|
@@ -53,7 +53,7 @@ module ConcernsOnRails
|
|
|
53
53
|
self.taggable_field = field.to_sym
|
|
54
54
|
self.taggable_delimiter = delimiter.to_s
|
|
55
55
|
self.taggable_downcase = downcase
|
|
56
|
-
ensure_columns!(LABEL, taggable_field)
|
|
56
|
+
ensure_columns!(LABEL, taggable_field, types: :string)
|
|
57
57
|
|
|
58
58
|
before_validation :taggable_normalize!
|
|
59
59
|
end
|
|
@@ -56,7 +56,7 @@ module ConcernsOnRails
|
|
|
56
56
|
type = type.to_sym
|
|
57
57
|
length = length.to_i
|
|
58
58
|
|
|
59
|
-
ensure_columns!("ConcernsOnRails::Models::Tokenizable", field)
|
|
59
|
+
ensure_columns!("ConcernsOnRails::Models::Tokenizable", field, types: "string:uniq")
|
|
60
60
|
validate_tokenizable_options!(type, length)
|
|
61
61
|
|
|
62
62
|
# Build a fresh hash so subclasses don't mutate the parent's config.
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
require "active_support/core_ext/string/inflections"
|
|
2
|
+
|
|
1
3
|
module ConcernsOnRails
|
|
2
4
|
module Support
|
|
3
5
|
# Shared schema-validation helper mixed into a concern's ClassMethods.
|
|
@@ -27,24 +29,41 @@ module ConcernsOnRails
|
|
|
27
29
|
# The phrase "does not exist" is preserved so existing specs that match
|
|
28
30
|
# /does not exist/ keep passing.
|
|
29
31
|
module ColumnGuard
|
|
30
|
-
|
|
31
|
-
|
|
32
|
+
# `types:` teaches the error message: a Symbol/String applies to every
|
|
33
|
+
# listed field, a Hash maps field => type. When present, the raised
|
|
34
|
+
# ArgumentError appends a ready-to-paste migration command. Generator
|
|
35
|
+
# column-modifier syntax is welcome ("string:uniq" — tokens/slugs want a
|
|
36
|
+
# unique index anyway).
|
|
37
|
+
def ensure_columns!(concern, *fields, types: nil)
|
|
38
|
+
ensure_columns_on!(concern, self, *fields, types: types)
|
|
32
39
|
end
|
|
33
40
|
|
|
34
41
|
# Same contract, validated against another class (e.g. CounterCacheable
|
|
35
42
|
# checks the counter column on the *parent* model).
|
|
36
|
-
def ensure_columns_on!(concern, klass, *fields)
|
|
43
|
+
def ensure_columns_on!(concern, klass, *fields, types: nil)
|
|
37
44
|
return false unless schema_reachable?(klass)
|
|
38
45
|
|
|
39
46
|
fields.flatten.compact.each do |field|
|
|
40
47
|
next if klass.column_names.include?(field.to_s)
|
|
41
48
|
|
|
42
49
|
raise ArgumentError,
|
|
43
|
-
"#{concern}: '#{field}' does not exist in the database (table: #{klass.table_name})"
|
|
50
|
+
"#{concern}: '#{field}' does not exist in the database (table: #{klass.table_name})." \
|
|
51
|
+
"#{column_migration_hint(klass, field, types)}"
|
|
44
52
|
end
|
|
45
53
|
true
|
|
46
54
|
end
|
|
47
55
|
|
|
56
|
+
# " Add it with: bin/rails generate migration AddDeletedAtToArticles
|
|
57
|
+
# deleted_at:datetime" — every missing-column failure becomes a
|
|
58
|
+
# copy-paste fix. Without a known type the column name goes out bare
|
|
59
|
+
# (the generator defaults to string).
|
|
60
|
+
def column_migration_hint(klass, field, types)
|
|
61
|
+
type = types.is_a?(Hash) ? types[field.to_sym] : types
|
|
62
|
+
column = [field, type].compact.join(":")
|
|
63
|
+
" Add it with: bin/rails generate migration " \
|
|
64
|
+
"Add#{field.to_s.camelize}To#{klass.table_name.to_s.camelize} #{column}"
|
|
65
|
+
end
|
|
66
|
+
|
|
48
67
|
# True when the class's table can actually be inspected. Connection
|
|
49
68
|
# errors (ConnectionNotEstablished, NoDatabaseError, adapter errors)
|
|
50
69
|
# all inherit from ActiveRecord::ActiveRecordError.
|
data/lib/concerns_on_rails.rb
CHANGED
|
@@ -3,9 +3,86 @@ require "active_support/deprecation"
|
|
|
3
3
|
require "concerns_on_rails/version"
|
|
4
4
|
|
|
5
5
|
module ConcernsOnRails
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
# Raised (as a LoadError subclass, so a bare `rescue LoadError` still works)
|
|
7
|
+
# when a concern needs a third-party gem that isn't available. friendly_id
|
|
8
|
+
# and acts_as_list load lazily with the concern that uses them (Sluggable /
|
|
9
|
+
# Sortable), so hosts that never touch those concerns never load them.
|
|
10
|
+
class MissingDependency < LoadError; end
|
|
11
|
+
|
|
12
|
+
# Everything below is autoloaded on first constant reference instead of
|
|
13
|
+
# eagerly required: an app that uses two concerns loads two files (plus
|
|
14
|
+
# their support helpers), not all forty — and friendly_id / acts_as_list
|
|
15
|
+
# stay unloaded unless Sluggable / Sortable is actually included.
|
|
16
|
+
# Each concern file requires the Support helpers it uses, so a direct
|
|
17
|
+
# `require "concerns_on_rails/models/sluggable"` keeps working too.
|
|
18
|
+
|
|
19
|
+
module Models
|
|
20
|
+
autoload :Sluggable, "concerns_on_rails/models/sluggable"
|
|
21
|
+
autoload :Sortable, "concerns_on_rails/models/sortable"
|
|
22
|
+
autoload :Publishable, "concerns_on_rails/models/publishable"
|
|
23
|
+
autoload :SoftDeletable, "concerns_on_rails/models/soft_deletable"
|
|
24
|
+
autoload :Hashable, "concerns_on_rails/models/hashable"
|
|
25
|
+
autoload :Schedulable, "concerns_on_rails/models/schedulable"
|
|
26
|
+
autoload :Expirable, "concerns_on_rails/models/expirable"
|
|
27
|
+
autoload :Normalizable, "concerns_on_rails/models/normalizable"
|
|
28
|
+
autoload :Searchable, "concerns_on_rails/models/searchable"
|
|
29
|
+
autoload :Activatable, "concerns_on_rails/models/activatable"
|
|
30
|
+
autoload :Tokenizable, "concerns_on_rails/models/tokenizable"
|
|
31
|
+
autoload :Stateable, "concerns_on_rails/models/stateable"
|
|
32
|
+
autoload :Addressable, "concerns_on_rails/models/addressable"
|
|
33
|
+
autoload :Sequenceable, "concerns_on_rails/models/sequenceable"
|
|
34
|
+
autoload :Taggable, "concerns_on_rails/models/taggable"
|
|
35
|
+
autoload :Sanitizable, "concerns_on_rails/models/sanitizable"
|
|
36
|
+
autoload :Maskable, "concerns_on_rails/models/maskable"
|
|
37
|
+
autoload :Monetizable, "concerns_on_rails/models/monetizable"
|
|
38
|
+
autoload :Auditable, "concerns_on_rails/models/auditable"
|
|
39
|
+
autoload :Lockable, "concerns_on_rails/models/lockable"
|
|
40
|
+
autoload :Aliasable, "concerns_on_rails/models/aliasable"
|
|
41
|
+
autoload :Storable, "concerns_on_rails/models/storable"
|
|
42
|
+
autoload :CounterCacheable, "concerns_on_rails/models/counter_cacheable"
|
|
43
|
+
autoload :Encryptable, "concerns_on_rails/models/encryptable"
|
|
44
|
+
autoload :Anonymizable, "concerns_on_rails/models/anonymizable"
|
|
45
|
+
autoload :Duplicable, "concerns_on_rails/models/duplicable"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
module Controllers
|
|
49
|
+
autoload :Paginatable, "concerns_on_rails/controllers/paginatable"
|
|
50
|
+
autoload :Filterable, "concerns_on_rails/controllers/filterable"
|
|
51
|
+
autoload :Sortable, "concerns_on_rails/controllers/sortable"
|
|
52
|
+
autoload :Respondable, "concerns_on_rails/controllers/respondable"
|
|
53
|
+
autoload :ErrorHandleable, "concerns_on_rails/controllers/error_handleable"
|
|
54
|
+
autoload :Includable, "concerns_on_rails/controllers/includable"
|
|
55
|
+
autoload :SecureHeadable, "concerns_on_rails/controllers/secure_headable"
|
|
56
|
+
autoload :Localizable, "concerns_on_rails/controllers/localizable"
|
|
57
|
+
autoload :Authorizable, "concerns_on_rails/controllers/authorizable"
|
|
58
|
+
autoload :Throttleable, "concerns_on_rails/controllers/throttleable"
|
|
59
|
+
autoload :Timezoneable, "concerns_on_rails/controllers/timezoneable"
|
|
60
|
+
autoload :Idempotentable, "concerns_on_rails/controllers/idempotentable"
|
|
61
|
+
autoload :WebhookVerifiable, "concerns_on_rails/controllers/webhook_verifiable"
|
|
62
|
+
autoload :CursorPaginatable, "concerns_on_rails/controllers/cursor_paginatable"
|
|
63
|
+
autoload :Deprecatable, "concerns_on_rails/controllers/deprecatable"
|
|
64
|
+
autoload :Cacheable, "concerns_on_rails/controllers/cacheable"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
module Support
|
|
68
|
+
autoload :ColumnGuard, "concerns_on_rails/support/column_guard"
|
|
69
|
+
autoload :ScalarParam, "concerns_on_rails/support/scalar_param"
|
|
70
|
+
autoload :UniqueRetry, "concerns_on_rails/support/unique_retry"
|
|
71
|
+
autoload :ErrorEnvelope, "concerns_on_rails/support/error_envelope"
|
|
72
|
+
autoload :FilterParameterRegistry, "concerns_on_rails/support/filter_parameter_registry"
|
|
73
|
+
autoload :RandomValue, "concerns_on_rails/support/random_value"
|
|
74
|
+
autoload :AddressData, "concerns_on_rails/support/address_data"
|
|
75
|
+
autoload :SequenceCalculator, "concerns_on_rails/support/sequence_calculator"
|
|
76
|
+
autoload :HtmlSanitizers, "concerns_on_rails/support/html_sanitizers"
|
|
77
|
+
autoload :Masker, "concerns_on_rails/support/masker"
|
|
78
|
+
autoload :Money, "concerns_on_rails/support/money"
|
|
79
|
+
autoload :Encryptor, "concerns_on_rails/support/encryptor"
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Encryption config + error types (Support::Encryptor requires it itself)
|
|
83
|
+
autoload :Encryption, "concerns_on_rails/encryption"
|
|
84
|
+
# Gem-wide configuration object behind ConcernsOnRails.setup
|
|
85
|
+
autoload :Configuration, "concerns_on_rails/configuration"
|
|
9
86
|
|
|
10
87
|
# Guards the lazy singletons below: the first encrypted attribute read (or
|
|
11
88
|
# deprecation warning) can happen on any request thread, and an unsynchronized
|
|
@@ -26,6 +103,20 @@ module ConcernsOnRails
|
|
|
26
103
|
end
|
|
27
104
|
end
|
|
28
105
|
|
|
106
|
+
# Gem-wide configuration (see Configuration), set from an initializer:
|
|
107
|
+
#
|
|
108
|
+
# ConcernsOnRails.setup do |config|
|
|
109
|
+
# config.cache_store = -> { Rails.cache }
|
|
110
|
+
# end
|
|
111
|
+
def self.config
|
|
112
|
+
@config || @config_mutex.synchronize { @config ||= Configuration.new }
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def self.setup
|
|
116
|
+
yield config if block_given?
|
|
117
|
+
config
|
|
118
|
+
end
|
|
119
|
+
|
|
29
120
|
# Gem-wide encryption configuration backing Models::Encryptable. Memoized like
|
|
30
121
|
# `deprecator`; the host app supplies the key (see ConcernsOnRails::Encryption):
|
|
31
122
|
#
|
|
@@ -54,70 +145,7 @@ module ConcernsOnRails
|
|
|
54
145
|
end
|
|
55
146
|
end
|
|
56
147
|
|
|
57
|
-
#
|
|
58
|
-
require "concerns_on_rails/encryption"
|
|
59
|
-
|
|
60
|
-
# Shared internal helpers (must load before the concerns that use them)
|
|
61
|
-
require "concerns_on_rails/support/column_guard"
|
|
62
|
-
require "concerns_on_rails/support/scalar_param"
|
|
63
|
-
require "concerns_on_rails/support/unique_retry"
|
|
64
|
-
require "concerns_on_rails/support/error_envelope"
|
|
65
|
-
require "concerns_on_rails/support/filter_parameter_registry"
|
|
66
|
-
require "concerns_on_rails/support/random_value"
|
|
67
|
-
require "concerns_on_rails/support/address_data"
|
|
68
|
-
require "concerns_on_rails/support/sequence_calculator"
|
|
69
|
-
require "concerns_on_rails/support/html_sanitizers"
|
|
70
|
-
require "concerns_on_rails/support/masker"
|
|
71
|
-
require "concerns_on_rails/support/money"
|
|
72
|
-
require "concerns_on_rails/support/encryptor"
|
|
73
|
-
|
|
74
|
-
# Model concerns
|
|
75
|
-
require "concerns_on_rails/models/sluggable"
|
|
76
|
-
require "concerns_on_rails/models/sortable"
|
|
77
|
-
require "concerns_on_rails/models/publishable"
|
|
78
|
-
require "concerns_on_rails/models/soft_deletable"
|
|
79
|
-
require "concerns_on_rails/models/hashable"
|
|
80
|
-
require "concerns_on_rails/models/schedulable"
|
|
81
|
-
require "concerns_on_rails/models/expirable"
|
|
82
|
-
require "concerns_on_rails/models/normalizable"
|
|
83
|
-
require "concerns_on_rails/models/searchable"
|
|
84
|
-
require "concerns_on_rails/models/activatable"
|
|
85
|
-
require "concerns_on_rails/models/tokenizable"
|
|
86
|
-
require "concerns_on_rails/models/stateable"
|
|
87
|
-
require "concerns_on_rails/models/addressable"
|
|
88
|
-
require "concerns_on_rails/models/sequenceable"
|
|
89
|
-
require "concerns_on_rails/models/taggable"
|
|
90
|
-
require "concerns_on_rails/models/sanitizable"
|
|
91
|
-
require "concerns_on_rails/models/maskable"
|
|
92
|
-
require "concerns_on_rails/models/monetizable"
|
|
93
|
-
require "concerns_on_rails/models/auditable"
|
|
94
|
-
require "concerns_on_rails/models/lockable"
|
|
95
|
-
require "concerns_on_rails/models/aliasable"
|
|
96
|
-
require "concerns_on_rails/models/storable"
|
|
97
|
-
require "concerns_on_rails/models/counter_cacheable"
|
|
98
|
-
require "concerns_on_rails/models/encryptable"
|
|
99
|
-
require "concerns_on_rails/models/anonymizable"
|
|
100
|
-
require "concerns_on_rails/models/duplicable"
|
|
101
|
-
|
|
102
|
-
# Controller concerns
|
|
103
|
-
require "concerns_on_rails/controllers/paginatable"
|
|
104
|
-
require "concerns_on_rails/controllers/filterable"
|
|
105
|
-
require "concerns_on_rails/controllers/sortable"
|
|
106
|
-
require "concerns_on_rails/controllers/respondable"
|
|
107
|
-
require "concerns_on_rails/controllers/error_handleable"
|
|
108
|
-
require "concerns_on_rails/controllers/includable"
|
|
109
|
-
require "concerns_on_rails/controllers/secure_headable"
|
|
110
|
-
require "concerns_on_rails/controllers/localizable"
|
|
111
|
-
require "concerns_on_rails/controllers/authorizable"
|
|
112
|
-
require "concerns_on_rails/controllers/throttleable"
|
|
113
|
-
require "concerns_on_rails/controllers/timezoneable"
|
|
114
|
-
require "concerns_on_rails/controllers/idempotentable"
|
|
115
|
-
require "concerns_on_rails/controllers/webhook_verifiable"
|
|
116
|
-
require "concerns_on_rails/controllers/cursor_paginatable"
|
|
117
|
-
require "concerns_on_rails/controllers/deprecatable"
|
|
118
|
-
require "concerns_on_rails/controllers/cacheable"
|
|
119
|
-
|
|
120
|
-
# Backwards compatibility (top-level aliases for pre-1.6 module paths)
|
|
148
|
+
# Backwards compatibility (lazy top-level aliases for pre-1.6 module paths)
|
|
121
149
|
require "concerns_on_rails/legacy_aliases"
|
|
122
150
|
|
|
123
151
|
# Boot-time integration (filter_parameters registration), Rails apps only
|
metadata
CHANGED
|
@@ -1,17 +1,57 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: concerns_on_rails
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.24.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ethan Nguyen
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-08-15 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
|
-
name:
|
|
14
|
+
name: actionpack
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '5.0'
|
|
20
|
+
- - "<"
|
|
21
|
+
- !ruby/object:Gem::Version
|
|
22
|
+
version: '9'
|
|
23
|
+
type: :runtime
|
|
24
|
+
prerelease: false
|
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
26
|
+
requirements:
|
|
27
|
+
- - ">="
|
|
28
|
+
- !ruby/object:Gem::Version
|
|
29
|
+
version: '5.0'
|
|
30
|
+
- - "<"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '9'
|
|
33
|
+
- !ruby/object:Gem::Dependency
|
|
34
|
+
name: activerecord
|
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '5.0'
|
|
40
|
+
- - "<"
|
|
41
|
+
- !ruby/object:Gem::Version
|
|
42
|
+
version: '9'
|
|
43
|
+
type: :runtime
|
|
44
|
+
prerelease: false
|
|
45
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
46
|
+
requirements:
|
|
47
|
+
- - ">="
|
|
48
|
+
- !ruby/object:Gem::Version
|
|
49
|
+
version: '5.0'
|
|
50
|
+
- - "<"
|
|
51
|
+
- !ruby/object:Gem::Version
|
|
52
|
+
version: '9'
|
|
53
|
+
- !ruby/object:Gem::Dependency
|
|
54
|
+
name: activesupport
|
|
15
55
|
requirement: !ruby/object:Gem::Requirement
|
|
16
56
|
requirements:
|
|
17
57
|
- - ">="
|
|
@@ -34,16 +74,22 @@ dependencies:
|
|
|
34
74
|
name: acts_as_list
|
|
35
75
|
requirement: !ruby/object:Gem::Requirement
|
|
36
76
|
requirements:
|
|
37
|
-
- - "
|
|
77
|
+
- - ">="
|
|
38
78
|
- !ruby/object:Gem::Version
|
|
39
79
|
version: 0.7.5
|
|
80
|
+
- - "<"
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '2'
|
|
40
83
|
type: :runtime
|
|
41
84
|
prerelease: false
|
|
42
85
|
version_requirements: !ruby/object:Gem::Requirement
|
|
43
86
|
requirements:
|
|
44
|
-
- - "
|
|
87
|
+
- - ">="
|
|
45
88
|
- !ruby/object:Gem::Version
|
|
46
89
|
version: 0.7.5
|
|
90
|
+
- - "<"
|
|
91
|
+
- !ruby/object:Gem::Version
|
|
92
|
+
version: '2'
|
|
47
93
|
- !ruby/object:Gem::Dependency
|
|
48
94
|
name: friendly_id
|
|
49
95
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -71,6 +117,7 @@ files:
|
|
|
71
117
|
- LICENSE.txt
|
|
72
118
|
- README.md
|
|
73
119
|
- lib/concerns_on_rails.rb
|
|
120
|
+
- lib/concerns_on_rails/configuration.rb
|
|
74
121
|
- lib/concerns_on_rails/controllers/authorizable.rb
|
|
75
122
|
- lib/concerns_on_rails/controllers/cacheable.rb
|
|
76
123
|
- lib/concerns_on_rails/controllers/cursor_paginatable.rb
|