wor-simple_crud 0.5.0 → 0.6.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 +22 -0
- data/Gemfile +5 -1
- data/README.md +16 -3
- data/lib/simple_crud/action_context.rb +30 -9
- data/lib/simple_crud/create_context.rb +2 -1
- data/lib/simple_crud/destroy_context.rb +2 -1
- data/lib/simple_crud/index_context.rb +10 -3
- data/lib/simple_crud/pagination/wor_paginate_adapter.rb +7 -1
- data/lib/simple_crud/persistence_context.rb +17 -1
- data/lib/simple_crud/rspec/config.rb +6 -1
- data/lib/simple_crud/rspec/helpers/authentication.rb +2 -1
- data/lib/simple_crud/rspec/helpers/controller_options.rb +2 -2
- data/lib/simple_crud/rspec/helpers/requests.rb +5 -2
- data/lib/simple_crud/serializer.rb +19 -0
- data/lib/simple_crud/show_context.rb +3 -1
- data/lib/simple_crud/simple_crud_controller.rb +3 -1
- data/lib/simple_crud/update_context.rb +2 -1
- data/lib/simple_crud/version.rb +1 -1
- data/lib/spec/matchers/have_been_serialized_with.rb +19 -2
- data/lib/spec/shared_examples/simple_crud_for_create.rb +6 -6
- data/lib/spec/shared_examples/simple_crud_for_create_with_block.rb +2 -2
- data/lib/spec/shared_examples/simple_crud_for_create_with_build.rb +1 -1
- data/lib/spec/shared_examples/simple_crud_for_destroy.rb +8 -8
- data/lib/spec/shared_examples/simple_crud_for_destroy_with_block.rb +1 -1
- data/lib/spec/shared_examples/simple_crud_for_destroy_with_finder.rb +3 -3
- data/lib/spec/shared_examples/simple_crud_for_edit.rb +2 -2
- data/lib/spec/shared_examples/simple_crud_for_index.rb +3 -3
- data/lib/spec/shared_examples/simple_crud_for_index_with_block.rb +1 -1
- data/lib/spec/shared_examples/simple_crud_for_index_with_scope.rb +1 -1
- data/lib/spec/shared_examples/simple_crud_for_new.rb +1 -1
- data/lib/spec/shared_examples/simple_crud_for_new_with_block.rb +1 -1
- data/lib/spec/shared_examples/simple_crud_for_new_with_build.rb +1 -1
- data/lib/spec/shared_examples/simple_crud_for_show.rb +2 -2
- data/lib/spec/shared_examples/simple_crud_for_show_with_block.rb +1 -1
- data/lib/spec/shared_examples/simple_crud_for_show_with_finder.rb +1 -1
- data/lib/spec/shared_examples/simple_crud_for_update.rb +7 -7
- data/lib/spec/shared_examples/simple_crud_for_update_with_finder.rb +1 -1
- data/lib/spec/shared_examples/simple_crud_not_found_with_finder.rb +1 -1
- data/spec/blueprint/dummy_models_controller_spec.rb +25 -0
- data/spec/blueprint_unpaginated/dummy_models_controller_spec.rb +9 -0
- data/spec/dummy/app/blueprints/dummy_model_blueprint.rb +10 -0
- data/spec/dummy/app/controllers/blueprint/dummy_models_controller.rb +21 -0
- data/spec/dummy/app/controllers/blueprint_unpaginated/dummy_models_controller.rb +10 -0
- data/spec/dummy/config/routes.rb +8 -0
- data/spec/dummy_model_blueprint_spec.rb +16 -0
- data/spec/simple_crud/action_context_spec.rb +19 -0
- data/spec/simple_crud/rspec_config_spec.rb +13 -0
- data/spec/simple_crud/serializer_spec.rb +28 -0
- metadata +11 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b40c7867a484ffc3807b8dceac29ac1f6cc49ccca6d8a9bb067f93f839703e0c
|
|
4
|
+
data.tar.gz: 5a72d84147fc1f149695fe0244a11e7f99f2c9b4debb0b99a291e16439b873ac
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2354c67d6866dd82b70172279fd47a65bced5a950ce4385df97a1d3ab3373724d7f49a2ee7185e9cca3f86f403892e732afeade705c74337d7ae32ed53815632
|
|
7
|
+
data.tar.gz: 851104267914cd47c8312723af3361a13e5ee5fa2a323f73add55a0a09e04d965492f870c66ffc81f24c5851239afd35485b367633192c80ce8b3d8d90a7753a
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
## Change log
|
|
2
2
|
|
|
3
|
+
### V0.6.0
|
|
4
|
+
|
|
5
|
+
Serializer support:
|
|
6
|
+
* Blueprinter: `serializer:` now accepts a [Blueprinter](https://github.com/blueprinter/blueprinter) blueprint on every action, which simple_crud renders with `render_as_hash(record, options)`, as well as an ActiveModelSerializers serializer. The shared examples' default `serializer_class` uses `"#{Model}Blueprint"` when no `"#{Model}Serializer"` constant exists, and `have_been_serialized_with` reads Blueprinter fields.
|
|
7
|
+
* `serializer_options:` on every action: a lambda that runs through `instance_exec`. simple_crud forwards its result to the serializer (`render_as_hash(record, options)` for Blueprinter, `new(record, options)` for AMS). Arity 0 runs with no arguments; arity ≥ 1 gets the record. On `:index` it runs once with no record, so arity-1 lambdas get `nil`. Paginated Blueprinter indexes need wor-paginate ≥ 0.5.
|
|
8
|
+
|
|
9
|
+
New options:
|
|
10
|
+
* `status:` on `:create`/`:update`/`:destroy` overrides the success status (`:created`/`:ok`/`:ok` by default). `status: :no_content` returns an empty body.
|
|
11
|
+
* `after_persist:` on `:create`/`:update`/`:destroy`: a `(record, saved)` lambda that runs after the write and before rendering. Use it for side effects like cache invalidation. It still runs when you pass a render block.
|
|
12
|
+
|
|
13
|
+
Behavior changes:
|
|
14
|
+
* `create`/`update` responses now honor `serializer:`. Before, they returned the raw record. AMS apps that never passed `serializer:` see no change.
|
|
15
|
+
* Cached `:show` now honors `serializer:`. Before, it returned `record.as_json`.
|
|
16
|
+
* Unpaginated `:index` now serializes through the same path as the other actions, so Blueprinter works there. Before, it passed `each_serializer` to `render`.
|
|
17
|
+
|
|
18
|
+
Fixes:
|
|
19
|
+
* `finder:` lambdas now run through `instance_exec` on the controller, so `current_user` and other controller methods are available, matching the README.
|
|
20
|
+
* `:index` resolves a private `current_user` method.
|
|
21
|
+
* Shared examples no longer inject `params[:format]`, which collided with model attributes named `format`.
|
|
22
|
+
* Shared-example `owner_foreign_key` honors custom `foreign_key:` declarations.
|
|
23
|
+
* The base `index` example compares ids with `match_array`; create/update/destroy examples honor a custom `status:`.
|
|
24
|
+
|
|
3
25
|
### V0.5.0
|
|
4
26
|
|
|
5
27
|
New options:
|
data/Gemfile
CHANGED
|
@@ -17,6 +17,7 @@ group :development, :test do
|
|
|
17
17
|
gem 'action_policy', '~> 0.7.6', require: false
|
|
18
18
|
gem 'active_model_serializers', '~> 0.10.16'
|
|
19
19
|
gem 'appraisal', '~> 2.5'
|
|
20
|
+
gem 'blueprinter', '~> 1.3'
|
|
20
21
|
gem 'byebug', '~> 13.0'
|
|
21
22
|
gem 'cancancan', '~> 3.6', require: false
|
|
22
23
|
gem 'database_cleaner-active_record', '~> 2.2', require: 'database_cleaner/active_record'
|
|
@@ -24,6 +25,9 @@ group :development, :test do
|
|
|
24
25
|
gem 'devise-jwt', '~> 0.13'
|
|
25
26
|
gem 'factory_bot_rails', '~> 6.5'
|
|
26
27
|
gem 'faker', '~> 3.8'
|
|
28
|
+
# json 3 dropped the `quirks_mode` keyword that ActionController::TestCase
|
|
29
|
+
# (and thus rails-controller-testing) still passes on Rails <= 8.0.
|
|
30
|
+
gem 'json', '< 3'
|
|
27
31
|
gem 'kaminari', '~> 1.2', require: false
|
|
28
32
|
gem 'overcommit', '~> 0.72'
|
|
29
33
|
gem 'pagy', '~> 43.0', require: false
|
|
@@ -39,5 +43,5 @@ group :development, :test do
|
|
|
39
43
|
# required range differs across supported versions.
|
|
40
44
|
gem 'webmock', '~> 3.26'
|
|
41
45
|
gem 'will_paginate', '~> 4.0', require: false
|
|
42
|
-
gem 'wor-paginate', '~> 0.
|
|
46
|
+
gem 'wor-paginate', '~> 0.5'
|
|
43
47
|
end
|
data/README.md
CHANGED
|
@@ -103,7 +103,10 @@ simple_crud_for :index, paginate: false, authorize: false, serializer: CustomSer
|
|
|
103
103
|
- Authorize: whether it should check authorization via the configured authorization adapter (Pundit by default)
|
|
104
104
|
- Authenticate: whether the generated action calls `authenticate_user!`. `true` (default) or `false`
|
|
105
105
|
- Authenticate_headers: whether shared examples set auth headers and run the unauthorized test. Defaults to `authenticate:`. Set independently when a base-controller `before_action` handles auth and the lambda should skip `authenticate_user!`
|
|
106
|
-
- Serializer: specify a particular serializer you should use
|
|
106
|
+
- Serializer: specify a particular serializer you should use. Accepts an ActiveModelSerializers serializer or a Blueprinter blueprint (see [Serializer](#serializer))
|
|
107
|
+
- Serializer_options: a `Proc`/`lambda` that runs with the controller as `self`. Its result goes to the serializer: `render_as_hash(record, options)` for Blueprinter, `new(record, options)` for ActiveModelSerializers. Arity 0 runs without arguments; arity ≥ 1 receives the record. On `:index` it runs once with no record
|
|
108
|
+
- Status: only valid for `:create`, `:update` and `:destroy`. Overrides the success status (`:created`, `:ok` and `:ok` by default). `status: :no_content` responds with an empty body
|
|
109
|
+
- After_persist: only valid for `:create`, `:update` and `:destroy`. A `Proc`/`lambda` `->(record, saved) { ... }` that runs with the controller as `self`, after the write and before rendering. Use it for side effects like cache invalidation. It still runs when you pass a render block
|
|
107
110
|
- Html: renders the action's ERB template instead of JSON (valid for `:index`, `:show`, `:new`, `:edit`, `:create`, `:update` and `:destroy`). Only meaningful in controllers that render templates
|
|
108
111
|
- 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`)
|
|
109
112
|
- Finder: only valid for `:show`, `:update`, `:destroy` and `:edit`. 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])`.
|
|
@@ -269,12 +272,22 @@ simple_crud_defaults authenticate: false, authenticate_headers: true
|
|
|
269
272
|
```
|
|
270
273
|
|
|
271
274
|
#### Serializer
|
|
272
|
-
|
|
275
|
+
`serializer:` picks the serializer for JSON responses on every action. Pass either an [ActiveModelSerializers](https://github.com/rails-api/active_model_serializers) serializer class, which simple_crud builds per record with `serializer.new(record, serializer_options)`, or a [Blueprinter](https://github.com/blueprinter/blueprinter) blueprint class, which it renders with `blueprint.render_as_hash(record, serializer_options)`.
|
|
276
|
+
|
|
277
|
+
Without `serializer:`, the response uses the record's `as_json`. The shared examples expect an `:id` attribute. Paginated `:index` with Blueprinter needs wor-paginate ≥ 0.5.
|
|
273
278
|
|
|
274
279
|
```ruby
|
|
275
280
|
class AuthorSerializer < ActiveModel::Serializer
|
|
276
|
-
attributes :email, :first_name, :last_name, :
|
|
281
|
+
attributes :email, :first_name, :last_name, :id
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
class AuthorBlueprint < Blueprinter::Base
|
|
285
|
+
identifier :id
|
|
286
|
+
fields :email, :first_name, :last_name
|
|
277
287
|
end
|
|
288
|
+
|
|
289
|
+
simple_crud_for :show, serializer: AuthorBlueprint
|
|
290
|
+
simple_crud_for :index, serializer: AuthorBlueprint, serializer_options: -> { { current_user: current_user } }
|
|
278
291
|
```
|
|
279
292
|
|
|
280
293
|
#### HTML
|
|
@@ -50,10 +50,14 @@ module SimpleCrud
|
|
|
50
50
|
|
|
51
51
|
def lookup_record
|
|
52
52
|
finder = parameters[:finder]
|
|
53
|
-
return
|
|
53
|
+
return find_by_id if finder.nil?
|
|
54
54
|
return klass.send(finder, controller.params) unless finder.respond_to?(:call)
|
|
55
55
|
|
|
56
|
-
|
|
56
|
+
controller.instance_exec(controller.params, &finder)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def find_by_id
|
|
60
|
+
klass.find(controller.params[:id])
|
|
57
61
|
end
|
|
58
62
|
|
|
59
63
|
def build_record
|
|
@@ -65,16 +69,33 @@ module SimpleCrud
|
|
|
65
69
|
end
|
|
66
70
|
|
|
67
71
|
def serialize_opts(key)
|
|
68
|
-
{ key => parameters[:serializer] }.compact
|
|
72
|
+
opts = { key => parameters[:serializer] }.compact
|
|
73
|
+
options = serializer_options
|
|
74
|
+
opts[:serializer_options] = options if options.present?
|
|
75
|
+
opts
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def serializer_options(record = nil)
|
|
79
|
+
callable = parameters[:serializer_options]
|
|
80
|
+
return {} unless callable
|
|
81
|
+
|
|
82
|
+
callable.arity.zero? ? controller.instance_exec(&callable) : controller.instance_exec(record, &callable)
|
|
69
83
|
end
|
|
70
84
|
|
|
71
85
|
def render_record(record, template)
|
|
72
|
-
if parameters[:html] || block
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
86
|
+
return render_html_record(record, template) if parameters[:html] || block
|
|
87
|
+
|
|
88
|
+
render_json_record(record)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def render_html_record(record, template)
|
|
92
|
+
controller.instance_variable_set(:@record, record)
|
|
93
|
+
block ? controller.instance_exec(record, &block) : controller.render(template)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def render_json_record(record)
|
|
97
|
+
serializer = parameters[:serializer]
|
|
98
|
+
controller.render json: SimpleCrud::Serializer.render(serializer, record, serializer_options(record))
|
|
78
99
|
end
|
|
79
100
|
|
|
80
101
|
def render_cached(cache_opts, &fetch_block)
|
|
@@ -10,7 +10,8 @@ module SimpleCrud
|
|
|
10
10
|
record.assign_attributes(attrs)
|
|
11
11
|
maybe_authorize(record)
|
|
12
12
|
persist = ->(bang:) { bang ? record.save! : record.save }
|
|
13
|
-
options = { status: :created, failure_template: :new,
|
|
13
|
+
options = { status: parameters[:status] || :created, failure_template: :new,
|
|
14
|
+
redirect: parameters[:redirect] || record }
|
|
14
15
|
persist_and_render(record, options, persist)
|
|
15
16
|
end
|
|
16
17
|
end
|
|
@@ -8,7 +8,8 @@ module SimpleCrud
|
|
|
8
8
|
record = find_record
|
|
9
9
|
maybe_authorize(record)
|
|
10
10
|
persist = ->(bang:) { bang ? record.destroy! : record.destroy }
|
|
11
|
-
options = { status: :ok, failure_template: :show,
|
|
11
|
+
options = { status: parameters[:status] || :ok, failure_template: :show,
|
|
12
|
+
redirect: parameters[:redirect] || klass }
|
|
12
13
|
persist_and_render(record, options, persist)
|
|
13
14
|
end
|
|
14
15
|
end
|
|
@@ -17,7 +17,14 @@ module SimpleCrud
|
|
|
17
17
|
relation = index_relation
|
|
18
18
|
records = index_records(relation, opts)
|
|
19
19
|
controller.instance_variable_set(:@records, records)
|
|
20
|
-
block ? controller.instance_exec(records, &block) : records
|
|
20
|
+
block ? controller.instance_exec(records, &block) : serialize_records(records)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def serialize_records(records)
|
|
24
|
+
serializer = parameters[:serializer]
|
|
25
|
+
return records.as_json if serializer.nil?
|
|
26
|
+
|
|
27
|
+
records.map { |record| SimpleCrud::Serializer.render(serializer, record, serializer_options) }
|
|
21
28
|
end
|
|
22
29
|
|
|
23
30
|
def render_index
|
|
@@ -28,7 +35,7 @@ module SimpleCrud
|
|
|
28
35
|
elsif parameters[:paginate]
|
|
29
36
|
SimpleCrud::Config.pagination_adapter.paginate(controller, relation, opts)
|
|
30
37
|
else
|
|
31
|
-
controller.render
|
|
38
|
+
controller.render json: serialize_records(relation)
|
|
32
39
|
end
|
|
33
40
|
end
|
|
34
41
|
|
|
@@ -50,7 +57,7 @@ module SimpleCrud
|
|
|
50
57
|
|
|
51
58
|
def call_scope
|
|
52
59
|
user_method = SimpleCrud::Config.user_method
|
|
53
|
-
user = controller.respond_to?(user_method) ? controller.
|
|
60
|
+
user = controller.respond_to?(user_method, true) ? controller.send(user_method) : nil
|
|
54
61
|
scope = parameters[:scope]
|
|
55
62
|
scope.arity == 1 ? scope.call(user) : scope.call(user, controller.params)
|
|
56
63
|
end
|
|
@@ -10,7 +10,13 @@ module SimpleCrud
|
|
|
10
10
|
include Adapter
|
|
11
11
|
|
|
12
12
|
def paginate(controller, relation, options)
|
|
13
|
-
|
|
13
|
+
serializer = options[:each_serializer]
|
|
14
|
+
return controller.send(:render_paginated, relation, options) if serializer.nil?
|
|
15
|
+
|
|
16
|
+
serializer_options = options[:serializer_options] || {}
|
|
17
|
+
controller.send(:render_paginated, relation, options) do |record|
|
|
18
|
+
SimpleCrud::Serializer.render(serializer, record, serializer_options)
|
|
19
|
+
end
|
|
14
20
|
end
|
|
15
21
|
|
|
16
22
|
def paginated_records(controller, relation, options)
|
|
@@ -6,12 +6,18 @@ module SimpleCrud
|
|
|
6
6
|
|
|
7
7
|
def persist_and_render(record, options, persist)
|
|
8
8
|
saved = parameters[:raise_on_invalid] ? persist.call(bang: true) : persist.call(bang: false)
|
|
9
|
+
run_after_persist(record, saved)
|
|
9
10
|
return render_persisted(record, saved, options) unless block || parameters[:html]
|
|
10
11
|
|
|
11
12
|
controller.instance_variable_set(:@record, record)
|
|
12
13
|
block ? controller.instance_exec(record, saved, &block) : render_html_redirect(record, saved, options)
|
|
13
14
|
end
|
|
14
15
|
|
|
16
|
+
def run_after_persist(record, saved)
|
|
17
|
+
hook = parameters[:after_persist]
|
|
18
|
+
controller.instance_exec(record, saved, &hook) if hook
|
|
19
|
+
end
|
|
20
|
+
|
|
15
21
|
def render_html_redirect(record, saved, options)
|
|
16
22
|
if saved
|
|
17
23
|
controller.redirect_to(redirect_target(record, options[:redirect]))
|
|
@@ -25,9 +31,19 @@ module SimpleCrud
|
|
|
25
31
|
end
|
|
26
32
|
|
|
27
33
|
def render_persisted(record, saved, options)
|
|
28
|
-
return
|
|
34
|
+
return render_success(record, options[:status]) if saved
|
|
29
35
|
|
|
30
36
|
controller.render json: { errors: record.errors.full_messages }, status: 422
|
|
31
37
|
end
|
|
38
|
+
|
|
39
|
+
def render_success(record, status)
|
|
40
|
+
return controller.head(status) if status == :no_content
|
|
41
|
+
|
|
42
|
+
controller.render json: payload(record), status: status
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def payload(record)
|
|
46
|
+
SimpleCrud::Serializer.render(parameters[:serializer], record, serializer_options(record))
|
|
47
|
+
end
|
|
32
48
|
end
|
|
33
49
|
end
|
|
@@ -51,7 +51,12 @@ module SimpleCrud
|
|
|
51
51
|
unauthenticated_status: :unauthorized,
|
|
52
52
|
assert_html_template: true,
|
|
53
53
|
policy_class: ->(klass) { "#{klass}Policy".constantize },
|
|
54
|
-
serializer_class:
|
|
54
|
+
serializer_class: lambda { |model|
|
|
55
|
+
klass = model.class
|
|
56
|
+
next "#{klass}Serializer".constantize if Kernel.const_defined?("#{klass}Serializer")
|
|
57
|
+
|
|
58
|
+
"#{klass}Blueprint".constantize
|
|
59
|
+
},
|
|
55
60
|
created_record_check: nil
|
|
56
61
|
}.freeze
|
|
57
62
|
|
|
@@ -19,7 +19,8 @@ module SimpleCrud
|
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
def owner_foreign_key
|
|
22
|
-
|
|
22
|
+
reflection = model_class_object.reflect_on_association(owner_association)
|
|
23
|
+
reflection ? reflection.foreign_key : :"#{owner_association}_id"
|
|
23
24
|
end
|
|
24
25
|
|
|
25
26
|
# The owner association attributed to current_user, if the model has one.
|
|
@@ -10,8 +10,8 @@ module SimpleCrud
|
|
|
10
10
|
described_class.instance_variable_get(:@simple_crud_metadata)[method][option]
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
-
%i[paginate authorize authenticate serializer
|
|
14
|
-
authenticate_headers].each do |option|
|
|
13
|
+
%i[paginate authorize authenticate serializer serializer_options status after_persist html finder
|
|
14
|
+
scope build raise_on_invalid block authenticate_headers].each do |option|
|
|
15
15
|
define_method("check_#{option}") { |method| get_option(method, option) }
|
|
16
16
|
end
|
|
17
17
|
|
|
@@ -11,8 +11,11 @@ module SimpleCrud
|
|
|
11
11
|
Rack::Utils::SYMBOL_TO_STATUS_CODE.invert[422]
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
# Rails copies the controller-test `format:` kwarg into params, which
|
|
15
|
+
# collides with a model attribute named `format`. Only HTML requests
|
|
16
|
+
# need it, so leave params untouched for JSON.
|
|
17
|
+
def format_param(action)
|
|
18
|
+
check_html(action) ? :html : nil
|
|
16
19
|
end
|
|
17
20
|
|
|
18
21
|
# Extra params (e.g. a parent slug) added to every request, for nested
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleCrud
|
|
4
|
+
# Renders a record with the app's serializer stack.
|
|
5
|
+
#
|
|
6
|
+
# Blueprinter blueprints respond to `render_as_hash` and receive the
|
|
7
|
+
# render options, so a blueprint can read `url_builder:` (or any other
|
|
8
|
+
# option) from them. For anything else, simple_crud assumes an
|
|
9
|
+
# ActiveModelSerializers-style serializer and calls `new(record, options)`.
|
|
10
|
+
# A `nil` serializer renders `record.as_json`.
|
|
11
|
+
module Serializer
|
|
12
|
+
def self.render(serializer, record, options = {})
|
|
13
|
+
return record.as_json if serializer.nil?
|
|
14
|
+
return serializer.render_as_hash(record, options) if serializer.respond_to?(:render_as_hash)
|
|
15
|
+
|
|
16
|
+
serializer.new(record, options).as_json
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -21,7 +21,9 @@ module SimpleCrud
|
|
|
21
21
|
record = find_record
|
|
22
22
|
maybe_authorize(record)
|
|
23
23
|
controller.instance_variable_set(:@record, record)
|
|
24
|
-
|
|
24
|
+
return controller.instance_exec(record, &block) if block
|
|
25
|
+
|
|
26
|
+
SimpleCrud::Serializer.render(parameters[:serializer], record, serializer_options(record))
|
|
25
27
|
end
|
|
26
28
|
end
|
|
27
29
|
end
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require 'active_support/all'
|
|
4
4
|
require_relative 'config'
|
|
5
|
+
require_relative 'serializer'
|
|
5
6
|
require_relative 'cache_helpers'
|
|
6
7
|
require_relative 'action_context'
|
|
7
8
|
require_relative 'persistence_context'
|
|
@@ -63,7 +64,8 @@ module SimpleCrudController
|
|
|
63
64
|
def parameters_with_defaults(parameters)
|
|
64
65
|
defaults = {
|
|
65
66
|
authorize: true, paginate: true, authenticate: true, authenticate_headers: nil,
|
|
66
|
-
serializer: nil,
|
|
67
|
+
serializer: nil, serializer_options: nil, status: nil, after_persist: nil, html: false,
|
|
68
|
+
finder: nil, scope: nil, build: nil, raise_on_invalid: false
|
|
67
69
|
}
|
|
68
70
|
defaults.merge(simple_crud_inherited_defaults).each do |key, value|
|
|
69
71
|
parameters[key] = value unless parameters.key?(key)
|
|
@@ -9,7 +9,8 @@ module SimpleCrud
|
|
|
9
9
|
maybe_authorize(record)
|
|
10
10
|
attrs = permitted_params
|
|
11
11
|
persist = ->(bang:) { bang ? record.update!(attrs) : record.update(attrs) }
|
|
12
|
-
options = { status: :ok, failure_template: :edit,
|
|
12
|
+
options = { status: parameters[:status] || :ok, failure_template: :edit,
|
|
13
|
+
redirect: parameters[:redirect] || record }
|
|
13
14
|
persist_and_render(record, options, persist)
|
|
14
15
|
end
|
|
15
16
|
end
|
data/lib/simple_crud/version.rb
CHANGED
|
@@ -4,12 +4,29 @@ RSpec::Matchers.define :have_been_serialized_with do |serializer|
|
|
|
4
4
|
# TODO: Add support for optional relations `has_many :zarasa, if: zarasa2`
|
|
5
5
|
# TODO: Support multilevel serialization
|
|
6
6
|
match do |json_response|
|
|
7
|
+
attributes = serializer_attributes(serializer)
|
|
8
|
+
reflections = serializer_reflections(serializer)
|
|
9
|
+
|
|
7
10
|
[json_response].flatten.all? do |json_response_item|
|
|
8
|
-
all_attributes_included?(
|
|
9
|
-
all_attributes_included?(
|
|
11
|
+
all_attributes_included?(attributes, json_response_item) &&
|
|
12
|
+
all_attributes_included?(reflections, json_response_item)
|
|
10
13
|
end
|
|
11
14
|
end
|
|
12
15
|
|
|
16
|
+
# Blueprinter blueprints expose their fields through the view collection;
|
|
17
|
+
# ActiveModelSerializers expose `._attributes` / `._reflections`.
|
|
18
|
+
def serializer_attributes(serializer)
|
|
19
|
+
return serializer._attributes unless serializer.respond_to?(:view_collection)
|
|
20
|
+
|
|
21
|
+
serializer.view_collection.fields_for(:default).map(&:name)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def serializer_reflections(serializer)
|
|
25
|
+
return serializer._reflections.keys unless serializer.respond_to?(:view_collection)
|
|
26
|
+
|
|
27
|
+
[]
|
|
28
|
+
end
|
|
29
|
+
|
|
13
30
|
failure_message do |json_response|
|
|
14
31
|
"expected attributes of #{serializer} serializer to be included in #{json_response}"
|
|
15
32
|
end
|
|
@@ -4,7 +4,7 @@ RSpec.shared_examples 'simple crud for create' do
|
|
|
4
4
|
describe 'POST #create' do
|
|
5
5
|
context 'without authenticated user' do
|
|
6
6
|
subject!(:req) do
|
|
7
|
-
post :create, params: with_route_params(body_params(params_for(model_class))), format:
|
|
7
|
+
post :create, params: with_route_params(body_params(params_for(model_class))), format: format_param(:create)
|
|
8
8
|
end
|
|
9
9
|
|
|
10
10
|
include_examples 'unauthorized when not logged in' if check_authenticate(:create)
|
|
@@ -16,7 +16,7 @@ RSpec.shared_examples 'simple crud for create' do
|
|
|
16
16
|
|
|
17
17
|
before do
|
|
18
18
|
make_policies_fail(:create)
|
|
19
|
-
post :create, params: with_route_params(body_params(model_params)), format:
|
|
19
|
+
post :create, params: with_route_params(body_params(model_params)), format: format_param(:create)
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
it 'fails with forbidden' do
|
|
@@ -30,7 +30,7 @@ RSpec.shared_examples 'simple crud for create' do
|
|
|
30
30
|
let(:create_params) { model_params.merge(owner_params) }
|
|
31
31
|
|
|
32
32
|
before do
|
|
33
|
-
post :create, params: with_route_params(body_params(create_params)), format:
|
|
33
|
+
post :create, params: with_route_params(body_params(create_params)), format: format_param(:create)
|
|
34
34
|
end
|
|
35
35
|
|
|
36
36
|
if check_html(:create)
|
|
@@ -38,8 +38,8 @@ RSpec.shared_examples 'simple crud for create' do
|
|
|
38
38
|
expect(response).to have_http_status(:found)
|
|
39
39
|
end
|
|
40
40
|
else
|
|
41
|
-
it 'response with
|
|
42
|
-
expect(response).to have_http_status(:created)
|
|
41
|
+
it 'response with the configured status code' do
|
|
42
|
+
expect(response).to have_http_status(check_status(:create) || :created)
|
|
43
43
|
end
|
|
44
44
|
|
|
45
45
|
it 'creates an article with valid attributes' do
|
|
@@ -58,7 +58,7 @@ RSpec.shared_examples 'simple crud for create' do
|
|
|
58
58
|
include_context 'with authenticated user' if check_authenticate(:create)
|
|
59
59
|
|
|
60
60
|
before do
|
|
61
|
-
post :create, params: with_route_params(body_params(owner_params)), format:
|
|
61
|
+
post :create, params: with_route_params(body_params(owner_params)), format: format_param(:create)
|
|
62
62
|
end
|
|
63
63
|
|
|
64
64
|
unless check_block(:create)
|
|
@@ -7,7 +7,7 @@ RSpec.shared_examples 'simple crud for create with block' do
|
|
|
7
7
|
let(:create_params) { model_params.merge(owner_params) }
|
|
8
8
|
|
|
9
9
|
before do
|
|
10
|
-
post :create, params: with_route_params(body_params(create_params)), format:
|
|
10
|
+
post :create, params: with_route_params(body_params(create_params)), format: format_param(:create)
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
it 'passes the saved record to the block' do
|
|
@@ -24,7 +24,7 @@ RSpec.shared_examples 'simple crud for create with block' do
|
|
|
24
24
|
include_context 'with authenticated user' if check_authenticate(:create)
|
|
25
25
|
|
|
26
26
|
before do
|
|
27
|
-
post :create, params: with_route_params(body_params(owner_params)), format:
|
|
27
|
+
post :create, params: with_route_params(body_params(owner_params)), format: format_param(:create)
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
it 'does not create a model' do
|
|
@@ -5,7 +5,7 @@ RSpec.shared_examples 'simple crud for create with build' do
|
|
|
5
5
|
include_context 'with authenticated user' if check_authenticate(:create)
|
|
6
6
|
|
|
7
7
|
before do
|
|
8
|
-
post :create, params: with_route_params(body_params(params_for(model_class))), format:
|
|
8
|
+
post :create, params: with_route_params(body_params(params_for(model_class))), format: format_param(:create)
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
it 'creates the record owned by the current user' do
|
|
@@ -5,7 +5,7 @@ RSpec.shared_examples 'simple crud for destroy' do
|
|
|
5
5
|
context 'without authenticated user' do
|
|
6
6
|
subject!(:req) do
|
|
7
7
|
delete :destroy, params: with_route_params(record_param(:destroy, nil, not_found: true)),
|
|
8
|
-
format:
|
|
8
|
+
format: format_param(:destroy)
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
include_examples 'unauthorized when not logged in' if check_authenticate(:destroy)
|
|
@@ -17,7 +17,7 @@ RSpec.shared_examples 'simple crud for destroy' do
|
|
|
17
17
|
|
|
18
18
|
before do
|
|
19
19
|
make_policies_fail(:destroy)
|
|
20
|
-
delete :destroy, params: with_route_params(record_param(:destroy, model)), format:
|
|
20
|
+
delete :destroy, params: with_route_params(record_param(:destroy, model)), format: format_param(:destroy)
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
it 'fails with forbidden' do
|
|
@@ -31,7 +31,7 @@ RSpec.shared_examples 'simple crud for destroy' do
|
|
|
31
31
|
|
|
32
32
|
before do
|
|
33
33
|
model
|
|
34
|
-
delete :destroy, params: with_route_params(record_param(:destroy, model)), format:
|
|
34
|
+
delete :destroy, params: with_route_params(record_param(:destroy, model)), format: format_param(:destroy)
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
if check_html(:destroy)
|
|
@@ -40,8 +40,8 @@ RSpec.shared_examples 'simple crud for destroy' do
|
|
|
40
40
|
expect(model_class_object.exists?(model.id)).to be false
|
|
41
41
|
end
|
|
42
42
|
else
|
|
43
|
-
it 'response with
|
|
44
|
-
expect(response).to have_http_status(:ok)
|
|
43
|
+
it 'response with the configured status code' do
|
|
44
|
+
expect(response).to have_http_status(check_status(:destroy) || :ok)
|
|
45
45
|
end
|
|
46
46
|
end
|
|
47
47
|
end
|
|
@@ -53,7 +53,7 @@ RSpec.shared_examples 'simple crud for destroy' do
|
|
|
53
53
|
model
|
|
54
54
|
allow(model).to receive(:destroy).and_return(false)
|
|
55
55
|
allow_any_instance_of(SimpleCrud::DestroyContext).to receive(:find_record).and_return(model)
|
|
56
|
-
delete :destroy, params: with_route_params(record_param(:destroy, model)), format:
|
|
56
|
+
delete :destroy, params: with_route_params(record_param(:destroy, model)), format: format_param(:destroy)
|
|
57
57
|
end
|
|
58
58
|
|
|
59
59
|
it 'keeps the record' do
|
|
@@ -77,7 +77,7 @@ RSpec.shared_examples 'simple crud for destroy' do
|
|
|
77
77
|
before do
|
|
78
78
|
model
|
|
79
79
|
delete :destroy, params: with_route_params(record_param(:destroy, nil, not_found: true)),
|
|
80
|
-
format:
|
|
80
|
+
format: format_param(:destroy)
|
|
81
81
|
end
|
|
82
82
|
|
|
83
83
|
it 'responds with not found status' do
|
|
@@ -92,7 +92,7 @@ RSpec.shared_examples 'simple crud for destroy' do
|
|
|
92
92
|
before do
|
|
93
93
|
model
|
|
94
94
|
make_policies_fail(:destroy)
|
|
95
|
-
delete :destroy, params: with_route_params(record_param(:destroy, model)), format:
|
|
95
|
+
delete :destroy, params: with_route_params(record_param(:destroy, model)), format: format_param(:destroy)
|
|
96
96
|
end
|
|
97
97
|
|
|
98
98
|
it 'responds with forbidden status' do
|
|
@@ -7,7 +7,7 @@ RSpec.shared_examples 'simple crud for destroy with block' do
|
|
|
7
7
|
let!(:record) { model }
|
|
8
8
|
|
|
9
9
|
before do
|
|
10
|
-
delete :destroy, params: with_route_params(record_param(:destroy, record)), format:
|
|
10
|
+
delete :destroy, params: with_route_params(record_param(:destroy, record)), format: format_param(:destroy)
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
it 'passes the destroyed record to the block' do
|
|
@@ -8,7 +8,7 @@ RSpec.shared_examples 'simple crud for destroy with finder' do
|
|
|
8
8
|
before do
|
|
9
9
|
model
|
|
10
10
|
delete :destroy, params: with_route_params(finder_key => model.public_send(finder_key)),
|
|
11
|
-
format:
|
|
11
|
+
format: format_param(:destroy)
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
it 'destroys the model' do
|
|
@@ -24,8 +24,8 @@ RSpec.shared_examples 'simple crud for destroy with finder' do
|
|
|
24
24
|
expect(response).to have_http_status(:found)
|
|
25
25
|
end
|
|
26
26
|
else
|
|
27
|
-
it 'responds with
|
|
28
|
-
expect(response).to have_http_status(:ok)
|
|
27
|
+
it 'responds with the configured status code' do
|
|
28
|
+
expect(response).to have_http_status(check_status(:destroy) || :ok)
|
|
29
29
|
end
|
|
30
30
|
end
|
|
31
31
|
end
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
RSpec.shared_examples 'simple crud for edit' do
|
|
4
4
|
describe 'GET #edit' do
|
|
5
|
-
subject(:edit_request) { get :edit, params: with_route_params(edit_params), format:
|
|
5
|
+
subject(:edit_request) { get :edit, params: with_route_params(edit_params), format: format_param(:edit) }
|
|
6
6
|
|
|
7
7
|
before do
|
|
8
8
|
model
|
|
@@ -10,7 +10,7 @@ RSpec.shared_examples 'simple crud for edit' do
|
|
|
10
10
|
|
|
11
11
|
context 'without authenticated user' do
|
|
12
12
|
subject!(:req) do
|
|
13
|
-
get :edit, params: with_route_params(record_param(:edit, model)), format:
|
|
13
|
+
get :edit, params: with_route_params(record_param(:edit, model)), format: format_param(:edit)
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
include_examples 'unauthorized when not logged in' if check_authenticate(:edit)
|
|
@@ -14,7 +14,7 @@ RSpec.shared_examples 'simple crud for index' do
|
|
|
14
14
|
|
|
15
15
|
before do
|
|
16
16
|
make_policies_succeed(:index) if check_authorize(:index)
|
|
17
|
-
get :index, params: with_route_params({}), format:
|
|
17
|
+
get :index, params: with_route_params({}), format: format_param(:index)
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
if check_html(:index)
|
|
@@ -25,7 +25,7 @@ RSpec.shared_examples 'simple crud for index' do
|
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
it 'renders the correct paginated models' do
|
|
28
|
-
expect(response_body['page'].map { |a| a['id'] }).to
|
|
28
|
+
expect(response_body['page'].map { |a| a['id'] }).to match_array(model_class_object.all.map(&:id))
|
|
29
29
|
end
|
|
30
30
|
else
|
|
31
31
|
it 'renders unpaginated models correctly serialized' do
|
|
@@ -33,7 +33,7 @@ RSpec.shared_examples 'simple crud for index' do
|
|
|
33
33
|
end
|
|
34
34
|
|
|
35
35
|
it 'renders the correct unpaginated models' do
|
|
36
|
-
expect(response_body.map { |a| a['id'] }).to
|
|
36
|
+
expect(response_body.map { |a| a['id'] }).to match_array(model_class_object.all.map(&:id))
|
|
37
37
|
end
|
|
38
38
|
end
|
|
39
39
|
end
|
|
@@ -8,7 +8,7 @@ RSpec.shared_examples 'simple crud for index with block' do
|
|
|
8
8
|
|
|
9
9
|
before do
|
|
10
10
|
created_models
|
|
11
|
-
get :index, params: with_route_params({}), format:
|
|
11
|
+
get :index, params: with_route_params({}), format: format_param(:index)
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
if check_html(:index)
|
|
@@ -13,7 +13,7 @@ RSpec.shared_examples 'simple crud for index with scope' do
|
|
|
13
13
|
make_policies_succeed(:index) if check_authorize(:index)
|
|
14
14
|
my_models
|
|
15
15
|
other_models
|
|
16
|
-
get :index, params: with_route_params(request_params), format:
|
|
16
|
+
get :index, params: with_route_params(request_params), format: format_param(:index)
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
if check_html(:index)
|
|
@@ -10,7 +10,7 @@ RSpec.shared_examples 'simple crud for new' do
|
|
|
10
10
|
|
|
11
11
|
before do
|
|
12
12
|
make_policies_succeed(:new) if check_authorize(:new)
|
|
13
|
-
get :new, params: with_route_params({}), format:
|
|
13
|
+
get :new, params: with_route_params({}), format: format_param(:new)
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
it 'responds with ok status' do
|
|
@@ -6,7 +6,7 @@ RSpec.shared_examples 'simple crud for new with block' do
|
|
|
6
6
|
|
|
7
7
|
before do
|
|
8
8
|
make_policies_succeed(:new) if check_authorize(:new)
|
|
9
|
-
get :new, params: with_route_params({}), format:
|
|
9
|
+
get :new, params: with_route_params({}), format: format_param(:new)
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
if check_html(:new)
|
|
@@ -5,7 +5,7 @@ RSpec.shared_examples 'simple crud for new with build' do
|
|
|
5
5
|
include_context 'with authenticated user' if check_authenticate(:new)
|
|
6
6
|
|
|
7
7
|
before do
|
|
8
|
-
get :new, params: with_route_params({}), format:
|
|
8
|
+
get :new, params: with_route_params({}), format: format_param(:new)
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
it 'builds the record owned by the current user', :aggregate_failures do
|
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
RSpec.shared_examples 'simple crud for show' do
|
|
4
4
|
describe 'GET #show' do
|
|
5
|
-
subject(:show_request) { get :show, params: with_route_params(show_params), format:
|
|
5
|
+
subject(:show_request) { get :show, params: with_route_params(show_params), format: format_param(:show) }
|
|
6
6
|
|
|
7
7
|
before do
|
|
8
8
|
model
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
context 'without authenticated user' do
|
|
12
|
-
subject!(:req) { get :show, params: with_route_params(record_param(:show, model)), format:
|
|
12
|
+
subject!(:req) { get :show, params: with_route_params(record_param(:show, model)), format: format_param(:show) }
|
|
13
13
|
|
|
14
14
|
include_examples 'unauthorized when not logged in' if check_authenticate(:show)
|
|
15
15
|
end
|
|
@@ -8,7 +8,7 @@ RSpec.shared_examples 'simple crud for show with block' do
|
|
|
8
8
|
|
|
9
9
|
before do
|
|
10
10
|
make_policies_succeed(:show) if check_authorize(:show)
|
|
11
|
-
get :show, params: with_route_params(record_param(:show, record)), format:
|
|
11
|
+
get :show, params: with_route_params(record_param(:show, record)), format: format_param(:show)
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
if check_html(:show)
|
|
@@ -8,7 +8,7 @@ RSpec.shared_examples 'simple crud for show with finder' do
|
|
|
8
8
|
before do
|
|
9
9
|
model
|
|
10
10
|
get :show, params: with_route_params(finder_key => model.public_send(finder_key)),
|
|
11
|
-
format:
|
|
11
|
+
format: format_param(:show)
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
if check_block(:show)
|
|
@@ -5,7 +5,7 @@ RSpec.shared_examples 'simple crud for update' do
|
|
|
5
5
|
context 'without authenticated user' do
|
|
6
6
|
subject!(:req) do
|
|
7
7
|
update_params = body_params(params_for(model_class)).merge(record_param(:update, model))
|
|
8
|
-
put :update, params: with_route_params(update_params), format:
|
|
8
|
+
put :update, params: with_route_params(update_params), format: format_param(:update)
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
include_examples 'unauthorized when not logged in' if check_authenticate(:update)
|
|
@@ -18,7 +18,7 @@ RSpec.shared_examples 'simple crud for update' do
|
|
|
18
18
|
before do
|
|
19
19
|
make_policies_fail(:update)
|
|
20
20
|
put :update, params: with_route_params(body_params(model_params).merge(record_param(:update, model))),
|
|
21
|
-
format:
|
|
21
|
+
format: format_param(:update)
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
it 'fails with forbidden' do
|
|
@@ -34,7 +34,7 @@ RSpec.shared_examples 'simple crud for update' do
|
|
|
34
34
|
|
|
35
35
|
before do
|
|
36
36
|
model
|
|
37
|
-
put :update, params: with_route_params(update_params), format:
|
|
37
|
+
put :update, params: with_route_params(update_params), format: format_param(:update)
|
|
38
38
|
end
|
|
39
39
|
|
|
40
40
|
if check_html(:update)
|
|
@@ -42,8 +42,8 @@ RSpec.shared_examples 'simple crud for update' do
|
|
|
42
42
|
expect(response).to have_http_status(:found)
|
|
43
43
|
end
|
|
44
44
|
else
|
|
45
|
-
it 'response with
|
|
46
|
-
expect(response).to have_http_status(:ok)
|
|
45
|
+
it 'response with the configured status code' do
|
|
46
|
+
expect(response).to have_http_status(check_status(:update) || :ok)
|
|
47
47
|
end
|
|
48
48
|
|
|
49
49
|
it 'updates an model' do
|
|
@@ -66,7 +66,7 @@ RSpec.shared_examples 'simple crud for update' do
|
|
|
66
66
|
|
|
67
67
|
before do
|
|
68
68
|
put :update, params: with_route_params(record_param(:update, nil, not_found: true)),
|
|
69
|
-
format:
|
|
69
|
+
format: format_param(:update)
|
|
70
70
|
end
|
|
71
71
|
|
|
72
72
|
it 'response with 404 status code' do
|
|
@@ -86,7 +86,7 @@ RSpec.shared_examples 'simple crud for update' do
|
|
|
86
86
|
model
|
|
87
87
|
put :update, params: with_route_params(body_params({ required_attribute => nil }.merge(owner_params))
|
|
88
88
|
.merge(record_param(:update, model))),
|
|
89
|
-
format:
|
|
89
|
+
format: format_param(:update)
|
|
90
90
|
end
|
|
91
91
|
|
|
92
92
|
unless check_block(:update)
|
|
@@ -12,7 +12,7 @@ RSpec.shared_examples 'simple crud for update with finder' do
|
|
|
12
12
|
|
|
13
13
|
before do
|
|
14
14
|
model
|
|
15
|
-
put :update, params: update_params, format:
|
|
15
|
+
put :update, params: update_params, format: format_param(:update)
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
it 'updates the model' do
|
|
@@ -4,7 +4,7 @@ RSpec.shared_examples 'simple crud not found with finder' do |http_method, actio
|
|
|
4
4
|
context 'when the model does not exist' do
|
|
5
5
|
before do
|
|
6
6
|
send(http_method, action, params: with_route_params(finder_key => "nonexistent-#{finder_key}"),
|
|
7
|
-
format:
|
|
7
|
+
format: format_param(action))
|
|
8
8
|
end
|
|
9
9
|
|
|
10
10
|
it 'responds with not found status' do
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
describe Blueprint::DummyModelsController, type: :controller do
|
|
6
|
+
let(:serializer) { DummyModelBlueprint }
|
|
7
|
+
|
|
8
|
+
include_examples 'simple crud for index'
|
|
9
|
+
include_examples 'simple crud for show'
|
|
10
|
+
include_examples 'simple crud for create'
|
|
11
|
+
include_examples 'simple crud for update'
|
|
12
|
+
include_examples 'simple crud for destroy'
|
|
13
|
+
|
|
14
|
+
describe 'GET #show' do
|
|
15
|
+
include_context 'with authenticated user'
|
|
16
|
+
|
|
17
|
+
let!(:record) { create(:dummy_model, user: current_user) }
|
|
18
|
+
|
|
19
|
+
it 'passes serializer_options to the blueprint' do
|
|
20
|
+
get :show, params: { id: record.id }
|
|
21
|
+
|
|
22
|
+
expect(response.parsed_body['url']).to eq(dummy_model_url(record))
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Blueprint
|
|
4
|
+
class DummyModelsController < ApplicationController
|
|
5
|
+
include Wor::Paginate
|
|
6
|
+
include Pundit::Authorization
|
|
7
|
+
extend SimpleCrudController
|
|
8
|
+
|
|
9
|
+
def dummy_model_params
|
|
10
|
+
params.permit(:name, :something, :user_id, :slug)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
SERIALIZER_OPTIONS = -> { { url_builder: self } }
|
|
14
|
+
|
|
15
|
+
simple_crud_for :index, serializer: DummyModelBlueprint
|
|
16
|
+
simple_crud_for :show, serializer: DummyModelBlueprint, serializer_options: SERIALIZER_OPTIONS
|
|
17
|
+
simple_crud_for :create, serializer: DummyModelBlueprint, serializer_options: SERIALIZER_OPTIONS
|
|
18
|
+
simple_crud_for :update, serializer: DummyModelBlueprint, serializer_options: SERIALIZER_OPTIONS
|
|
19
|
+
simple_crud_for :destroy, serializer: DummyModelBlueprint, serializer_options: SERIALIZER_OPTIONS
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BlueprintUnpaginated
|
|
4
|
+
class DummyModelsController < ApplicationController
|
|
5
|
+
include Pundit::Authorization
|
|
6
|
+
extend SimpleCrudController
|
|
7
|
+
|
|
8
|
+
simple_crud_for :index, paginate: false, serializer: DummyModelBlueprint
|
|
9
|
+
end
|
|
10
|
+
end
|
data/spec/dummy/config/routes.rb
CHANGED
|
@@ -120,6 +120,14 @@ Rails.application.routes.draw do
|
|
|
120
120
|
resources :dummy_models, only: :create
|
|
121
121
|
end
|
|
122
122
|
|
|
123
|
+
namespace :blueprint do
|
|
124
|
+
resources :dummy_models
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
namespace :blueprint_unpaginated do
|
|
128
|
+
resources :dummy_models, only: :index
|
|
129
|
+
end
|
|
130
|
+
|
|
123
131
|
namespace :nested_route do
|
|
124
132
|
resources :classrooms, only: [], param: :slug do
|
|
125
133
|
resources :dummy_models, param: :slug, only: %i[show create destroy]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
describe DummyModelBlueprint do
|
|
6
|
+
let(:dummy_model) { DummyModel.new(id: 3, name: 'widget', something: 'thing', user_id: 7) }
|
|
7
|
+
let(:full_response) { described_class.render_as_hash(dummy_model) }
|
|
8
|
+
|
|
9
|
+
it 'matches a response including all blueprint attributes' do
|
|
10
|
+
expect(full_response).to have_been_serialized_with(described_class)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
it 'does not match a response missing blueprint attributes' do
|
|
14
|
+
expect({ 'id' => dummy_model.id }).not_to have_been_serialized_with(described_class)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -19,4 +19,23 @@ describe SimpleCrud::ActionContext do
|
|
|
19
19
|
expect(key).to eq('dummy_model:show:v1:/dummy_models/1')
|
|
20
20
|
end
|
|
21
21
|
end
|
|
22
|
+
|
|
23
|
+
describe '#serializer_options' do
|
|
24
|
+
let(:record) { DummyModel.new(name: 'widget') }
|
|
25
|
+
|
|
26
|
+
it 'returns an empty hash when no callable is configured' do
|
|
27
|
+
ctx = described_class.new(double, DummyModel, {})
|
|
28
|
+
expect(ctx.send(:serializer_options, record)).to eq({})
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it 'calls a zero-arity callable without the record' do
|
|
32
|
+
ctx = described_class.new(double, DummyModel, { serializer_options: -> { { prefix: :none } } })
|
|
33
|
+
expect(ctx.send(:serializer_options, record)).to eq(prefix: :none)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it 'calls an arity-one callable with the record' do
|
|
37
|
+
ctx = described_class.new(double, DummyModel, { serializer_options: ->(record) { { name: record.name } } })
|
|
38
|
+
expect(ctx.send(:serializer_options, record)).to eq(name: 'widget')
|
|
39
|
+
end
|
|
40
|
+
end
|
|
22
41
|
end
|
|
@@ -58,6 +58,19 @@ describe SimpleCrud::RSpec do
|
|
|
58
58
|
SimpleCrud::RSpec.configure { |c| c.required_attribute = original }
|
|
59
59
|
expect(config.required_attribute).to eq(:name)
|
|
60
60
|
end
|
|
61
|
+
|
|
62
|
+
describe 'the default serializer_class' do
|
|
63
|
+
it 'prefers the AMS serializer when the constant exists' do
|
|
64
|
+
expect(config.serializer_class.call(DummyModel.new)).to eq(DummyModelSerializer)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it 'falls back to the Blueprint when there is no AMS serializer' do
|
|
68
|
+
allow(Kernel).to receive(:const_defined?).and_call_original
|
|
69
|
+
allow(Kernel).to receive(:const_defined?).with('DummyModelSerializer').and_return(false)
|
|
70
|
+
|
|
71
|
+
expect(config.serializer_class.call(DummyModel.new)).to eq(DummyModelBlueprint)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
61
74
|
end
|
|
62
75
|
|
|
63
76
|
describe SimpleCrud::RSpec::Helpers do
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
describe SimpleCrud::Serializer do
|
|
6
|
+
let(:record) { DummyModel.new(id: 1, name: 'widget', something: 'thing', user_id: 9) }
|
|
7
|
+
|
|
8
|
+
describe '.render' do
|
|
9
|
+
it 'falls back to the record as_json when there is no serializer' do
|
|
10
|
+
expect(described_class.render(nil, record)).to eq(record.as_json)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
it 'renders through a Blueprinter blueprint with the given options' do
|
|
14
|
+
result = described_class.render(DummyModelBlueprint, record, { url_builder: nil })
|
|
15
|
+
|
|
16
|
+
expect(result).to include(id: record.id, name: record.name, something: record.something, user_id: record.user_id)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it 'instantiates an AMS-style serializer with the given options', :aggregate_failures do
|
|
20
|
+
allow(DummyModelSerializer).to receive(:new).and_call_original
|
|
21
|
+
|
|
22
|
+
result = described_class.render(DummyModelSerializer, record, { root: false })
|
|
23
|
+
|
|
24
|
+
expect(DummyModelSerializer).to have_received(:new).with(record, { root: false })
|
|
25
|
+
expect(result).to include(id: record.id, something: record.something)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: wor-simple_crud
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- icoluccio
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-09-
|
|
10
|
+
date: 2026-09-17 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: rails
|
|
@@ -76,6 +76,7 @@ files:
|
|
|
76
76
|
- lib/simple_crud/rspec/helpers/policies.rb
|
|
77
77
|
- lib/simple_crud/rspec/helpers/requests.rb
|
|
78
78
|
- lib/simple_crud/rspec/helpers/settings.rb
|
|
79
|
+
- lib/simple_crud/serializer.rb
|
|
79
80
|
- lib/simple_crud/show_context.rb
|
|
80
81
|
- lib/simple_crud/simple_crud_controller.rb
|
|
81
82
|
- lib/simple_crud/update_context.rb
|
|
@@ -116,11 +117,14 @@ files:
|
|
|
116
117
|
- spec/block_new/dummy_models_controller_spec.rb
|
|
117
118
|
- spec/block_redirect/dummy_models_controller_spec.rb
|
|
118
119
|
- spec/block_show/dummy_models_controller_spec.rb
|
|
120
|
+
- spec/blueprint/dummy_models_controller_spec.rb
|
|
121
|
+
- spec/blueprint_unpaginated/dummy_models_controller_spec.rb
|
|
119
122
|
- spec/built/dummy_models_controller_spec.rb
|
|
120
123
|
- spec/cached/dummy_models_controller_spec.rb
|
|
121
124
|
- spec/cached_defaults/dummy_models_controller_spec.rb
|
|
122
125
|
- spec/dummy/Rakefile
|
|
123
126
|
- spec/dummy/app/assets/config/manifest.js
|
|
127
|
+
- spec/dummy/app/blueprints/dummy_model_blueprint.rb
|
|
124
128
|
- spec/dummy/app/controllers/application_controller.rb
|
|
125
129
|
- spec/dummy/app/controllers/block/dummy_models_controller.rb
|
|
126
130
|
- spec/dummy/app/controllers/block_create/dummy_models_controller.rb
|
|
@@ -131,6 +135,8 @@ files:
|
|
|
131
135
|
- spec/dummy/app/controllers/block_new/dummy_models_controller.rb
|
|
132
136
|
- spec/dummy/app/controllers/block_redirect/dummy_models_controller.rb
|
|
133
137
|
- spec/dummy/app/controllers/block_show/dummy_models_controller.rb
|
|
138
|
+
- spec/dummy/app/controllers/blueprint/dummy_models_controller.rb
|
|
139
|
+
- spec/dummy/app/controllers/blueprint_unpaginated/dummy_models_controller.rb
|
|
134
140
|
- spec/dummy/app/controllers/built/dummy_models_controller.rb
|
|
135
141
|
- spec/dummy/app/controllers/cached/dummy_models_controller.rb
|
|
136
142
|
- spec/dummy/app/controllers/cached_defaults/dummy_models_controller.rb
|
|
@@ -206,6 +212,7 @@ files:
|
|
|
206
212
|
- spec/dummy/spec/factories/dummy_models.rb
|
|
207
213
|
- spec/dummy/spec/factories/users.rb
|
|
208
214
|
- spec/dummy_controller_spec.rb
|
|
215
|
+
- spec/dummy_model_blueprint_spec.rb
|
|
209
216
|
- spec/dummy_model_policy_spec.rb
|
|
210
217
|
- spec/dummy_model_serializer_spec.rb
|
|
211
218
|
- spec/finder/dummy_models_controller_spec.rb
|
|
@@ -231,6 +238,7 @@ files:
|
|
|
231
238
|
- spec/simple_crud/index_context_spec.rb
|
|
232
239
|
- spec/simple_crud/pagination/adapters_spec.rb
|
|
233
240
|
- spec/simple_crud/rspec_config_spec.rb
|
|
241
|
+
- spec/simple_crud/serializer_spec.rb
|
|
234
242
|
- spec/simple_crud/show_context_spec.rb
|
|
235
243
|
- spec/simple_crud_configuration_spec.rb
|
|
236
244
|
- spec/simple_crud_controller_spec.rb
|
|
@@ -258,7 +266,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
258
266
|
- !ruby/object:Gem::Version
|
|
259
267
|
version: '0'
|
|
260
268
|
requirements: []
|
|
261
|
-
rubygems_version: 3.6.
|
|
269
|
+
rubygems_version: 3.6.7
|
|
262
270
|
specification_version: 4
|
|
263
271
|
summary: Simplified CRUD endpoints for Rails API controllers
|
|
264
272
|
test_files: []
|