PragDave nails it. Again. 4
REST is easy, it’s just smart clients using HTTP to the full.
Except, as Dave ‘PragDave’ Thomas points out, browsers aren’t smart. They’re just dumb terminals with prettier graphics.
And what does that mean? It means that a typical Rails controller is trying to multiplex two protocols in the same chunk of code. There’s the pure four and then there’s the others: ;new and ;edit, offering up forms for clients too dumb to know how to build their own; and RJS helpers like ;preview, ;autocomplete and whatever else you need to do server side to make the Browser look smart.
But when you have a class that’s trying to be two things at once, you really have two classes, you just don’t know it yet.
So, Dave proposes splitting controllers; write a RESTful controller that only has to concern itself with serving up resources that smart clients can reuse and remix as appropriate. Then, you a server side smart client that decorates your resources with all the scaffolding that a browser needs.
Dave calls this RADAR: RESTful Application talking to Dumb Ass Recipient.
My first reaction is that Dave’s definitely onto something. If nothing else it deals with one obvious wart involving authentication and REST. The problem is, a good RESTful application doesn’t roll its authentication, just uses HTTP Digest authentication or SSL or whatever. With a smart client that’s fine; smart clients know how to log off as well as on. Browsers don’t. Essentially, once you’ve logged on using HTTP authentication, you’re authenticated until you restart the browser, which is not necessarily what you need a lot of the time. So most applications use cookies for authentication because the application has rather more control over a cookie. But that’s not really very RESTful because the connection is now stateful. Disaster!
However, if you decompose your application along RADAR lines, all the conversation with the REST kernel continues on its stateless way, but the combination of your browser presentation layer and the browser share a bit of state through cookies (and possibly a session) in order to do a better imitation of a smart client.
A thought…
Hmm… suddenly the Zimki approach of writing everything in JavaScript is looking more attractive. If you write the presentation layer in JavaScript there’s nothing to stop it residing either on the server while talking to browsers with JavaScript turned off and running directly on the browser (entirely or partially as the case may be) and talking JSON to the REST kernel.
Or you could pull out all the stops and have a Seaside style front end, doing all that shiny continuation based, rich session magic that makes DabbleDB such an amazing bit of software; after all, Avi’s been busy showing people how to take Seaside’s ideas an implement ‘em in Ruby (or JavaScript…).
I wonder if Rails is malleable enough to get a proof of concept of this sort of thing up quickly?
Initial release of acts_as_resource 18
Right. I’ve bundled acts_as_resource up and stuck it on the typosphere SVN server. You can grab it from http://svn.typosphere.org/typo/plugins/acts_as_resource if you’re interested.
It’s currently in what I’d call an all convention, no configuration state – if your resources don’t look pretty similar to the kind of things you get from the resource scaffolding, you’ll probably have some pain, but I expect to rectify that with coming releases. One thing I want/need to do for instance is to allow for ‘relative’ ids in your resource url. For instance, if you’re looking at /albums/10/tracks/982, it’s not the most readable of permalinks… next trick is to allow you to have urls like /albums/because-its-there/tracks/1, ie: the first track on the album ‘Because it’s There’. I’m sort of expecting that you’d do that by doing:
class Album
has_many :tracks
acts_as_resource :uri_field => :name_dasherized
end
class Track
belongs_to :album
acts_as_list
acts_as_resource :uri_field => :position, :parent => :album
end
However, my first priority is to add some tests (or, more likely,Rspec specifications) so I’ve got some confidence that I’m not breaking things as I go.
Anyhow, go grab the plugin, have a play, let me know what you think.
'acts_as_resource' progress
I’m very nearly ready to release acts_as_resource, I just have to pull up and tidy code that’s currently in my working directory’s ApplicationController and we’re laughing. However, I thought you’d like to see what my nested controller looks like.
class ChildrenController < ApplicationController before_filter :fetch_resources
def index end end
def show
end
def edit
end
def create
@child = @children.build(params[:child])
if @child.save
flash[:notice] = 'Child was successfully created.'
redirect_to child_path(@child)
end
end
def update
if @child.update_attributes(params[:child])
redirect_to child_path(@child)
else
render :action => 'edit'
end
end
def destroy
@child.destroy
redirect_to children_path
end
I’ve removed the XML responses to save vertical space, but they work pretty straightforwardly.
Note that fetch_resources is absolutely generic. It is unscaffolded, uses the resourceful conventions and extra information provided by each model’s acts_as_resource declations to work out what instance variables should be called and sets them appropriately.
With a little work to set up a generically named variable as well as the conventionally named ones (say, resource_chain) and a wee bit of simply_helpful pixie dust (polymorphic paths), it should be possible to write a ResourceController class that does all the work for you but allows you to alter behaviour without having to rewrite an entire action method. But that’s definitely for the next release of the plugin.
Tips for data smugglers
While I was working on the acts_as_resource plugin trying to fix things up so that the resource finding side of things works neatly, I realised that I needed some way to get at the ordered list of parameter keys that were matched by the routing system.
One way to do it would have been to parse the path again, but that smacked a little too much of repetition, after all, the routing system knows this stuff already, but how to get at it?
What I needed was the route object that matches the current path.
I ended up modifying ActionController::Routing::Route::parameter_shell. In case you aren’t familiar with the innards of Rails’ routing system, parameter_shell is the ‘skeleton’ params hash that the route populates with information from the current url. So, if you have a route like:
map.connect 'some/paths/are/from/:planet', \
:controller => 'planets', :action => 'index'
then the generated route’s parameter shell would look like:
{:controller => 'planets', :action => 'index'}
I needed to stuff the route into that hash, but what key should I use? My first thought was to use :matched_route, but what would happen if some future rails application decided that it needed the :matched_route for something it wanted to do?
So, then I thought of adding a ‘ghost’ key to the hash:
matched_route = self
class << @parameter_shell
def matched_route
matched_route
end
end
That would have worked by hanging a method off the side of the parameter hash which, through the magic of closures would return the matching route. At least, I think so (I’m still not entirely sure when Ruby closes over things). Before I started to test it, I remembered that the first thing the recognize method does when it has a successful match is dup the parameter shell, and singleton class magic doesn’t survive dup.
Then, I remembered something lovely about the way Ruby’s hashes work; keys can be arbitrary objects! All I needed to do was to use a key that was neither a string nor a symbol to smuggle the data through, and there would be no way that it could clash with some future application. So, my current implementation of parameter_shell_with_matched_route looks something like:
def parameter_shell_with_matched_route
@parameter_shell ||
returning(parameter_shell_without_matched_route) do |params|
params[ [:matched_route] ] = self
end
end
alias_method_chain :parameter_shell, :matched_route
It turns out that various of the Hash support methods in Rails don’t play that well with guarded hash keys like that, so I’ve adapted ActionController::AbstractRequest to pull the matched route out of the parameter hash at the earliest opportunity and stash it in a new method. Here’s the code that does that:
attr_accessor :matched_route
def path_parameters_with_matched_route=(parameters)
if route = parameters.delete([:matched_route])
self.matched_route = route
end
self.path_parameters_without_matched_route = parameters
end
alias_method_chain :path_parameters=, :matched_route
And... relax...
Rails 1.1 got released on Sunday.
Lots of hosting services upgraded.
Typo, both the ‘stable but old’ and the bleading edge versions, didn’t get on with Rails 1.1. For quite spectacular values of ‘not getting on’. So, we ran around like headless chickens for a bit, gradually refining the incantations needed to get your existing typo installation working again – rake freeze_edge VERSION=3303 does the trick if you’re still having problems – and then Scott, Kevin and I spent the best part of a day nailing down the issues.
It turns out that there was rather a lot of ‘cargo cult’ code in older versions of Typo. Cargo cult code is code that isn’t there because it works, but because it reassures the programmer into thinking it will work. Rails 1.0 was far more forgiving of our cargo cult (excessive use of require_dependency, an undocumented method that doesn’t quite do what we thought it did) than 1.1.
So, with that done, and a few more bugs squashed and we’re almost ready for a release (future distributions will come with rails baked into the tarball – but not if you get it from the repository), both of 2.6.0 with a frozen rails distribution and of the forthcoming 4.0.0. As I write their are two issues outstanding on the 4.0.0 milestone, the trickiest of which is Scott’s plan to make typo installation substantially less user hostile. However, if you’re happy to deal with the slightly tricksy installation, there’s never really been a better time to jump onto the Trunk. Grab a copy and start blogging. You know you want to.
Bwah hah ha ha!
This evening, I acquired a commit bit for the Typo project. I’ve been chucking the occasional big patch and ideas at Scott Laird, the most active of the current maintainers, but he’s been busy doing all sorts of stuff, and he just started working at Google (go Scott!) so he doesn’t have quite as much copious free time as before.
So, this evening I asked for, and received, my very own commit bit. Which means I can make changes to Typo without needing to funnel them through a maintainer—I’m now part of the funnel.
Of course, responsible programming means I’ll still run big changes by Scott and the other maintainers, but it’s also useful to be able to make the kind of small changes that sometimes need to be made without it blocking on a busy man.
Wish me luck.
Thinking aloud about Typo
Unless you’re interested in the internals of the Typo blogging engine and a possible rejigging of it, don’t bother reading the rest of this.
Typo Content Models
I’ve been attempting to follow the DRY Principle and rejigging Typo so that there’s fewer places with similar code to handle the cached html associated with a content object. Also, I’ve been thinking of how to build a more intelligent cache sweeper—at the moment, if you change a single article, every page in the cache is removed, which isn’t ideal. To that end I wrote a ContentModel mixin that adds a new class method:
class Article < ActiveRecord::Base
include ContentModel
content_fields :body, :extended
...Declaring a content_field sets up a setter method that would notify any object observers that the content has changed and reset the associated _html attribute.
Which is all very well, but I think the time might have come to make a Content superclass. The various typo content models already have quite a few fields in common, and even Article, the ‘biggest’ of them doesn’t have too many unique fields, so using Single Table Inheritance a completely insane idea.
What does that buy us?
- Conceptually, it buys us a neater model, albeit at the expense of a slightly scruffier table structure (Rails can’t do multi-table inheritance yet).
- A unified ID scheme for our content models means that any putative table mapping content objects to currently cached files is going to be substantially simpler.
- Threading. Add
parent_idcolumn to the table and, because of the uniform ID structure, a comment gets to have a parent that is either an Article, another Comment or (possibly) a Trackback.
How could it hurt?
- The database table’s a bit scruffier.
- Writing the migrations could be fun.
- We’d have to make sure that the old
articles/read/idURLs continued to work—Adding anold_idcolumn is one way forward, but it strikes me that a better way to do it would be to extend thearticlestable until it had all the fields required then rename it appropriately and then copy all the other content objects into it.
Conclusions
Well, I think it’s worth doing. But it’s my idea, so I would say that wouldn’t I. If you’re a Typo person and you’re reading this, what do you think?
