This post originated from an RSS feed registered with Ruby Buzz
by Michael Neumann.
Original Post: Comparing the underlying models of Wee and Rails
Feed Title: Mike's Weblog
Feed URL: http://www.ntecs.de/blog-old/index.rss?cat=ruby&count=7
Feed Description: Blogging about Ruby and other interesting stuff.
I’m trying to marry my child Wee with some of Rails features, mainly
REST-like, pretty URLs. But before, I have to understand the differencies
in how they work.
Disclaimer: This is my understanding of how Rails work. I might be
wrong.
Rails
Rails model is very simple compared to that of Wee. The controller
class and the method to invoke is extracted directly from the URL. For
example:
/blog/show
would invoke BlogController#show. Now imaging the following URL:
/blog/show/4
This would invoke BlogController#show with @params = {‘id’
=> 4}. I don’t know how Rails knows that 4 maps to the
‘id’ key, but that’s another story.
So you have these parts of a URL:
/controller/action/arguments
In Rails, there’s also no (conceptual) distinction between performing
an action and rendering, as is the case in Wee. Both are inseparable from
each other (in Rails).
Another difference to Wee is, that the controller classes are stateless,
and instances of it might be reused (pooling). On the other hand this
implicates that you can’t store information inside a controller
instance across requests.
Wee
In Wee, we differenciate between an action phase, where callbacks
are invoked and as such possibly modify the state of components, and a
render phase, which ideally is side-effect free and whose sole
purpose is to render the component to HTML (or whatever format you like).
Imagine you look at a simple Wee application which displays an HTML anchor
tag. If you click on that anchor, you’ll trigger an action
phase, which eventually will find the registered callback associated
with the anchor you have clicked on and invokes it. At the end of the
action phase, Wee will forward you to a new (automatically
generated) URL, which when requested by your browser, will trigger the
render phase. This in turn renders the whole component tree.
"Why redirect", you might ask. Well, this simply avoids that the
same callback will be invoked again if you hit your browsers reload button.
Whenever you hit on reload, this will only trigger a render phase
event.
Additionally, at the end of each action phase, a snapshot of the
component tree is taken, so that you can go back to older states (called
back-tracking). The information about which state of the component tree we
refer to, is stored as page_id inside the URL. This
page_id increases whenever an action phase is performed.
So, an URL in Wee basically consists of the following parts:
session_id page_id [callback_id]
where callback_id is optional. If it is given, the URL triggers an
action phase. Otherwise, a render phase.
Compared to Rails controllers, Wee’s components are composites,
meaning that they may contain sub-components which itself might contain
sub-components and so on. And in Wee there’s only ever one root
component, whereas in Rails there are usually multiple controllers. All
this makes it nearly impossible to have REST-like URLs in Wee. Also due to
the reason that a sub-component cannot be rendered on it’s own,
whereas a controller in Rails builds a whole page. So it does not make
sense to have URLs in Wee like:
The concept of multiple top-level controllers can be easily added onto Wee
by using the following RootComponent class:
# NOTE: not fully functional code!
class RootComponent < Wee::Component
def initialize
super()
@controllers = {
'blog' => add_child(BlogComponent.new),
'list' => add_child(ListComponent.new)
}
end
def render
controller = # extract information from URL
r.render @controllers[controller]
end
end
This RootComponent merely acts as a dispatcher. It looks at the URL and
extracts the desired controller out of it and then forwards to it.
Likewise, we could map the Rails action part (the "show" in
"/blog/show"), to invoke render_xxx (e.g. render_show) of the
controller-component (I use this term now, to distinguish it from a
"regular" Wee component and to mark the similarity with a Rails
controller):
def render
controller = # extract information from URL
action = # extract Rails action part from URL
component = @controllers[controller]
component.with_renderer {
component.send("render_" + action)
}
end
And even further, we could also extract the additional arguments of the URL
for use inside the called render method. We could use a custom
parse_arguments method here in each controller-component.
Now lets look at a simple example:
class BlogController < Wee::Component
def parse_arguments(str)
# for /blog/show/5, str would be "5"
@params = ... # e.g. {'id' => 5}
end
def render_show
entry = BlogEntry.get(@params['id'])
# render it
end
def render_list
BlogEntry.find_all do |entry|
# render it
end
end
end
What else would be needed is to tell which controller/action pair should be
used. This could for example be specified each time when generating an
anchor or form tag:
Note that this would first invoke the callback and then render
‘blog/show/5’. Of course you could ommit in this case the
callback. But specifying the controller/action pair each time is tedious,
as this would have to be done in each sub-component, too, which completely
breaks the concept of a component. So the second approach is much
better:
r.anchor.callback {
... do something
request.controller = 'blog/show/5'
}
where the request.controller setting is carried inside the URL as long as
you assign a new value to it.
Note that you can still use sub-components with this approach, but they
would no longer be subject of REST-like URLs.