wor-simple_crud 0.2.1 → 0.3.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 +32 -0
- data/Gemfile +1 -0
- data/README.md +157 -2
- data/lib/simple_crud/authorization/adapter.rb +7 -0
- data/lib/simple_crud/authorization/pundit_adapter.rb +6 -0
- data/lib/simple_crud/config.rb +6 -1
- data/lib/simple_crud/controller_helpers.rb +119 -0
- data/lib/simple_crud/pagination/adapter.rb +7 -1
- data/lib/simple_crud/pagination/kaminari_adapter.rb +23 -2
- data/lib/simple_crud/pagination/pagy_adapter.rb +7 -2
- data/lib/simple_crud/pagination/will_paginate_adapter.rb +7 -3
- data/lib/simple_crud/pagination/wor_paginate_adapter.rb +7 -2
- data/lib/simple_crud/rspec/config.rb +61 -0
- data/lib/simple_crud/rspec/helpers.rb +147 -0
- data/lib/simple_crud/rspec.rb +34 -0
- data/lib/simple_crud/simple_crud_controller.rb +51 -36
- data/lib/simple_crud/version.rb +1 -1
- data/lib/spec/shared_contexts/authenticate_user.rb +1 -7
- data/lib/spec/shared_examples/authorization_adapter_authorize.rb +11 -0
- data/lib/spec/shared_examples/simple_crud_for_create.rb +43 -11
- data/lib/spec/shared_examples/simple_crud_for_create_with_block.rb +18 -0
- data/lib/spec/shared_examples/simple_crud_for_create_with_build.rb +15 -0
- data/lib/spec/shared_examples/simple_crud_for_destroy.rb +19 -8
- data/lib/spec/shared_examples/simple_crud_for_destroy_with_block.rb +13 -0
- data/lib/spec/shared_examples/simple_crud_for_destroy_with_finder.rb +24 -0
- data/lib/spec/shared_examples/simple_crud_for_index.rb +8 -23
- data/lib/spec/shared_examples/simple_crud_for_index_with_block.rb +14 -0
- data/lib/spec/shared_examples/simple_crud_for_index_with_scope.rb +21 -0
- data/lib/spec/shared_examples/simple_crud_for_new.rb +29 -0
- data/lib/spec/shared_examples/simple_crud_for_new_with_block.rb +9 -0
- data/lib/spec/shared_examples/simple_crud_for_new_with_build.rb +13 -0
- data/lib/spec/shared_examples/simple_crud_for_show.rb +14 -10
- data/lib/spec/shared_examples/simple_crud_for_show_with_block.rb +11 -0
- data/lib/spec/shared_examples/simple_crud_for_show_with_finder.rb +24 -0
- data/lib/spec/shared_examples/simple_crud_for_update.rb +55 -15
- data/lib/spec/shared_examples/simple_crud_for_update_with_finder.rb +29 -0
- data/lib/spec/shared_examples/simple_crud_not_found_with_finder.rb +13 -0
- data/lib/spec/shared_examples/simple_crud_renders_template.rb +8 -0
- data/lib/spec/shared_examples/simple_crud_when_not_authorized.rb +18 -0
- data/lib/spec/shared_examples/simple_crud_without_authenticated_user.rb +9 -0
- data/lib/spec/shared_examples/unauthorized_when_not_logged_in.rb +2 -2
- data/spec/block/dummy_models_controller_spec.rb +8 -0
- data/spec/block_create/dummy_models_controller_spec.rb +7 -0
- data/spec/block_destroy/dummy_models_controller_spec.rb +7 -0
- data/spec/block_new/dummy_models_controller_spec.rb +7 -0
- data/spec/block_show/dummy_models_controller_spec.rb +7 -0
- data/spec/built/dummy_models_controller_spec.rb +8 -0
- data/spec/dummy/app/controllers/block/dummy_models_controller.rb +17 -0
- data/spec/dummy/app/controllers/block_create/dummy_models_controller.rb +13 -0
- data/spec/dummy/app/controllers/block_destroy/dummy_models_controller.rb +9 -0
- data/spec/dummy/app/controllers/block_modes/base_controller.rb +11 -0
- data/spec/dummy/app/controllers/block_new/dummy_models_controller.rb +9 -0
- data/spec/dummy/app/controllers/block_show/dummy_models_controller.rb +9 -0
- data/spec/dummy/app/controllers/built/dummy_models_controller.rb +21 -0
- data/spec/dummy/app/controllers/dummy_models_controller.rb +2 -1
- data/spec/dummy/app/controllers/finder/dummy_models_controller.rb +22 -0
- data/spec/dummy/app/controllers/html/dummy_models_controller.rb +7 -0
- data/spec/dummy/app/controllers/html_create/dummy_models_controller.rb +7 -0
- data/spec/dummy/app/controllers/html_destroy/dummy_models_controller.rb +7 -0
- data/spec/dummy/app/controllers/html_modes/base_controller.rb +17 -0
- data/spec/dummy/app/controllers/html_new/dummy_models_controller.rb +7 -0
- data/spec/dummy/app/controllers/html_scoped/dummy_models_controller.rb +8 -0
- data/spec/dummy/app/controllers/html_show/dummy_models_controller.rb +7 -0
- data/spec/dummy/app/controllers/html_update/dummy_models_controller.rb +7 -0
- data/spec/dummy/app/controllers/invalid_status/dummy_models_controller.rb +13 -0
- data/spec/dummy/app/controllers/nested/dummy_models_controller.rb +21 -0
- data/spec/dummy/app/controllers/nested_route/dummy_models_controller.rb +26 -0
- data/spec/dummy/app/controllers/redirect/dummy_models_controller.rb +10 -0
- data/spec/dummy/app/controllers/redirect_auth/dummy_models_controller.rb +28 -0
- data/spec/dummy/app/controllers/scoped/dummy_models_controller.rb +16 -0
- data/spec/dummy/app/controllers/scoped_params/dummy_models_controller.rb +16 -0
- data/spec/dummy/app/controllers/strict/dummy_models_controller.rb +21 -0
- data/spec/dummy/app/models/dummy_model.rb +4 -0
- data/spec/dummy/app/models/dummy_model_policy.rb +4 -0
- data/spec/dummy/app/views/block/dummy_models/index.html.erb +3 -0
- data/spec/dummy/app/views/block_new/dummy_models/new.html.erb +1 -0
- data/spec/dummy/app/views/block_show/dummy_models/show.html.erb +1 -0
- data/spec/dummy/app/views/html/dummy_models/index.html.erb +3 -0
- data/spec/dummy/app/views/html_create/dummy_models/new.html.erb +1 -0
- data/spec/dummy/app/views/html_destroy/dummy_models/show.html.erb +1 -0
- data/spec/dummy/app/views/html_new/dummy_models/new.html.erb +1 -0
- data/spec/dummy/app/views/html_scoped/dummy_models/index.html.erb +3 -0
- data/spec/dummy/app/views/html_show/dummy_models/show.html.erb +1 -0
- data/spec/dummy/app/views/html_update/dummy_models/edit.html.erb +1 -0
- data/spec/dummy/app/views/invalid_status/dummy_models/new.html.erb +1 -0
- data/spec/dummy/app/views/layouts/application.html.erb +4 -0
- data/spec/dummy/app/views/redirect/dummy_models/new.html.erb +1 -0
- data/spec/dummy/config/routes.rb +90 -0
- data/spec/dummy/db/migrate/20260806000000_add_slug_to_dummy_models.rb +7 -0
- data/spec/dummy/db/migrate/20260807000000_add_classroom_slug_to_dummy_models.rb +7 -0
- data/spec/dummy/db/schema.rb +3 -1
- data/spec/dummy/spec/factories/dummy_models.rb +1 -0
- data/spec/dummy_controller_spec.rb +1 -0
- data/spec/dummy_model_policy_spec.rb +8 -16
- data/spec/finder/dummy_models_controller_spec.rb +12 -0
- data/spec/html/dummy_models_controller_spec.rb +7 -0
- data/spec/html_create/dummy_models_controller_spec.rb +7 -0
- data/spec/html_destroy/dummy_models_controller_spec.rb +7 -0
- data/spec/html_new/dummy_models_controller_spec.rb +7 -0
- data/spec/html_scoped/dummy_models_controller_spec.rb +7 -0
- data/spec/html_show/dummy_models_controller_spec.rb +7 -0
- data/spec/html_update/dummy_models_controller_spec.rb +7 -0
- data/spec/invalid_status/dummy_models_controller_spec.rb +10 -0
- data/spec/nested/dummy_models_controller_spec.rb +12 -0
- data/spec/nested_route/dummy_models_controller_spec.rb +19 -0
- data/spec/redirect/dummy_models_controller_spec.rb +23 -0
- data/spec/redirect_auth/dummy_models_controller_spec.rb +11 -0
- data/spec/scoped/dummy_models_controller_spec.rb +7 -0
- data/spec/scoped_params/dummy_models_controller_spec.rb +11 -0
- data/spec/simple_crud/authorization/action_policy_adapter_spec.rb +1 -9
- data/spec/simple_crud/authorization/can_can_can_adapter_spec.rb +1 -7
- data/spec/simple_crud/authorization/pundit_adapter_spec.rb +41 -0
- data/spec/simple_crud/controller_helpers_spec.rb +49 -0
- data/spec/simple_crud/pagination/adapters_spec.rb +37 -13
- data/spec/simple_crud/rspec_config_spec.rb +67 -0
- data/spec/simple_crud_configuration_spec.rb +19 -16
- data/spec/simple_crud_controller_spec.rb +4 -4
- data/spec/spec_helper.rb +1 -0
- data/spec/strict/dummy_models_controller_spec.rb +23 -0
- metadata +87 -4
- data/lib/spec/shared_examples/helpers.rb +0 -50
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 667672176b1c817bdcf1d9c5248e1b3701253d52f289a85bc2e4b3e632c47188
|
|
4
|
+
data.tar.gz: f03748bda6004b670a8989fed13d82e0c6e0e0ec6bd9323a1c4e43e786da4dfe
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '04257318d37af96ef5df3d17d2561dfad551cf3eaca51dc4630d7e7d9a7d19cd05852a4b31e00902ab245610d0e35379e3314586f18646afe3ddb246a17a1756'
|
|
7
|
+
data.tar.gz: 81b0fda087fc2723f8f7a6789f7f2542037b7054b36e9db8aed05eba03f5cdbcb0c38fdf45da85f60e9b3e67c58332118d73be257e5d16b108559eade35adba7
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,37 @@
|
|
|
1
1
|
## Change log
|
|
2
2
|
|
|
3
|
+
### V0.3.0
|
|
4
|
+
|
|
5
|
+
Server-rendered (HTML) support:
|
|
6
|
+
* `html: true` or a render block on every action: `:index` renders `index.html.erb` with the paginated records as `@records`; `:show`/`:new` expose `@record`; `:create`/`:update` redirect to the record on success and re-render the form with errors on failure; `:destroy` redirects to the collection (re-rendering `show.html.erb` when a callback aborts). Blocks receive the records (`:index`), the record (`:show`/`:new`) or `(record, saved)` (`:create`/`:update`/`:destroy`) and override the auto-render.
|
|
7
|
+
* New `:new` action: builds a record, authorizes it, and renders `new.html.erb` (or returns JSON in API mode).
|
|
8
|
+
|
|
9
|
+
Custom lookups, scoping and building:
|
|
10
|
+
* `finder:` on `:show`/`:update`/`:destroy`: a `Proc`/lambda invoked with the controller's params, or a `Symbol` naming a class method on the model. Defaults to `klass.find(params[:id])`.
|
|
11
|
+
* `build:` on `:new`/`:create` for owner-scoped or nested builds (`current_user.classrooms.build`); runs with the controller as `self`, and `:create` assigns the permitted params to the built record before saving.
|
|
12
|
+
* With `authorize: true`, `:index` paginates the Pundit `policy_scope` of the model (falling back to the full relation when no `Scope` is defined) instead of `klass.all`, with a per-action `scope: ->(user[, params]) { ... }` override.
|
|
13
|
+
* `redirect:` on HTML-mode `:create`/`:update`/`:destroy`: a `Proc` called with the record, or a literal path, overriding the default success redirect.
|
|
14
|
+
|
|
15
|
+
Validation errors:
|
|
16
|
+
* `:create` returns `201` / `422 { errors: [...] }` and `:update` returns `200` / `422` instead of raising; `raise_on_invalid: true` keeps strict `create!`/`update!` semantics.
|
|
17
|
+
|
|
18
|
+
Authorization changes:
|
|
19
|
+
* Breaking: `authorize: true` now always enforces policy checks, including for `authenticate: false` actions, which reach policies with a `nil` user. Policies must tolerate a `nil` user.
|
|
20
|
+
* `SimpleCrud::Config.user_method` (default `:current_user`) selects the controller method providing the user to policies and scope lambdas, so apps with other conventions (`current_admin`, ...) need no shims.
|
|
21
|
+
|
|
22
|
+
Adapter interfaces:
|
|
23
|
+
* Authorization adapters gain `policy_scope`; pagination adapters gain `paginated_records`, so HTML-mode `:index` can fetch the current page without rendering. Both are implemented by every shipped adapter (Pundit, CanCanCan, Action Policy / wor-paginate, Kaminari, will_paginate, Pagy).
|
|
24
|
+
|
|
25
|
+
Fixes:
|
|
26
|
+
* Kaminari adapter: string query params corrupted offsets (`?page=3&per_page=10` resolved to `OFFSET 1010`), and `per_page` now respects `Kaminari.config.max_per_page`.
|
|
27
|
+
* Custom finders returning anything but a single record raise `ActiveRecord::RecordNotFound` instead of failing later with an obscure error.
|
|
28
|
+
* Invalid `simple_crud_for` usage (unknown action, missing policy, missing serializer) raises `ArgumentError` instead of throwing uncatchable string tags.
|
|
29
|
+
|
|
30
|
+
Testing library:
|
|
31
|
+
* The shared examples are a reusable library: configure via `SimpleCrud::RSpec.configure`, wire up explicitly with `SimpleCrud::RSpec.install!`. Every app-specific assumption has an overridable default matching the gem's own stack (Devise-JWT + FactoryBot + Pundit + AMS): `authenticate`/`current_user`/`other_user`, `create_record`/`create_records`/`params_for`, `owner_association`/`required_attribute`/`required_error`, `finder_key`, `params_key`, `route_params`, `invalid_status`, `unauthenticated_status`, `assert_html_template`, `policy_class`, `serializer_class`.
|
|
32
|
+
* Examples are HTML-aware (branching on the `html:` option, inferring it for block-form actions) and issue requests by action name, so one set covers API and server-rendered controllers, namespaced and nested routes alike.
|
|
33
|
+
* Refactor `SimpleCrudController` helpers into `SimpleCrud::ControllerHelpers`, dedupe repeated test code, and require `rails-controller-testing` for template assertions. Tested across Rails 6.1-8.1.
|
|
34
|
+
|
|
3
35
|
### V0.2.1
|
|
4
36
|
* Rename the published gem from `simple_crud` to `wor-simple_crud`. `simple_crud` is already taken on RubyGems by an unrelated gem, and RubyGems also rejects names that differ from an existing one only by hyphen/underscore (so `simple-crud` was rejected too). Install with `gem 'wor-simple_crud', require: 'simple_crud'` (the require path is unchanged).
|
|
5
37
|
* Drop the last Wolox references now that this gem lives at icoluccio/simple-crud, and point the wor-paginate link and CI/gem badges at their new homes.
|
data/Gemfile
CHANGED
data/README.md
CHANGED
|
@@ -14,7 +14,10 @@ SimpleCrud
|
|
|
14
14
|
- [Options](#options)
|
|
15
15
|
- [Paginate](#paginate)
|
|
16
16
|
- [Authorize](#authorize)
|
|
17
|
+
- [Authenticate](#authenticate)
|
|
17
18
|
- [Serializer](#serializer)
|
|
19
|
+
- [HTML](#html)
|
|
20
|
+
- [Finder](#finder)
|
|
18
21
|
- [Shared examples](#shared-examples)
|
|
19
22
|
- [Contributing](#contributing)
|
|
20
23
|
- [Releases](#releases)
|
|
@@ -64,7 +67,7 @@ Before SimpleCrud can be used, some boilerplate is needed. Add the following to
|
|
|
64
67
|
```ruby
|
|
65
68
|
include Pundit::Authorization
|
|
66
69
|
include Wor::Paginate
|
|
67
|
-
extend
|
|
70
|
+
extend SimpleCrudController
|
|
68
71
|
|
|
69
72
|
before_action :set_params
|
|
70
73
|
|
|
@@ -91,6 +94,7 @@ simple_crud_for :show
|
|
|
91
94
|
simple_crud_for :index
|
|
92
95
|
simple_crud_for :create
|
|
93
96
|
simple_crud_for :destroy
|
|
97
|
+
simple_crud_for :new
|
|
94
98
|
```
|
|
95
99
|
|
|
96
100
|
Each method supports different options, as in:
|
|
@@ -102,6 +106,12 @@ simple_crud_for :index, paginate: false, authorize: false, serializer: CustomSer
|
|
|
102
106
|
- Authorize: whether it should check authorization via the configured authorization adapter (Pundit by default)
|
|
103
107
|
- Authenticate: whether it should use Devise to check for a current_user
|
|
104
108
|
- Serializer: specify a particular serializer you should use
|
|
109
|
+
- Html: renders the action's ERB template instead of JSON (valid for `:index`, `:show`, `:new`, `:create`, `:update` and `:destroy`). Only meaningful in controllers that render templates
|
|
110
|
+
- Scope: only valid for `:index`. A `Proc`/`lambda` taking `current_user` (plus the controller's `params` if it takes a second argument) that returns the relation to list, overriding the default `policy_scope`. The user is resolved via `SimpleCrud::Config.user_method` (`:current_user` by default; set it to e.g. `:current_admin`)
|
|
111
|
+
- Finder: only valid for `:show`, `:update` and `:destroy`. A `Proc`/`lambda` (invoked with the controller's params) or a `Symbol` naming a class method on the model, used to look up the record instead of `klass.find(params[:id])`.
|
|
112
|
+
- Build: only valid for `:new` and `:create`. A `Proc`/`lambda` that builds the record (invoked with the controller as `self`, so `current_user`, `params` and any instance variables are available), for building nested or owner-scoped records like `current_user.classrooms.build`. `:create` then assigns the permitted params to the built record before saving
|
|
113
|
+
- Raise_on_invalid: only valid for `:create` and `:update`. Keeps the strict `create!`/`update!` semantics (raising on invalid input) instead of returning `422` with the validation errors
|
|
114
|
+
- Redirect: only valid for HTML-mode `:create`, `:update` and `:destroy`. A `Proc`/`lambda` (called with the record) or a literal path used as the success redirect target. Defaults to the record (`:create`/`:update`) or the model's collection path (`:destroy`)
|
|
105
115
|
|
|
106
116
|
You'll need a few things so they work correctly:
|
|
107
117
|
|
|
@@ -143,10 +153,11 @@ end
|
|
|
143
153
|
|
|
144
154
|
SimpleCrud.configure { |config| config.pagination_adapter = MyPaginationAdapter.new }
|
|
145
155
|
```
|
|
146
|
-
|
|
147
156
|
#### Authorize
|
|
148
157
|
Authorization checks go through [Pundit](https://github.com/varvet/pundit) by default. Name the policy after the model plus `Policy`, e.g. `AuthorPolicy`, written as a regular Pundit policy:
|
|
149
158
|
|
|
159
|
+
> **Note:** authorization runs whenever `authorize: true`, independently of `authenticate`. With no signed-in user the policy receives `nil` as the user, so write policies that tolerate it (e.g. `user.nil? ? false : ...`). Devise provides `authenticate_user!` and `current_user` for you; non-Devise apps must define a `current_user` (or point `SimpleCrud::Config.user_method` at their own method) for policies and `scope:` lambdas to receive the user.
|
|
160
|
+
|
|
150
161
|
```ruby
|
|
151
162
|
class AuthorPolicy
|
|
152
163
|
attr_reader :user, :author
|
|
@@ -159,10 +170,30 @@ class AuthorPolicy
|
|
|
159
170
|
def show?
|
|
160
171
|
user.present?
|
|
161
172
|
end
|
|
173
|
+
|
|
174
|
+
class Scope
|
|
175
|
+
def initialize(user, scope)
|
|
176
|
+
@user = user
|
|
177
|
+
@scope = scope
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Used by :index to scope the listed records to the current user.
|
|
181
|
+
def resolve
|
|
182
|
+
@scope.where(user: @user)
|
|
183
|
+
end
|
|
184
|
+
end
|
|
162
185
|
end
|
|
163
186
|
|
|
164
187
|
```
|
|
165
188
|
|
|
189
|
+
When `authorize: true`, the `:index` action paginates the Pundit `policy_scope` of the model (falling back to the full relation when no `Scope` is defined) instead of `klass.all`, so "only my records" scoping works out of the box. Override the scope per action with the `scope:` option, a callable that receives the user resolved via `SimpleCrud::Config.user_method` (`nil` when there is none, and `params` too when it takes a second argument):
|
|
190
|
+
|
|
191
|
+
```ruby
|
|
192
|
+
SimpleCrud.configure { |c| c.user_method = :current_admin } # non-Devise naming conventions
|
|
193
|
+
simple_crud_for :index, scope: ->(user) { Model.visible_to(user) }
|
|
194
|
+
simple_crud_for :index, scope: ->(user, params) { Model.where(status: params[:status]).visible_to(user) }
|
|
195
|
+
```
|
|
196
|
+
|
|
166
197
|
Prefer CanCanCan or Action Policy instead? Both have adapters ready to go:
|
|
167
198
|
|
|
168
199
|
```ruby
|
|
@@ -200,6 +231,13 @@ class MyAuthorizationAdapter
|
|
|
200
231
|
def policy_defined?(model_class)
|
|
201
232
|
Kernel.const_defined?("#{model_class}Policy")
|
|
202
233
|
end
|
|
234
|
+
|
|
235
|
+
# Called when rendering :index with authorize: true. Must return the
|
|
236
|
+
# relation the current user is allowed to list. The default returns
|
|
237
|
+
# the full relation.
|
|
238
|
+
def policy_scope(controller, model_class)
|
|
239
|
+
model_class.all
|
|
240
|
+
end
|
|
203
241
|
end
|
|
204
242
|
|
|
205
243
|
SimpleCrud.configure do |config|
|
|
@@ -219,6 +257,58 @@ class AuthorSerializer < ActiveModel::Serializer
|
|
|
219
257
|
end
|
|
220
258
|
```
|
|
221
259
|
|
|
260
|
+
#### HTML
|
|
261
|
+
For server-rendered apps, `html: true` renders the action's ERB template instead of JSON, so be sure the controller can render templates (e.g. `ActionController::Base`). Behavior per action:
|
|
262
|
+
|
|
263
|
+
- `:index` renders `index.html.erb` with the paginated records exposed as `@records` (pagination still applies, or use `paginate: false`).
|
|
264
|
+
- `:show` renders `show.html.erb` with the record exposed as `@record` (custom `finder:` still applies).
|
|
265
|
+
- `:new` builds a new record, authorizes it, and renders `new.html.erb` with it exposed as `@record`.
|
|
266
|
+
- `:create` saves and redirects to the created record on success, or re-renders `new.html.erb` (with `@record` and its errors) on failure.
|
|
267
|
+
- `:update` saves and redirects to the record on success, or re-renders `edit.html.erb` on failure.
|
|
268
|
+
- `:destroy` destroys and redirects to the collection (`redirect_to Model`) on success, or re-renders `show.html.erb` if a callback aborts the destroy.
|
|
269
|
+
|
|
270
|
+
```ruby
|
|
271
|
+
simple_crud_for :index, html: true
|
|
272
|
+
simple_crud_for :show, html: true
|
|
273
|
+
simple_crud_for :new, html: true
|
|
274
|
+
simple_crud_for :create, html: true
|
|
275
|
+
simple_crud_for :update, html: true
|
|
276
|
+
simple_crud_for :destroy, html: true
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Or pass a block that renders explicitly, overriding the auto-render. The block receives the records for `:index`, the record for `:show`/`:new`, or the record plus a saved flag for `:create`/`:update`/`:destroy`. Passing a block also implies `html: true` for the shared examples (so a server-rendered block is asserted as HTML); if your block renders JSON instead, pass `html: false` explicitly:
|
|
280
|
+
|
|
281
|
+
```ruby
|
|
282
|
+
simple_crud_for :index do |records|
|
|
283
|
+
render :index, locals: { models: records }
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
simple_crud_for :create do |record, saved|
|
|
287
|
+
saved ? redirect_to(record) : render(:new, locals: { model: record })
|
|
288
|
+
end
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
#### Build
|
|
292
|
+
`simple_crud_for :new` and `simple_crud_for :create` build the record with `klass.new`, which can't express owner-scoped or nested records (`current_user.classrooms.build`, `@classroom.assignments.build`). Pass a `build:` lambda; it runs with the controller as `self`, so `current_user`, `params` and any instance variables set by a `before_action` are available:
|
|
293
|
+
|
|
294
|
+
```ruby
|
|
295
|
+
simple_crud_for :new, build: -> { current_user.classrooms.build }
|
|
296
|
+
simple_crud_for :create, build: -> { current_user.classrooms.build }
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
`:create` assigns the permitted params to the built record before saving, so the owner/parent association survives. `:update` keeps finding the record via the `finder:`.
|
|
300
|
+
|
|
301
|
+
#### Finder
|
|
302
|
+
By default records are looked up by primary key via `klass.find(params[:id])`. The `finder:` option on `:show`, `:update` and `:destroy` replaces that with any lookup you want: pretty URLs (`resources :posts, param: :slug`), tokens, composite keys, or scoping by a parent resource. Pass a `Proc`/`lambda` that maps the controller's `params` to a record, or a `Symbol` naming a class method on the model that takes the params:
|
|
303
|
+
|
|
304
|
+
```ruby
|
|
305
|
+
simple_crud_for :show, finder: ->(params) { Model.find_by!(token: params[:token]) }
|
|
306
|
+
simple_crud_for :update, finder: :find_by_slug
|
|
307
|
+
simple_crud_for :destroy, finder: ->(params) { current_user.models.find(params[:id]) }
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
When omitted it defaults to `klass.find(params[:id])`, and `not_found` is still returned whenever the finder finds no record.
|
|
311
|
+
|
|
222
312
|
### Shared examples
|
|
223
313
|
While optional, using the included shared examples saves you from writing the standard test cases for the methods. You can even use them if you didn't use `simple_crud_for`, as a set of basic tests. To include them, just add `require 'simple_crud/rspec'` to your `rails_helper.rb` file and add the lines you need to your `*_spec.rb` files:
|
|
224
314
|
```ruby
|
|
@@ -233,8 +323,73 @@ describe V1::Backoffice::AuthorsController do
|
|
|
233
323
|
end
|
|
234
324
|
```
|
|
235
325
|
|
|
326
|
+
The `create` and `update` examples cover the `422` response with validation errors (skipped when `raise_on_invalid: true`), and all base examples adapt to `html: true` controllers (asserting the rendered template/redirect instead of JSON). Controllers using the extra options can include their dedicated examples too:
|
|
327
|
+
|
|
328
|
+
```ruby
|
|
329
|
+
include_examples 'simple crud for new' # the :new action
|
|
330
|
+
include_examples 'simple crud for index with block' # render block
|
|
331
|
+
include_examples 'simple crud for index with scope' # scope: ->(user) { ... }
|
|
332
|
+
include_examples 'simple crud for show with block' # render block on :show
|
|
333
|
+
include_examples 'simple crud for new with block' # render block on :new
|
|
334
|
+
include_examples 'simple crud for create with block' # render block on :create
|
|
335
|
+
include_examples 'simple crud for destroy with block' # render block on :destroy
|
|
336
|
+
include_examples 'simple crud for show with finder' # finder on :show
|
|
337
|
+
include_examples 'simple crud for update with finder' # finder on :update
|
|
338
|
+
include_examples 'simple crud for destroy with finder' # finder on :destroy
|
|
339
|
+
include_examples 'simple crud for new with build' # build: -> { ... } on :new
|
|
340
|
+
include_examples 'simple crud for create with build' # build: -> { ... } on :create
|
|
341
|
+
```
|
|
342
|
+
|
|
236
343
|
It's not needed to specify paginate: true and such, since the shared examples will use the configuration that was originally passed to simple_crud_for
|
|
237
344
|
|
|
345
|
+
#### Adopting the shared examples
|
|
346
|
+
The shared examples assume the gem's own stack by default (Devise-JWT authentication, FactoryBot, Pundit, ActiveModel Serializers). Wiring is opt-in: require the file and call `SimpleCrud::RSpec.install!` in `spec/spec_helper.rb` (or `rails_helper.rb`), then configure anything your app differs on:
|
|
347
|
+
|
|
348
|
+
```ruby
|
|
349
|
+
SimpleCrud::RSpec.configure do |config|
|
|
350
|
+
# How the current user (and a secondary "other user") is built.
|
|
351
|
+
config.current_user = -> { User.create!(email: 'user@example.com', password: 'secret') }
|
|
352
|
+
config.other_user = -> { User.create!(email: 'other@example.com', password: 'secret') }
|
|
353
|
+
|
|
354
|
+
# How to sign the current user in for a request (no Devise-JWT here).
|
|
355
|
+
config.authenticate = -> { request.session[:user_id] = current_user.id }
|
|
356
|
+
|
|
357
|
+
# How records and create/update params are built (no FactoryBot here).
|
|
358
|
+
config.create_record = ->(klass, attributes) { klass.create!(attributes) }
|
|
359
|
+
config.create_records = ->(klass, count, attributes) { count.times.map { klass.create!(attributes) } }
|
|
360
|
+
config.params_for = ->(klass) { klass.new.attributes.slice('title') }
|
|
361
|
+
|
|
362
|
+
# The owner association and the validation this app's models enforce.
|
|
363
|
+
config.owner_association = :instructor
|
|
364
|
+
config.required_attribute = :title
|
|
365
|
+
config.required_error = "Title can't be blank"
|
|
366
|
+
|
|
367
|
+
# Redirect-based apps: unauthenticated requests get a redirect, not a 401.
|
|
368
|
+
# And if the rendered template name doesn't match the action, drop the
|
|
369
|
+
# render_template assertion.
|
|
370
|
+
config.unauthenticated_status = :found
|
|
371
|
+
config.assert_html_template = false
|
|
372
|
+
|
|
373
|
+
# Server-rendered apps usually use nested strong params
|
|
374
|
+
# (params.require(:classroom).permit(:name)); wrap request bodies under the
|
|
375
|
+
# model's params key instead of posting flat params.
|
|
376
|
+
config.params_key = :classroom
|
|
377
|
+
|
|
378
|
+
# Nested resources (/classrooms/:classroom_slug/assignments): extra params
|
|
379
|
+
# (e.g. the parent slug) added to every request.
|
|
380
|
+
config.route_params = -> { { classroom_slug: model.classroom_slug } }
|
|
381
|
+
|
|
382
|
+
# Re-render the form with a 422 on validation failure (instead of 200).
|
|
383
|
+
config.invalid_status = :unprocessable_entity
|
|
384
|
+
|
|
385
|
+
# Pundit policy and serializer class lookup.
|
|
386
|
+
config.policy_class = ->(klass) { "#{klass}Policy".constantize }
|
|
387
|
+
config.serializer_class = ->(model) { "#{model.class}Serializer".constantize }
|
|
388
|
+
end
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
Each setting has a sensible default, so you only override what differs. Callable settings (`current_user`, `authenticate`, `create_record`, `create_records`, `params_for`, `policy_class`, `serializer_class`) run in the example-group context, so they can call `request`, `create`, `current_user`, etc. The examples are controller-agnostic (they issue requests by action name, not hardcoded paths), so they work for namespaced and nested controllers alike. If you keep `assert_html_template` on (the default), add `gem 'rails-controller-testing'` for the `render_template` matcher.
|
|
392
|
+
|
|
238
393
|
## Contributing
|
|
239
394
|
|
|
240
395
|
1. Fork it
|
|
@@ -15,6 +15,13 @@ module SimpleCrud
|
|
|
15
15
|
def policy_defined?(model_class)
|
|
16
16
|
raise NotImplementedError
|
|
17
17
|
end
|
|
18
|
+
|
|
19
|
+
# Called when rendering :index with authorize: true. Must return the
|
|
20
|
+
# relation the current user is allowed to list. Defaults to the full
|
|
21
|
+
# relation so no scoping dependency is required.
|
|
22
|
+
def policy_scope(_controller, model_class)
|
|
23
|
+
model_class.all
|
|
24
|
+
end
|
|
18
25
|
end
|
|
19
26
|
end
|
|
20
27
|
end
|
|
@@ -16,6 +16,12 @@ module SimpleCrud
|
|
|
16
16
|
def policy_defined?(model_class)
|
|
17
17
|
Kernel.const_defined?("#{model_class}Policy")
|
|
18
18
|
end
|
|
19
|
+
|
|
20
|
+
def policy_scope(controller, model_class)
|
|
21
|
+
controller.send(:policy_scope, model_class)
|
|
22
|
+
rescue Pundit::NotDefinedError
|
|
23
|
+
model_class.all
|
|
24
|
+
end
|
|
19
25
|
end
|
|
20
26
|
end
|
|
21
27
|
end
|
data/lib/simple_crud/config.rb
CHANGED
|
@@ -4,7 +4,7 @@ module SimpleCrud
|
|
|
4
4
|
# Holds simple_crud's configurable pieces, set via SimpleCrud.configure.
|
|
5
5
|
class Config
|
|
6
6
|
class << self
|
|
7
|
-
attr_writer :authorization_adapter, :pagination_adapter
|
|
7
|
+
attr_writer :authorization_adapter, :pagination_adapter, :user_method
|
|
8
8
|
|
|
9
9
|
def authorization_adapter
|
|
10
10
|
@authorization_adapter ||= begin
|
|
@@ -19,6 +19,11 @@ module SimpleCrud
|
|
|
19
19
|
Pagination::WorPaginateAdapter.new
|
|
20
20
|
end
|
|
21
21
|
end
|
|
22
|
+
|
|
23
|
+
# Controller method providing the current user; override for non-Devise apps.
|
|
24
|
+
def user_method
|
|
25
|
+
@user_method ||= :current_user
|
|
26
|
+
end
|
|
22
27
|
end
|
|
23
28
|
end
|
|
24
29
|
end
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleCrud
|
|
4
|
+
# Class-level helpers shared by the CRUD lambdas defined in SimpleCrudController.
|
|
5
|
+
module ControllerHelpers
|
|
6
|
+
def maybe_authorize(controller, record, parameters)
|
|
7
|
+
return unless parameters[:authorize]
|
|
8
|
+
|
|
9
|
+
SimpleCrud::Config.authorization_adapter.authorize(controller, record)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def render_index(controller, klass, options, parameters, &block)
|
|
13
|
+
relation = index_relation(controller, klass, parameters)
|
|
14
|
+
|
|
15
|
+
if parameters[:html] || block
|
|
16
|
+
records = index_records(controller, relation, options, parameters)
|
|
17
|
+
controller.instance_variable_set(:@records, records)
|
|
18
|
+
block ? controller.instance_exec(records, &block) : controller.render(:index)
|
|
19
|
+
elsif parameters[:paginate]
|
|
20
|
+
SimpleCrud::Config.pagination_adapter.paginate(controller, relation, options)
|
|
21
|
+
else
|
|
22
|
+
controller.render({ json: relation }.merge(options))
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def render_show(controller, record, options, parameters, &block)
|
|
27
|
+
if parameters[:html] || block
|
|
28
|
+
controller.instance_variable_set(:@record, record)
|
|
29
|
+
block ? controller.instance_exec(record, &block) : controller.render(:show)
|
|
30
|
+
else
|
|
31
|
+
controller.render({ json: record }.merge(options))
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def render_new(controller, record, parameters, &block)
|
|
36
|
+
if parameters[:html] || block
|
|
37
|
+
controller.instance_variable_set(:@record, record)
|
|
38
|
+
block ? controller.instance_exec(record, &block) : controller.render(:new)
|
|
39
|
+
else
|
|
40
|
+
controller.render json: record
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def build_record(controller, klass, parameters)
|
|
45
|
+
parameters[:build] ? controller.instance_exec(¶meters[:build]) : klass.new
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def index_relation(controller, klass, parameters)
|
|
49
|
+
if parameters[:scope]
|
|
50
|
+
call_scope(parameters[:scope], controller)
|
|
51
|
+
elsif parameters[:authorize]
|
|
52
|
+
SimpleCrud::Config.authorization_adapter.policy_scope(controller, klass)
|
|
53
|
+
else
|
|
54
|
+
klass.all
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def call_scope(scope, controller)
|
|
59
|
+
user_method = SimpleCrud::Config.user_method
|
|
60
|
+
user = controller.respond_to?(user_method) ? controller.public_send(user_method) : nil
|
|
61
|
+
return scope.call(user) if scope.arity == 1
|
|
62
|
+
|
|
63
|
+
scope.call(user, controller.params)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def index_records(controller, relation, options, parameters)
|
|
67
|
+
return relation unless parameters[:paginate]
|
|
68
|
+
|
|
69
|
+
SimpleCrud::Config.pagination_adapter.paginated_records(controller, relation, options)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def find_record(klass, controller, parameters)
|
|
73
|
+
record = lookup_record(klass, controller, parameters)
|
|
74
|
+
raise ActiveRecord::RecordNotFound, "couldn't find #{klass}" if record.nil?
|
|
75
|
+
unless record.is_a?(ActiveRecord::Base)
|
|
76
|
+
raise ActiveRecord::RecordNotFound, "#{klass} finder must return a single record"
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
record
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def lookup_record(klass, controller, parameters)
|
|
83
|
+
finder = parameters[:finder]
|
|
84
|
+
return klass.find(controller.params[:id]) if finder.nil?
|
|
85
|
+
return klass.send(finder, controller.params) unless finder.respond_to?(:call)
|
|
86
|
+
|
|
87
|
+
finder.call(controller.params)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def persist_and_render(controller, record, parameters, options, persist, &block)
|
|
91
|
+
saved = parameters[:raise_on_invalid] ? persist.call(bang: true) : persist.call(bang: false)
|
|
92
|
+
return render_persisted(controller, record, saved, options) unless block || parameters[:html]
|
|
93
|
+
|
|
94
|
+
controller.instance_variable_set(:@record, record)
|
|
95
|
+
return controller.instance_exec(record, saved, &block) if block
|
|
96
|
+
|
|
97
|
+
if saved
|
|
98
|
+
controller.redirect_to(redirect_target(controller, record, options[:redirect]))
|
|
99
|
+
else
|
|
100
|
+
controller.render(options[:failure_template])
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def save_and_render(controller, record, parameters, options, persist, &block)
|
|
105
|
+
persist_and_render(controller, record, parameters,
|
|
106
|
+
options.merge(redirect: parameters[:redirect] || record), persist, &block)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def redirect_target(controller, record, target)
|
|
110
|
+
target.is_a?(Proc) ? controller.instance_exec(record, &target) : target
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def render_persisted(controller, record, saved, options)
|
|
114
|
+
return controller.render(json: record, status: options[:status]) if saved
|
|
115
|
+
|
|
116
|
+
controller.render json: { errors: record.errors.full_messages }, status: 422
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
@@ -6,7 +6,13 @@ module SimpleCrud
|
|
|
6
6
|
# (the default, see WorPaginateAdapter).
|
|
7
7
|
module Adapter
|
|
8
8
|
# Called when paginate: true. Must render the response itself.
|
|
9
|
-
def paginate(controller,
|
|
9
|
+
def paginate(controller, relation, options)
|
|
10
|
+
raise NotImplementedError
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Called when rendering :index as HTML (html: true or a block given) and
|
|
14
|
+
# paginate: true. Must return the current page's records without rendering.
|
|
15
|
+
def paginated_records(controller, relation, options)
|
|
10
16
|
raise NotImplementedError
|
|
11
17
|
end
|
|
12
18
|
end
|
|
@@ -10,10 +10,31 @@ module SimpleCrud
|
|
|
10
10
|
class KaminariAdapter
|
|
11
11
|
include Adapter
|
|
12
12
|
|
|
13
|
-
def paginate(controller,
|
|
14
|
-
records =
|
|
13
|
+
def paginate(controller, relation, options)
|
|
14
|
+
records = paginated_records(controller, relation, options)
|
|
15
15
|
controller.render({ json: records }.merge(options))
|
|
16
16
|
end
|
|
17
|
+
|
|
18
|
+
# Manual page scope: will_paginate shadows Relation#page when both load.
|
|
19
|
+
def paginated_records(controller, relation, _options)
|
|
20
|
+
page = [controller.params[:page].to_i, 1].max
|
|
21
|
+
per_page = page_size(controller)
|
|
22
|
+
|
|
23
|
+
relation.limit(per_page).offset(per_page * (page - 1)).extending do
|
|
24
|
+
include Kaminari::ActiveRecordRelationMethods
|
|
25
|
+
include Kaminari::PageScopeMethods
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
# Params arrive as strings: coerce before arithmetic or "5" * 2 == "55".
|
|
32
|
+
def page_size(controller)
|
|
33
|
+
size = (controller.params[:per_page] || Kaminari.config.default_per_page).to_i
|
|
34
|
+
size = [size, 1].max
|
|
35
|
+
size = [size, Kaminari.config.max_per_page].min if Kaminari.config.max_per_page
|
|
36
|
+
size
|
|
37
|
+
end
|
|
17
38
|
end
|
|
18
39
|
end
|
|
19
40
|
end
|
|
@@ -13,10 +13,15 @@ module SimpleCrud
|
|
|
13
13
|
# the pre-rename 43.4.x, so pick whichever key the loaded Pagy expects.
|
|
14
14
|
MAX_LIMIT_KEY = Gem::Version.new(Pagy::VERSION) >= Gem::Version.new('43.5.0') ? :max_limit : :client_max_limit
|
|
15
15
|
|
|
16
|
-
def paginate(controller,
|
|
17
|
-
|
|
16
|
+
def paginate(controller, relation, options)
|
|
17
|
+
records = paginated_records(controller, relation, options)
|
|
18
18
|
controller.render({ json: records }.merge(options))
|
|
19
19
|
end
|
|
20
|
+
|
|
21
|
+
def paginated_records(controller, relation, _options)
|
|
22
|
+
_pagy, records = controller.send(:pagy, relation, MAX_LIMIT_KEY => 100)
|
|
23
|
+
records
|
|
24
|
+
end
|
|
20
25
|
end
|
|
21
26
|
end
|
|
22
27
|
end
|
|
@@ -11,11 +11,15 @@ module SimpleCrud
|
|
|
11
11
|
class WillPaginateAdapter
|
|
12
12
|
include Adapter
|
|
13
13
|
|
|
14
|
-
def paginate(controller,
|
|
14
|
+
def paginate(controller, relation, options)
|
|
15
|
+
records = paginated_records(controller, relation, options)
|
|
16
|
+
controller.render({ json: records }.merge(options))
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def paginated_records(controller, relation, _options)
|
|
15
20
|
page = controller.params[:page] || 1
|
|
16
21
|
per_page = controller.params[:per_page] || WillPaginate.per_page
|
|
17
|
-
|
|
18
|
-
controller.render({ json: records }.merge(options))
|
|
22
|
+
relation.paginate(page: page, per_page: per_page)
|
|
19
23
|
end
|
|
20
24
|
end
|
|
21
25
|
end
|
|
@@ -9,8 +9,13 @@ module SimpleCrud
|
|
|
9
9
|
class WorPaginateAdapter
|
|
10
10
|
include Adapter
|
|
11
11
|
|
|
12
|
-
def paginate(controller,
|
|
13
|
-
controller.send(:render_paginated,
|
|
12
|
+
def paginate(controller, relation, options)
|
|
13
|
+
controller.send(:render_paginated, relation, options)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def paginated_records(controller, relation, options)
|
|
17
|
+
adapter = controller.send(:find_adapter_for_content, relation, options)
|
|
18
|
+
adapter.paginated_content
|
|
14
19
|
end
|
|
15
20
|
end
|
|
16
21
|
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleCrud
|
|
4
|
+
# Test helpers: SimpleCrud::RSpec.configure exposes the shared examples'
|
|
5
|
+
# configuration for apps on a different stack than the gem's.
|
|
6
|
+
module RSpec
|
|
7
|
+
def self.configure
|
|
8
|
+
yield Config.instance
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Configures how the shared examples create records, authenticate
|
|
12
|
+
# requests and introspect policies/serializers. The defaults match the
|
|
13
|
+
# gem's own stack (Devise-JWT + FactoryBot + Pundit + ActiveModel
|
|
14
|
+
# Serializers), so apps on a different stack can override them via
|
|
15
|
+
# SimpleCrud::RSpec.configure instead of editing the examples.
|
|
16
|
+
#
|
|
17
|
+
# Callable settings (authenticate, current_user, create_record,
|
|
18
|
+
# create_records, attributes_for, policy_class, serializer_class) run in
|
|
19
|
+
# the example-group context, so they can call helpers such as `create`,
|
|
20
|
+
# `request` and `current_user`.
|
|
21
|
+
class Config
|
|
22
|
+
DEFAULTS = {
|
|
23
|
+
authenticate: lambda {
|
|
24
|
+
headers = { 'Accept' => 'application/json', 'Content-Type' => 'application/json' }
|
|
25
|
+
request.headers.merge!(Devise::JWT::TestHelpers.auth_headers(headers, current_user))
|
|
26
|
+
},
|
|
27
|
+
current_user: -> { create(:user) },
|
|
28
|
+
other_user: -> { create(:user) },
|
|
29
|
+
create_record: ->(klass, attributes) { create(klass, **attributes) },
|
|
30
|
+
create_records: ->(klass, count, attributes) { create_list(klass, count, **attributes) },
|
|
31
|
+
params_for: ->(klass) { attributes_for(klass) },
|
|
32
|
+
owner_association: :user,
|
|
33
|
+
required_attribute: :name,
|
|
34
|
+
required_error: "Name can't be blank",
|
|
35
|
+
finder_key: :slug,
|
|
36
|
+
params_key: nil,
|
|
37
|
+
route_params: -> { {} },
|
|
38
|
+
invalid_status: :ok,
|
|
39
|
+
unauthenticated_status: :unauthorized,
|
|
40
|
+
assert_html_template: true,
|
|
41
|
+
policy_class: ->(klass) { "#{klass}Policy".constantize },
|
|
42
|
+
serializer_class: ->(model) { "#{model.class}_serializer".classify.constantize }
|
|
43
|
+
}.freeze
|
|
44
|
+
|
|
45
|
+
class << self
|
|
46
|
+
def instance
|
|
47
|
+
@instance ||= new
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def initialize
|
|
52
|
+
@values = {}
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
DEFAULTS.each_key do |key|
|
|
56
|
+
define_method(key) { @values.fetch(key, DEFAULTS[key]) }
|
|
57
|
+
define_method("#{key}=") { |value| @values[key] = value }
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|