My first 'acts_as' plugin 7
So, you’ve upgraded to Rails 1.2.1 and you’re working on a tool to maintain a database of all the tunes you have in your various songbooks and (eventually) your record collection. You start with:
$ ./script/generate rspec_resource MusicBook title:string author_id:integer \ abstract:text $ ./script/generate rspec_resource Tune title:string composer_id:integer \ abc:text book_id:integerYou decide to come back to composers and authors later, so you set up your models1:
MusicBook.has_many :tunes Tune.belongs_to :music_bookAnd your routes:
map.resource :music_books do |book| book.resource :tunes endProblems start here
Being a cautious sort, before you start adding behaviour, you fire up a development server and go and check things with the browser. The /music_books/ stuff works fine, but once you start looking at /music_books/1/tunes things start to get weird; all of a sudden your links aren’t making sense.
The problem is, that your scaffolding is calling named routes with something like: edit_tune_url @tune, when, because of the nesting of your resources, they should really be calling edit_tune_url @tune.book, @tune. But how to fix things?
Well, you could go through all your controllers and views, replacing all the calls to named_routes with the right version. But, if you’re anything like me, you’ll get bored stiff of the repetition after you’ve fixed up the first file. And that’s before you start fixing up your controllers to do
@tune = Book.find(params[:book_id]).tunes.find(params[:id])So, I’m going to suggest that a better bet is to install the acts_as_resource plugin I’m in the process of writing.
Making the model pull its weight
Once you’ve got acts_as_resource installed, you can just do:
And your models will magically acquire a resource_chain which returns exactly the list of objects that your named routes need. I’m currently investigating the innards of named routes, but the plan is that you’ll never have to call resource_chain yourself, the named_route helper will do it for you and use the resulting list to build the url.
Your model class, meanwhile, gets a handy
Tune.find_resource(params)which will find your resource, verifying that it can be reached through the chain of resources specified in your params hash, which means we can fix up the scaffolding generator to use find_resource (or, more likely, a helper method that will set appropriately named instance variables in the controller).
If I understand what simply_helpful is up to, the named_routes hack should play well with that too, which means that form_for @tune will get its url right without you having to remember to call form_for @tune.book, @tune.
Release date?
I’m not quite ready to release yet, I’m busy wrapping my head around how named routes work so I can fix ’em up to use resource_chain. find_resource is written though.
It’s amazing how much leverage you can get, simply by adding one class attribute and a support method to your model…
Expect a release some time next week. However, if you’re desperate for a look, drop me a line and I’ll send you my local snapshot.
1 Look, vertical space is precious. Pretend this is in the usual blocky type stuff you usually find in the files you’ll find in app/models/whatever.rb

Ah, I’m taking a slightly different tack — modifying the existing generator to spit out the right code in the first place. Instead of putting in:
@tune = Book.find(params[:book_id]).tunes.find(params[:id])everywhere, the generator spits out a
before_filter :load_bookbefore_filter:which does something along the lines of:
private def before_filter load_book @book = Book.find(params[:book_id], :include => :tunes) endThat way the controllers now look like:
@tune = @book.tunes.find(params[:id])which is a bit more familiar. Hmm, just thinking out loud as I type, but I wonder if I could use an
around_filterwith awith_scopeto make the controllers work unchanged?resource_chainlooks like a really good idea. I’ve been spending far too much time digging through the routing recently, so I might be able to make some suggestions, if you haven’t figured it out already.My affection for the
resource_chainapproach is that the various named controllers get used in magic fashion all over the place, so if you can fix ‘em up to take a single object as an argument, there’s a huge win.Also, making generators do the ‘right thing’ becomes enormously tricky once you start considering routes like:
map.resources :books do |book| book.resources :annotations book.resources :tunes |tune| tune.resources :annotations end endRight now, that route declaration is going to fall over in a heap trying to make
annotation_*routes, but if your models have aresource_chainmechanism, routing can just check to see if the named route already exists and carry on regardless. The same controller and views will work whether your annotation is on a book or a tune.Once that’s working, there’s the possibility of taking skinny controller, fat model to extremes and have your resource controllers looking like:
Which abstracts the boilerplate away, which would be no bad thing.
But, this comment is a little narrow for this stuff. Expect another post looking at that.
How far can ttis parent-child relationship be extended?
Grandparent.has_many :parents
Parent.has_many :children
Child.has_many :grandchildren
Grandchild.has_many :greatgrandchildren
And what about multiple decendants as in lists?
The ‘child’ has an ‘uncle’ which is another parent of the same grandparent.
Then there’s ‘cousins’, the children of an ‘uncle’.
The ‘child’ could also have ‘schoolfriend’ .
Let me guess. You’d do this as a polymorphic “Person”. OK, I can accept that, but there are going to be situations where the takes do have these kinds of relationships but aren’t the single table.
Libraries,
recordings
books
articles that refer to articles in other books
or to recodings
How general is this model?
I think you may be missing the point.
I have finally incorporated the acts_as_resource plugin into one of the apps I’ve been working on and it’s cleaned things up nicely. Thanks! On first reading of the source, I missed that it does clean up the URL helpers because I assumed there wouldn’t be any ‘real’ code in
init.rband couldn’t figure out how it could possibly work, but once I got past that…One issue that cropped up with the URL helpers. We use STI for
Authors < Users, both of which are exposed as resources. I’m not convinced this is the right way to do it, because I’m generally wary of STI — I’d rather anAuthor has_one :login— but I digress. At one point we’re callingtoggle_activation_user_path(author)and of course theresource_chainis set up for that of an author (who’s parent is a content_provider) rather than a user (who has no parent).Now that I’m writing this out, it strikes me that the patch I’m about to point to is not a general solution — it’ll only work if the user resource has no parent or is a sibling of the author’s ancestors. Umm, I think.
Anyway, all it does is to reverse the resource chain and segment keys before zipping them, so that the last one on the chain gets :id, and ignores nil keys. You can find it here: http://woss.name/dist/fix_for_sti_resources.diff
Yeah, reading over my comment again in preview, that’s definitely a bad smell in the design, really, isn’t it?
Is there any reason you’re pushing the collection on to
resource_chaininfetch_resources? It appears to break things later on…Um… d’you know, I’m not entirely sure why.
I’m just writing acts_as_resource up for a presentation and it’s got me worrying about how things work. Mostly to do with the routing, but that’s more that I’m not entirely keen on how routing works anyway…