generating
Generated controller inheritance chain
Both the resource generator and the controller generator create base controllers in every nested directory along the path to the target controller being generated:
- The controller will extend the base controller in its directory
- The base controller will extend the base controller in its parent directory
- The base controller in the outermost controller directory is either the
AuthedController
or theUnauthedController
(the default of for generated routes is always theAuthedController
to ensure that generated routes never accidentally expose controllers at unauthenticated routes) - When generating within the
admin
namespace (e.g., viayarn psy g:controller admin/content/lessons
), the root controller in the inheritance chain will be theAuthedAdminController
By using resource and controller generators and following these conventions, access controls will be automatically applied to new resources. To apply different access controls to all controllers rooted at a particular directory, simply add a @BeforeAction method that calls this.forbidden()
when the currently authenticated user does not have the necessary permissions.
Resources
A resource is a model with a corresponding controller with the one or more of the standard CRUD operations. The resource generator automatically creates everything needed to perform these basic actions on a model, including:
- model + placeholder spec file
- migration
- serializer
- controller with index/show/create/update/destroy routes (customizable with the
--only
option) - routes
- controller spec matching the routes in the controller
- a model spec placeholder
All of the controller action implementations generated by this command are by default commented out to prevent accidental publishing of an unintended endpoint, and it is expected that the boilerplate authentication used to load this.currentUser
(see AuthedController
) will be replaced with your production authentication scheme.
options
yarn psy g:resource --help
option | effect |
---|---|
‑‑singular | adds "resource" to the routes file, instead of "resources", omits the index action from the controller and spec files, and doesn’t include id in the path or use the id to find the model (used when the User or other owning model has a HasOne association to this resource) |
‑‑only <onlyActions> | comma separated list of resourceful endpionts (e.g. "‑‑only=create,show"); any of: index, create, show, update, delete |
‑‑sti‑base‑serializer | omits the serializer from the dream model, but does create the serializer so it can be extended by STI children |
‑‑owning‑model <modelName> | the model class of the object that associationQuery /createAssociation will be performed on in the created controller and spec (e.g., "Host", "Guest") (simply to save time making changes to the generated code). Defaults to User |
nested resource routes
In this example, we’ll generate a Places resource and then a nested Rooms resource within Places (so that when a Room is created, it will be associated with the specified Place, and the index will return Rooms for a given Place).
yarn psy g:resource --owning-model=Host v1/host/places Place name:citext style:enum:place_styles:cottage,cabin,lean_to,treehouse,tent,cave,dump sleeps:integer deleted_at:datetime:optional
The routes file will be modified with a namespaced resource.
// conf/routes.ts
r.namespace('v1', r => {
r.namespace('host', r => {
r.resources('places')
})
})
The resource generator doesn’t know about nested resources, so when generating a nested resource, the nesting namespace will need to be changed into a resource. For example generating a nested Rooms resource within Places:
yarn psy g:resource --sti-base-serializer --owning-model=Place v1/host/places/rooms Room type:enum:room_types:Bathroom,Bedroom,Kitchen,Den,LivingRoom Place:belongs_to position:integer:optional deleted_at:datetime:optional
would update the above routes like so:
r.namespace('v1', r => {
r.namespace('host', r => {
r.namespace('places', r => {
r.resources('rooms')
})
r.resources('places')
})
})
which would then need to be updated to:
r.namespace('v1', r => {
r.namespace('host', r => {
r.resources('places', r => {
r.resources('rooms')
})
})
})
At this point, displaying routes:
yarn psy routes
will nest the rooms
routes within places
. Notice that in this case, the Place id param is named placeId
:
verb | path | controller and action |
---|---|---|
GET | /v1/host/places/:placeId/rooms | V1/Host/Places/Rooms#index |
POST | /v1/host/places/:placeId/rooms | V1/Host/Places/Rooms#create |
PUT | /v1/host/places/:placeId/rooms/:id | V1/Host/Places/Rooms#update |
PATCH | /v1/host/places/:placeId/rooms/:id | V1/Host/Places/Rooms#update |
GET | /v1/host/places/:placeId/rooms/:id | V1/Host/Places/Rooms#show |
DELETE | /v1/host/places/:placeId/rooms/:id | V1/Host/Places/Rooms#destroy |
GET | /v1/host/places | V1/Host/Places#index |
POST | /v1/host/places | V1/Host/Places#create |
PUT | /v1/host/places/:id | V1/Host/Places#update |
PATCH | /v1/host/places/:id | V1/Host/Places#update |
GET | /v1/host/places/:id | V1/Host/Places#show |
DELETE | /v1/host/places/:id | V1/Host/Places#destroy |
Simple Controller
To generate a controller, use the provided cli tool as demonstrated below:
yarn psy g:controller V1/Guest/Places index show
The controller produced will automatically have the methods specified provided for you, with OpenAPI
decorators automatically registered on your methods, but no implementation.
Additionally, a spec file will be generated, which is empty by default, but ready for you to add spec examples.