Skip to content

some questions to get us started #3

@ericgj

Description

@ericgj

\1. The most striking thing about Cuba coming to it from Rails or Sinatra is the 'finite state machine' routing. The Readme does a good job explaining this, but even so, I find it sometimes hard to get my head around concrete examples. Maybe someone would like to walk us through the cuba-app example's routes from the top? This gets somewhat into Shield as well (the authentication library).

  on root do
    res.write view("home", title: "My Site Home")
  end

  on authenticated(User) do
    run Users
  end

  on "admin" do
    run Admins
  end

  on default do
    run Guests
  end

\2. Another starting point is the config.ru. In the cuba-app example, this consists of basically one line after loading the app: run Cuba . Interesting to consider how this differs from Sinatra, where you would subclass Sinatra::Base and run that. Closely related to this is that routes are defined within a Cuba.define block, rather than directly on the class. And the individual routes are not compiled when defined, and then matched to the request, but evaluated upon each request. Can someone walk us through Cuba.define?

    def self.define(&block)
      app.run new(&block)
    end

\3. Yet another starting point is to consider when a request comes in, how it is matched to a route, and how captures are extracted. The Readme gives a good explanation of how this works, but it's a bit tricky to work out how it's implemented with nested calls to on, and the catch(:halt) :

    def call!(env)
      @env = env
      @req = Rack::Request.new(env)
      @res = Cuba::Response.new

      # This `catch` statement will either receive a
      # rack response tuple via a `halt`, or will
      # fall back to issuing a 404.
      #
      # When it `catch`es a throw, the return value
      # of this whole `_call` method will be the
      # rack response tuple, which is exactly what we want.
      catch(:halt) do
        instance_eval(&@blk)

        res.status = 404
        res.finish
      end
    end

    # The heart of the path / verb / any condition matching.
    #
    # @example
    #
    #   on get do
    #     res.write "GET"
    #   end
    #
    #   on get, "signup" do
    #     res.write "Signup
    #   end
    #
    #   on "user/:id" do |uid|
    #     res.write "User: #{uid}"
    #   end
    #
    #   on "styles", extension("css") do |file|
    #     res.write render("styles/#{file}.sass")
    #   end
    #
    def on(*args, &block)
      try do
        # For every block, we make sure to reset captures so that
        # nesting matchers won't mess with each other's captures.
        @captures = []

        # We stop evaluation of this entire matcher unless
        # each and every `arg` defined for this matcher evaluates
        # to a non-false value.
        #
        # Short circuit examples:
        #    on true, false do
        #
        #    # PATH_INFO=/user
        #    on true, "signup"
        return unless args.all? { |arg| match(arg) }

        # The captures we yield here were generated and assembled
        # by evaluating each of the `arg`s above. Most of these
        # are carried out by #consume.
        yield(*captures)

        halt res.finish
      end
    end

\4. Another interesting feature of Cuba compared to Sinatra is it allows you to switch to another handler entirely from within a route, while preserving the current env. (At least, I don't know a straightforward way to do this in Sinatra.) In this way it functions somewhat like Rack::URLMap (as can be seen in the top-level routes in the cuba-app example). Also, the inline example in the code shows how to use this to implement a redirect helper. Anyone care to walk us through this?

    # If you want to halt the processing of an existing handler
    # and continue it via a different handler.
    #
    # @example
    #   def redirect(*args)
    #     run Cuba.new { on(default) { res.redirect(*args) }}
    #   end
    #
    #   on "account" do
    #     redirect "/login" unless session["uid"]
    #
    #     res.write "Super secure account info."
    #   end
    def run(app)
      halt app.call(req.env)
    end

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions