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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +22 -0
  3. data/Gemfile +5 -1
  4. data/README.md +16 -3
  5. data/lib/simple_crud/action_context.rb +30 -9
  6. data/lib/simple_crud/create_context.rb +2 -1
  7. data/lib/simple_crud/destroy_context.rb +2 -1
  8. data/lib/simple_crud/index_context.rb +10 -3
  9. data/lib/simple_crud/pagination/wor_paginate_adapter.rb +7 -1
  10. data/lib/simple_crud/persistence_context.rb +17 -1
  11. data/lib/simple_crud/rspec/config.rb +6 -1
  12. data/lib/simple_crud/rspec/helpers/authentication.rb +2 -1
  13. data/lib/simple_crud/rspec/helpers/controller_options.rb +2 -2
  14. data/lib/simple_crud/rspec/helpers/requests.rb +5 -2
  15. data/lib/simple_crud/serializer.rb +19 -0
  16. data/lib/simple_crud/show_context.rb +3 -1
  17. data/lib/simple_crud/simple_crud_controller.rb +3 -1
  18. data/lib/simple_crud/update_context.rb +2 -1
  19. data/lib/simple_crud/version.rb +1 -1
  20. data/lib/spec/matchers/have_been_serialized_with.rb +19 -2
  21. data/lib/spec/shared_examples/simple_crud_for_create.rb +6 -6
  22. data/lib/spec/shared_examples/simple_crud_for_create_with_block.rb +2 -2
  23. data/lib/spec/shared_examples/simple_crud_for_create_with_build.rb +1 -1
  24. data/lib/spec/shared_examples/simple_crud_for_destroy.rb +8 -8
  25. data/lib/spec/shared_examples/simple_crud_for_destroy_with_block.rb +1 -1
  26. data/lib/spec/shared_examples/simple_crud_for_destroy_with_finder.rb +3 -3
  27. data/lib/spec/shared_examples/simple_crud_for_edit.rb +2 -2
  28. data/lib/spec/shared_examples/simple_crud_for_index.rb +3 -3
  29. data/lib/spec/shared_examples/simple_crud_for_index_with_block.rb +1 -1
  30. data/lib/spec/shared_examples/simple_crud_for_index_with_scope.rb +1 -1
  31. data/lib/spec/shared_examples/simple_crud_for_new.rb +1 -1
  32. data/lib/spec/shared_examples/simple_crud_for_new_with_block.rb +1 -1
  33. data/lib/spec/shared_examples/simple_crud_for_new_with_build.rb +1 -1
  34. data/lib/spec/shared_examples/simple_crud_for_show.rb +2 -2
  35. data/lib/spec/shared_examples/simple_crud_for_show_with_block.rb +1 -1
  36. data/lib/spec/shared_examples/simple_crud_for_show_with_finder.rb +1 -1
  37. data/lib/spec/shared_examples/simple_crud_for_update.rb +7 -7
  38. data/lib/spec/shared_examples/simple_crud_for_update_with_finder.rb +1 -1
  39. data/lib/spec/shared_examples/simple_crud_not_found_with_finder.rb +1 -1
  40. data/spec/blueprint/dummy_models_controller_spec.rb +25 -0
  41. data/spec/blueprint_unpaginated/dummy_models_controller_spec.rb +9 -0
  42. data/spec/dummy/app/blueprints/dummy_model_blueprint.rb +10 -0
  43. data/spec/dummy/app/controllers/blueprint/dummy_models_controller.rb +21 -0
  44. data/spec/dummy/app/controllers/blueprint_unpaginated/dummy_models_controller.rb +10 -0
  45. data/spec/dummy/config/routes.rb +8 -0
  46. data/spec/dummy_model_blueprint_spec.rb +16 -0
  47. data/spec/simple_crud/action_context_spec.rb +19 -0
  48. data/spec/simple_crud/rspec_config_spec.rb +13 -0
  49. data/spec/simple_crud/serializer_spec.rb +28 -0
  50. metadata +11 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b5b50548416811b034a75f72c96d4e94c41cdb9883d7112cdf720ad3febaeb11
4
- data.tar.gz: 99ae1562bae38c2fa8ff1d697859f8643e2b818aaeb66f2e14220171fa9f28dc
3
+ metadata.gz: b40c7867a484ffc3807b8dceac29ac1f6cc49ccca6d8a9bb067f93f839703e0c
4
+ data.tar.gz: 5a72d84147fc1f149695fe0244a11e7f99f2c9b4debb0b99a291e16439b873ac
5
5
  SHA512:
6
- metadata.gz: ef7dd6acb64f882534e9951a9adf2d8deab27f2264451cb1dee2613c78e4842e38d10a638a2ed9ec623984d0d571aee558e2a3626f7bf8f016eca65dc3174ae3
7
- data.tar.gz: 43a9b8fb46febb90d101814a33d48049ba0a5c2df3a3b14568c5423816ebd55325966fabe8673320ef3ed89b1a7bfe0ac10f4940c16b0e412649b100dd08e051
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.4'
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
- The name of the serializer, by default, is the name of the model followed by Serializer, as is the standard for [ActiveModelSerializers](https://github.com/rails-api/active_model_serializers). It's possible to just pass a custom serializer class though. As for the serializer itself, it's a standard serializer, with the gotcha that you need to include `:id` for the SimpleCrud examples to work.
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, :institution, :role, :id
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 klass.find(controller.params[:id]) if finder.nil?
53
+ return find_by_id if finder.nil?
54
54
  return klass.send(finder, controller.params) unless finder.respond_to?(:call)
55
55
 
56
- finder.call(controller.params)
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
- controller.instance_variable_set(:@record, record)
74
- block ? controller.instance_exec(record, &block) : controller.render(template)
75
- else
76
- controller.render({ json: record }.merge(serialize_opts(:serializer)))
77
- end
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, redirect: parameters[:redirect] || record }
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, redirect: parameters[:redirect] || klass }
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.as_json
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({ json: relation }.merge(opts))
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.public_send(user_method) : nil
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
- controller.send(:render_paginated, relation, options)
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 controller.render(json: record, status: options[:status]) if saved
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: ->(model) { "#{model.class}_serializer".classify.constantize },
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
- :"#{owner_association}_id"
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 html finder scope build raise_on_invalid block
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
- def request_format(action)
15
- check_html(action) ? :html : :json
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
- block ? controller.instance_exec(record, &block) : record.as_json
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, html: false, finder: nil, scope: nil, build: nil, raise_on_invalid: false
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, redirect: parameters[:redirect] || record }
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SimpleCrud
4
- VERSION = '0.5.0'
4
+ VERSION = '0.6.0'
5
5
  end
@@ -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?(serializer._attributes, json_response_item) &&
9
- all_attributes_included?(serializer._reflections.keys, json_response_item)
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: request_format(:create)
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: request_format(:create)
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: request_format(:create)
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 201 status code' do
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: request_format(:create)
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: request_format(:create)
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: request_format(:create)
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: request_format(:create)
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: request_format(:destroy)
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: request_format(:destroy)
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: request_format(:destroy)
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 200 status code' do
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: request_format(:destroy)
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: request_format(:destroy)
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: request_format(:destroy)
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: request_format(:destroy)
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: request_format(:destroy)
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 ok status' do
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: request_format(:edit) }
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: request_format(:edit)
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: request_format(:index)
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 eq(model_class_object.all.map(&:id))
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 eq(model_class_object.all.map(&:id))
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: request_format(:index)
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: request_format(:index)
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: request_format(:new)
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: request_format(:new)
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: request_format(:new)
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: request_format(:show) }
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: request_format(:show) }
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: request_format(:show)
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: request_format(:show)
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: request_format(:update)
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: request_format(:update)
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: request_format(:update)
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 200 status code' do
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: request_format(:update)
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: request_format(:update)
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: request_format(:update)
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: request_format(action))
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,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe BlueprintUnpaginated::DummyModelsController, type: :controller do
6
+ let(:serializer) { DummyModelBlueprint }
7
+
8
+ include_examples 'simple crud for index'
9
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ class DummyModelBlueprint < Blueprinter::Base
4
+ identifier :id
5
+ fields :name, :something, :user_id
6
+
7
+ field :url do |record, options|
8
+ options[:url_builder]&.dummy_model_url(record)
9
+ end
10
+ 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
@@ -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.5.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-01 00:00:00.000000000 Z
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.9
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: []