rails
(64,008 views)

make-resourceful and nested polymorphic associations

Here's a patch that enables make_resourceful to handle polymorphic resources with nested routes.

polymorphic resources

A polymorphic model can belong to more than one parent model class. For example, Comments can belong to both Articles and Documents. The Rails idiom for setting this up is like this:

comments table schema includes:

commentable_id:integer
commentable_type:string 

models/comment.rb contains

belongs_to :commentable, :polymorphic => true

then both the models/article.rb and document.rb contains

has_many :comments, :as => :commentable

In this way, you can reference article.comments and document.comments for an Article and Document object, respectively. With RESTful resource routes, you can have url's and named routes like

article_comment_path(@article,@comment) # => /articles/1/comments/22
article_new_comment_path(@article)     # => /articles/1/comments/new

make_resourceful

The make_resourceful ("mr") plugin (ver 0.1.0) automatically creates typical RESTful actions in controllers, but does not presently support these associations.

Although mr supports has_many nested resources (and multiple levels of them at that), it assumes that a child resource has just one kind of parent.

I've submitted this patch to the authors but its not tightly integrated. So if you use this, consider it a temporary patch until the plugin authors integrate it or come up with their own (better) solution.

Most of the code can be put in application.rb. There are a few other methods that go into the controller for the polymorphic resource.

multiple parents

Important: When using polymorphic resources, I've changed the meaning of parents from the standard mr convention. Normally, mr's parents is a list of models in a nested resource chain, such as /projects/3/articles/4/comments/5, then parents contains ["project","article"].

In the polymorphic case, we can have several alternative parents. Thus, I use parents to represent the list of possible parent resources, one level up.

This also means that in the polymorphic case, we only handle one level of nesting. That's not so bad because that's being strongly recommended by the Rails core team. See http://weblog.jamisbuck.org/2007/2/5/nesting-resource

URL helpers

As a sidebar, I want to mention an undocumented feature: mr has a number of very handy URL generator methods, only you need to expose them as helpers to use them in views. They are :object_path, :objects_path, :new_object_path, and :edit_object_path.

You can use these instead of the built-in RESTful named routes. If your resources are nested the nested paths will be generated. My patch now lets them support polymorphic nesting too.

I've also added a new helper, parent_path, that can be used for example in a "Back" link. Thus, for example:

object_path(item) # => /articles/1/comments/22 
object_path       # assumes current_object 
edit_object_path  # => /articles/1/comments/22/edit
parent_path       # => /articles/1 

application.rb

Add the following to your application.rb to extend the make_resourceful library:

 

  # make_resourceful fixes for polymorphic models
  # use this to override parent_objects method 
  # def parent_objects
  #   parent_objects_poly
  # end
  def parent_objects_poly
    return [] if parents.empty?
    return @parent_objects if @parent_objects
    # find index of first (and only) non-zero parent_param 
    parent_i = parent_params.index( parent_params.find { |el| el != 0 })
    return @parent_objects = [] unless parent_i
    model = parent_models[ parent_i ]
    @parent_objects= [model.find(parent_params[parent_i])]
  end
  
  # parent object helper
  def parent_object
    parent_objects[0] unless parent_objects.empty?
  end
  helper_method(:parent_object)
  
  def parent_path
    send("#{namespace_prefix}path", *parent_objects)
  end
  helper_method(:parent_path) 
   
  # expose the url methods as helpers
  helper_method(:object_path, :objects_path, :new_object_path, :edit_object_path)

 

The first method, parent_objects_poly, is the body of the override of the parent_object method.

I introduce a new parent_object helper that returns the parent of the current_object. Related to this, I've also added a parent_path URL helper.

The last line exposes the built-in mr URL generators as helpers.

You can keep this code in application.rb. Normally these will not affect make_resourceful controllers, unless you add the following code to the controller to make it a polymorphic resource.

your_controller.rb

The second part of the implementation requires adding a few methods to your controller.

If this part looks a little kludgey, its because I'm trying to avoid editing the plugin code directly, to force the order of method overrides (internally, make_resourceful method calls load_parent_objects in base from kontroller.before_filter).

 

  make_resourceful do
    actions: all
  end
 
  # add the following to make this controller for a polymorphic resource 
  def parents
    ["article","document"] #this is the only line specific to this resource
  end
  
  def parent_objects
    parent_objects_poly
  end
  
  def namespaces
    parent_i = parent_params.index( parent_params.find { |p| p != 0 })
    [parents[parent_i]]
  end

(Note, we do not use the belongs_to declaration, rather we override the parents method directly.)

As noted above, parents returns the list of possible parents, one level up. You need to insert your own parents array there.

parent_objects is overriden using the body we put in application.rb. It still returns an array, but it will have no more than one element.

Finally, the internal method for generating namespaces is also overridden for the URL helper methods (again, this didn't work when I put it in application.rb)

 

That it. It seems to be working fine for me. I hope it helps you too.

 

Comments

by Thomas on Sep 09, 2007

>>

I've given this a good effort and still can't get it to work. Could you show what the routes.rb info should look like for this example?

by admin on Sep 15, 2007

>>

<p>Thomas, if you're using rails edge, the routes might contain:</p>

<pre> map.resources :articles, :has_many => :comments map.resources :documents, :has_many => :comments </pre>

<p>Otherwise, its a bit more verbose,</p>

<pre> map.resources :articles do |article| article.resources :comments, :controller => "Comments", :name_prefix => "articles" end map.resources :documents do |doc| doc.resources :comments, :controller => "Comments", :name_prefix => "documents" end </pre>

by spiceee/\gmail.com on Nov 14, 2007

>>

Hey, Jonathan, great hack!

I'm wondering why parent_params is returning nil instead of 0 when that given parent is not in the path for a request, using the new m_r release with edge.

I've had to change p != 0 to not p.nil? in both places and it seemed to pull the trick, tho!

Cheers,

Fabio.

New Comment

markdown formatting permitted