This patch (with comprehensive tests) is an enhancement to generated url helpers. The code has been lifted from my ResourceFu plugin and has received quite a bit of exercise in production systems.
I have *not* included any documentation changes with this patch because a) I couldn't spot an obvious place to put it and b) I didn't want to waste effort if the patch's changes are not welcome in the first place. I'll add a documentation patch to this if someone in core comments that they'll apply.
Here's what it's all about. Assuming this routes.rb setup:
map.resources :artists do |artist|
artist.resources :albums, :name_prefix => nil do |album|
album.resources :songs, :name_prefix => nil
end
end
The standard url helpers generated for 'albums' require that you always specify the artist_id:
album_path(@artist, @album)
And, if you want to have additional parameters (such as a :page number when paginating) you can't use the positional arguments form (above) when calling the url helper, you have to use a hash for everything:
album_path(:artist_id => @artist, :id => @album, :page => 2)
Also, the standard resource helpers fill in the route by processing your positional arguments from left-to-right.
album_path(@album) == album_path(:artist_id => @album) # ACK!
This patch changes the way generated helpers behave so that they try to 'infer' any parameters which are required by the route, but not supplied in arguments.
The generated helpers also 'anchor' any positional arguments on the *right* of the route, which means that if your route has three segments and you only supply two positional arguments, the url helper assumes you are supplying the *last* two segments in the route.
Using the above example of artists, albums and songs:
song_path(@album, @song) == song_path(:album_id => @album, :id => @song)
However, the route for song_path is still missing a critical piece of information, namely the artist_id.
The inferencing helpers try to 'guess' that value with a simple convention:
Any route segments that are missing are filled in by looking for instance variables with a relevant name. So in the example:
song_path(@album, @song)
The helper will try to set the :artist_id param by first looking for an instance variable called @artist and then falling back to looking for an instance variable called @artist_id.
In album_controller.rb:
def show
@artist_id = params[:artist_id] # or fetch the actual Artist if you want
@album = Album.find(params[:id], :conditions => {:artist_id => @artist_id})
@album.songs.each do |song|
song_path(song) #=> /artists/@artist_id/albums/@album.id/songs/song.id
end
end
Finally, if you want additional, non-route-segment params, you *don't* have to completely switch to the hash-argument style for route helpers:
song_path(@artist, @album, @song, :page => 2, :per_page => 20)
So, to summarize, the order of precedence for 'filling-in' the parameters by a generated url helper are:
- Positional Arguments (right-anchored)
- Hash options (last argument to helper)
- @param instance variable
- @param_id instance variable
The inferencing_helpers_controller.rb file (used by inferencing_helpers_test.rb) is pretty chatty with comments so it should be pretty easy to understand how things are expected to work.