Standalone (routing to components)
As stated earlier, Compony can generate routes to your components. This is achieved by using the standalone DSL inside the setup block. The first step is calling the method standalone with a path. Inside this block, you will then specify which HTTP verbs (e.g. GET, PATCH etc.) the component should listen to. As soon as both are specified, Compony will generate an appropriate route.
Assume that you want to create a simple component statics/welcome.rb that displays a static welcome page. The component should be exposed under the route '/welcome' and respond to the GET method. Here is the complete code for making this happen:
# app/components/statics/welcome.rb
class Components::Statics::Welcome < Compony::Component
setup do
label(:all) { 'Welcome' }
standalone path: 'welcome' do
verb :get do
{ true }
end
end
content do
h1 'Welcome to my dummy site!'
end
end
end
This is the minimal required code for standalone. For security, every verb config must provide an authorize block that specifies who has access to this standalone verb. The block is given the request context and is expected to return either true (access ok) or false (causing the request to fail with Cancan::AccessDenied).
Typically, you would use this block to check authorization using the CanCanCan gem, such as authorize { can?(:read, :welcome) }. However, since we skip authentication in this simple example, we pass true to allow all access.
The standalone DSL has more features than those presented in the minimal example above. Excluding resourceful features, the full list is:
standalonecan be called multiple times, for components that need to expose multiple paths, as described below. Inside eachstandalonecall, you can call:skip_authentication!which disables authentication, in case you provided some. You need to implementauthorizeregardless.skip_forgery_protection!which disables CSRF protection for the controller action generated for this standalone configuration.layoutwhich takes the file name of a Rails layout and defaults tolayouts/application. Use this to have your Rails application look differently depending on the component.verbwhich takes an HTTP verb as a symbol, one of:%i[get head post put delete connect options trace patch].verbcan be called up to once per verb. Inside eachverbcall, you can call (in the non-resourceful case):authorizeis mandatory and explained above.respondcan be used to implement special behavior that in plain Rails would be placed in a controller action. The default, which callsbefore_renderand thecontentblocks, is usually the right choice, so you will rarely implementrespondon your own. See below howrespondcan be used to handle different formats or redirecting clients. Caution:authorizeis evaluated in the default implementation ofrespond, so when you override that block, you must perform authorization yourself!
Exposing multiple paths in the same component (calling standalone multiple times)
If your component loads data dynamically from a JavaScript front-end (e.g. implemented via Stimulus), you will find yourself in the situation where you need an extra route for a functionality that inherently belongs to the same component. Example use cases would be search fields that load data as the user types, maps that load tiles, dynamic photo galleries etc.
In this case, you can call standalone a second time and provide a name for your extra route:
setup do
# Regular route for rendering the content
standalone path: 'map/viewer' do
verb :get do
{ true }
end
end
# Extra route for loading tiles via AJAX
standalone :tiles, path: 'map/viewer/tiles' do
verb :get do
respond do # Again: overriding `respond` skips authorization! This is why we don't need to provide an `authorize` block here.
controller.render(json: MapTiler.load(params, current_ability)) # current_ability is provided by CanCanCan and made available by Compony.
end
end
end
# More code for labelling, content etc.
end
Please note that the idea here is to package things that belong together, not to provide different kinds of content in a single component. For displaying different pages, use multiple components and have each expose a single route.
Naming of exposed routes
The routes to standalone components are named and you can point to them using Rails' ..._path and ..._url helpers. The naming scheme is: [standalone]_[component]_[family]_comp. Examples:
- Default standalone:
Components::Users::Indexexportsindex_users_compand thusindex_users_comp_pathcan be used. - Named standalone: If
standalone :foo, path: ...is used withinComponents::Users::Index, the exported name isfoo_index_users_comp.
Handling formats
Compony is capable of responding to formats like Rails does. This is useful to deliver PDFs, CSV files etc. to a user from within Compony. This can be achieved by specifying the respond block:
setup do
standalone path: 'generate/report' do
verb :get do
# Respond with a file when generate/report.pdf is GETed:
respond :pdf do
file, filename = PdfGenerator.generate(params, current_ability)
send_data(file, filename:, type: 'application/pdf')
end
# If someone visits generate/report, issue a 404:
respond do
fail ActionController::RoutingError, 'Unsupported format - please make sure your URL ends with `.pdf`.'
end
end
end
end
Redirect in respond or in before_render?
Rails controller redirects can be issued both in a verb DSL's respond block and in before_render. The rule of thumb that tells you which way to go is:
- If you want to redirect depending on the HTTP verb, use
respond. - If you want to redirect depending on params, state, time etc. independently of the HTTP verb, use
before_render, as this is more convenient than writing a standalone -> verb -> respond tree.
Path constraints
When calling standalone, you may specify the keyword constraints that will be passed to the route. For example:
# In your component
standelone path: '/:lang', constraints: { lang: /([a-z]{2})?/i }
# This will automatically lead to a route of this form:
get ':lang', constraints: { lang: /([a-z]{2})?/i }
Passing scopes
When calling standalone, you may specify the keyword scope to wrap the component's Rails route into a route scope. Additionally, you may specify a hash scope_args, which will be passed as keyword arguments to the scope call in the route:
# In your component
standalone path: '/welcome', scope: '(:lang)', scope_args: { lang: /([a-z]{2})?/i } do
verb :get do
# ....
end
end
# This will automatically lead to a route of this form:
scope '(:lang)', lang: /([a-z]{2})?/i do
get 'welcome', to: 'compony#your_component'
end
Customizing path generation
By implementing path do ... end inside the setup method of a component, you can override the way paths to that component are generated. Customizing the path generation will affect all mentioned methods mentioned here involving paths, such as Compony.path, render_intent etc.
The block runs outside the request context, so build URLs via
Rails.application.routes.url_helpers (not controller/helpers). It is given an optional
model, positional path-helper args, the standalone_name: kwarg, and any extra kwargs.
This is an advanced usage. Refer to the default implementation of Component's path_block
to see the baseline example.
Where overriding path is genuinely useful:
- Inject a derived/looked-up param. Callers pass a high-level argument and the block turns it into concrete path params.
- Mint a signed token into the URL so an unauthenticated link can authorize itself — a full worked recipe is in Real-world patterns §18 (signed-token capability links) (magic login, password reset, invite/confirm links).
- Custom slugs / vanity paths that differ from the Rails route helper's default shape.