Skip to main content

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 the UnauthedController (the default of for generated routes is always the AuthedController to ensure that generated routes never accidentally expose controllers at unauthenticated routes)
  • When generating within the admin namespace (e.g., via yarn psy g:controller admin/content/lessons), the root controller in the inheritance chain will be the AuthedAdminController

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
optioneffect
‑‑singularadds "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‑serializeromits 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:

verbpathcontroller and action
GET/v1/host/places/:placeId/roomsV1/Host/Places/Rooms#index
POST/v1/host/places/:placeId/roomsV1/Host/Places/Rooms#create
PUT/v1/host/places/:placeId/rooms/:idV1/Host/Places/Rooms#update
PATCH/v1/host/places/:placeId/rooms/:idV1/Host/Places/Rooms#update
GET/v1/host/places/:placeId/rooms/:idV1/Host/Places/Rooms#show
DELETE/v1/host/places/:placeId/rooms/:idV1/Host/Places/Rooms#destroy
GET/v1/host/placesV1/Host/Places#index
POST/v1/host/placesV1/Host/Places#create
PUT/v1/host/places/:idV1/Host/Places#update
PATCH/v1/host/places/:idV1/Host/Places#update
GET/v1/host/places/:idV1/Host/Places#show
DELETE/v1/host/places/:idV1/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.