Rare Ruby Reasons

Hello

Daniel Farina, a founder of Ubicloud

What is it?

The Rare Reasons

  • Rare Libraries
  • A Rare Engineer
  • Rare Testing

(in twelve minutes)

{2011, 2015}

Ruby

Postgres

Sequel

Sinatra

2019

Ruby

Postgres

Sequel

Roda

2023

Ruby

Postgres

Sequel

Roda

Rodauth

Never used Rails!

it just never happened

don't ask me to compare, I won't know

spent my one and only youth writing "fancy ssh clients"

Roda

"Routing Tree" Framework

 

The "tree" takes the form of nested blocks.

 

Matchers accept literal values, simple data types, http methods...

 

Replaces "before" filters, and sometimes middleware

 

class App < Roda
  route do |r|
    # Matches "/items/<whatever>"
    r.on "items" do
      # Matches /items/[0-9]+$
      r.is Integer do |item_id|
        @item = Item[item_id]

        # e.g. GET /items/123
        r.get { "Rendering item: #{@item}" }
        
        # e.g. DELETE /items/123
        r.delete { @item.destroy }
      end
    end

    # Matches /foo, /foo/bar, /pear/apple/peach
    r.on String do |a_prefix|
      r.get { "You asked for #{a_prefix}" }
    end
  end
end
class CloverWeb < Roda
  # [...Lots of plugin setup...]

  route do |r|
    r.public
    r.assets
    check_csrf!

    @current_user = Account[rodauth.session_value]
    r.rodauth
    rodauth.load_memory
    rodauth.check_active_session
 
    r.root do
      r.redirect rodauth.login_route
    end
    rodauth.require_authentication

    r.hash_branches
  end
end

Example from Ubicloud

class CloverWeb
  hash_branch("project", "firewall") do |r|
    r.get true do
      authorized_firewalls = @project.firewalls_dataset.authorized(@current_user.id, "Firewall:view").all
      @firewalls = Serializers::Firewall.serialize(authorized_firewalls, {include_path: true})

      view "networking/firewall/index"
    end

    r.on "create" do
      r.get true do
        Authorization.authorize(@current_user.id, "Firewall:create", @project.id)
        authorized_subnets = @project.private_subnets_dataset.authorized(@current_user.id, "PrivateSubnet:edit").all
        @subnets = Serializers::PrivateSubnet.serialize(authorized_subnets)
        @default_location = @project.default_location
        view "networking/firewall/create"
      end
    end

Reopen Class, define branch

  • still "CloverWeb" (one class, many files)
  • hash_branch jumps to the matching block
  • runs more familiar Roda code

matches "/project/firewall"

Reopens

clover_web.rb:class CloverWeb < Roda
routes/web/project.rb:class CloverWeb
routes/web/github.rb:class CloverWeb
routes/web/project/firewall.rb:class CloverWeb
routes/web/project/github.rb:class CloverWeb
routes/web/project/user.rb:class CloverWeb
routes/web/project/load_balancer.rb:class CloverWeb
routes/web/project/vm.rb:class CloverWeb
routes/web/project/billing.rb:class CloverWeb
routes/web/project/policy.rb:class CloverWeb
routes/web/project/private_subnet.rb:class CloverWeb
routes/web/project/usage_alert.rb:class CloverWeb
routes/web/project/location/firewall.rb:class CloverWeb
routes/web/project/location/load_balancer.rb:class CloverWeb
routes/web/project/location/vm.rb:class CloverWeb
routes/web/project/location/private_subnet.rb:class CloverWeb
routes/web/project/location/postgres.rb:class CloverWeb
routes/web/project/postgres.rb:class CloverWeb
routes/web/project/location.rb:class CloverWeb
routes/web/account.rb:class CloverWeb
routes/web/webhook/github.rb:class CloverWeb

Reopen a lot, it's okay

Plugins that Ship with Roda

    Routing:
        autoload_hash_branches: Allows autoloading file for a hash branch when there is a request for that branch.
        autoload_named_routes: Allows autoloading file for a named route when there is a request for that route.
        all_verbs: Adds request routing methods for all http verbs.
        backtracking_array: Allows array matchers to backtrack if later matchers do not match.
        break: Supports break inside routing blocks for skipping the current matching route block as if it didn't match.
        class_level_routing: Adds class level routing methods, for a DSL similar to sinatra.
        error_handler: Adds ability to automatically handle errors raised by the application.
        hash_branches: Supports O(1) dispatching to multiple branches at all levels in the routing tree.
        hash_paths: Supports O(1) dispatching to multiple paths at all levels in the routing tree.
        hash_routes: Provides a DSL to configure the hash_branches and hash_paths plugins.
        head: Treat HEAD requests like GET requests with an empty response body.
        hmac_paths: Prevent path enumeration and support access control using HMACs in paths.
        hooks: Adds before/after hook methods.
        match_hook: Adds a hook method which is called when a path segment is matched.
        match_hook_args: Similar to match_hook plugin, but supports passing matchers and block args to hooks.
        multi_route: Allows dispatching to multiple named route blocks in a single call.
        multi_run: Adds the ability to dispatch to multiple rack applications based on the request path prefix.
        named_routes: Allows for multiple named route blocks that can be dispatched to inside the main route block.
        not_allowed: Adds support for automatically returning 405 Method Not Allowed responses.
        not_found: Adds not_found method for handling responses not otherwise handled by a route.
        pass: Adds pass method for skipping the current matching route block as if it didn't match.
        path_rewriter: Adds support for rewriting paths before routing.
        route_block_args: Controls which arguments are passed to the route block.
        run_append_slash: Makes r.run use "/" instead of "" for app's PATH_INFO
        run_handler: Allows for modifying rack response arrays when using r.run, and continuing routing for 404 responses.
        run_require_slash: Skip dispatching to another application if PATH_INFO for dispatch would violate Rack SPEC
        static_routing: Adds class level static routing methods, for maximum performance when handling static routes (routes without placeholders).
        status_handler: Adds status_handler method for handling responses without bodies for a given status code.
        type_routing: Route based on path extensions and Accept headers.
        unescape_path: Decodes URL-encoded PATH_INFO before routing.
    Rendering/View:
        additional_render_engines: Allows for considering multiple render engines, using path for first template that exists.
        additional_view_directories: Allows for checking for templates in multiple view directories, using path for first template that exists.
        assets: Adds support for rendering CSS/JS javascript assets on the fly in development, or compiling them into a single compressed file in production.
        assets_preloading: Adds support for generating browser-hinting preload link tags and headers.
        branch_locals: Adds ability to specify defaults for template locals on a per-branch basis.
        capture_erb: Allows capturing the output of ERB template blocks, instead of injecting them into the template output.
        chunked: Adds support for streaming template responses using Transfer-Encoding: chunked.
        content_for: Allows storage of content in one template and retrieval of that content in a different template.
        custom_block_results: Adds support for arbitrary objects as block results.
        erb_h: Adds faster (if slightly less safe method) h method for html escaping, based on erb/escape.
        exception_page: Shows page with debugging information for exceptions, designed for use in error handler in development mode.
        h: Adds h method for html escaping.
        hash_branch_view_subdir: Automatically appends a view subdirectory for each successful hash branch taken.
        inject_erb: Allows injecting arbitrary content directly into ERB template output.
        json: Allows match blocks to return arrays and hashes, using a json representation as the response body.
        link_to: Simplifies creation of HTML links.
        multi_public: Adds support for serving all files in multiple public directories.
        multi_view: Allows for easily setting up routing for rendering multiple views.
        named_templates: Adds the ability to create inline templates by name, instead of storing them in the file system.
        padrino_render: Makes render method that work similarly to Padrino's rendering, using a layout by default.
        partials: Adds partial method for rendering partials (templates prefixed with an underscore).
        precompile_templates: Adds support for precompiling templates, saving memory when using a forking webserver.
        public: Adds support for serving all files in the public directory.
        recheck_precompiled_assets: Allows checking for the precompiled assets metadata file for updates.
        render: Adds render method for rendering templates, using tilt.
        render_each: Render a template for each value in an enumerable.
        render_coverage: Sets compiled_path for Tilt templates, allowing coverage of compiled templates before Ruby 3.2 (requires tilt 2.1+).
        render_locals: Adds ability to specify defaults for template locals.
        static: Adds support for serving static files using Rack::Static.
        streaming: Adds ability to stream responses.
        symbol_views: Allows match blocks to return template name symbols, uses the template view as the response body.
        timestamp_public: Adds support for serving files in the public directory, with paths that change based on file modification time.
        view_options: Allows for setting view options on a per-request basis.
    Request/Response:
        assume_ssl: Makes request ssl? method always return true, for use with SSL-terminating reverse proxies that do not set appropriate headers.
        caching: Adds request and response methods related to http caching.
        content_security_policy: Allows setting an appropriate Content-Security-Policy header for the application/branch/action.
        cookie_flags: Adds checks for certain cookie flags, to update, warn, or error if they are not set correctly.
        cookies: Adds response methods for handling cookies.
        default_headers: Allows modifying the default headers for responses.
        default_status: Allows overriding the default status for responses.
        delegate: Adds class methods for creating instance methods that delegate to the request, response, or class.
        delete_empty_headers: Automatically delete response headers with empty values.
        disallow_file_uploads: Disallow multipart file uploads.
        drop_body: Automatically drops response body and Content-Type/Content-Length headers for response statuses indicating no body.
        halt: Augments request halt method for support for setting response status and/or response body.
        invalid_request_body: Allows for custom handling of invalid request bodies.
        module_include: Adds request_module and response_module class methods for adding modules/methods to request/response classes.
        permissions_policy: Allows setting an appropriate Permissions-Policy header for the application/branch/action.
        plain_hash_response_headers: Uses plain hashes for response headers on Rack 3, for much better performance.
        r: Adds r method for accessing the request, useful when r local variable is not in scope.
        redirect_http_to_https: Adds request method to redirect HTTP requests to the same location using HTTPS.
        request_aref: Adds configurable handling for [] and []= request methods.
        request_headers: Adds a headers method to the request object, for easier access to request headers.
        response_request: Gives response object access to request object.
        sinatra_helpers: Port of Sinatra::Helpers methods not covered by other plugins.
        status_303: Uses 303 as the default redirect status for non-GET requests by HTTP 1.1 clients.
        symbol_status: Allows the use of symbols as status codes, converting them to the appropriate integer.
        typecast_params: Allows for easily converting parameter values to explicit types.
        typecast_params_sized_integers: Allows for easily converting parameter values to integers for specific integer sizes (8-bit, 16-bit, 32-bit, and 64-bit).
    Matchers:
        class_matchers: Adds support for handling matchers for arbitrary classes, with support for type conversion.
        custom_matchers: Adds support for arbitrary objects as matchers.
        empty_root: Makes root matcher match empty string in addition to single slash.
        hash_matcher: Adds hash_matcher class method for easily defining hash matchers.
        header_matchers: Adds matchers using information from the request headers.
        Integer_matcher_max: Sets a maximum integer value that will be matched by the default Integer matcher.
        match_affix: Adds support for overriding default prefix/suffix used in match patterns.
        multibyte_string_matcher: Makes string matcher handle multibyte characters.
        param_matchers: Adds matchers using information from the request params.
        params_capturing: Stores matcher captures in the request params.
        path_matchers: Adds matchers using information from the request path.
        placeholder_string_matchers: Supports placeholders in strings for backwards compatibility.
        optimized_segment_matchers: Adds performance optimized matchers for single String class argument.
        optimized_string_matchers: Adds performance optimized matchers for single string arguments.
        slash_path_empty: Considers a path of "/" as an empty path when doing a terminal match.
        symbol_matchers: Adds support for symbol-specific matching regexps.
    Mail:
        error_email: Adds ability to easily email a notification when an error is raised by the application, using net/smtp.
        error_mail: Adds ability to easily email a notification when an error is raised by the application, using mail.
        mail_processor: Adds support for processing emails using the routing tree.
        mailer: Adds support for sending emails using the routing tree.
    Middleware:
        direct_call: Makes Roda.call skip the middleware stack, allowing more optimization when dispatching routes.
        middleware: Allows the Roda app to be used as middleware by another app.
        middleware_stack: Allows removing middleware and inserting middleware before the end of the stack.
    Other:
        common_logger: Adds support for logging in common log format.
        csrf: Older CSRF plugin for backwards compatibility using rack_csrf.
        environments: Adds support for handling different execution environments (development/test/production).
        early_hints: Adds support for using 103 Early Hints responses when using a compatible server.
        filter_common_logger: Adds support for skipping the logging of certain requests when using the common_logger plugin.
        flash: Adds flash handling.
        heartbeat: Adds support for heartbeats.
        host_authorization: Allows configuring an authorized host or an array of authorized hosts.
        indifferent_params: Adds params method for indifferent parameters.
        json_parser: Parses request bodies in JSON format.
        path: Adds support for named paths.
        relative_path: Adds support for turning absolute paths into paths relative to current request.
        route_csrf: Recommended CSRF plugin with request-specific tokens and control over where CSRF tokens are checked during routing.
        sessions: Implements support for encrypted sessions.
        shared_vars: Stores and retrives variables shared between multiple Roda apps.
        strip_path_prefix: Strips prefixes off internal absolute paths, making them relative paths.

Micro

Framework?

"Plugins"

Rodauth

A plugin for

All Things Authentication

Features Like

  • Webauthn
  • 2FA
  • Session Management
  • Password Recovery
  • Account Verification
  • Template overriding
  • ...quite a few others
  plugin :rodauth do
    enable :argon2, :change_login, :change_password, :close_account, :create_account,
      :lockout, :login, :logout, :remember, :reset_password,
      :disallow_password_reuse, :password_grace_period, :active_sessions,
      :verify_login_change, :change_password_notify, :confirm_password,
      :otp, :webauthn, :recovery_codes

    title_instance_variable :@page_title

    hmac_secret Config.clover_session_secret

    login_view { view "auth/login", "Login" }
    login_redirect { "/after-login" }
    login_return_to_requested_location? true
    login_label "Email Address"
    two_factor_auth_return_to_requested_location? true
    already_logged_in { redirect login_redirect }
    after_login { remember_login if request.params["remember-me"] == "on" }

    before_login do
      if Account[account_id].suspended_at
        flash["error"] = "Your account has been suspended. " \
          "If you believe there's a mistake, or if you need further assistance, " \
          "please reach out to our support team at support@ubicloud.com."

        redirect login_route
      end
    end

    [...]
    # Multifactor Auth
    two_factor_auth_view { view "auth/two_factor_auth", "Two-factor Authentication" }
    two_factor_auth_notice_flash { login_notice_flash }
    # don't show error message when redirected after login
    # :nocov:
    two_factor_need_authentication_error_flash { (flash["notice"] == login_notice_flash) ? nil : super() }
    # :nocov:

    # If the single multifactor auth method is setup, redirect to it
    before_two_factor_auth_route do
      redirect otp_auth_path if otp_exists? && !webauthn_setup?
      redirect webauthn_auth_path if webauthn_setup? && !otp_exists?
    end

    # OTP Setup
    otp_setup_route "account/multifactor/otp-setup"
    otp_setup_view { view "account/multifactor/otp_setup", "My Account" }
    otp_setup_link_text "Enable"
    otp_setup_button "Enable One-Time Password Authentication"
    otp_setup_notice_flash "One-time password authentication is now setup, please make note of your recovery codes"
    otp_setup_error_flash "Error setting up one-time password authentication"

    # Webauthn Setup
    webauthn_setup_route "account/multifactor/webauthn-setup"
    webauthn_setup_view { view "account/multifactor/webauthn_setup", "My Account" }
    webauthn_setup_link_text "Add"
    webauthn_setup_button "Setup Security Key"
    webauthn_setup_notice_flash "Security key is now setup, please make note of your recovery codes"
    webauthn_setup_error_flash "Error setting up security key"
    webauthn_key_insert_hash { |credential| super(credential).merge(name: request.params["name"]) }

    # Webauthn Auth
    webauthn_auth_view { view "auth/webauthn_auth", "Security Keys" }
    webauthn_auth_button "Authenticate Using Security Keys"
    webauthn_auth_link_text "Security Keys"

    # Recovery Codes
    recovery_codes_route "account/multifactor/recovery-codes"
    recovery_codes_view { view "account/multifactor/recovery_codes", "My Account" }
    recovery_codes_link_text "View"
    add_recovery_codes_view { view "account/multifactor/recovery_codes", "My Account" }
    auto_add_recovery_codes? true
    auto_remove_recovery_codes? true
    recovery_auth_view { view "auth/recovery_auth", "Recovery Codes" }
  end

200 Lines of this for a complex application...

A Rare Engineer

Jeremy Evans

Maintains all of:

Not only an acknowledgement.

The three programs are cohesive.

  Roda: request routing

  Rodauth: authentication

  Sequel: database access

Jeremy's Humility

Blink and you'll miss something important in his talks.

...it gives me a sense of accomplishment to be able to fix bugs in Ruby that have been known but unfixed for many years. That's a situation that doesn't happen in my other open source projects.

—From one tiny corner of an interview

Fact Check: TRUE

Monthly(?!) Releases

Check out

roda-sequel-stack

separately, Rodauth

Rare Testing

100%* Branch Coverage

Dynamic Language is Key

Can do it without major program distortions

Reasons

  • Avoid "leaking" found bugs
  • Easy to inspect uncovered code

:nocov: okay

    # Negative modulus is a handy trick to fill out pads like this.
    padding = (nested_private_key.length % -8).abs
    nested_private_key.write("12345678".slice(0, padding))
    # :nocov:
    fail "BUG: padding broken" unless nested_private_key.length % 8 == 0
    # :nocov

Better to have some files at 100% than every file at 95%.

Conclusion

Rare Ruby Reasons

By fdrfdr

Rare Ruby Reasons

  • 281