Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

Ticket #8469 (new enhancement)

Opened 1 year ago

Last modified 3 months ago

[PATCH] make generated url helpers infer missing segments

Reported by: protocool Assigned to: core
Priority: normal Milestone: 2.x
Component: ActionPack Version: edge
Severity: normal Keywords: nested resources routing
Cc:

Description

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:

  1. Positional Arguments (right-anchored)
  2. Hash options (last argument to helper)
  3. @param instance variable
  4. @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.

Attachments

inferencing_url_helpers.diff (12.9 kB) - added by protocool on 05/25/07 19:44:43.
Code and tests - no documentation

Change History

05/25/07 19:44:43 changed by protocool

  • attachment inferencing_url_helpers.diff added.

Code and tests - no documentation

05/25/07 20:51:15 changed by bitsweat

I don't set instance variables for the parent objects, so this wouldn't work for me. What conventions are others using?

05/25/07 23:02:36 changed by bitsweat

  • keywords set to nested resources routing.

05/25/07 23:39:55 changed by bitsweat

06/07/07 18:01:23 changed by bitsweat

The explicit alternative is to pass an array of [parent, child]. See #6432 which is in trunk now.

06/07/07 19:28:53 changed by protocool

Bitsweat I have no idea what you mean by "explicit alternative" or by quoting #6432.

The explicit alternative to song_path(@song) would be song_path(@artist, @album, @song).

As for what's happening around #6432, I won't bother repeating here what I've already said

06/07/07 21:28:47 changed by bitsweat

Right, #6432 is for polymorphic url_for, link_to, and url_for. Sorry to bother you.

07/04/08 12:09:25 changed by turnip

It would be great to be able to infer nested routes but I think the approach of the "inferred routes" plugin is more natural and obvious.

A problem with the resource_fu way would be if you are iterating through a list of objects - you would have to set up the necessary instance variables on each iteration.