Open ericgj opened 12 years ago
I'll take a stab briefly:
1) We pretty much design with the mindset: "first to appear, gets the call". In that example you pasted, it goes like:
Users
.Admins
.Then much like an else in a case or if-elseif chain, we have an on default which gets run if no other handler gets called.
2) The example you mentioned provides the most straightforward approach, where your app root runs on Cuba
, and then it delegates certain responsibilities to subclasses of Cuba
(i.e. Users
, Admins
, Guests
).
But being a subclass of Cuba, you could quite literally run anything else, e.g.
class App < Cuba
define do
on root do
res.write 'Home'
end
end
end
and in your config.ru
,
require File.expand_path("app", File.dirname(__FILE__))
run App
I guess it boils down to this: Cuba
and any subclass of it is a Rack app, and can be expected to behave properly when you do Cuba.call(env)
, App.call(env)
, etc.
3) Nesting an on
is actually ~ almost ~ identical to a Rack::URLMap in the sense that both share the implementations. It's no secret that Cuba was originally based and inspired from Rum.
So what Cuba (and Rum, and Rack::URLMap) does is it mutates PATH_INFO
and SCRIPT_NAME
. I discuss this in detail in one of my articles.
The catch
- instance_eval
combo, we admit, does a bit of trickery (well they say catch
is a poor man's GOTO).
res = catch :foo do
throw :foo, "bar"
end
assert_equal "bar", res
So in Cuba context, that would be:
response = catch(:halt) do
throw :halt, [200, { "Content-Type" => "text/html" }, ["Hello world"]]
end
Anytime an on
executes completely, it does a throw :halt
essentially, and sends the rack response tuple.
IF on the event we don't match anything, that's where these lines get executed:
res.status = 404
res.finish
Which results to our app returning a 404.
4) The answer to this is tied closely to what I said in number 3 above with PATH_INFO
and SCRIPT_NAME
being mutated. At the point that do you the delegation, both are still in their mutated states, i.e.
# PATH_INFO = /users/1
# SCRIPT_NAME = ""
on "users/:id" do |id|
# PATH_INFO = ""
# SCRIPT_NAME = "/users/1"
SubApp.run(req.env) # gets called with PATH_INFO = "", SCRIPT_NAME = "/users/1"
end
Anyway hope these answers shed more light on the questions you posted above.
Thanks @cyx. I had meant these questions to spur others to delve into the code, but I'm glad to hear some of your thinking about the design "from the inside". And it looks like @robodisco has started a code dive now.
I hadn't quite grasped how the routing works re. the mutation of PATH_INFO
and SCRIPT_NAME
, that's really a key to the whole implementation.
BTW I think Cuba is a very nice framework. So much leverage in so few lines of code! You are very careful about state, which is refreshing. And it's nicely documented too.
I have a whole set of questions as I start to use it on a real-ish project, is this the best place to ask or is there a mailing list/user group I should be looking at for 'best practices' first ?
Hi Eric,
We typically hang out in #cuba.rb on freenode, we can probably give much better advice if we have more context regarding what the problem domain is, and so on.
Thanks, cyx
On Jul 26, 2012, at 9:14 PM, Eric Gjertsen wrote:
Thanks @cyx. I had meant these questions to spur others to delve into the code, but I'm glad to hear some of your thinking about the design "from the inside". And it looks like @robodisco has started a code dive now.
I hadn't quite grasped how the routing works re. the mutation of
PATH_INFO
andSCRIPT_NAME
, that's really a key to the whole implementation.BTW I think Cuba is a very nice framework. So much leverage in so few lines of code! You are very careful about state, which is refreshing. And it's nicely documented too.
I have a whole set of questions as I start to use it on a real-ish project, is this the best place to ask or is there a mailing list/user group I should be looking at for 'best practices' first ?
Reply to this email directly or view it on GitHub: https://github.com/codereading/cuba/issues/3#issuecomment-7277134
\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).\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 subclassSinatra::Base
and run that. Closely related to this is that routes are defined within aCuba.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 throughCuba.define
?\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 thecatch(:halt)
:\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 thecuba-app
example). Also, the inline example in the code shows how to use this to implement aredirect
helper. Anyone care to walk us through this?