views:

239

answers:

1

Suppose I have a polymorphic structure like this.

map.resources :bar, :has_many => :foo
map.resources :baz, :has_many => :foo
map.resources :qux, :has_many => :foo

class Foo
  belongs_to :parent, :polymorphic => true
end

class FooController < AC
 before_filter :find_parent

 ...

 private
 def find_parent
  # ugly way:
  @parent = if params[:bar_id]
   Bar.find(params[:bar_id])
  elsif params[:baz_id]
   Baz.find(params[:baz_id])
  elsif params[:qux_id]
   Qux.find(params[:qux_id])
  end 
 end
end

That's pretty ugly. Whenever we add a new thing that it might belong to, we need to add it un-DRYly to that before_filter.

It gets worse, too. Suppose that Foos are really polymorphic things that could show up anywhere, like comments or tags. And suppose that you have the following routes:

map.resources :bar, :has_many => :foo do |bar|
 bar.resources :baz, :has_many => :foo
end
map.resources :qux, :has_many => :foo do |qux|
 qux.resources :baz, :has_many => :foo
end

... now we have to worry about whether to check for bar_id or baz_id first.

For more complex resources, it's possible that this won't even be enough to be sure you get the parent's id.

What would be ideal is if we could do something like this:

def get_parent
 # fetch the parameter that immediately preceeded :id
 @parent = if x = params.before(:id)
   # polymorphic find
   x.key.to_s[0..-4].classify.constantize.find x.value
 end
end

After all, our routes already encode the parent by virtue of the order of parameters in the URL. Why discard that information?

So: how can this be done?

+3  A: 

You should be able to ask for foo.parent

You'll need to have something like this:

class Bar < ActiveRecord::Base
  has_many :foos, :as => :parent
  ...
end
Swards
Yep, this way you'll certainly know that you get the real parent of your foo. Since user's can pass anything into url and you'd get any Bar/Baz/Qux for defined Foo).
Eimantas
Right - and using the URL to find the parent isn't a guarantee. The url could be 'shallow' - ie there would be no 'bar_id' in the params to look at.
Swards
That wouldn't necessarily work in some kinds of has_many polymorphy, and it won't work at all for new records.For the former, suppose that a single Foo is shared between both a Bar and a Baz, and I need to tell (for a particular request) which one was the effective one.
Sai Emrys
Also doesn't work for FooController#index (which needs to know which parent's foos to query).
Sai Emrys
You'd need to modify the Foo class to have many parents. In your hierarchy, it looks like a Foo can only belong to one parent. If Foo can have many parents, you'd have a foo_parents table that would have a polymorphic column and a foo_id column.Class FooParent belongs_to :foo belongs_to :parent, :polymorphic => trueendIf you have the requirement, you would still use deep routes, and would get the Bar or Baz by bar_id or baz_id (do this in a before_filter - you'll want it in update, edit, show). You would then do Bar.foos.find(params[:id]) to get the Foo (in the show action).
Swards