layered-resource-rails 0.4.0 → 0.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 403ad781bad901c2882718ad739de8346faa81dcce400046aa2092de5c235ab3
4
- data.tar.gz: a78bb873e92d3b814c87a1d989febe815baf67fdb8b74bab8c7193a53d29a7f7
3
+ metadata.gz: dd456a360f63f94c4c38e833251b2d4b22a20c7df76b2b78612d9baf55135e9c
4
+ data.tar.gz: 783f255e36a42959f5565862cf2354d5a57cb1ec5e2d98fbf281477613446c43
5
5
  SHA512:
6
- metadata.gz: 16d28757cc081b902fad287938d6668893b12cc17e9e39d6c0419ef79ea6908ad796236796725dadfc3c14e3540fad66bd85e579f7c0b6308d361cc654131524
7
- data.tar.gz: 59cbe7172af51a80c7107792e9109d5fb4b41a2934933462ee39543b2e7e6807daa012cbdc6d3d82a220681ab913b27c6652fdabaaf51555fdfc4e5920ed3dd8
6
+ metadata.gz: e29f1a82da384c20cf1d2b99793fedd5079d129a3767cd0c18ba81a55b41b4cf7621c9d0528d992239dc75480607ec0702bb6c70f2562660bb5a063d4faa33d6
7
+ data.tar.gz: 6b144d42bb808d4ef5cab32ed5119ec6a976046e4dd82b9d3fac221c2aacfd2015985f0428f1011493a2bd46289e75876b44bce62e1c69b2f22cb42f0c2e1ff7
@@ -178,7 +178,7 @@ Inference by column type: `enum`/`belongs_to` -> multi-select (`_in`; `multiple:
178
178
 
179
179
  **Long option lists switch control.** A select-type filter renders as the plain list (multi-select: checkboxes + Apply; single-choice: instant-apply links) up to `Layered::Resource.filter_combobox_threshold` options (10), and as a type-ahead **combobox** past it — a checkbox list of every user is no way to pick one. A single-choice combobox posts a scalar (no `[]`), so `_eq` gets the value rather than an array. The count is taken per request, after a `collection:` callable resolves, so the control follows the data; the decision lives in `layered_filter_control` (the view helper), not in `resolved_filters`, for exactly that reason. Declaring `as:` pins the control and opts out: `as: :select` keeps the list however long, `as: :combobox` uses the combobox however short.
180
180
 
181
- **Remote options.** `url:` fetches the options from an endpoint as the user types instead of rendering a collection — such a filter is always a combobox (nothing to render or count). Give it as a callable (`-> { user_options_path }`) so it resolves per request in the view, where route helpers exist. The endpoint is an ordinary host-app action including `Layered::Ui::ComboboxOptions` and returning `l_ui_combobox_options(scope, label:, search:)` — the gem never routes it, so it's authorised however any index is. Since the browser has no collection to look a label up in, an active remote filter's values are labelled server-side from the records (same `name`/`title`/`label`/`email` fallbacks), so the tag reads "User: Alice" not "User: 12"; a `url:` over a plain column has no records and its values label themselves. The label lookup is confined to records the resource's `scope(controller)` references (via the foreign key), so a hand-edited id outside it - e.g. another tenant's user - labels itself instead of leaking a name; the cost is that an in-scope option with no matching records also shows its raw id. `min_chars:` and `text:` pass through too; the write-side options (`create:`, `create_name:`, `reorder:`) deliberately don't — a filter picks among values that already exist.
181
+ **Remote options.** `url:` fetches the options from an endpoint as the user types instead of rendering a collection — such a filter is always a combobox (nothing to render or count). Give it as a callable (`-> { user_options_path }`) so it resolves per request in the view, where route helpers exist. The endpoint is an ordinary host-app action including `Layered::Ui::ComboboxOptions` and returning `l_ui_combobox_options(scope, label:, search:)` — the gem never routes it, so it's authorised however any index is. It searches outside any resource, so the model needs its own `ransackable_attributes` covering the `search:` fields. Since the browser has no collection to look a label up in, an active remote filter's values are labelled server-side from the records (same `name`/`title`/`label`/`email` fallbacks), so the tag reads "User: Alice" not "User: 12"; a `url:` over a plain column has no records and its values label themselves. The label lookup is confined to records the resource's `scope(controller)` references (via the foreign key), so a hand-edited id outside it - e.g. another tenant's user - labels itself instead of leaking a name; the cost is that an in-scope option with no matching records also shows its raw id. `min_chars:` and `text:` pass through too; the write-side options (`create:`, `create_name:`, `reorder:`) deliberately don't — a filter picks among values that already exist.
182
182
 
183
183
  **The blank a combobox posts.** `l_ui_combobox` emits a leading blank hidden input so clearing every token submits an empty collection rather than omitting the key. Ransack prunes blanks from an `_in` array itself, and `layered_filter_query_params` prunes them too — an array left with nothing becomes an explicit `""`, the same shape the Clear link writes, so the tag reads as inactive *and* a `default:` doesn't immediately re-apply.
184
184
 
@@ -444,7 +444,7 @@ columns [
444
444
 
445
445
  `as: :badge, rounded: true` gives the rounded count pill; `link:` (optional) makes it link to the children's nested index. If the parent doesn't already carry a counter-cache column, add the migration and `counter_cache: true` rather than reaching for `.size`.
446
446
 
447
- To make an association searchable, add a Ransack-walk-shaped entry to `search_fields` - e.g. `search_fields [:title, :user_name]` resolves `:user_name` against `belongs_to :user` + `users.name` and joins into the association. The gem scopes the Ransack allowlists per resource: it only responds when the resource class is the auth object (on both the parent and the associated model), so host-app Ransack config is preserved. A walked search field is also *sortable* (`q[s]=user_name asc` orders by `users.name`) because Ransack derives its sort allowlist from the search allowlist. Cross-model sort/filter on associations *not* declared in `search_fields` remains off; to opt in, define `ransackable_associations` on the parent model yourself and allowlist the attributes on the child model - the gem detects the host-defined override and unions with it. The search box placeholder labels a walk as "<association> <attribute>" via each model's `human_attribute_name` - translate `activerecord.attributes.<model>.<attr>` to rename a half, or set `search_placeholder` on the resource to replace the whole string.
447
+ To make an association searchable, add a Ransack-walk-shaped entry to `search_fields` - e.g. `search_fields [:title, :user_name]` resolves `:user_name` against `belongs_to :user` + `users.name` and joins into the association. The gem scopes the Ransack allowlists per resource: it only responds when the resource class is the auth object (on both the parent and the associated model), so host-app Ransack config is preserved. A host-app `Model.ransack` call outside a resource still needs the model's own allowlist: with none defined, the gem's patch raises a "needs explicitly allowlisted" error, as Ransack does for an unconfigured model, rather than allowlisting everything. A walked search field is also *sortable* (`q[s]=user_name asc` orders by `users.name`) because Ransack derives its sort allowlist from the search allowlist. Cross-model sort/filter on associations *not* declared in `search_fields` remains off; to opt in, define `ransackable_associations` on the parent model yourself and allowlist the attributes on the child model - the gem detects the host-defined override and unions with it. The search box placeholder labels a walk as "<association> <attribute>" via each model's `human_attribute_name` - translate `activerecord.attributes.<model>.<attr>` to rename a half, or set `search_placeholder` on the resource to replace the whole string.
448
448
 
449
449
  ## Common issues
450
450
 
data/CHANGELOG.md CHANGED
@@ -2,6 +2,22 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. This project follows [Semantic Versioning](https://semver.org/).
4
4
 
5
+ ## [0.5.0] - 2026-11-11
6
+
7
+ ### Fixed
8
+
9
+ - The "needs explicitly allowlisted" error raised for a configured model with no allowlist of its own now names the class being searched. An STI subclass of a configured model was reported as its parent
10
+
11
+ ### Changed
12
+
13
+ - Refresh the development/test bundle to Rails 8.1.4, layered-ui-rails 0.28.1, Pagy 43.8.1, concurrent-ruby 1.3.8, SQLite 2.10.2, and compatible transitive updates.
14
+ - Raise runtime and development dependency minimums to the versions verified by the suite, retaining their current major-version ranges. Ruby >= 3.3 and Rails ~> 8.0 support are unchanged; Rails keeps its existing minimum so Rails 8.0 apps remain supported.
15
+
16
+ ### Security
17
+
18
+ - Require Ransack >= 4.4.4 within the existing 4.x range, including the sort-link reflected XSS and open redirect fix for GHSA-58rx-96gf-w92x.
19
+ - A model the gem configures for Ransack no longer becomes fully searchable by the rest of the app. The scoped `ransackable_attributes`/`ransackable_associations` fell through to Ransack's builtin for any other caller. Because the override counted as an explicit definition, Ransack logged a deprecation warning and allowlisted every column and association, including fields like `encrypted_password`. Where the host app hasn't defined its own allowlist, another layered resource now gets an empty list, and any other caller gets the same "needs explicitly allowlisted" error Ransack raises for an unconfigured model. A host app that searched such a model outside a resource, and relied on the accidental widening, now needs to define `ransackable_attributes`/`ransackable_associations` on it
20
+
5
21
  ## [0.4.0] - 2026-10-04
6
22
 
7
23
  ### Added
data/README.md CHANGED
@@ -45,9 +45,9 @@ curl -fsSL https://raw.githubusercontent.com/layered-ai-public/layered-resource-
45
45
  ## Requirements
46
46
 
47
47
  - Ruby on Rails >= 8.0
48
- - [layered-ui-rails](https://github.com/layered-ai-public/layered-ui-rails) ~> 0.27
49
- - Ransack ~> 4.0
50
- - Pagy ~> 43.2
48
+ - [layered-ui-rails](https://github.com/layered-ai-public/layered-ui-rails) ~> 0.28, >= 0.28.1
49
+ - Ransack ~> 4.4, >= 4.4.4
50
+ - Pagy ~> 43.8, >= 43.8.1
51
51
 
52
52
  ## Getting started
53
53
 
@@ -386,7 +386,7 @@ class PostResource < Layered::Resource::Base
386
386
  end
387
387
  ```
388
388
 
389
- The Ransack allowlists are scoped to the resource: the association and the associated model's attribute are only ransackable when this resource is the one searching, so nothing else gains access to the associated model and any host-app Ransack config is preserved.
389
+ The Ransack allowlists are scoped to the resource: the association and the associated model's attribute are only ransackable when this resource is the one searching, so nothing else gains access to the associated model and any host-app Ransack config is preserved. A model the gem configures isn't opened up for anything else either: other Ransack calls still go through the model's own `ransackable_attributes`/`ransackable_associations`, and if it doesn't define them, the gem raises a "needs explicitly allowlisted" error, as Ransack does for a model with no allowlist.
390
390
 
391
391
  A walked search field is also sortable — `q[s]=user_name asc` orders the index by `users.name` — because Ransack derives its sort allowlist from the search allowlist. Associations not declared in `search_fields` stay unsearchable and unsortable.
392
392
 
@@ -562,7 +562,7 @@ class PostResource < Layered::Resource::Base
562
562
  end
563
563
  ```
564
564
 
565
- Give `url:` as a callable so it resolves per request in the view, where route helpers are available. The endpoint is an ordinary action in your app — the gem never routes it — so it's authorised however any index is:
565
+ Give `url:` as a callable so it resolves per request in the view, where route helpers are available. The endpoint is an ordinary action in your app — the gem never routes it — so it's authorised however any index is. It searches with Ransack outside any resource, so the model it searches needs its own `ransackable_attributes` covering the `search:` fields:
566
566
 
567
567
  ```ruby
568
568
  class UserOptionsController < ApplicationController
@@ -401,24 +401,46 @@ module Layered
401
401
  # `m`. The overrides only answer when the `auth_object` is a layered
402
402
  # resource that references `m` — either as its own model, or via an
403
403
  # association-walking search field. Every other caller falls through
404
- # to the methods captured below (whether host-defined or the framework
405
- # default), so any allowlist the host app has set up is preserved.
404
+ # to the host's own allowlist when it has one. When it doesn't, a
405
+ # layered resource gets nothing and anyone else gets the error Ransack
406
+ # raises for an unconfigured model. Delegating to Ransack's builtin
407
+ # instead would be deprecated, and since these overrides count as an
408
+ # explicit definition it would allowlist every column — so patching a
409
+ # model must not widen what the rest of the app can search.
406
410
  def patch_ransack(m)
407
411
  return if m.instance_variable_get(:@_layered_resource_ransack_configured)
408
412
 
409
413
  original_attributes = m.method(:ransackable_attributes)
410
414
  original_associations = m.method(:ransackable_associations)
411
415
  # Detect a host-defined override anywhere in the model's singleton
412
- # ancestry (Post, ApplicationRecord, ActiveRecord::Base, etc.).
413
- # Ransack supplies the default via a regular Module mixin, so its
414
- # `owner` is a Module but not a Class; an explicit `def self.x` in
415
- # any host class produces a singleton-class owner, which is a Class.
416
- host_attributes_defined = original_attributes.owner.is_a?(Class)
417
- host_associations_defined = original_associations.owner.is_a?(Class)
416
+ # ancestry: a `def self.x` in Post or ApplicationRecord, or a module
417
+ # brought in with `extend` or a concern's `class_methods`. Only
418
+ # Ransack's own default (the module it extends ActiveRecord::Base
419
+ # with) counts as "no allowlist".
420
+ host_attributes_defined = original_attributes.owner != ::Ransack::Adapters::ActiveRecord::Base
421
+ host_associations_defined = original_associations.owner != ::Ransack::Adapters::ActiveRecord::Base
422
+ fallback_attributes = ->(klass, auth_object) do
423
+ if host_attributes_defined
424
+ original_attributes.call(auth_object)
425
+ elsif auth_object.is_a?(Class) && auth_object < Layered::Resource::Base
426
+ []
427
+ else
428
+ Layered::Resource::Base.send(:raise_unconfigured_ransack, klass, :ransackable_attributes)
429
+ end
430
+ end
431
+ fallback_associations = ->(klass, auth_object) do
432
+ if host_associations_defined
433
+ original_associations.call(auth_object)
434
+ elsif auth_object.is_a?(Class) && auth_object < Layered::Resource::Base
435
+ []
436
+ else
437
+ Layered::Resource::Base.send(:raise_unconfigured_ransack, klass, :ransackable_associations)
438
+ end
439
+ end
418
440
 
419
441
  m.define_singleton_method(:ransackable_attributes) do |auth_object = nil|
420
442
  unless auth_object.is_a?(Class) && auth_object < Layered::Resource::Base
421
- next original_attributes.call(auth_object)
443
+ next fallback_attributes.call(self, auth_object)
422
444
  end
423
445
 
424
446
  if auth_object.model == self
@@ -452,12 +474,7 @@ module Layered
452
474
  declared = auth_object.association_search_fields
453
475
  .select { |a| a[:klass] == self }
454
476
  .map { |a| a[:attribute] }
455
- if declared.any?
456
- base = host_attributes_defined ? original_attributes.call(auth_object) : []
457
- base | declared
458
- else
459
- original_attributes.call(auth_object)
460
- end
477
+ fallback_attributes.call(self, auth_object) | declared
461
478
  end
462
479
  end
463
480
 
@@ -468,16 +485,26 @@ module Layered
468
485
  # rather than 500ing.
469
486
  m.define_singleton_method(:ransackable_associations) do |auth_object = nil|
470
487
  if auth_object.is_a?(Class) && auth_object < Layered::Resource::Base && auth_object.model == self
471
- base = host_associations_defined ? original_associations.call(auth_object) : []
472
- base | auth_object.association_search_fields.map { |a| a[:association] }
488
+ fallback_associations.call(self, auth_object) | auth_object.association_search_fields.map { |a| a[:association] }
473
489
  else
474
- original_associations.call(auth_object)
490
+ fallback_associations.call(self, auth_object)
475
491
  end
476
492
  end
477
493
 
478
494
  m.instance_variable_set(:@_layered_resource_ransack_configured, true)
479
495
  end
480
496
 
497
+ # Mirrors Ransack 4's refusal to search a model that hasn't
498
+ # allowlisted anything, for callers outside the gem.
499
+ def raise_unconfigured_ransack(m, method)
500
+ raise <<~MESSAGE.squish
501
+ Ransack needs #{m.name} #{method.to_s.delete_prefix("ransackable_")} explicitly
502
+ allowlisted as searchable. Define `#{m.name}.#{method}(auth_object = nil)`,
503
+ leaving out anything you don't want searchable. layered-resource-rails
504
+ only allowlists for its own resources.
505
+ MESSAGE
506
+ end
507
+
481
508
  # Walks the resource class ancestry to find the first ancestor that
482
509
  # has the given ivar set. Class-level ivars are not inherited in Ruby,
483
510
  # so we explicitly walk to give subclasses access to a parent's
@@ -1,5 +1,5 @@
1
1
  module Layered
2
2
  module Resource
3
- VERSION = "0.4.0"
3
+ VERSION = "0.5.0"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: layered-resource-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - layered.ai
@@ -13,44 +13,62 @@ dependencies:
13
13
  name: concurrent-ruby
14
14
  requirement: !ruby/object:Gem::Requirement
15
15
  requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '1.3'
16
19
  - - ">="
17
20
  - !ruby/object:Gem::Version
18
- version: '1.0'
21
+ version: 1.3.8
19
22
  type: :runtime
20
23
  prerelease: false
21
24
  version_requirements: !ruby/object:Gem::Requirement
22
25
  requirements:
26
+ - - "~>"
27
+ - !ruby/object:Gem::Version
28
+ version: '1.3'
23
29
  - - ">="
24
30
  - !ruby/object:Gem::Version
25
- version: '1.0'
31
+ version: 1.3.8
26
32
  - !ruby/object:Gem::Dependency
27
33
  name: layered-ui-rails
28
34
  requirement: !ruby/object:Gem::Requirement
29
35
  requirements:
30
36
  - - "~>"
31
37
  - !ruby/object:Gem::Version
32
- version: '0.27'
38
+ version: '0.28'
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: 0.28.1
33
42
  type: :runtime
34
43
  prerelease: false
35
44
  version_requirements: !ruby/object:Gem::Requirement
36
45
  requirements:
37
46
  - - "~>"
38
47
  - !ruby/object:Gem::Version
39
- version: '0.27'
48
+ version: '0.28'
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: 0.28.1
40
52
  - !ruby/object:Gem::Dependency
41
53
  name: pagy
42
54
  requirement: !ruby/object:Gem::Requirement
43
55
  requirements:
44
56
  - - "~>"
45
57
  - !ruby/object:Gem::Version
46
- version: '43.2'
58
+ version: '43.8'
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: 43.8.1
47
62
  type: :runtime
48
63
  prerelease: false
49
64
  version_requirements: !ruby/object:Gem::Requirement
50
65
  requirements:
51
66
  - - "~>"
52
67
  - !ruby/object:Gem::Version
53
- version: '43.2'
68
+ version: '43.8'
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: 43.8.1
54
72
  - !ruby/object:Gem::Dependency
55
73
  name: rails
56
74
  requirement: !ruby/object:Gem::Requirement
@@ -71,14 +89,20 @@ dependencies:
71
89
  requirements:
72
90
  - - "~>"
73
91
  - !ruby/object:Gem::Version
74
- version: '4.0'
92
+ version: '4.4'
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: 4.4.4
75
96
  type: :runtime
76
97
  prerelease: false
77
98
  version_requirements: !ruby/object:Gem::Requirement
78
99
  requirements:
79
100
  - - "~>"
80
101
  - !ruby/object:Gem::Version
81
- version: '4.0'
102
+ version: '4.4'
103
+ - - ">="
104
+ - !ruby/object:Gem::Version
105
+ version: 4.4.4
82
106
  - !ruby/object:Gem::Dependency
83
107
  name: devise
84
108
  requirement: !ruby/object:Gem::Requirement
@@ -86,6 +110,9 @@ dependencies:
86
110
  - - "~>"
87
111
  - !ruby/object:Gem::Version
88
112
  version: '5.0'
113
+ - - ">="
114
+ - !ruby/object:Gem::Version
115
+ version: 5.0.4
89
116
  type: :development
90
117
  prerelease: false
91
118
  version_requirements: !ruby/object:Gem::Requirement
@@ -93,90 +120,129 @@ dependencies:
93
120
  - - "~>"
94
121
  - !ruby/object:Gem::Version
95
122
  version: '5.0'
123
+ - - ">="
124
+ - !ruby/object:Gem::Version
125
+ version: 5.0.4
96
126
  - !ruby/object:Gem::Dependency
97
127
  name: importmap-rails
98
128
  requirement: !ruby/object:Gem::Requirement
99
129
  requirements:
100
130
  - - "~>"
101
131
  - !ruby/object:Gem::Version
102
- version: '2.0'
132
+ version: '2.2'
133
+ - - ">="
134
+ - !ruby/object:Gem::Version
135
+ version: 2.2.3
103
136
  type: :development
104
137
  prerelease: false
105
138
  version_requirements: !ruby/object:Gem::Requirement
106
139
  requirements:
107
140
  - - "~>"
108
141
  - !ruby/object:Gem::Version
109
- version: '2.0'
142
+ version: '2.2'
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: 2.2.3
110
146
  - !ruby/object:Gem::Dependency
111
147
  name: propshaft
112
148
  requirement: !ruby/object:Gem::Requirement
113
149
  requirements:
114
150
  - - "~>"
115
151
  - !ruby/object:Gem::Version
116
- version: '1.0'
152
+ version: '1.3'
153
+ - - ">="
154
+ - !ruby/object:Gem::Version
155
+ version: 1.3.2
117
156
  type: :development
118
157
  prerelease: false
119
158
  version_requirements: !ruby/object:Gem::Requirement
120
159
  requirements:
121
160
  - - "~>"
122
161
  - !ruby/object:Gem::Version
123
- version: '1.0'
162
+ version: '1.3'
163
+ - - ">="
164
+ - !ruby/object:Gem::Version
165
+ version: 1.3.2
124
166
  - !ruby/object:Gem::Dependency
125
167
  name: pundit
126
168
  requirement: !ruby/object:Gem::Requirement
127
169
  requirements:
128
170
  - - "~>"
129
171
  - !ruby/object:Gem::Version
130
- version: '2.4'
172
+ version: '2.5'
173
+ - - ">="
174
+ - !ruby/object:Gem::Version
175
+ version: 2.5.2
131
176
  type: :development
132
177
  prerelease: false
133
178
  version_requirements: !ruby/object:Gem::Requirement
134
179
  requirements:
135
180
  - - "~>"
136
181
  - !ruby/object:Gem::Version
137
- version: '2.4'
182
+ version: '2.5'
183
+ - - ">="
184
+ - !ruby/object:Gem::Version
185
+ version: 2.5.2
138
186
  - !ruby/object:Gem::Dependency
139
187
  name: puma
140
188
  requirement: !ruby/object:Gem::Requirement
141
189
  requirements:
142
190
  - - "~>"
143
191
  - !ruby/object:Gem::Version
144
- version: '7.0'
192
+ version: '7.2'
193
+ - - ">="
194
+ - !ruby/object:Gem::Version
195
+ version: 7.2.1
145
196
  type: :development
146
197
  prerelease: false
147
198
  version_requirements: !ruby/object:Gem::Requirement
148
199
  requirements:
149
200
  - - "~>"
150
201
  - !ruby/object:Gem::Version
151
- version: '7.0'
202
+ version: '7.2'
203
+ - - ">="
204
+ - !ruby/object:Gem::Version
205
+ version: 7.2.1
152
206
  - !ruby/object:Gem::Dependency
153
207
  name: sqlite3
154
208
  requirement: !ruby/object:Gem::Requirement
155
209
  requirements:
156
210
  - - "~>"
157
211
  - !ruby/object:Gem::Version
158
- version: '2.0'
212
+ version: '2.10'
213
+ - - ">="
214
+ - !ruby/object:Gem::Version
215
+ version: 2.10.2
159
216
  type: :development
160
217
  prerelease: false
161
218
  version_requirements: !ruby/object:Gem::Requirement
162
219
  requirements:
163
220
  - - "~>"
164
221
  - !ruby/object:Gem::Version
165
- version: '2.0'
222
+ version: '2.10'
223
+ - - ">="
224
+ - !ruby/object:Gem::Version
225
+ version: 2.10.2
166
226
  - !ruby/object:Gem::Dependency
167
227
  name: stimulus-rails
168
228
  requirement: !ruby/object:Gem::Requirement
169
229
  requirements:
170
230
  - - "~>"
171
231
  - !ruby/object:Gem::Version
172
- version: '1.0'
232
+ version: '1.3'
233
+ - - ">="
234
+ - !ruby/object:Gem::Version
235
+ version: 1.3.4
173
236
  type: :development
174
237
  prerelease: false
175
238
  version_requirements: !ruby/object:Gem::Requirement
176
239
  requirements:
177
240
  - - "~>"
178
241
  - !ruby/object:Gem::Version
179
- version: '1.0'
242
+ version: '1.3'
243
+ - - ">="
244
+ - !ruby/object:Gem::Version
245
+ version: 1.3.4
180
246
  - !ruby/object:Gem::Dependency
181
247
  name: turbo-rails
182
248
  requirement: !ruby/object:Gem::Requirement
@@ -184,6 +250,9 @@ dependencies:
184
250
  - - "~>"
185
251
  - !ruby/object:Gem::Version
186
252
  version: '2.0'
253
+ - - ">="
254
+ - !ruby/object:Gem::Version
255
+ version: 2.0.23
187
256
  type: :development
188
257
  prerelease: false
189
258
  version_requirements: !ruby/object:Gem::Requirement
@@ -191,6 +260,9 @@ dependencies:
191
260
  - - "~>"
192
261
  - !ruby/object:Gem::Version
193
262
  version: '2.0'
263
+ - - ">="
264
+ - !ruby/object:Gem::Version
265
+ version: 2.0.23
194
266
  description: A convention-over-configuration CRUD engine for Rails 8+, built on the
195
267
  layered-ui-rails gem. Declare a resource class, mount it in routes.rb, and get index,
196
268
  new/create, edit/update, and destroy actions with clean default views, Ransack-powered