layered-resource-rails 0.1.0 → 0.2.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/.claude/skills/layered-resource-rails/SKILL.md +9 -0
- data/CHANGELOG.md +15 -0
- data/README.md +23 -1
- data/app/controllers/layered/resource/controller.rb +19 -0
- data/app/controllers/layered/resource/internal/columns.rb +30 -0
- data/lib/generators/layered/resource/controller/controller_generator.rb +27 -1
- data/lib/generators/layered/resource/resource_generator.rb +11 -1
- data/lib/generators/layered/resource/templates/resource.rb.tt +1 -1
- data/lib/layered/resource/routing.rb +19 -4
- data/lib/layered/resource/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: '0429425d0767f40488539bbaef1d13ef629598c52a40592ecc914d9196a6f3e7'
|
|
4
|
+
data.tar.gz: a45581d887a80412baaeab41ffc0e0310eaaeb1bd495cc11576c12d957424416
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7571835a600e4d34659fcfedcd6573c2593a3653fdd23f8c80fa823ea7014b3226078492b8eb217a2247ccbf500d266d77495939f07b65d5178ffed3c53e3196
|
|
7
|
+
data.tar.gz: 9e980c9c01d37611a40c6172007932e4f62ac1e58702d2a8490c5c902e920985e72a099db7a31d7b890e2a82b779806718a2e416ba32898bccebf7d5679724a0
|
|
@@ -39,6 +39,8 @@ This produces:
|
|
|
39
39
|
- `app/layered_resources/post_resource.rb` with `columns` and `fields` derived from the attributes
|
|
40
40
|
- `layered_resources :posts` appended to `config/routes.rb`
|
|
41
41
|
|
|
42
|
+
A `references` attribute (e.g. `speaker:references`) becomes a `:speaker_id` field - inferred as a combobox of the associated records - but is left out of `columns`, since a raw foreign key is rarely the column an index wants. Polymorphic references are left out of both: setting one takes a `_type` too, so write that field yourself.
|
|
43
|
+
|
|
42
44
|
Useful flags:
|
|
43
45
|
|
|
44
46
|
- `--skip-model` - the model already exists
|
|
@@ -194,10 +196,14 @@ layered_resources :posts, except: [:destroy] # everything but
|
|
|
194
196
|
layered_resources :posts, controller: "posts" # use a custom controller
|
|
195
197
|
layered_resources :posts, resource: "Admin::PostResource" # explicit resource class
|
|
196
198
|
layered_resources :posts, namespace: "Admin" # derives Admin::PostResource and Admin::ResourcesController
|
|
199
|
+
layered_resources :posts, layout: "manage" # render inside app/views/layouts/manage.html.erb
|
|
200
|
+
layered_resources :posts, layout: false # render with no layout
|
|
197
201
|
```
|
|
198
202
|
|
|
199
203
|
Incoherent `only:` combos raise at boot time - e.g. `:new` without `:create`, or `:edit` without `:update`.
|
|
200
204
|
|
|
205
|
+
`layout:` is the hook for putting a resource on a host-app layout **without ejecting a controller** - reach for it before generating one just to write a `layout` line. It applies to every action on that route, and each route decides independently, so the same resource can be plain under `/posts` and wrapped under `/manage/posts`. A `layout` declared in an ejected controller replaces the hook and wins. Anything but a String, Symbol, or `false` raises at boot.
|
|
206
|
+
|
|
201
207
|
### Nested routes
|
|
202
208
|
|
|
203
209
|
```ruby
|
|
@@ -344,6 +350,8 @@ rails g layered:resource:views posts # copies index/show/new/edit ERB int
|
|
|
344
350
|
rails g layered:resource:controller posts # generates a controller subclass for custom actions
|
|
345
351
|
```
|
|
346
352
|
|
|
353
|
+
Pass the **plural** name: a singular one is pluralised (`talk` → `TalksController`) because `layered_resources` routes under the plural and `controller:` must match. Don't eject a controller just to set a layout - use the route's `layout:` option.
|
|
354
|
+
|
|
347
355
|
The controller's `_prefixes` is overridden so `app/views/layered/<plural>/` overrides win automatically - no extra wiring. Delete any individual ejected template to fall back to the gem default.
|
|
348
356
|
|
|
349
357
|
To outgrow the gem entirely: drop the inheritance, write a plain Rails controller, swap `layered_resources :posts` for `resources :posts` in routes.
|
|
@@ -440,6 +448,7 @@ To make an association searchable, add a Ransack-walk-shaped entry to `search_fi
|
|
|
440
448
|
- **`NoMethodError: undefined method 'l_ui_table'`** - the host app hasn't installed `layered-ui-rails`. Run `bin/rails generate layered:ui:install`.
|
|
441
449
|
- **Search/sort returns empty** - the attribute isn't in `search_fields`, or Ransack's `ransackable_attributes` on the model excludes it. The resource patches Ransack only when itself is the auth object; verify nothing in the host app removes the attribute unconditionally.
|
|
442
450
|
- **`only:` validation error at boot** - `:new` requires `:create`, `:edit` requires `:update`. Adjust the action list.
|
|
451
|
+
- **`<Name>Resource declares column :foo, but <Model> has no public method by that name`** - a column names something the model doesn't publicly answer to (a typo, or a private method - cells are read with `public_send`). Define or `delegate` a public method, fix the attribute name, or give the column a `render:` proc (procs are exempt from the check).
|
|
443
452
|
- **Ejected view not picked up** - the controller looks under `app/views/layered/<plural_name>/`, where `<plural_name>` is the symbol passed to `layered_resources` (ignoring Rails namespaces). The generator mirrors this; if you've moved files manually, match that path.
|
|
444
453
|
|
|
445
454
|
## Further reference
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,21 @@
|
|
|
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.2.0] - 2026-09-10
|
|
6
|
+
|
|
7
|
+
### Routing and controllers
|
|
8
|
+
|
|
9
|
+
- `layered_resources :posts, layout: "manage"` renders a resource's pages inside one of the host app's layouts, and `layout: false` renders them with no layout at all. The option belongs to the route rather than the resource, so the same resource can be plain under `/posts` and wrapped in admin chrome under `/manage/posts`. Previously the only way to put a resource on an app layout was to eject a controller purely to write one `layout` line. A `layout` declared in an ejected controller still wins; a route declaring no layout resolves exactly as before, landing on the host's `ApplicationController` layout.
|
|
10
|
+
|
|
11
|
+
### Columns
|
|
12
|
+
|
|
13
|
+
- A column naming a method the model has no public answer for now raises before the table renders, naming the resource, the attribute, and the model, and suggesting the `delegate`. It used to surface as a bare `NoMethodError` from inside a column partial. Columns with a `render:` proc are exempt — the proc decides what to call, and `attribute:` is then only the header and sort key.
|
|
14
|
+
|
|
15
|
+
### Generators
|
|
16
|
+
|
|
17
|
+
- `rails g layered:resource talk speaker:references` now emits a `:speaker_id` field, which infers a combobox of the associated records. References stay out of `columns` (a raw foreign key is rarely the column an index wants), but excluding them from `fields` too left the generated form with no way to set the association — and since `belongs_to` is required by default, every create failed with "Speaker must exist". Polymorphic references stay out of both: setting one takes a `_type` as well.
|
|
18
|
+
- `rails g layered:resource:controller talk` now generates `TalksController` and advises `layered_resources :talks, controller: "talks"`. It used to generate `talk_controller.rb` and advise `layered_resources :talk`, which moved the collection from `/talks` to `/talk` and broke every path helper pointing at it.
|
|
19
|
+
|
|
5
20
|
## [0.1.0] - 2026-08-30
|
|
6
21
|
|
|
7
22
|
Initial release.
|
data/README.md
CHANGED
|
@@ -73,6 +73,8 @@ rails g layered:resource:scaffold post title:string body:text
|
|
|
73
73
|
|
|
74
74
|
This invokes Rails' built-in `model` generator (so you get the migration and model), writes `app/layered_resources/post_resource.rb` with `columns` and `fields` derived from the attributes, and appends `layered_resources :posts` to `config/routes.rb`. Views are intentionally not generated - the gem's defaults render until you eject them with `rails g layered:resource:views posts`.
|
|
75
75
|
|
|
76
|
+
A `references` attribute (e.g. `speaker:references`) becomes a `:speaker_id` field - which infers a combobox of the associated records - but is left out of `columns`, since a raw foreign key is rarely the column you want on an index. Polymorphic references are left out of both: setting one takes a `_type` as well, so it needs a field you write yourself.
|
|
77
|
+
|
|
76
78
|
Pass `--skip-model` if the model already exists. Restrict which CRUD actions get routed with `--actions index show` (emits `only:`) or `--except destroy` (emits `except:`). Pass `--controller` to also eject a controller and wire it into the route, or `--views` to eject the templates upfront.
|
|
77
79
|
|
|
78
80
|
If the model already exists and you just want the resource class plus its route, use `rails g layered:resource post title:string body:text` instead. It writes `app/layered_resources/post_resource.rb` and appends `layered_resources :posts` - pass `--skip-route` to skip the route line.
|
|
@@ -163,6 +165,15 @@ layered_resources :posts, except: [:destroy]
|
|
|
163
165
|
|
|
164
166
|
The default `show` view is intentionally a blank canvas - the gem doesn't auto-render an attribute list, since generated detail pages tend to be low-value and always need customizing. The index links each record's title to its **edit** page, not its show page, so `show` is only worth keeping if you're going to build a real detail view (eject it with `rails g layered:resource:views` and fill in the template). If you're not, pass `except: [:show]` to drop the route.
|
|
165
167
|
|
|
168
|
+
**Layout:** the gem's controller renders inside your `ApplicationController`'s layout. Point a route at a different one - an admin chrome, say - without ejecting a controller:
|
|
169
|
+
|
|
170
|
+
```ruby
|
|
171
|
+
layered_resources :posts, layout: "manage" # app/views/layouts/manage.html.erb
|
|
172
|
+
layered_resources :posts, layout: false # no layout at all
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
The option covers every action on that route, and each route decides independently, so the same resource can be plain under `/posts` and wrapped under `/manage/posts`. An ejected controller that declares its own `layout` wins over the route option.
|
|
176
|
+
|
|
166
177
|
**Root breadcrumb:** top-level resources render no breadcrumb trail by default. Declare a static first crumb — typically a link back to the host app's dashboard:
|
|
167
178
|
|
|
168
179
|
```ruby
|
|
@@ -607,6 +618,17 @@ rails g layered:resource:column priority_badge # scaffold a brand-new type
|
|
|
607
618
|
|
|
608
619
|
A custom partial receives `record`, `value`, and `options` (the column hash) as locals - read keys like `:variants` or `:format` straight off `options`.
|
|
609
620
|
|
|
621
|
+
A column's `attribute:` has to be something the model publicly answers to - a DB column, an association, an `attribute`, a delegated method, or any public method you define (the default renderer reads the cell with `public_send`). Naming one it doesn't have raises before the table renders, telling you which resource and which attribute:
|
|
622
|
+
|
|
623
|
+
```
|
|
624
|
+
PostResource declares column :author_name, but Post has no public method by that
|
|
625
|
+
name. Add it to Post (`delegate :author_name, to: :<association>` for an
|
|
626
|
+
associated model's attribute), correct the column's attribute:, or give the
|
|
627
|
+
column a `render:` proc that produces the value.
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
A column with a `render:` proc is exempt - the proc decides what to call, and `attribute:` is then just the header and sort key.
|
|
631
|
+
|
|
610
632
|
### Sortable headers
|
|
611
633
|
|
|
612
634
|
A column header renders a sort link only when the attribute is sortable. This defaults to `true` for real DB columns and `false` for anything else - virtual attributes and delegated association values - because Ransack can't sort those without the associated model allowlisting the underlying field, and the sort link would 500 when clicked. Set `sortable: true` on the column to opt back in; you're then responsible for that model's `ransackable_attributes` (see [Associations](#associations)).
|
|
@@ -851,7 +873,7 @@ This copies the gem's actual `index`, `show`, `new`, and `edit` templates into `
|
|
|
851
873
|
rails g layered:resource:controller posts
|
|
852
874
|
```
|
|
853
875
|
|
|
854
|
-
This gives you a controller that inherits from the base - override any of the standard CRUD actions and call `super` when you only want to tweak behaviour.
|
|
876
|
+
This gives you a controller that inherits from the base - override any of the standard CRUD actions and call `super` when you only want to tweak behaviour. Pass a singular name and it's pluralised (`talk` generates `TalksController`): `layered_resources` declares its routes under the plural name and `controller:` has to match, so a `talk_controller.rb` would only be reachable by moving the collection from `/talks` to `/talk`.
|
|
855
877
|
|
|
856
878
|
If you outgrow the gem entirely, drop the inheritance and write a plain Rails controller:
|
|
857
879
|
|
|
@@ -26,6 +26,11 @@ module Layered
|
|
|
26
26
|
helper Layered::Ui::BreadcrumbsHelper
|
|
27
27
|
helper Layered::Resource::FiltersHelper
|
|
28
28
|
|
|
29
|
+
# Hooks the route's `layout:` option in. Returning nil (the common
|
|
30
|
+
# case) falls through to Rails' normal layout resolution, so routes
|
|
31
|
+
# without the option are unaffected.
|
|
32
|
+
layout :layered_resource_layout
|
|
33
|
+
|
|
29
34
|
before_action :load_layered_resource
|
|
30
35
|
before_action :load_layered_member_record
|
|
31
36
|
before_action :require_layered_fields, only: %i[new create edit update]
|
|
@@ -195,6 +200,20 @@ module Layered
|
|
|
195
200
|
@resource_can_show = resource_actions.include?(:show)
|
|
196
201
|
end
|
|
197
202
|
|
|
203
|
+
# The layout named by `layered_resources :posts, layout: "manage"`,
|
|
204
|
+
# or nil when the route declared none — which Rails reads as "resolve
|
|
205
|
+
# the layout the usual way", landing on the host's
|
|
206
|
+
# ApplicationController layout. `layout: false` renders bare. Nil is
|
|
207
|
+
# also what an ejected controller sees before `load_layered_resource`
|
|
208
|
+
# has run (e.g. rendering an error page), which is the right default
|
|
209
|
+
# there too.
|
|
210
|
+
def layered_resource_layout
|
|
211
|
+
layout = @_route_entry && @_route_entry[:layout]
|
|
212
|
+
return nil if layout.nil?
|
|
213
|
+
|
|
214
|
+
layout == false ? false : layout.to_s
|
|
215
|
+
end
|
|
216
|
+
|
|
198
217
|
# For custom member actions declared in a `layered_resources` block,
|
|
199
218
|
# populate @record from params[:id] so action bodies don't have to
|
|
200
219
|
# repeat `@resource.scope(self).find(params[:id])`. Skip this with
|
|
@@ -13,12 +13,42 @@ module Layered
|
|
|
13
13
|
# that doesn't already have one, then wraps columns with a `link:`
|
|
14
14
|
# option in a link to the named route.
|
|
15
15
|
def decorate_columns
|
|
16
|
+
validate_column_attributes!
|
|
16
17
|
apply_column_sortability
|
|
17
18
|
apply_column_renderers
|
|
18
19
|
apply_column_links
|
|
19
20
|
apply_primary_column_link if action_name == "index"
|
|
20
21
|
end
|
|
21
22
|
|
|
23
|
+
# `columns` is declarative, so a column naming a method the model
|
|
24
|
+
# doesn't have is checkable before anything renders. Without this the
|
|
25
|
+
# miss surfaces as a bare NoMethodError raised from inside a column
|
|
26
|
+
# partial, naming neither the resource nor the column. Columns with a
|
|
27
|
+
# `render:` proc are exempt: the proc decides what to call, and
|
|
28
|
+
# `attribute` is then just a header/sort key.
|
|
29
|
+
def validate_column_attributes!
|
|
30
|
+
model = @resource.model
|
|
31
|
+
# Attribute readers are defined lazily, so ask for them before
|
|
32
|
+
# asking whether they exist.
|
|
33
|
+
model.define_attribute_methods
|
|
34
|
+
|
|
35
|
+
# Public only: the default renderer reads the cell with
|
|
36
|
+
# `record.public_send`, so a private method is as unrenderable as
|
|
37
|
+
# a missing one.
|
|
38
|
+
missing = @columns.reject { |col| col[:render] }
|
|
39
|
+
.filter_map { |col| col[:attribute] }
|
|
40
|
+
.reject { |attr| model.method_defined?(attr) }
|
|
41
|
+
return if missing.empty?
|
|
42
|
+
|
|
43
|
+
raise ArgumentError,
|
|
44
|
+
"#{@resource.name} declares column#{'s' if missing.size > 1} " \
|
|
45
|
+
"#{missing.map(&:inspect).join(', ')}, but #{model.name} has no public method " \
|
|
46
|
+
"by that name. " \
|
|
47
|
+
"Add it to #{model.name} (`delegate :#{missing.first}, to: :<association>` for " \
|
|
48
|
+
"an associated model's attribute), correct the column's attribute:, or give the " \
|
|
49
|
+
"column a `render:` proc that produces the value."
|
|
50
|
+
end
|
|
51
|
+
|
|
22
52
|
# Marks columns sortable: false unless they map to a real DB column.
|
|
23
53
|
# Virtual / association-derived columns (e.g. :user_name on Post) can't
|
|
24
54
|
# be sorted by Ransack without the associated model also having
|
|
@@ -28,6 +28,14 @@ module Layered
|
|
|
28
28
|
template "controller.rb.tt", path
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
+
def note_pluralisation
|
|
32
|
+
return unless pluralised?
|
|
33
|
+
|
|
34
|
+
say ""
|
|
35
|
+
say "Named the controller #{file_name} (not #{given_name}): layered_resources " \
|
|
36
|
+
"declares its routes under the plural name, and controller: has to match."
|
|
37
|
+
end
|
|
38
|
+
|
|
31
39
|
def show_routing_instructions
|
|
32
40
|
say ""
|
|
33
41
|
say "Point the route at the new controller:"
|
|
@@ -45,8 +53,26 @@ module Layered
|
|
|
45
53
|
|
|
46
54
|
private
|
|
47
55
|
|
|
56
|
+
# `layered_resources` declares its routes under the plural name, and
|
|
57
|
+
# `controller:` has to name the controller's own path, so a singular
|
|
58
|
+
# argument is pluralised. Generating `talk_controller.rb` and telling
|
|
59
|
+
# the user to write `layered_resources :talk, controller: "talk"`
|
|
60
|
+
# would move the collection from /talks to /talk and break every
|
|
61
|
+
# path helper already pointing at it.
|
|
48
62
|
def file_name
|
|
49
|
-
@_file_name ||=
|
|
63
|
+
@_file_name ||= given_name.pluralize
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# The name as typed, minus any `_controller` suffix. `super` here
|
|
67
|
+
# would re-enter the override, so read NamedBase's own reader.
|
|
68
|
+
def given_name
|
|
69
|
+
@_given_name ||= Rails::Generators::NamedBase
|
|
70
|
+
.instance_method(:file_name).bind_call(self)
|
|
71
|
+
.sub(/_?controller$/i, "")
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def pluralised?
|
|
75
|
+
given_name != file_name
|
|
50
76
|
end
|
|
51
77
|
end
|
|
52
78
|
end
|
|
@@ -44,12 +44,22 @@ module Layered
|
|
|
44
44
|
singular_name.camelize
|
|
45
45
|
end
|
|
46
46
|
|
|
47
|
+
# `speaker_id` is a poor index column - the table wants the
|
|
48
|
+
# speaker's label, which is the consumer's call - so references are
|
|
49
|
+
# left out of `columns`.
|
|
47
50
|
def column_attributes
|
|
48
51
|
attributes.reject { |a| a.reference? || a.password_digest? }
|
|
49
52
|
end
|
|
50
53
|
|
|
54
|
+
# References belong in `fields` as their foreign key: `belongs_to` is
|
|
55
|
+
# required by default, so a form without the FK cannot create a
|
|
56
|
+
# record. A `:<name>_id` field infers a combobox of the associated
|
|
57
|
+
# records (see `Layered::Resource::Base#infer_association_field`).
|
|
58
|
+
# Polymorphic references stay out: setting one needs a `_type` as
|
|
59
|
+
# well, and there is no single class whose records could fill a
|
|
60
|
+
# picker.
|
|
51
61
|
def field_attributes
|
|
52
|
-
|
|
62
|
+
attributes.reject { |a| a.password_digest? || (a.reference? && a.polymorphic?) }
|
|
53
63
|
end
|
|
54
64
|
|
|
55
65
|
def field_as(attr)
|
|
@@ -12,7 +12,7 @@ class <%= resource_class_name %>Resource < Layered::Resource::Base
|
|
|
12
12
|
|
|
13
13
|
fields [
|
|
14
14
|
<% field_attributes.each_with_index do |attr, i| -%>
|
|
15
|
-
{ attribute: :<%= attr.
|
|
15
|
+
{ attribute: :<%= attr.column_name %><%= field_as(attr) %> }<%= "," unless i == field_attributes.length - 1 %>
|
|
16
16
|
<% end -%>
|
|
17
17
|
]
|
|
18
18
|
<% end -%>
|
|
@@ -6,11 +6,12 @@ module Layered
|
|
|
6
6
|
@registry = Concurrent::Map.new
|
|
7
7
|
|
|
8
8
|
class << self
|
|
9
|
-
def register(route_key, resource_class_name, actions: [], routes: nil, parent_params: [], parent_collection_keys: {}, resource_name: nil, member_actions: [], collection_actions: [])
|
|
9
|
+
def register(route_key, resource_class_name, actions: [], routes: nil, parent_params: [], parent_collection_keys: {}, resource_name: nil, member_actions: [], collection_actions: [], layout: nil)
|
|
10
10
|
@registry[route_key.to_s] = {
|
|
11
11
|
resource: resource_class_name.to_s,
|
|
12
12
|
actions: actions,
|
|
13
13
|
routes: routes,
|
|
14
|
+
layout: layout,
|
|
14
15
|
parent_params: parent_params,
|
|
15
16
|
parent_collection_keys: parent_collection_keys,
|
|
16
17
|
resource_name: resource_name.to_s,
|
|
@@ -82,7 +83,13 @@ module Layered
|
|
|
82
83
|
end
|
|
83
84
|
end
|
|
84
85
|
|
|
85
|
-
|
|
86
|
+
# `layout:` renders the resource's pages inside one of the host app's
|
|
87
|
+
# layouts (`layout: "manage"` → `app/views/layouts/manage.html.erb`),
|
|
88
|
+
# or without one (`layout: false`). Without it the controller keeps
|
|
89
|
+
# Rails' normal resolution, which lands on the host's
|
|
90
|
+
# `ApplicationController` layout. An explicit `layout` declaration in
|
|
91
|
+
# an ejected controller replaces the hook and wins over this option.
|
|
92
|
+
def layered_resources(resource_name, resource: nil, controller: nil, namespace: nil, layout: nil, only: RESOURCE_ACTIONS, except: nil, **options, &block)
|
|
86
93
|
# When called inside `resources :foo do ... end` (or `resource :foo do`),
|
|
87
94
|
# Rails has set up a resource_scope but hasn't pushed the parent's
|
|
88
95
|
# path into @scope. Push it ourselves via scope(path:) and recurse.
|
|
@@ -107,7 +114,7 @@ module Layered
|
|
|
107
114
|
begin
|
|
108
115
|
layered_resources(resource_name,
|
|
109
116
|
resource: resource, controller: controller, namespace: namespace,
|
|
110
|
-
only: only, except: except, **options, &block)
|
|
117
|
+
layout: layout, only: only, except: except, **options, &block)
|
|
111
118
|
ensure
|
|
112
119
|
@scope.frame[:as] = saved_as
|
|
113
120
|
end
|
|
@@ -124,6 +131,13 @@ module Layered
|
|
|
124
131
|
# `scope path: "foo", module: "foo"` and pass `namespace:` here.
|
|
125
132
|
namespace = namespace.to_s.presence
|
|
126
133
|
|
|
134
|
+
unless layout.nil? || layout == false || layout.is_a?(String) || layout.is_a?(Symbol)
|
|
135
|
+
raise ArgumentError,
|
|
136
|
+
"layered_resources :#{resource_name} got layout: #{layout.inspect}. " \
|
|
137
|
+
"Pass a layout name (String or Symbol, resolved under app/views/layouts) " \
|
|
138
|
+
"or `false` to render without a layout."
|
|
139
|
+
end
|
|
140
|
+
|
|
127
141
|
resource_class_name = resource ||
|
|
128
142
|
(namespace ? "#{namespace}::#{resource_name.to_s.classify}Resource" : "#{resource_name.to_s.classify}Resource")
|
|
129
143
|
route_key = resource_name.to_s
|
|
@@ -282,7 +296,8 @@ module Layered
|
|
|
282
296
|
parent_collection_keys: parent_collection_keys,
|
|
283
297
|
resource_name: route_key,
|
|
284
298
|
member_actions: custom_member.map { |a| a[:action] },
|
|
285
|
-
collection_actions: custom_collection.map { |a| a[:action] }
|
|
299
|
+
collection_actions: custom_collection.map { |a| a[:action] },
|
|
300
|
+
layout: layout)
|
|
286
301
|
|
|
287
302
|
route_defaults = (options[:defaults] || {}).merge(
|
|
288
303
|
_layered_resource_route_key: as_base
|