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