Roda Web Toolkit Documentation

repository·master·Indexed 24 days ago

https://github.com/jeremyevans/roda

Roda is a routing tree web toolkit for Ruby designed for speed, simplicity, and extensibility. It utilizes a tree-based routing structure for efficient request handling and a comprehensive plugin system to extend instance, class, request, and response methods. Key features include a flexible plugin architecture, Rack middleware integration, and specialized plugins for JSON parsing, error handling, flash messages, and class-level routing.

Tokens
6.6K
Snippets
17
Records
43
Agent score
80%

What's inside Roda

  1. How Roda's plugin system works

    master

    Roda is designed to be lightweight by moving almost all functionality into plugins. The core Roda class is mostly empty, relying on the Roda::RodaPlugins::Base plugin for default behavior.

    When a plugin is loaded, it can extend the application in several ways:

    • Instance Methods: Added to the Roda instance (available inside the route block).
    • Class Methods: Added to the Roda class itself.
    • Request Methods: Added to the RodaRequest class (the r object).
    • Request Class Methods: Added to the RodaRequest class.
    • Response Methods: Added to the RodaResponse class.
    • Response Class Methods: Added to the RodaResponse class.

    This architecture allows plugins to modify every layer of the request/response lifecycle.

  2. How Roda plugins work

    master

    Roda has a minimal core; all non-essential features are added via plugins. Plugins are highly extensible because they can override any Roda method and use super to call the original behavior.

    When creating a plugin, you can define specific modules to inject functionality into different parts of the Roda lifecycle:

    • InstanceMethods: Included in the Roda class (for methods available within route blocks).
    • ClassMethods: Extends the Roda class.
    • RequestMethods: Included in the request class.
    • RequestClassMethods: Extends the request class.
    • ResponseMethods: Included in the response class.
    • ResponseClassMethods: Extends the response class.

    Plugins can also implement two lifecycle hooks:

    1. load_dependencies: Called first; use this if your plugin depends on other plugins.
    2. configure: Called last; use this to handle configuration passed during the plugin call.

    Both hooks receive the additional arguments and the block provided to the plugin call.

    module MarkdownHelper
        module InstanceMethods
          def markdown(str)
            BlueCloth.new(str).to_html
          end
        end
      end
    
    Roda.plugin MarkdownHelper
  3. How the Roda routing tree works

    master

    Roda uses a routing tree structure where requests are matched by traversing branches. The route block is called for every request and is yielded an instance of a Rack::Request subclass (conventionally named r).

    Key routing methods:

    • r.on: Matches if all arguments match, creating a new branch in the tree.
    • r.is: Matches if all arguments match AND there are no further segments in the path (finalizes the route).
    • r.root: Matches only GET requests where the path is exactly /.
    • r.get / r.post: Matches specific HTTP verbs. If called without arguments, they match any request of that verb. If called with arguments, they match the verb AND the path segments.

    Example of a basic routing tree:

    class App < Roda
      route do |r|
        r.root do
          "Home"
        end
    
        r.on "api" do
          r.on "v1" do
            r.get "users" do
              "User list"
            end
          end
        end
      end
    end
    class App < Roda
        route do |r|
          # GET / request
          r.root do
            r.redirect "/hello"
          end
    
    # /hello branch
          r.on "hello" do
            # Set variable for all routes in /hello branch
            @greeting = 'Hello'
    
    # GET /hello/world request
            r.get "world" do
              "#{@greeting} world!"
            end
    
    # /hello request
            r.is do
              # GET /hello request
              r.get do
                "#{@greeting}!"
              end
    
    # POST /hello request
              r.post do
                puts "Someone said #{@greeting}!"
                r.redirect
              end
            end
          end
        end
      end
    
    run App.freeze.app
  4. Route introspection in Roda

    master

    Because Roda uses a routing tree that executes the routing block directly rather than storing routes in a data structure, you cannot natively introspect routes.

    If you need to introspect routes, use the external plugin roda-route_list. This plugin allows you to add comments to your routing files, which it then parses into routing metadata for introspection.

  5. Middleware inheritance in Roda subclasses

    master

    By default, middleware added to a Roda class is inherited by its subclasses. You can control this behavior using the inherit_middleware attribute.

    • Set inherit_middleware = true (default) to have subclasses share the same middleware stack.
    • Set inherit_middleware = false if you are using a design where the parent class accepts requests and uses run to dispatch them to a subclass, and you want the subclass to have its own independent middleware stack.
    class Parent < Roda
      use MyMiddleware
      inherit_middleware = false
    end
    
    class Child < Parent
      # Child will NOT have MyMiddleware
    end
  6. Handle non-Hash JSON payloads with the :wrap option

    master

    By default, if a client sends a JSON array (e.g., [1, 2, 3]) instead of a JSON object, calling r.params might fail because Roda expects a Hash. To prevent this, use the :wrap option:

    • :always: Wraps the parsed JSON in a hash: {"_json" => parsed_data}.
    • :unless_hash: Only wraps the data if the parsed JSON is not already a Hash.

    This ensures that r.params and r.POST consistently return a Hash structure.

  7. Compare multi_run with hash_branches and multi_route

    master

    While hash_branches and multi_route keep all routing subtrees within the same Roda application class, multi_run dispatches to entirely different Rack applications.

    Use multi_run when:

    • You want to isolate routing subtrees into separate applications.
    • You want to dispatch to non-Roda Rack apps (like Sinatra).

    Limitation:

    • Because multi_run dispatches to a different Rack app, you cannot access instance variables set in the main Roda application within the dispatched sub-applications.
  8. Use the precompile_templates plugin to save memory

    master

    The precompile_templates plugin allows you to precompile template code in the parent process before forking a webserver. This is particularly useful for forking webservers (like Unicorn or Puma in cluster mode) to achieve significant memory savings, as child processes can share the same precompiled templates instead of each having its own copy.

    Additionally, once templates are precompiled, access to the original template files on the file system is no longer required, which can be useful for certain security configurations.

  9. Install the error_handler plugin

    master

    The error_handler plugin allows you to catch exceptions raised during routing and return a custom error response (like a nice error page) instead of a raw crash.

    When an exception is caught, the plugin resets the response, sets the status to 500 by default, and executes your handler block. The handler block receives the exception instance as an argument and should return a string representing the response body.

    plugin :error_handler do |e|
      "Oh No!"
    end
  10. Use the status_handler plugin to define custom error responses

    master

    The status_handler plugin allows you to define custom blocks that are executed whenever a response with a specific HTTP status code is returned with an empty body.

    To use it, first load the plugin using plugin :status_handler, then define handlers for specific codes using the status_handler(code, opts) method.

    By default, all existing headers on the response are cleared before the handler block is called. If you want to preserve specific headers, use the keep_headers option by passing an array of header names.

    plugin :status_handler
    
    status_handler(403) do
      "You are forbidden from seeing that!"
    end
    
    status_handler(404) do
      "Where did it go?"
    end
    
    # Example preserving specific headers
    status_handler(405, keep_headers: ['Accept']) do
      "Use a different method!"
    end
  11. Use the multi_run plugin to dispatch to Rack applications

    master

    The multi_run plugin allows a Roda application to dispatch requests to other Rack applications (including other Roda apps or Sinatra apps) based on a URL path prefix. This is ideal for isolating routing subtrees into separate applications.

    To use it:

    1. Load the plugin in your main Roda class.
    2. Register applications using App.run(prefix, app) or App.run(prefix) { block }.
    3. Call r.multi_run within your route block to trigger the dispatching logic.
    class App < Roda
      plugin :multi_run
    end
    
    # Registering apps
    App.run "ra", PlainRackApp
    App.run "ro", OtherRodaApp
    App.run "si", SinatraApp
    
    App.route do |r|
      # Dispatches based on prefix
      r.multi_run
    end
  12. Use the json_parser plugin to parse JSON request bodies

    master

    The json_parser plugin automatically parses request bodies in JSON format if the Content-Type header includes json. Once parsed, the data is available via r.POST and r.params (which merges r.GET and r.POST).

    This plugin is primarily designed for JSON API sites. It ignores empty request bodies and only triggers parsing when the appropriate content type is detected.