Rails 3 - Nested resources and polymorphic paths: OK to two levels, but break at three
Asked Answered
L

3

9

I'm trying to do a simple family reunion site with: "posts", "families", "kids", and "pictures". Ideally I'd like the routes/relationships to be structured this way:

  resources :posts do
    resources :pictures
  end

  resources :fams do
     resources :pictures
     resources :kids do
       resources :pictures
     end
  end

In the models I have the necessary "belongs_to" and "has_many" relationships set between fams and kids. Fams, kids, and posts all are defined with "has_many :pictures, :as => :imageable" while pictures are defined as: belongs_to :imageable, :polymorphic => true

When trying to do link_to "Edit" and link_to "Destroy" in the pictures views I run into all sorts of _path problems. polymoric_path works fine at two levels, namely for posts-pictures and fams-pictures but it fails to handle the three level case of fams-kids-pictures. I'm guessing that it was not designed to handle the two levels of "imageable" objects above the picture object. Another issue is that in one instance the pictures controller has to handle a "one level" resource-nesting situation and in another it has to handle a "two levels" situation. Not sure how to approach this.

One thing I did try was to not nest resources more than one deep, per the Ruby Guides directions. I structured them like this:

  resources :posts do
    resources :pictures
  end

  resources :fams do
     resources :pictures
     resources :kids
  end

  resources :kids do
     resources :pictures
  end

This caused another set of problems with paths since the fam to kid relationship was no longer preserved. I also could not get polymorphic_path to function correctly accross all the different picture views.

So here is my main question: Does anyone know of a Rails 3 example/tutorial where nested resources, belongs-to/has_many, and polymorphic relationships are all put together, especially where it is not just the simple, two-level relationship that most examples show? (I'm fairly new to Rails and the Rails 2 examples I've found in these areas are confusing given my lack of Rails historical experience.)

Or can someone tell me how to structure the link_to EDIT and link_to DELETE statements for my picture views, as well as the redirect-to statement for my create, update, and destroy methods in my pictures controller?

Thanks!

Louralourdes answered 7/5, 2011 at 0:34 Comment(0)
M
7

Your code example that limited your nesting to 2 levels is quite near the answer. To avoid duplicate routes for fams->kids and kids, you can use the :only option with a blank array so that the 1st-level kids will not generate routes except in the context of kids->pictures, like so:

resources :posts do
  resources :pictures
end

resources :fams do
  resources :pictures
  resources :kids
end

resources :kids, only: [] do # this will not generate kids routes
   resources :pictures
end

For the above code, you can use the following to construct your polymorphic edit url:

polymorphic_url([fam, picture], action: :edit) # using Ruby 1.9 hash syntax
polymorphic_url([kid, picture], action: :edit)
Meyerbeer answered 21/7, 2011 at 8:0 Comment(0)
A
2

Have been having this exact same problem for a while. I have it working now, but it isn't beautiful :S

From a nested monster like:

http://localhost:3000/destinations/3/accommodations/3/accommodation_facilities/52

Your params object ends up looking like this:

action: show
id: "52"
destination_id: "3"
accommodation_id: "3"
controller: accommodation_facilities

where "id" represents the current model id (last on the chain) and the other ones have model_name_id

To correctly render another nested link on this page, you need to pass in an array of objects that make up the full path, eg to link to a fictional FacilityType object you'd have to do:

 <%= link_to "New", new_polymorphic_path([@destination, @accommodation, @accommodation_facility, :accommodation_facility_type]) %>

To generate this array from the params object, I use this code in application_helper.rb

def find_parent_models(current_model = nil)
    parents = Array.new

    params.each do |name, value|
      if name =~ /(.+)_id$/
        parents.push $1.classify.constantize.find(value)
      end
    end

    parents.push current_model

    parents
end

Then to automatically make the same link, you can merrily do:

 <%= link_to "New", new_polymorphic_path(find_parent_models(@accommodation_facility).push(:accommodation_facility_type)) %>

Any pointers on making this solution less sketchy are very welcome :]

Adagietto answered 20/7, 2011 at 9:46 Comment(6)
What if some user types an address ending with: ..facilities/52?user_id=1&pathname_id=/etc/passwd&application_controller_id=1&xyzzy_id=1&file_utils_id=1... and so on? I assume you have a nice design of 500.html ? ;-))Sigler
It's a picture of a kitten. Interesting point though, but doesn't rails have this issue anyway? If I try: localhost:3000/destinations/1/?destination_id=1 it freaks out alreadyAdagietto
Having a parameter named 'xyzzy_id' Rails will not constantize it by itself. If an attacker is able to find a class which responds to 'find' method, he may do a really nasty things.Sigler
Good point. Though an attacker would only be able to get an unwanted object into scope to build the polymorphic_path, which would likely be invalid. Do you have an alternative solution?Adagietto
I suppose that a white-list of acceptable parents would make everyone happy.Sigler
.. as a defensive precaution: change parents.push $1.classify.constantize.find(value) into parents.push $1.classify.constantize.find(value.to_i) if $1.classify.constantize.exists?(value.to_i)Chlorenchyma
C
0

I can't speak for the polymorphic association problem (probably need more info on the actual error) but you are indeed headed in the right direction by defining your nested resources only one level deep. Here's a popular article by Jamis Buck that has become a reference and that you should probably check out.

Cleome answered 7/5, 2011 at 0:43 Comment(1)
Yes, I've read through that article many times. I find it very superficial. Goes for simplicity, but does not address how to maintain the relationships through the multiple levels. Add polymorphism with the dynamic paths' it requires, and you have a very complex problem that is not well addressed by anything I've seen. I'm to the point that I'm about ready to dump the polymorphism and start repeating myself by building different picture controllers for each of my main data models. So much for DRY.Louralourdes

© 2022 - 2024 — McMap. All rights reserved.