Skip to content

Routing - Basics #1

@crashtech

Description

@crashtech

Summary

Semantically and functionally enhance routes to provide extended configurations.

Example

# config/routes.rb
Rails.application.routes.draw do
  admin do # 1.
    resources :users do # 2.
      action :approve # 3.
      action :toggle, as: [:enable, :disable] # 4.

      action :change_password, view: true # 5.

      member do # 6.
        action :impersonate
      end
    end

    resources :orders, actions: [:accept, :ship] # 7.

    section :store do # 8.
      dashboard cards: [:sells, :views] # 9.
    end

    authenticate :users, with: :devise # 10.
  end

  admin :internal, path: :system do # 11.
    resources :admin_users

    simple do # 12.
      resources :categories
    end

    unauthenticated do # 13.
      get 'uptime', to: 'rails/health#show', as: :rails_health_check
    end

    searchable :templates # 14.

    authenticate :admin_user, with: :rails
  end
end

Output

$ rails routes
Prefix    Verb URI Pattern Controller#Action
    admin      /admin      Torque::Admin[:admin]
 internal      /system     Torque::Admin[:internal]
     root GET  /           application#index

Routes for Torque::Admin[:admin]:
           approve_users PATCH  /users(/:id)/approve(.:format)         admin/users#approve
            enable_users PATCH  /users(/:id)/enable(.:format)          admin/users#toggle
           disable_users PATCH  /users(/:id)/disable(.:format)         admin/users#toggle
   change_password_users PATCH  /users(/:id)/change_password(.:format) admin/users#change_password
                         GET    /users(/:id)/change_password(.:format) admin/users#change_password
                         DELETE /users(/:id)(.:format)                 admin/users#destroy
        impersonate_user PATCH  /users/:id/impersonate(.:format)       admin/users#impersonate
            preview_user GET    /users/:id/preview(.:format)           admin/users#preview
               edit_user GET    /users/:id/edit(.:format)              admin/users#edit
                    user GET    /users/:id(.:format)                   admin/users#show
                         PATCH  /users/:id(.:format)                   admin/users#update
                         PUT    /users/:id(.:format)                   admin/users#update
            search_users GET    /users/search(.:format)                admin/users#search
                new_user GET    /users/new(.:format)                   admin/users#new
                   users GET    /users(.:format)                       admin/users#index
                         POST   /users(.:format)                       admin/users#create
                         PATCH  /users(.:format)                       admin/users#upsert

           accept_orders PATCH  /orders(/:id)/accept(.:format)         admin/orders#accept
             ship_orders PATCH  /orders(/:id)/ship(.:format)           admin/orders#ship
                         DELETE /orders(/:id)(.:format)                admin/orders#destroy
           preview_order GET    /orders/:id/preview(.:format)          admin/orders#preview
              edit_order GET    /orders/:id/edit(.:format)             admin/orders#edit
                   order GET    /orders/:id(.:format)                  admin/orders#show
                         PATCH  /orders/:id(.:format)                  admin/orders#update
                         PUT    /orders/:id(.:format)                  admin/orders#update
           search_orders GET    /orders/search(.:format)               admin/orders#search
               new_order GET    /orders/new(.:format)                  admin/orders#new
                  orders GET    /orders(.:format)                      admin/orders#index
                         POST   /orders(.:format)                      admin/orders#create
                         PATCH  /orders(.:format)                      admin/orders#upsert
                         
                         GET    /store(.:format)                       redirect(301, /store/dashboard)
         store_dashboard GET    /store/dashboard(.:format)             admin/store_dashboard#index
   store_dashboard_stock GET    /store/dashboard/stock(.:format)       admin/store_dashboard#stock
store_dashboard_products GET    /store/dashboard/products(.:format)    admin/store_dashboard#products
                         DELETE /store/products(/:id)(.:format)        admin/products#destroy
   preview_store_product GET    /store/products/:id/preview(.:format)  admin/products#preview
      edit_store_product GET    /store/products/:id/edit(.:format)     admin/products#edit
           store_product GET    /store/products/:id(.:format)          admin/products#show
                         PATCH  /store/products/:id(.:format)          admin/products#update
                         PUT    /store/products/:id(.:format)          admin/products#update
   search_store_products GET    /store/products/search(.:format)       admin/products#search
       new_store_product GET    /store/products/new(.:format)          admin/products#new
          store_products GET    /store/products(.:format)              admin/products#index
                         POST   /store/products(.:format)              admin/products#create
                         PATCH  /store/products(.:format)              admin/products#upsert

        new_user_session GET    /login(.:format)                       admin/sessions#new
            user_session POST   /login(.:format)                       admin/sessions#create
    destroy_user_session DELETE /logout(.:format)                      admin/sessions#destroy
       new_user_password GET    /password/new(.:format)                admin/passwords#new
      edit_user_password GET    /password/edit(.:format)               admin/passwords#edit
           user_password PATCH  /password(.:format)                    admin/passwords#update
                         PUT    /password(.:format)                    admin/passwords#update
                         POST   /password(.:format)                    admin/passwords#create
cancel_user_registration GET    /cancel(.:format)                      admin/registrations#cancel
   new_user_registration GET    /sign_up(.:format)                     admin/registrations#new
  edit_user_registration GET    /edit(.:format)                        admin/registrations#edit
       user_registration PATCH  /                                      admin/registrations#update
                         PUT    /                                      admin/registrations#update
                         DELETE /                                      admin/registrations#destroy
                         POST   /                                      admin/registrations#create
   new_user_confirmation GET    /confirmation/new(.:format)            admin/confirmations#new
       user_confirmation GET    /confirmation(.:format)                admin/confirmations#show
                         POST   /confirmation(.:format)                admin/confirmations#create
         new_user_unlock GET    /unlock/new(.:format)                  admin/unlocks#new
             user_unlock GET    /unlock(.:format)                      admin/unlocks#show
                         POST   /unlock(.:format)                      admin/unlocks#create

                         GET    /(.:format)                            redirect(301, /dashboard)
               dashboard GET    /dashboard(.:format)                   admin/dashboard#index

Routes for Torque::Admin[:internal]:
                         DELETE /admin_users(/:id)(.:format)           internal/admin_users#destroy
      preview_admin_user GET    /admin_users/:id/preview(.:format)     internal/admin_users#preview
         edit_admin_user GET    /admin_users/:id/edit(.:format)        internal/admin_users#edit
              admin_user GET    /admin_users/:id(.:format)             internal/admin_users#show
                         PATCH  /admin_users/:id(.:format)             internal/admin_users#update
                         PUT    /admin_users/:id(.:format)             internal/admin_users#update
      search_admin_users GET    /admin_users/search(.:format)          internal/admin_users#search
          new_admin_user GET    /admin_users/new(.:format)             internal/admin_users#new
             admin_users GET    /admin_users(.:format)                 internal/admin_users#index
                         POST   /admin_users(.:format)                 internal/admin_users#create
                         PATCH  /admin_users(.:format)                 internal/admin_users#upsert
                         
                         DELETE /categories(/:id)(.:format)            internal/simple_resource#destroy
           edit_category GET    /categories/:id/edit(.:format)         internal/simple_resource#edit
                category GET    /categories/:id(.:format)              internal/simple_resource#show
                         PATCH  /categories/:id(.:format)              internal/simple_resource#update
                         PUT    /categories/:id(.:format)              internal/simple_resource#update
       search_categories GET    /categories/search(.:format)           internal/simple_resource#search
            new_category GET    /categories/new(.:format)              internal/simple_resource#new
              categories GET    /categories(.:format)                  internal/simple_resource#index
                         POST   /categories(.:format)                  internal/simple_resource#create
                         PATCH  /categories(.:format)                  internal/simple_resource#upsert
                         
      rails_health_check GET    /uptime(.:format)                      * rails/health#show
      
             new_session GET    /session/new(.:format)                 internal/sessions#new
            edit_session GET    /session/edit(.:format)                internal/sessions#edit
                 session GET    /session(.:format)                     internal/sessions#show
                         PATCH  /session(.:format)                     internal/sessions#update
                         PUT    /session(.:format)                     internal/sessions#update
                         DELETE /session(.:format)                     internal/sessions#destroy
                         POST   /session(.:format)                     internal/sessions#create
               passwords GET    /passwords(.:format)                   internal/passwords#index
                         POST   /passwords(.:format)                   internal/passwords#create
            new_password GET    /passwords/new(.:format)               internal/passwords#new
           edit_password GET    /passwords/:token/edit(.:format)       internal/passwords#edit
                password GET    /passwords/:token(.:format)            internal/passwords#show
                         PATCH  /passwords/:token(.:format)            internal/passwords#update
                         PUT    /passwords/:token(.:format)            internal/passwords#update
                         DELETE /passwords/:token(.:format)            internal/passwords#destroy

        search_templates GET    /templates/search(.:format)            internal/simple_resource#search

                         GET    /(.:format)                            redirect(301, /dashboard)
               dashboard GET    /dashboard(.:format)                   internal/dashboard#index

This might not be 100% accurate due to additional customization and sorting

1. The admin definition

This method will define a new admin. An extended config file may or may not exist on an initializer (like config/initializers/admin.rb). It automatically sets a new Engine, proper namespace, and any necessary Torque::Admin::Application configurations.

2. Extended resources

For the most part, this will behave like a standard Rails resources definition. However, notice some small changes and additions:

  • destroy is now a member and collection action, meaning it can handle a single ID or multiple IDs (more on that later).
  • preview is a new member action intended for situations where we want a quick snip (probably a modal or similar) of a record.
  • search is a new collection action intended for searching/filtering records, resulting in a list of options.
  • upsert is a new collection action intended for creating or updating multiple records at once.

3. Resource(s) actions

Resource(s) can be defined with multiple actions. They represent operations that can be performed on one or more records at once. This is the same mechanism by which destroy was moved to.

4. Multiple paths, same action

This is quite frequent, and such features address the need for multiple actions doing similar things, or for a single action with an improper path or intention. Metadata may be provided, giving the exact action_path that triggered the action, for a more specific handling.

5. Actions that display a view first

Sometimes an action may require a form that provides more information before proceeding. In such cases, the action may be defined with view: true, which indicates what should happen depending on the HTTP VERB.

6. Expected behavior with Rails defaults

If actions need to be tied exclusively to a member or only via collection, it can by done by using the on: option, or using member and collection blocks. The behavior of these options is the same as in Rails defaults.

7. Quick definitions

The actions argument simplifies the definition of multiple actions. It also allows sharing behavior across different resources.
This can allow a wide range of possibilities. For example, you can define a Concern, add it to multiple controllers, and then use actions with a constant to all those resources. It can even be dynamic using .public_instance_methods.

One side note, this is applied before except and only options, so those can still be used to further limit the actions created.

8. A simple organizational shorthand

It's just a simple scope path: VALUE, as: VALUE when we want to organize routes and helpers without nesting controllers. It also helps when building an automatic menu.

9. A new type of "resource"

Dashboards are intended to be a landing page for an admin or a section of an admin. They are not tied to a model, but rather to a set of widgets that display information.

Although the dashboard can display multiple cards at the same time, cards allows defining multiple endpoints that can be loaded asynchronously or even updated periodically.

10. Authentication sugar syntax

This is just a shorthand for defining authentication routes, which are common in admin interfaces. It sets up routes for login, logout, password reset, and registration.

Each provider (defined by the mandatory with: key) will have its own set of routes, allowing for multiple authentication methods within the same admin interface.

11. Multiple admin applications under the same Rails

It's possible to define multiple admin applications within the same Rails application. Each admin will have its own namespace, engine, and configurations.

They will operate independently in all respects. They can have different authentication methods, different frontend frameworks, different everything.

12. The key to reducing pointless controllers

More often than not, admin interfaces require a lot of boilerplate controllers that do very little beyond the standard CRUD operations. With this routing DSL, you can define resources and actions without creating a controller.

It will simply be handled by the default and base controller Torque::Admin::ResourceController with default properties.
This also drops the preview routes, since they're less likely to be needed.

13. Very clear indication of public routes

Admins are in an authenticated area by default. However, there are cases where some routes must be public (e.g., login and registration). This will explicitly do this job, without adding any route constraints or anything like that. It will simply add an extra internal metadata indication that an authenticated user is not necessary.

14. Not fully manageable resources

Sometimes there are resources that we don't need to manage, there is no need for pages and forms or anything like that. But, those resources tend to be the ones we need to at least give as select options or reference on filters. That is another time that we can take advantage of the simple_resource.

Notes

  1. Because the Torque::Admin::Applications are heavily controlled under their own namespace, controllers do not necessarily need to exist. The application will seamlessly fallback to the default behavior (controllers will be defined automatically if not found).
  2. Since this is simply built on top of Rails routing DSL, all standard Rails routing features are available and work as expected.
  3. You won't be able to define an admin inside another admin. Each admin is a top-level definition. But other engines can be nested inside an admin.

A word about meta information

The admin is being implemented on something that I call "continuous definition", where the definition and full setup of an admin is not found in one place, but rather spread across multiple files and locations. This is done deliberately to give the feel of an extended Rails application, not to teach something completely new.

That said, the Admin application will continuously collect meta information about itself and store it internally for later use. In the example, admin application will be aware that it has users, orders, and products as managed resources, and that is primarly authenticated via an users resource.

This metadata is used throughout the admin application to enable features such as automatic menu generation, cross-referencing, and more.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    Status

    NEW

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions