Polymorphic resources enhancement review request

http://dev.rubyonrails.org/ticket/11031

This patch adds a new option to map.resources named :polymorphic,
which will attach a :type requirement to the generated route. It keeps
URLs absolutely clean.

Simple example:

map.resources :posts, :polymorphic => [:events, :articles] do |posts|
  posts.resources :assets, :polymorphic => [:images, :movies]
end

edit_post_asset_path(:post_type => 'events', :post_id => 1, :type =>
'movies', :id => 2)

=> "/events/1/movies/2/edit"

Updated, applies to edge. Still need a review.

Wildchild,
Your patch takes an approach that differs from the one taken by the
ResourcesController plugin. I like the RESULT of your patch (:*_type
key in the params) but, as shown by the RC plugin, I'm not sure the
specification with :polymorphic => <array> is required or wise. Right
now, there is a lot of support for organizing the abstract resources
defined by routes.rb in a nested structure. Your proposal may limit
the effectiveness of that approach.

For example, if I had another resource type with assets that was more
deeply embedded than (events|articles) -say /forums/3/posts/2/assets/
1, I would not be able to cleanly spec the nested, polymorphic asset
with your synta (or alternatively, I would end up with :polymorphic
nearly everywhere). In a nutshell, your syntax gets hairy when a
specific polymorphic resource appears at several different depths.
Couple that with the fact that such syntax IS NOT REALLY REQUIRED
ANYWAY in the classic use case of identifying concrete resources in a
controller and I can't support this patch.

PLEASE see the ResourcesController plugin here:
http://groups.google.com/group/resources_controller?hl=en

I think you'll find it achieves what you are trying to achieve with
the :*_type tag without the burdensome syntax in the routes.

PS - for those Core team members reading this, please consider
supporting efforts to link from the recognized route (in the
controller) back to the abstract resource that generated the route AND
have the abstract resource be linked to its nesting parents. The
patch wildchild has submitted and the ResourcesController (and
probably 85% of the other plugins with 'Resource' in the title) are
all about the struggle to cleanly identify/instantiate the concrete
resource hierarchy in the controller. Having the abstract hierarchy
available would be huge. Read more here: http://cho.hapgoods.com/wordpress/?p=151

-Chris

Wildchild,
Your patch takes an approach that differs from the one taken by the
ResourcesController plugin. I like the RESULT of your patch (:*_type
key in the params) but, as shown by the RC plugin, I'm not sure the
specification with :polymorphic => <array> is required or wise. Right
now, there is a lot of support for organizing the abstract resources
defined by routes.rb in a nested structure. Your proposal may limit
the effectiveness of that approach.

For example, if I had another resource type with assets that was more
deeply embedded than (events|articles) -say /forums/3/posts/2/assets/
1, I would not be able to cleanly spec the nested, polymorphic asset
with your synta (or alternatively, I would end up with :polymorphic
nearly everywhere). In a nutshell, your syntax gets hairy when a
specific polymorphic resource appears at several different depths.

I can't see this. Are you really needing that level of nesting?
Post#2 belongs to Forum#3, that's the same as:
/posts/2/assets
Isn't it?

Couple that with the fact that such syntax IS NOT REALLY REQUIRED
ANYWAY in the classic use case of identifying concrete resources in a
controller and I can't support this patch.

Not really, but it's useful in limiting the type of parent resources, and
writing named routes.

http://dev.rubyonrails.org/ticket/11031

This patch adds a new option to map.resources named :polymorphic,
which will attach a :type requirement to the generated route. It keeps
URLs absolutely clean.

Useful for me!
I'm already using this in my CMSplugin
http://cmsplugin.rubyforge.org/

Simple example:

map.resources :posts, :polymorphic => [:events, :articles] do |posts|
  posts.resources :assets, :polymorphic => [:images, :movies]
end

edit_post_asset_path(:post_type => 'events', :post_id => 1, :type =>
'movies', :id => 2)

Even cooler if we could do:
edit_post_asset_path(@event, @movie)

Antonio,
I agree that doing something like "edit_post_asset_path(@event,
@movie) is nice. But, again, you don't need to put syntax on routes
to achieve nearly the same effect. As one example, consider the
ResourcesController (RC) plugin which provides helpers like...

edit_resource_path

The identified resources is @movie, the enclosing_resource is @post
and the edit_resource_path expands to edit_post_movie_path(@post,
@movie) automatically. So the url helper syntax is cleaner and
shorter than your example.

All WITHOUT ANY SYNTAX BURDEN in routes.rb.

*****I think most of the objectives of this ticket are worthwhile but
I can't agree that you need to burden the routes with the syntax. RC
is but one example of how to solve the problem cleanly.

even though the first part :

map.resources :posts, :polymorphic => [:events, :articles] do |posts|
  posts.resources :assets, :polymorphic => [:images, :movies]
end

seems great i can't really see where's the good thing with

edit_post_asset_path(:post_type => 'events', :post_id => 1, :type =>
'movies', :id => 2)

instead of asking for "edit_event_movies_path".
edit_post_asset_path(@event, @movie) seems better but still, i prefer
the previous one...

Personally, I think that a URL structure that looks like a directory
structure is a much more useful structure, if only because it's easy
for the user to, for instance, lop bits off the end of the long url
have resources that make sense. Given /forums/3/posts/2/assets/1, it's
a safe bet that /forums/3/posts/ will give me all the posts in forum
3, meanwhile /posts/, isn't really anywhere near as useful.

I would argue that the shallow hierarchy style of URL schema that's
becoming all to common is recommended not because it's simple for the
user but because it's simple for the implementer. The host of resource
related plugins out there are all trying to address that issue and to
reduce the pain of deep hierarchies for the implementer. My Projects
directory contains several of my own attempts to address this, most of
which attempt to refactor the way the routing macros in
ActionController::Resource work to make it easier to subclass/extend
them.

The really tricky bit is getting autogenerated finders to work - life
would be so much easier here if params was an ordered hash, then you
could write a generic finder like:

resources = params.inject([]) { |o,(k,v)|
  case k.to_s
  when /^(.+)_id$/
    o << $1.camelize.constantize.find(v)
  when /^id$/
    o << params[:controller].singularize.camelize.constantize.find(v)
    break o
  when /^(?:controller|action/)$/
    o
  else
    break o
  end
}

(assuming a straightforward mapping from foo_id to Foo.)

Obviously any real implementation would need to be rather more
flexible, but all it takes to enable some pretty powerful automatic
resource fetching stuff is to make params ordered (or make it into a
first class object which you can query with something like a
#path_params accessor).

<rant>
Piers is dead on. I really hope that Rails will add support for
simpler identification of resources.

AFAICS, ordered hashs are not supported in Ruby 1.8 (but are Ruby
1.9). But a nested hash would also solve the problem of identifying
the hierarchy. In fact, I made a ticket just for such an enhancement
(http://dev.rubyonrails.org/ticket/8105) over a year ago. And while
it may not be a practical solution, my ticket and Piers wish are
pretty much one and the same: ease the burden for identifying nested
resources.

If we accept that a nested/ordered hash of params is not going to
happen any time soon (and I think that is the reality), then an
alternative is for the controller to examine the matched route and
"regenerate" the hierarchy manually. The ResourcesController plugin
does just that. It's not pretty how it get the job done, but it does
work very well if you don't look behind the curtain.

Full Circle REST is what I call this approach. By defining abstract
resources in routes.rb, it should be possible to...

    1. Instantiate the appropriate controller (available now)
    2. Identify the resources (including parent resources) in the
controller (missing, but available in RC)
    3. Respond with content that includes links built by simple named
route url helpers (mostly available, but further abstracted by RC)
    Repeat

At every stage, the concept of "REST resource" is available to give
your application structure. Concrete resources are determined by the
request URL and used to determine the controller to instantiate. Then
they are available within that controller (hydrated selectively to
manage performance) and finally they are used to generate other links
in the content.

I've blogged here about how Rails could go about making step two (the
current squeaky wheel) easier: http://cho.hapgoods.com/wordpress/?p=151
</rant>

Thanks also, Piers, for expressing my attachment to deep(er) urls.
Shallow URLs usually work, but they lack expressive context. In a
more flexible framework (and even in Rails, with some work), /forums/3/
posts/2 may mean the second post in forum 3, or even the second post
in the third forum and consequently /posts/2 alone is probably
meaningless.

-Chris