cuprum-rails 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +145 -0
- data/DEVELOPMENT.md +20 -0
- data/README.md +356 -63
- data/lib/cuprum/rails/action.rb +32 -16
- data/lib/cuprum/rails/actions/create.rb +62 -15
- data/lib/cuprum/rails/actions/destroy.rb +23 -7
- data/lib/cuprum/rails/actions/edit.rb +23 -7
- data/lib/cuprum/rails/actions/index.rb +30 -10
- data/lib/cuprum/rails/actions/middleware/associations/cache.rb +112 -0
- data/lib/cuprum/rails/actions/middleware/associations/find.rb +23 -0
- data/lib/cuprum/rails/actions/middleware/associations/parent.rb +70 -0
- data/lib/cuprum/rails/actions/middleware/associations/query.rb +140 -0
- data/lib/cuprum/rails/actions/middleware/associations.rb +12 -0
- data/lib/cuprum/rails/actions/middleware/log_request.rb +126 -0
- data/lib/cuprum/rails/actions/middleware/log_result.rb +51 -0
- data/lib/cuprum/rails/actions/middleware/resources/find.rb +44 -0
- data/lib/cuprum/rails/actions/middleware/resources/query.rb +91 -0
- data/lib/cuprum/rails/actions/middleware/resources.rb +11 -0
- data/lib/cuprum/rails/actions/middleware.rb +13 -0
- data/lib/cuprum/rails/actions/new.rb +16 -4
- data/lib/cuprum/rails/actions/parameter_validation.rb +60 -0
- data/lib/cuprum/rails/actions/resource_action.rb +119 -42
- data/lib/cuprum/rails/actions/show.rb +23 -7
- data/lib/cuprum/rails/actions/update.rb +70 -22
- data/lib/cuprum/rails/actions.rb +11 -7
- data/lib/cuprum/rails/collection.rb +27 -47
- data/lib/cuprum/rails/command.rb +3 -1
- data/lib/cuprum/rails/commands/destroy_one.rb +10 -6
- data/lib/cuprum/rails/commands/find_many.rb +8 -1
- data/lib/cuprum/rails/commands/find_matching.rb +1 -1
- data/lib/cuprum/rails/commands/find_one.rb +8 -0
- data/lib/cuprum/rails/commands/insert_one.rb +17 -6
- data/lib/cuprum/rails/commands/update_one.rb +16 -5
- data/lib/cuprum/rails/constraints/parameters_contract.rb +14 -0
- data/lib/cuprum/rails/constraints.rb +10 -0
- data/lib/cuprum/rails/controller.rb +12 -2
- data/lib/cuprum/rails/controllers/action.rb +100 -0
- data/lib/cuprum/rails/controllers/class_methods/actions.rb +33 -7
- data/lib/cuprum/rails/controllers/class_methods/configuration.rb +36 -0
- data/lib/cuprum/rails/controllers/class_methods/middleware.rb +88 -0
- data/lib/cuprum/rails/controllers/class_methods/validations.rb +2 -2
- data/lib/cuprum/rails/controllers/configuration.rb +41 -1
- data/lib/cuprum/rails/controllers/middleware.rb +59 -0
- data/lib/cuprum/rails/controllers.rb +2 -0
- data/lib/cuprum/rails/errors/invalid_parameters.rb +55 -0
- data/lib/cuprum/rails/errors/invalid_statement.rb +11 -0
- data/lib/cuprum/rails/errors/missing_parameter.rb +42 -0
- data/lib/cuprum/rails/errors/resource_error.rb +46 -0
- data/lib/cuprum/rails/errors.rb +6 -1
- data/lib/cuprum/rails/map_errors.rb +29 -1
- data/lib/cuprum/rails/query.rb +1 -1
- data/lib/cuprum/rails/repository.rb +12 -25
- data/lib/cuprum/rails/request.rb +149 -60
- data/lib/cuprum/rails/resource.rb +119 -85
- data/lib/cuprum/rails/responders/base_responder.rb +78 -0
- data/lib/cuprum/rails/responders/html/plural_resource.rb +9 -39
- data/lib/cuprum/rails/responders/html/rendering.rb +81 -0
- data/lib/cuprum/rails/responders/html/resource.rb +107 -0
- data/lib/cuprum/rails/responders/html/singular_resource.rb +9 -38
- data/lib/cuprum/rails/responders/html.rb +2 -0
- data/lib/cuprum/rails/responders/html_responder.rb +8 -52
- data/lib/cuprum/rails/responders/json/resource.rb +3 -3
- data/lib/cuprum/rails/responders/json_responder.rb +31 -16
- data/lib/cuprum/rails/responders/matching.rb +29 -27
- data/lib/cuprum/rails/responders/serialization.rb +11 -9
- data/lib/cuprum/rails/responders.rb +1 -0
- data/lib/cuprum/rails/responses/head_response.rb +24 -0
- data/lib/cuprum/rails/responses/html/redirect_back_response.rb +55 -0
- data/lib/cuprum/rails/responses/html/redirect_response.rb +19 -4
- data/lib/cuprum/rails/responses/html/render_response.rb +17 -5
- data/lib/cuprum/rails/responses/html.rb +6 -2
- data/lib/cuprum/rails/responses.rb +1 -0
- data/lib/cuprum/rails/result.rb +36 -0
- data/lib/cuprum/rails/routes.rb +36 -23
- data/lib/cuprum/rails/rspec/contract_helpers.rb +57 -0
- data/lib/cuprum/rails/rspec/contracts/action_contracts.rb +754 -0
- data/lib/cuprum/rails/rspec/contracts/actions/create_contracts.rb +289 -0
- data/lib/cuprum/rails/rspec/contracts/actions/destroy_contracts.rb +164 -0
- data/lib/cuprum/rails/rspec/contracts/actions/edit_contracts.rb +73 -0
- data/lib/cuprum/rails/rspec/contracts/actions/index_contracts.rb +108 -0
- data/lib/cuprum/rails/rspec/contracts/actions/new_contracts.rb +111 -0
- data/lib/cuprum/rails/rspec/contracts/actions/show_contracts.rb +72 -0
- data/lib/cuprum/rails/rspec/contracts/actions/update_contracts.rb +263 -0
- data/lib/cuprum/rails/rspec/contracts/actions.rb +8 -0
- data/lib/cuprum/rails/rspec/contracts/command_contracts.rb +479 -0
- data/lib/cuprum/rails/rspec/contracts/responder_contracts.rb +232 -0
- data/lib/cuprum/rails/rspec/contracts/routes_contracts.rb +363 -0
- data/lib/cuprum/rails/rspec/contracts/serializers_contracts.rb +70 -0
- data/lib/cuprum/rails/rspec/contracts.rb +8 -0
- data/lib/cuprum/rails/rspec/matchers/be_a_result_matcher.rb +64 -0
- data/lib/cuprum/rails/rspec/matchers.rb +41 -0
- data/lib/cuprum/rails/serializers/base_serializer.rb +60 -0
- data/lib/cuprum/rails/serializers/context.rb +84 -0
- data/lib/cuprum/rails/serializers/json/active_record_serializer.rb +2 -2
- data/lib/cuprum/rails/serializers/json/array_serializer.rb +9 -8
- data/lib/cuprum/rails/serializers/json/attributes_serializer.rb +95 -172
- data/lib/cuprum/rails/serializers/json/error_serializer.rb +2 -2
- data/lib/cuprum/rails/serializers/json/hash_serializer.rb +9 -8
- data/lib/cuprum/rails/serializers/json/identity_serializer.rb +3 -3
- data/lib/cuprum/rails/serializers/json/properties_serializer.rb +252 -0
- data/lib/cuprum/rails/serializers/json.rb +2 -1
- data/lib/cuprum/rails/serializers.rb +3 -1
- data/lib/cuprum/rails/version.rb +1 -1
- data/lib/cuprum/rails.rb +19 -16
- metadata +73 -131
- data/lib/cuprum/rails/controller_action.rb +0 -121
- data/lib/cuprum/rails/errors/missing_parameters.rb +0 -33
- data/lib/cuprum/rails/errors/missing_primary_key.rb +0 -46
- data/lib/cuprum/rails/errors/undefined_permitted_attributes.rb +0 -34
- data/lib/cuprum/rails/rspec/command_contract.rb +0 -460
- data/lib/cuprum/rails/rspec/define_route_contract.rb +0 -84
- data/lib/cuprum/rails/serializers/json/serializer.rb +0 -66
data/README.md
CHANGED
@@ -8,6 +8,7 @@ Cuprum::Rails defines the following objects:
|
|
8
8
|
- [Commands](#commands): Each collection is comprised of `Cuprum` commands, which implement common collection operations such as inserting or querying data.
|
9
9
|
- [Controllers](#controllers): Decouples controller responsibilities for precise control, reusability, and reduction of boilerplate code.
|
10
10
|
- [Actions](#actions): Implement a controller's actions as a `Cuprum` command.
|
11
|
+
- [Middleware](#middleware): Wraps a controller's actions with additional functionality.
|
11
12
|
- [Requests](#requests): Encapsulates a controller request.
|
12
13
|
- [Resources](#resources) and [Routes](#routes): Configuration for a resourceful controller.
|
13
14
|
- [Responders](#responders) and [Responses](#responses): Generate controller responses from action results.
|
@@ -31,7 +32,7 @@ The second benefit is *reusability*. Breaking down a controller into its constit
|
|
31
32
|
|
32
33
|
### Compatibility
|
33
34
|
|
34
|
-
Cuprum::
|
35
|
+
Cuprum::Rails is tested against Ruby (MRI) 3.0 through 3.2, and Rails 6.1 through 7.1.
|
35
36
|
|
36
37
|
### Documentation
|
37
38
|
|
@@ -39,7 +40,7 @@ Documentation is generated using [YARD](https://yardoc.org/), and can be generat
|
|
39
40
|
|
40
41
|
### License
|
41
42
|
|
42
|
-
Copyright (c) 2021 Rob Smith
|
43
|
+
Copyright (c) 2021-2022 Rob Smith
|
43
44
|
|
44
45
|
Stannum is released under the [MIT License](https://opensource.org/licenses/MIT).
|
45
46
|
|
@@ -59,7 +60,7 @@ Please note that the `Cuprum::Collections` project is released with a [Contribut
|
|
59
60
|
|
60
61
|
## Reference
|
61
62
|
|
62
|
-
<
|
63
|
+
<span id="collections"></span>
|
63
64
|
|
64
65
|
### Collections
|
65
66
|
|
@@ -110,6 +111,7 @@ Initializing a collection requires the `:record_class` keyword, which should be
|
|
110
111
|
- The `:member_name` parameter is used to create an envelope for singular query commands such as the `FindOne` command. If not given, the member name will be generated automatically as a singular form of the collection name.
|
111
112
|
- The `:primary_key_name` parameter specifies the attribute that serves as the primary key for the collection entities. The default value is `:id`.
|
112
113
|
- The `:primary_key_type` parameter specifies the type of the primary key attribute. The default value is `Integer`.
|
114
|
+
- The `:qualified_name` parameter acts as a unique identifier for the collection. It is used as the unique key in repositories.
|
113
115
|
|
114
116
|
<a id="commands"></a>
|
115
117
|
|
@@ -388,20 +390,39 @@ require 'cuprum/rails/repository'
|
|
388
390
|
A `Cuprum::Rails::Repository` is a group of Rails collections. A single repository might represent all or a subset of the tables in your database.
|
389
391
|
|
390
392
|
```ruby
|
391
|
-
repository = Cuprum::
|
393
|
+
repository = Cuprum::Rails::Repository.new
|
392
394
|
repository.key?('books')
|
393
395
|
#=> false
|
394
396
|
|
395
|
-
repository.
|
396
|
-
|
397
|
-
|
398
|
-
#=>
|
399
|
-
|
400
|
-
#=>
|
397
|
+
collection = repository.find_or_create(entity_class: Book)
|
398
|
+
#=> a Cuprum::Rails::Collection
|
399
|
+
collection.collection_name
|
400
|
+
#=> 'books'
|
401
|
+
collection.qualified_name
|
402
|
+
#=> 'books'
|
401
403
|
repository['books']
|
402
404
|
#=> the books collection
|
403
405
|
```
|
404
406
|
|
407
|
+
If the model has a namespace, e.g. `Authentication::User`, the `#collection_name` will be based on the last name segment, while the `#qualified_name` will be based on the entire name.
|
408
|
+
|
409
|
+
```ruby
|
410
|
+
repository = Cuprum::Rails::Repository.new
|
411
|
+
repository.key?('authentication/users')
|
412
|
+
#=> false
|
413
|
+
|
414
|
+
collection = repository.find_or_create(entity_class: Authentication::User)
|
415
|
+
#=> a Cuprum::Rails::Collection
|
416
|
+
collection.collection_name
|
417
|
+
#=> 'users'
|
418
|
+
collection.qualified_name
|
419
|
+
#=> 'authentication/users'
|
420
|
+
repository['authentication/users']
|
421
|
+
#=> the users collection
|
422
|
+
```
|
423
|
+
|
424
|
+
You can also pass the `#collection_name` and `#qualified_name` as parameters.
|
425
|
+
|
405
426
|
<a id="controllers"></a>
|
406
427
|
|
407
428
|
### Controllers
|
@@ -439,7 +460,7 @@ class BooksController
|
|
439
460
|
)
|
440
461
|
end
|
441
462
|
|
442
|
-
responder :html, Cuprum::Rails::Responders::Html::
|
463
|
+
responder :html, Cuprum::Rails::Responders::Html::Resource
|
443
464
|
responder :json, Cuprum::Rails::Responders::Json::Resource
|
444
465
|
|
445
466
|
action :create, Cuprum::Rails::Actions::Create
|
@@ -464,6 +485,14 @@ The [Responders](#responders) determine what request formats are accepted by the
|
|
464
485
|
|
465
486
|
The [Serializers](#serializers) are used in API responses (such as a JSON response) to convert application data into a serialized format. `Cuprum::Rails` defines a base set of serializers for simple data; applications can either set a generic serializer for records (as in `BooksController`, above) or set specific serializers for each record class on a per-controller basis. Serializers are defined by overriding the `.serializers` class method - make sure to call `super()` and merge the results, unless you specifically want to override the default values.
|
466
487
|
|
488
|
+
You can also define `.default_format`, which sets a default value for when the request does not specify a format. For example, a request to `/api/books.html` specifies the `:html` format, while there is no format specified for `/api/books`.
|
489
|
+
|
490
|
+
```ruby
|
491
|
+
class BooksController
|
492
|
+
default_format :html
|
493
|
+
end
|
494
|
+
```
|
495
|
+
|
467
496
|
#### Defining Actions
|
468
497
|
|
469
498
|
A non-abstract controller should define at least one [Action](#actions), corresponding to a page, process, or API endpoint for the application. Actions are defined using the `.action` class method, which takes two parameters: an `action_name`, which should be either a string or a symbol (e.g. `:publish`), and an `action_class`, which is a subclass of `Cuprum::Rails::Action`.
|
@@ -484,6 +513,47 @@ class BooksController
|
|
484
513
|
end
|
485
514
|
```
|
486
515
|
|
516
|
+
<a id="controllers-defining-middleware"></a>
|
517
|
+
|
518
|
+
#### Defining Middleware
|
519
|
+
|
520
|
+
You can use [middleware](#middleware) to insert functionality before, after, or around controller actions. Think of it as a supercharged alternative to the traditional Rails `before_action` and `after_action` hooks, but without the magic behavior. Use cases for middleware include:
|
521
|
+
|
522
|
+
- Authentication
|
523
|
+
- Logging
|
524
|
+
- Profiling
|
525
|
+
|
526
|
+
Middleware commands have a specific interface. See [Middleware](#middleware), below, for how to define your own middleware commands.
|
527
|
+
|
528
|
+
```ruby
|
529
|
+
class BooksController
|
530
|
+
middleware LoggingMiddleware
|
531
|
+
middleware AuthenticationMiddleware, except: %i[index show]
|
532
|
+
middleware ProfilingMiddleware, only: %i[create update]
|
533
|
+
end
|
534
|
+
```
|
535
|
+
|
536
|
+
Adding middleware to a controller is straightforward. In our example above, the `LoggingMiddleware` will run for all actions, the `AuthenticationMiddleware` will run for all actions except for `:index` and `:show`, and the `ProfilingMiddleware` will run for the `:create` and `:update` actions.
|
537
|
+
|
538
|
+
Each middleware command can have functionality that runs before, after, or around the action (and subsequent middleware). Code that runs before the action has access to the `request:`, and can modify the request passed to the next command or even skip the action and return its own result. Code that runs after the action has access to the `request:` and the action `result`, and can modify or replace the result.
|
539
|
+
|
540
|
+
The middleware is executed in the order it is defined. For the `BooksController#create` action, the code would run as follows:
|
541
|
+
|
542
|
+
1. `LoggingMiddleware`: Any code that executes before the action.
|
543
|
+
1. `AuthenticationMiddleware`: Any code that executes before the action.
|
544
|
+
1. `ProfilingMiddleware`: Any code that executes before the action.
|
545
|
+
1. `Books::CreateAction`
|
546
|
+
1. `ProfilingMiddleware`: Any code that executes after the action.
|
547
|
+
1. `AuthenticationMiddleware`: Any code that executes after the action.
|
548
|
+
1. `LoggingMiddleware`: Any code that executes after the action.
|
549
|
+
|
550
|
+
Code that runs before or around the action can skip the action and return its own result. For example, the `AuthenticationMiddleware` will check for a valid session. If there is not a valid session, it will return a failing result rather than calling the action. In this case, the code would run as follows:
|
551
|
+
|
552
|
+
1. `LoggingMiddleware`: Any code that executes before the action.
|
553
|
+
1. `AuthenticationMiddleware`: The session is not found, so the action is not called.
|
554
|
+
1. `AuthenticationMiddleware`: Any code that executes after the action.
|
555
|
+
1. `LoggingMiddleware`: Any code that executes after the action.
|
556
|
+
|
487
557
|
<a id="controllers-action-lifecycle"></a>
|
488
558
|
|
489
559
|
#### The Action Lifecycle
|
@@ -492,10 +562,11 @@ Inside a controller action, `Cuprum::Rails` splits up the responsibilities of re
|
|
492
562
|
|
493
563
|
1. The Action
|
494
564
|
1. The `action_class` is initialized, passing the controller `resource` to the constructor and returning the `action`.
|
495
|
-
2. The
|
565
|
+
2. The `action` is wrapped with any `middleware` that is defined by the controller for that action.
|
566
|
+
3. The controller `#request` is wrapped in a `Cuprum::Rails::Request`, which is passed to the `action`'s `#call` method, returning the `result`.
|
496
567
|
2. The Responder
|
497
568
|
1. The `responder_class` is found for the request based on the request's `format` and the configured `responders`.
|
498
|
-
2. The `responder_class` is initialized with the `action_name`, `resource`, and `serializers`, returning the `responder`.
|
569
|
+
2. The `responder_class` is initialized with the `action_name`, `controller_name`, `resource`, and `serializers`, returning the `responder`.
|
499
570
|
3. The `responder` is called with the action `result`, and finds a matching `response` based on the action name, the result's success or failure, and the result error (if any).
|
500
571
|
3. The Response
|
501
572
|
1. The `response` is then called with the controller, which allows it to reference native Rails controller methods for rendering or redirecting.
|
@@ -506,15 +577,15 @@ Let's walk through this step by step. We start by making a `POST` request to `/b
|
|
506
577
|
1. We initialize our configured action class, which is `Cuprum::Rails::Actions::Index`.
|
507
578
|
2. We wrap the request in a `Cuprum::Rails::Request`, and call our `action` with the wrapped `request`. The action performs the business logic (building, validating, and persisting a new `Book`) and returns an instance of `Cuprum::Result`. In our case, the book's attributes are valid, so the result has a `:status` of `:success` and a value of `{ 'book' => #<Book id: 0, title: 'Gideon the Ninth'> }`.
|
508
579
|
2. The Responder
|
509
|
-
1. We're making an HTML request, so our controller will use the responder configured for the `:html` format. In our case, this is `Cuprum::Rails::Responders::Html::
|
510
|
-
2. Our `Responders::Html::
|
580
|
+
1. We're making an HTML request, so our controller will use the responder configured for the `:html` format. In our case, this is `Cuprum::Rails::Responders::Html::Resource`, which defines default behavior for responding to resourceful requests.
|
581
|
+
2. Our `Responders::Html::Resource` is initialized, giving us a `responder`.
|
511
582
|
3. The `responder` is called with our `result`. There is a match for a successful `:create` action, which returns an instance of `Cuprum::Rails::Responses::Html::RedirectResponse` with a `path` of `/books/0`.
|
512
583
|
3. The Response
|
513
584
|
1. Finally, our `response` object is called. The `RedirectResponse` directs the controller to redirect to `/books/0`, which is the `:show` page for our newly created `Book`.
|
514
585
|
|
515
586
|
<a id="actions"></a>
|
516
587
|
|
517
|
-
### Actions
|
588
|
+
### Controller Actions
|
518
589
|
|
519
590
|
```ruby
|
520
591
|
require 'cuprum/rails/action'
|
@@ -547,8 +618,11 @@ class PublishBook < Cuprum::Rails::Actions::ResourceAction
|
|
547
618
|
private
|
548
619
|
|
549
620
|
def process(request)
|
550
|
-
|
551
|
-
|
621
|
+
super
|
622
|
+
|
623
|
+
step { require_resource_id }
|
624
|
+
|
625
|
+
book = step { collection.find_one.call(primary_key: resource_id) }
|
552
626
|
|
553
627
|
book.published_at = DateTime.current
|
554
628
|
|
@@ -561,12 +635,57 @@ end
|
|
561
635
|
|
562
636
|
`ResourceAction` delegates `#collection`, `#resource_name`, and `#singular_resource_name` to the `#resource`. In addition, it defines the following helper methods. Each method returns a `Cuprum::Result`, so you can use the `#step` control flow to handle command errors.
|
563
637
|
|
564
|
-
- `#resource_id`:
|
565
|
-
- `#resource_params`:
|
638
|
+
- `#resource_id`: Returns `params[:id]`.
|
639
|
+
- `#resource_params`: Filters `params[singular_resource_name]` and using `resource.permitted_attributes`.
|
640
|
+
|
641
|
+
#### Transactions
|
642
|
+
|
643
|
+
`Cuprum::Rails` integrates with `ActiveRecord` to support database transactions. The `#transaction` method integrates native transactions with the `Cuprum` control flow:
|
644
|
+
|
645
|
+
```ruby
|
646
|
+
class ReturnBook < Cuprum::Rails::Actions::ResourceAction
|
647
|
+
private
|
648
|
+
|
649
|
+
def books_collection
|
650
|
+
@books_collection ||= repository['books']
|
651
|
+
end
|
652
|
+
|
653
|
+
def process(request)
|
654
|
+
super
|
655
|
+
|
656
|
+
step { require_resource_id }
|
657
|
+
|
658
|
+
loan = step { collection.find_one.call(primary_key: resource_id) }
|
659
|
+
|
660
|
+
transaction do
|
661
|
+
step { return_book(loan.book_id) }
|
662
|
+
|
663
|
+
step { collection.destroy_one.call(entity: loan) }
|
664
|
+
end
|
665
|
+
end
|
666
|
+
|
667
|
+
def return_book(book_id)
|
668
|
+
step do
|
669
|
+
books_collection.assign_one.call(
|
670
|
+
attributes: { 'borrowed' => false },
|
671
|
+
entity: book
|
672
|
+
)
|
673
|
+
end
|
674
|
+
|
675
|
+
books_collection.update_one.call(entity: book)
|
676
|
+
end
|
677
|
+
end
|
678
|
+
```
|
679
|
+
|
680
|
+
Here, we are defining a custom action for returning a borrowed library book. Inside our transaction, we are defining two steps. First, we are marking the book as no longer borrowed, so other patrons will be able to check it out or request it. Second, we destroy the join model between the user and the book. If either of these steps returns a failing result, the transaction will automatically roll back.
|
681
|
+
|
682
|
+
If you do not want to roll back on a failed step, use the native `ActiveRecord.transaction` method instead.
|
683
|
+
|
684
|
+
#### Actions
|
566
685
|
|
567
686
|
`Cuprum::Rails` also provides some pre-defined actions to implement classic resourceful controllers. Each resource action calls one or more commands from the resource collection to query or persist the record or records.
|
568
687
|
|
569
|
-
|
688
|
+
##### Create
|
570
689
|
|
571
690
|
The `Create` action passes the resource params to `collection.build_one`, validates the record using `collection.validate_one`, and finally inserts the new record into the collection using the `collection.insert_one` command. The action returns a Hash containing the created record.
|
572
691
|
|
@@ -583,13 +702,11 @@ Book.where(title: 'Gideon the Ninth').exist?
|
|
583
702
|
#=> true
|
584
703
|
```
|
585
704
|
|
586
|
-
If the
|
587
|
-
|
588
|
-
If the params do not include attributes for the resource, the action returns a failing result with a `Cuprum::Rails::Errors::MissingParameters` error.
|
705
|
+
If the params do not include attributes for the resource, the action returns a failing result with a `Cuprum::Rails::Errors::InvalidParameters` error.
|
589
706
|
|
590
|
-
If the
|
707
|
+
If the created record is not valid, the action returns a failing result with a `Cuprum::Collections::Errors::FailedValidation` error.
|
591
708
|
|
592
|
-
|
709
|
+
##### Destroy
|
593
710
|
|
594
711
|
The `Destroy` action removes the record from the collection via `collection.destroy_one`. The action returns a Hash containing the deleted record.
|
595
712
|
|
@@ -606,9 +723,11 @@ Book.where(id: 0).exist?
|
|
606
723
|
#=> false
|
607
724
|
```
|
608
725
|
|
726
|
+
If the params do not include a primary key for the resource, the action returns a failing result with a `Cuprum::Rails::Errors::InvalidParameters` error.
|
727
|
+
|
609
728
|
If the record with the given primary key does not exist, the action returns a failing result with a `Cuprum::Collections::Errors::NotFound` error.
|
610
729
|
|
611
|
-
|
730
|
+
##### Edit
|
612
731
|
|
613
732
|
The `Edit` action finds the record with the given primary key via `collection.find_one` and returns a Hash containing the found record.
|
614
733
|
|
@@ -622,9 +741,11 @@ result.value
|
|
622
741
|
#=> { 'book' => #<Book id: 0> }
|
623
742
|
```
|
624
743
|
|
744
|
+
If the params do not include a primary key for the resource, the action returns a failing result with a `Cuprum::Rails::Errors::InvalidParameters` error.
|
745
|
+
|
625
746
|
If the record with the given primary key does not exist, the action returns a failing result with a `Cuprum::Collections::Errors::NotFound` error.
|
626
747
|
|
627
|
-
|
748
|
+
##### Index
|
628
749
|
|
629
750
|
The `Index` action performs a query on the records using `collection.find_matching`, and returns a Hash containing the found records. You can pass `:limit`, `:offset`, `:order`, and `:where` parameters to filter the results.
|
630
751
|
|
@@ -642,7 +763,7 @@ result.value
|
|
642
763
|
#=> { 'books' => [#<Book>, #<Book>, #<Book>] }
|
643
764
|
```
|
644
765
|
|
645
|
-
|
766
|
+
##### New
|
646
767
|
|
647
768
|
The `New` action builds a new record with empty attributes using `collection.build_one`, and returns a Hash containing the new record.
|
648
769
|
|
@@ -655,7 +776,7 @@ result.value
|
|
655
776
|
#=> { 'book' => #<Book> }
|
656
777
|
```
|
657
778
|
|
658
|
-
|
779
|
+
##### Show
|
659
780
|
|
660
781
|
The `Show` action finds the record with the given primary key via `collection.find_one` and returns a Hash containing the found record.
|
661
782
|
|
@@ -669,9 +790,11 @@ result.value
|
|
669
790
|
#=> { 'book' => #<Book id: 0> }
|
670
791
|
```
|
671
792
|
|
793
|
+
If the params do not include a primary key for the resource, the action returns a failing result with a `Cuprum::Rails::Errors::InvalidParameters` error.
|
794
|
+
|
672
795
|
If the record with the given primary key does not exist, the action returns a failing result with a `Cuprum::Collections::Errors::NotFound` error.
|
673
796
|
|
674
|
-
|
797
|
+
##### Update
|
675
798
|
|
676
799
|
The `Update` action finds the record with the given primary key via `collection.find_one`, assigns the given attributes using `collection.assign_one`, validates the record using `collection.validate_one`, and finally updates the record in the collection using the `collection.update_one` command. The action returns a Hash containing the created record.
|
677
800
|
|
@@ -688,13 +811,120 @@ Book.find(0).title
|
|
688
811
|
#=> 'Gideon the Ninth'
|
689
812
|
```
|
690
813
|
|
814
|
+
If the params do not include a primary key and attributes for the resource, the action returns a failing result with a `Cuprum::Rails::Errors::InvalidParameters` error.
|
815
|
+
|
691
816
|
If the record with the given primary key does not exist, the action returns a failing result with a `Cuprum::Collections::Errors::NotFound` error.
|
692
817
|
|
693
818
|
If the updated record is not valid, the action returns a failing result with a `Cuprum::Collections::Errors::FailedValidation` error.
|
694
819
|
|
695
|
-
|
820
|
+
<a id="middleware"></a>
|
821
|
+
|
822
|
+
### Middleware
|
823
|
+
|
824
|
+
A middleware command takes two parameters. First, a `next_command` argument, which is the next item in the middleware chain (or the controller action if the middleware is the last one in the chain). Second, a `request:` keyword - this is the [request](#requests) passed down from the controller.
|
825
|
+
|
826
|
+
See [Defining Middleware](#controllers-defining-middleware), above, for using middleware in a `Cuprum::Rails::Controller`, or see [Cuprum](github.com/sleepingkingstudios/cuprum) for more information on middleware.
|
696
827
|
|
697
|
-
|
828
|
+
#### Creating Middleware
|
829
|
+
|
830
|
+
Each middleware class should be a subclass of `Cuprum::Command` and include `Cuprum::Middleware`. The constructor can optionally take either `:repository` and `:resource` keywords; if these are defined, they are passed the relevant controller property when the middleware is initialized.
|
831
|
+
|
832
|
+
```ruby
|
833
|
+
class ExampleMiddleware < Cuprum::Command
|
834
|
+
include Cuprum::Middleware
|
835
|
+
|
836
|
+
def initialize(repository:, resource:)
|
837
|
+
super()
|
838
|
+
|
839
|
+
@repository = repository
|
840
|
+
@resource = resource
|
841
|
+
end
|
842
|
+
end
|
843
|
+
```
|
844
|
+
|
845
|
+
#### Before An Action
|
846
|
+
|
847
|
+
Middleware commands can run before an action, similar to a native Rails `before_action` filter.
|
848
|
+
|
849
|
+
```ruby
|
850
|
+
class AuthenticationMiddleware < Cuprum::Command
|
851
|
+
include Cuprum::Middleware
|
852
|
+
|
853
|
+
private def process(next_command, request:)
|
854
|
+
step { Authentication::RequireUser.call(request: request) }
|
855
|
+
|
856
|
+
super
|
857
|
+
end
|
858
|
+
end
|
859
|
+
```
|
860
|
+
|
861
|
+
Here, we are creating a basic middleware command. We call our authentication command in a `step`, meaning that if the authentication command returns a failing result, we will immediately return that result. This means that our action will not run if the session is invalid.
|
862
|
+
|
863
|
+
If the authentication command returns a passing result, we call `super` to invoke the default behavior of `Cuprum::Middleware`. This calls `next_command.call(request: request)` to continue the middleware or invoke the action.
|
864
|
+
|
865
|
+
#### After An Action
|
866
|
+
|
867
|
+
Likewise, middleware commands can run after an action, similar to a native Rails `after_action` filter.
|
868
|
+
|
869
|
+
```ruby
|
870
|
+
class LoggingMiddleware < Cuprum::Command
|
871
|
+
include Cuprum::Middleware
|
872
|
+
|
873
|
+
private def process(next_command, request:)
|
874
|
+
result = next_command.call(request: request)
|
875
|
+
|
876
|
+
if result.success?
|
877
|
+
Rails.logger.info(
|
878
|
+
"Successful Request: controller: #{request.controller_name}, action:" \
|
879
|
+
" #{request.action_name}"
|
880
|
+
)
|
881
|
+
else
|
882
|
+
Rails.logger.error(
|
883
|
+
"Failed Request: controller: #{request.controller_name}, action:" \
|
884
|
+
" #{request.action_name}, error: #{result.error.as_json}"
|
885
|
+
)
|
886
|
+
end
|
887
|
+
|
888
|
+
result
|
889
|
+
end
|
890
|
+
end
|
891
|
+
```
|
892
|
+
|
893
|
+
This middleware is a little more complicated. Instead of intercepting the request before the action, here we are taking the result of the action and implementing some custom behavior based on the success or failure of the action. Finally, make sure to return the result.
|
894
|
+
|
895
|
+
Note that we are explicitly calling `next_command.call(request: request)` rather than relying on `super`. This is because `super` calls the next command inside a `step`, and will immediately return a failing result rather than continuing through `#process`. For our logging middleware, however, we actually want to handle both passing and failing results.
|
896
|
+
|
897
|
+
#### Around An Action
|
898
|
+
|
899
|
+
Finally, we can run middleware around an action, similiar to a native Rails `around_action` filter.
|
900
|
+
|
901
|
+
```ruby
|
902
|
+
class ProfilingMiddleware < Cuprum::Command
|
903
|
+
include Cuprum::Middleware
|
904
|
+
|
905
|
+
private
|
906
|
+
|
907
|
+
def process(next_command, request:)
|
908
|
+
start_time = Time.current
|
909
|
+
|
910
|
+
value = super(next_command, request: request)
|
911
|
+
|
912
|
+
return if value.nil?
|
913
|
+
|
914
|
+
end_time = Time.current
|
915
|
+
|
916
|
+
value.merge('time_elapsed' => time_elapsed(start_time, end_time))
|
917
|
+
end
|
918
|
+
|
919
|
+
def time_elapsed(start_time, end_time)
|
920
|
+
difference = ((end_time - start_time).round(3) * 1_000).to_i
|
921
|
+
|
922
|
+
"#{difference} milliseconds"
|
923
|
+
end
|
924
|
+
end
|
925
|
+
```
|
926
|
+
|
927
|
+
We start by capturing the current time, before the action is run. We then call the action via `super`; this means that the middleware will return immediately on a failed result. Once the action has run, we calculate how long the action took to run and merge that into the result value. In a production environment, we would probably pass that data to a monitoring service.
|
698
928
|
|
699
929
|
<a id="requests"></a>
|
700
930
|
|
@@ -715,8 +945,11 @@ Each request defines the following properties:
|
|
715
945
|
- `#method`: The HTTP method used for the request as a `Symbol`, e.g. `:get` or `:post`.
|
716
946
|
- `#parameters`: (also `#params`) The complete parameters for the request, including both params from the request body and from the query string. A `Hash` with `String` keys.
|
717
947
|
- `#path`: The relative path of the request, including query params.
|
948
|
+
- `#path_parameters`: (also `#path_params`) The path parameters for the request, minus the Rails-provided `action` and `controller` params. A `Hash` with `String` keys.
|
718
949
|
- `#query_parameters`: (also `#query_params`) The query parameters for the request. A `Hash` with `String` keys.
|
719
950
|
|
951
|
+
The request properties can also be accessed via the `#[]` method (using either String or Symbol keys), or updated via the `#[]=` method. The `#properties` method returns all of the request properties as a `Hash`.
|
952
|
+
|
720
953
|
<a id="resources"></a>
|
721
954
|
|
722
955
|
### Resources
|
@@ -738,7 +971,8 @@ resource.resource_name
|
|
738
971
|
|
739
972
|
A resource must be initialized with either a `resource_class` or a `resource_name`. It defines the following properties:
|
740
973
|
|
741
|
-
- `#
|
974
|
+
- `#base_url`: The base url for the collection, used when generating routes.
|
975
|
+
- `#collection`: A `Cuprum::Collections` collection, used to perform queries and persistence operations on the resource data. If not given and the collection has a `#resource_class`, then a `Cuprum::Rails::Collection` is automatically generated.
|
742
976
|
- `#resource_class`: The `Class` of items in the resource.
|
743
977
|
- `#resource_name`: The name of the resource as a `String`. If the resource is initialized with a `resource_class`, the `resource_name` is derived from the given class.
|
744
978
|
- `#routes`: A [Cuprum::Rails::Routes](#routes) object for the resource. If not given, a default routes object is generated for the resource.
|
@@ -853,9 +1087,11 @@ Provides default responses for HTML requests.
|
|
853
1087
|
- For a successful result, renders the template for the action and assigns the result value as local variables.
|
854
1088
|
- For a failing result, redirects to the resource `:index` page (for a collection action) or the resource `:show` page (for a member action).
|
855
1089
|
|
856
|
-
**Cuprum::Rails::Responders::Html::
|
1090
|
+
**Cuprum::Rails::Responders::Html::Resource**
|
857
1091
|
|
858
|
-
Provides some additional response handling for
|
1092
|
+
Provides some additional response handling for resources.
|
1093
|
+
|
1094
|
+
If the resource is plural:
|
859
1095
|
|
860
1096
|
- For a failed `#create` result, renders the `:new` template.
|
861
1097
|
- For a successful `#create` result, redirects to the `:show` page.
|
@@ -864,9 +1100,7 @@ Provides some additional response handling for plural resources.
|
|
864
1100
|
- For a failed `#update` result, renders the `:edit` template.
|
865
1101
|
- For a successful `#update` result, redirects to the `:show` page.
|
866
1102
|
|
867
|
-
|
868
|
-
|
869
|
-
Provides some additional response handling for singular resources.
|
1103
|
+
If the resource is singular:
|
870
1104
|
|
871
1105
|
- For a failed `#create` result, renders the `:new` template.
|
872
1106
|
- For a successful `#create` result, redirects to the `:show` page.
|
@@ -879,14 +1113,14 @@ Provides some additional response handling for singular resources.
|
|
879
1113
|
Provides default responses for JSON requests.
|
880
1114
|
|
881
1115
|
- For a successful result, serializes the result value and generates a JSON object of the form `{ ok: true, data: serialized_value }`.
|
882
|
-
- For a failing result, creates and serializes a generic error and generates a JSON object of the form `{ ok: false, error: serialized_error }` and a status of `500 Internal Server Error`.
|
1116
|
+
- For a failing result, creates and serializes a generic error and generates a JSON object of the form `{ ok: false, error: serialized_error }` and a status of `500 Internal Server Error`. If the Rails environment is `:development`, it will instead serialize the error from the result.
|
883
1117
|
|
884
1118
|
**Cuprum::Rails::Responders::Json::Resource**
|
885
1119
|
|
886
1120
|
- For a successful `#create` result, serializes the result value with a status of `201 Created`.
|
887
1121
|
- For a failed result with an `AlreadyExists` error, serializes the error with a status of `422 Unprocessable Entity`.
|
888
1122
|
- For a failed result with a `FailedValidation` error, serializes the error with a status of `422 Unprocessable Entity`.
|
889
|
-
- For a failed result with
|
1123
|
+
- For a failed result with an `InvalidParameters` error, serializes the error with a status of `400 Bad Request`.
|
890
1124
|
- For a failed result with a `NotFound` error, serializes the error with a status of `404 Not Found`.
|
891
1125
|
|
892
1126
|
<a id="responses"></a>
|
@@ -932,23 +1166,26 @@ A response for a JSON request. Takes the serialized `:data` to return as well as
|
|
932
1166
|
|
933
1167
|
Serializers convert entities and data structures into serialized data. Each serializer is specific to one format and one type of object - for example, the `Cuprum::Rails::Serializers::Json::ErrorSerializer` generates a JSON representation of a `Cuprum::Error`.
|
934
1168
|
|
935
|
-
|
1169
|
+
Serialization is context-specific - one controller may use one serializer for a particular record class, while another controller may use a limited set of attributes, such as an admin versus a user-facing controller. To handle this, the `#call` method must accept a `:context` keyword, which is an instance of `Cuprum::Rails::Serializers::Context`. Each context is initialized with a set of serializers that are used to serialize attributes, array items or hash values, associated models, or otherwise nested properties. All of this is handled automatically inside the controller action.
|
936
1170
|
|
937
1171
|
```ruby
|
938
1172
|
class StructSerializer < Cuprum::Rails::Serializers::JsonSerializer
|
939
|
-
def call(struct,
|
1173
|
+
def call(struct, context:)
|
940
1174
|
struct.each_pair.with_object do |(key, value), hsh|
|
941
|
-
hsh[key] = super(value,
|
1175
|
+
hsh[key] = super(value, context: context)
|
942
1176
|
end
|
943
1177
|
end
|
944
1178
|
end
|
945
1179
|
|
946
1180
|
serializer = StructSerializer.new
|
1181
|
+
context = Cuprum::Rails::Serializers::Context.new(
|
1182
|
+
serializers: Cuprum::Rails::Serializers::Json.default_serializers
|
1183
|
+
)
|
947
1184
|
struct =
|
948
1185
|
Struct
|
949
1186
|
.new(:series, :author, :titles)
|
950
1187
|
.new('The Locked Tomb', 'Tamsyn Muir', ['Gideon the Ninth', 'Harrow the Ninth'])
|
951
|
-
serializer.call(struct,
|
1188
|
+
serializer.call(struct, context: context)
|
952
1189
|
#=> {
|
953
1190
|
# 'series' => 'The Locked Tomb',
|
954
1191
|
# 'author' => 'Tamsyn Muir',
|
@@ -956,13 +1193,13 @@ serializer.call(struct, serializers: Cuprum::Rails::Serializers::Json.default_se
|
|
956
1193
|
# }
|
957
1194
|
```
|
958
1195
|
|
959
|
-
Above, we define a custom serializer for serializing `Struct` instances. We then use the serializer on our Book-like struct by passing it to the `#call` method, along with the default JSON serializers. The `#call` method takes each pair of keys and values and calls `super()`, which finds the configured serializer for each value. In our case, the default serializer for a `String` returns the string, while the default serializer for an `Array` returns a new array whose items are the serialized array items. Finally, a `Hash` with `String` keys is generated, which is our `Struct` serialized into a JSON-compatible object.
|
1196
|
+
Above, we define a custom serializer for serializing `Struct` instances. We then use the serializer on our Book-like struct by passing it to the `#call` method, along with a serialization context that contains the default JSON serializers. The `#call` method takes each pair of keys and values and calls `super()`, which finds the configured serializer for each value. In our case, the default serializer for a `String` returns the string, while the default serializer for an `Array` returns a new array whose items are the serialized array items. Finally, a `Hash` with `String` keys is generated, which is our `Struct` serialized into a JSON-compatible object.
|
960
1197
|
|
961
1198
|
`Cuprum::Rails` defines the following serializers:
|
962
1199
|
|
963
1200
|
**Cuprum::Rails::Serializers::Json::Serializer**
|
964
1201
|
|
965
|
-
The base class for JSON serializers. Takes a configured `
|
1202
|
+
The base class for JSON serializers. Takes a configured `context:` and finds the serializer for the given object, then calls that serializer with the object and the given context.
|
966
1203
|
|
967
1204
|
The serializer for an object is determined based on the object's class. Specifically, for each ancestor of the object's class, the configured serializers are checked for a key matching that ancestor. If that class or module is a key in the configured hash, then the corresponding serializer is used to serialize the object. If the configured serializers do not include a serializer for any of the object class's ancestors, raises an `UndefinedSerializerError`.
|
968
1205
|
|
@@ -1002,44 +1239,100 @@ class RecordSerializer < Cuprum::Rails::Serializers::Json::AttributesSerializer
|
|
1002
1239
|
end
|
1003
1240
|
|
1004
1241
|
class BookSerializer < RecordSerializer
|
1005
|
-
|
1006
|
-
|
1007
|
-
|
1242
|
+
attributes \
|
1243
|
+
:title,
|
1244
|
+
:author,
|
1245
|
+
:series
|
1008
1246
|
end
|
1009
1247
|
|
1010
1248
|
class DetailedBookSerializer < BookSerializer
|
1011
|
-
|
1012
|
-
|
1249
|
+
attributes \
|
1250
|
+
:category,
|
1251
|
+
published_at: :iso8601
|
1013
1252
|
end
|
1014
1253
|
|
1015
|
-
|
1016
|
-
|
1017
|
-
|
1018
|
-
|
1019
|
-
|
1020
|
-
|
1021
|
-
|
1254
|
+
|
1255
|
+
context = Cuprum::Rails::Serializers::Context.new(
|
1256
|
+
serializers: Cuprum::Rails::Serializers::Json.default_serializers
|
1257
|
+
)
|
1258
|
+
book = Book.new(
|
1259
|
+
id: 0,
|
1260
|
+
title: 'Nona The Ninth',
|
1261
|
+
author: 'Tamsyn Muir',
|
1262
|
+
series: 'The Locked Tomb',
|
1263
|
+
category: 'Science Fiction and Fantasy',
|
1264
|
+
published_at: Date.new(2022, 9, 13)
|
1022
1265
|
)
|
1023
1266
|
|
1024
|
-
BookSerializer.new.call(book,
|
1267
|
+
BookSerializer.new.call(book, context: context)
|
1025
1268
|
#=> {
|
1026
1269
|
# 'id' => 0,
|
1027
1270
|
# 'title' => 'Nona The Ninth',
|
1028
1271
|
# 'author' => 'Tamsyn Muir',
|
1029
|
-
# 'series' => 'The Locked
|
1272
|
+
# 'series' => 'The Locked Tomb'
|
1030
1273
|
# }
|
1031
1274
|
|
1032
|
-
DetailedBookSerializer.new.call(book,
|
1275
|
+
DetailedBookSerializer.new.call(book, context: context)
|
1033
1276
|
#=> {
|
1034
1277
|
# 'id' => 0,
|
1035
1278
|
# 'title' => 'Nona The Ninth',
|
1036
1279
|
# 'author' => 'Tamsyn Muir',
|
1037
1280
|
# 'series' => 'The Locked Tombs',
|
1038
1281
|
# 'category' => 'Science Fiction and Fantasy',
|
1039
|
-
# 'published_at' =>
|
1282
|
+
# 'published_at' => '2022-09-13'
|
1040
1283
|
# }
|
1041
1284
|
```
|
1042
1285
|
|
1043
1286
|
Above, we define an abstract `RecordSerializer` and a `BookSerializer`, which inherits the `:id` attribute and defines the `:title`, `:author`, and `:series` attributes. When the book serializer is called, it serializes the values of each attribute using the configured serializers; any attributes that are not defined on the serializer are ignored.
|
1044
1287
|
|
1045
1288
|
We also define a `DetailedBookSerializer` which inherits from `BookSerializer`. This allows us to reuse the attributes defined for our basic book serializer.
|
1289
|
+
|
1290
|
+
Attribute serializers also inherit from `PropertiesSerializer` (see below), and can use the `.property` method. This allows the user to serialize compound properties, or to handle cases where the desired serialization key is different from the name of the attribute.
|
1291
|
+
|
1292
|
+
#### Property Serializers
|
1293
|
+
|
1294
|
+
Property serializers define a set of properties to be serialized. This is useful for serializing data structures such as database models.
|
1295
|
+
|
1296
|
+
```ruby
|
1297
|
+
class EmployeeSerializer < Cuprum::Rails::Serializers::Json::PropertiesSerializer
|
1298
|
+
property :first_name, scope: :first_name
|
1299
|
+
property(:last_name, &:last_name)
|
1300
|
+
property(:full_name) { |user| "#{user.first_name} #{user.last_name}" }
|
1301
|
+
property(:hire_date, scope: :hire_date, &:iso8601)
|
1302
|
+
property :salary, serializer: BigDecimalSerializer.new
|
1303
|
+
property :department, scope: %i[department name]
|
1304
|
+
end
|
1305
|
+
|
1306
|
+
context = Cuprum::Rails::Serializers::Context.new(
|
1307
|
+
serializers: Cuprum::Rails::Serializers::Json.default_serializers
|
1308
|
+
)
|
1309
|
+
employee = Employee.new(
|
1310
|
+
first_name: 'Alan',
|
1311
|
+
last_name: 'Bradley',
|
1312
|
+
hire_date: Date.new(1977, 5, 25)
|
1313
|
+
salary: BigDecimal.new('100000')
|
1314
|
+
department: Department.new(name: 'Engineering')
|
1315
|
+
)
|
1316
|
+
|
1317
|
+
EmployeeSerializer.new.call(employee, context: context)
|
1318
|
+
#=> {
|
1319
|
+
# first_name: 'Alan',
|
1320
|
+
# last_name: 'Bradley',
|
1321
|
+
# full_name: 'Alan Bradley',
|
1322
|
+
# hire_date: '1977-05-25',
|
1323
|
+
# salary: '0.1e6',
|
1324
|
+
# department: 'Engineering'
|
1325
|
+
# }
|
1326
|
+
```
|
1327
|
+
|
1328
|
+
Here, we're creating a serializer for our `Employee` model, which serializes each employee into a `Hash` with the configured `property` keys.
|
1329
|
+
|
1330
|
+
- The property name determines the key used to serialize the value in the resulting Hash.
|
1331
|
+
- The `:scope` keyword determines the initial value to be serialized.
|
1332
|
+
- If the scope is `nil`, the object as a whole will be passed to the mapping and then the serializer.
|
1333
|
+
- If the scope is a String or a Symbol, then the value of the object property with that key will be mapped. Above, the `first_name` property is defined with `scope: :first_name`, so the initial value will be `employee.first_name`.
|
1334
|
+
- If the scope is an Array, then the value of the nested property at those keys will be mapped. Above, the `department` property is defined with `scope: %i[department name]`, so the initial value will be `employee.department.name`.
|
1335
|
+
- The `:serializer` keyword specifies how the mapped value is to be serialized. It should either be an instance of `Cuprum::Rails::Serializers::BaseSerializer` or a `Proc` that accepts two parameters: an `object` argument, and a `:context` keyword that is the current `Cuprum::Rails::Serializers::Context`.
|
1336
|
+
- The block, if any, is used to map the scoped value before passing it to the serializer. Above, the `full_name` property is generated by combining the `employee.first_name` and `employee.last_name`.
|
1337
|
+
|
1338
|
+
When the `property` does not specify a `scope`, a `serializer`, or provide a block, it will raise an `ArgumentError`. This would otherwise serialize the object itself using the default serializers. If, for some reason, this is the desired behavior, pass an identity block or `&:itself` as the mapping block.
|