Resourceful components
So far, we have mainly seen how to present static content, without considering how loading and storing data is handled. Whenever a component is about data, be it a collection (e.g. index, list) or a single instance (e.g. new, show, edit, destroy, form), that component typically becomes resourceful. In order to implement a resourceful component, include the mixin Compony::ComponentMixins::Resourceful.
Resourceful components use an instance variable @data and provide a reader data for it. As a convention, always store the data the component "is about" in this variable.
Further, the class of which data should be can be specified and retrieved by using data_class. By default, data_class is inferred from the component's family name, i.e. Components::User::Show will automatically return User as data_class.
The mixin adds extra hooks that can be used to store logic that can be executed in the request context when the component is rendered standalone. The formulation of that sentence is important, as the decision which of these blocks are executed depends on the verb DSL. But before elaborating on that, let's first look at all the available hooks provided by the Resourceful mixin:
load_data: Important. Specify a block that assigns something to@datahere. The block will be run before authorization - thus, you can check@datafor authorizing (e.g.can?(:read, @data)).after_load_data: Optional. If a block is specified, it is run immediately afterload_data. This is useful if you inherit from a component that loads data but you need to alter something, e.g. refining a collection.assign_attributes: Important for components that alter data, e.g. New, Edit. Specify a block that assigns attributes to your model fromload_data. The model is now dirty, which is important: do not save your model here, as authorization has not yet been performed. Also, do not forget to validate params before assigning them to attributes.after_assign_attributes: Optional. If a block is specified, it is run immediately afterassign_attributes. Its usage is similar to that ofafter_load_data.- (At this point, your
authorizeblock is executed, throwing aCanCan::AccessDeniedexception causing HTTP 403 not authorized if the block returns false.) store_data: Important for components that alter data, e.g. New, Edit. This is where you save your model stored in@datato the database.
Another important aspect of the Resourceful mixin is that it also extends the Verb DSL available in the component. The added calls are:
load_dataassign_attributesstore_data
Unlike the calls above, which are global for the entire component, the ones in the Verb DSL are on a per-verb basis, same as the authorize call. If the same hook is both given as a global hook and in the Verb DSL, the Verb DSL hook overwrites the global one. The rule of thumb on where to place logic is:
- If multiple verbs use the same logic for a hook, place it in the global hook. For example, let us consider an Edit component: if GET is called on it, the model is loaded and parameters are assigned to it in order to fill the form's inputs. If PATCH is called, the exact same thing is done before attempting to save the model. In this case, you would implement both
load_dataandassign_attributesas global hooks. - If a hook is specific to a single verb, place it in the verb config.
Let's build an example of a simplified Destroy component. In practice, you'd instead inherit from Compony::Components::Destroy. However, for the sake of demonstration, we will implement it from scratch:
class Components::Users::Destroy < Compony::Component
# Make the component resourceful
include Compony::ComponentMixins::Resourceful
setup do
# Let the path be of the form users/42/destroy
standalone path: 'users/:id/destroy' do
verb :get do
# In the case of a GET request, ask for confirmation, not deleting anything.
# Nevertheless, we should authorize :destroy, not :read.
# Reason: this way, buttons pointing to this component will not be shown
# to users which lack the permission to destroy @data.
{ can?(:destroy, @data) }
end
verb :delete do
# In the case of a DELETE request, the record will be destroyed.
{ can?(:destroy, @data) }
store_data { @data.destroy! }
# We overwrite the respond block because we want to redirect, not render
respond do
flash.notice = "#{@data.label} was deleted."
redirect_to Compony.path(:index, :users)
end
end
end
# Resourceful components have a default `load_data` block that loads the model.
# Therefore, the default behavior is already set to:
# load_data { @data = User.find(params[:id]) }
label(:short) { |_| 'Delete' }
label(:long) { |data| "Delete #{data.label}" }
content do
h1 "Are you sure to delete #{@data.label}?"
div render_intent(:destroy, @data, label: 'Yes, delete', method: :delete)
end
end
end
Complete resourceful lifecycle
This graph documents a typical resourceful lifecycle according to which Compony's pre-built components are implemented.
load_datacreates or fetches the resource from the database.after_load_datacan refine the resource, e.g. add scopes to a relation.assign_attributestakes the HTTP parameters, validates them and assigns them to the resource.after_assign_attributescan refine the assigned resource, e.g. provide defaults for blank attributes.authorizeis called.store_datacreates/updates/destroys the resource.respondtypically shows a flash and redirects to another component.

Nesting resourceful components
The Intent API makes it very easy to pass a resource to nested components. For more details, refer to this example.