Are deeply nested polymorphic resources worth the effort? - ruby-on-rails-3

I am at the point in my development that I am thinking that deeply (>1) nested resources are not worth the effort.
I have something like this:
resources :first-level do
resources :comments
resources :second-level do
resources :comments
resources :first-child do
resources :comments
end
resources :second-child do
resources :comments
end
resources :third-child do
resources :comments
end
end
end
The kicker is that the comments are polymorphic to the other resources. My intent was to have clean looking URLs like ~/first-level/34/comments, ~/first-level/34/second-level/56/third-level/comments etc.
The problem so far is the polymorphic routes when nested cause nothing but grief. I am following a couple of Ryan Bates Railscasts as an example. For example if I try to use polymorphic_path on the first level it works fine and I get:
polymorphic_path([#commentable, comments]) => ~/first-level/34/comments
but the same code on ~/first-level/34/second-level/23 fails with:
undefined method 'second-level_comment_path' for #<#<Class:0x007fcc4acfbe58>:0x007fcc4ae73d08> but when I look at my routes the actual named route is first-level_second-level_comment. I tried to manually create that second-level_comment_path to basically alias to first-level_second-level_comment but I could not seem to make that work either.
Unless someone can point out an obvious error here I am leaning towards this approach (http://weblog.jamisbuck.org/2007/2/5/nesting-resources) and just un-nesting these. I have a breadcrumb style navigation to show the hiearchy so that should suffice and the more I look at it the urls do get a bit unwieldily.

With nested resources, you will need to specify all the parent levels when you want to access a child level. Otherwise Rails will not know how to get to your child level. So, you will need to use first-level_second-level_comment and supply first-level and second-level values like this:
first-level_second-level_comments_path(#my_first_level, #my_second_level)
will render:
~/first-level/34/second-level/23/comments
EDIT:
I don't see why you will need to build path incrementally.
You can always build first_level comments path:
first-level_comments_path(#my_first_level)
will render
~/first-level/34/comments
Or listing of all second levels within the first level: (second-level's index action for a given first-level)
first-level_second-levels_path(#my_first_level)
will render
~/first-level/34/second-levels

Related

How do you use :as and match a route correctly?

I don't know what I'm doing wrong. This is my route:
resources :stores do
get '/add_shoes' => 'stores#add_shoes', :as => :add_shoes
end
And my path should be: <%= link_to "Add Shoes", add_shoes_path %>
But it gives the error that the path does not exist. How do I use both of them?
If I recall correctly, because it's nested in a resources block, it's going to append stores to the end. So the correct route is add_shoes_stores_path. Sometimes it also adds an index to the end (not sure why), to give you add_shoes_stores_index_path.
Since it's a get call, you could always put it outside the resources block.
Additionally, the more Rails way to do it would be:
resources :stores do
collection do
get :add_shoes
end
end
You've got a bunch of options here on how you want to handle it. But just a quick tip, you can always type rake routes from the command line to get a list of all available routes and where they point.
If you're looking at doing it this way for your clarity of code, just remember this: Your routes should always mention where they're pointing to. That would be both add_shoes and stores. Having an add_shoes_path could point to literally any controller, as it's not really verbose. I would definitely stick to the Rails way of doing it - it will make more sense as you dive in deeper.
Finally, another thought - If you're adding shoes in the stores model, it would make sense for each store to have shoes. You should probably create a new model for Shoes and use RESTFUL routing.

Rail3 | How to create standard route/action for ALL controllers?

Well, DRY! So i thought it should be easy to add a new action (like the existing new, edit) to all my controllers (in my case copy). But how do you setup a new route for ALL controllers?
Without going in to 'loops' (i.e. %w().each ...) inside the routes.rb ?
I mean, we want DRY right? So you don't want copy your action inside the routes file for each resource. I guess you should be able to extend the default actions/routes (index, new, edit,etc.) easy?
Thanks!
AFIK no way to do this by default. You could monkey-patch resources to include this functionality:
https://github.com/rails/rails/blob/b229bc70e50ec0887c5bb3aaaa9c6ee8af054026/actionpack/lib/action_dispatch/routing/mapper.rb#L982
...but my hunch is you would be better off re-considering whether this functionality can be created another way, since what you want to do is "off the Rails".
One option is create a CloneController#new that accepts a model and id and creates a clone. This seems like it would be drier, and wouldn't require you to pepper a gazillion "clone_article" "clone_blog" "clone_user" paths all over the place.
Obviously you would want to carefully white-list the models/ids that can be passed in.
Looking through the source there isn't a way to add to the default actions for a resource.
But, as #juwiley says, the methods resources :item is just a shortcut for creating a load of member and collection methods.
All you need to do is something like this
class ActionDispatch::Routing::Mapper
def resources_with_copy(*resources, &block)
block_with_copy = lambda do
block.call
member do
post :copy
end
end
resources(*resources, &block_with_copy)
end
end
Then in your routes.rb just say
resources_with_copy :items
resources_with_copy :posts do
member do
post :share
end
end
...

Passing :new to Rails url_for

Maybe I'm stupid but Rails provides this nifty syntax for generating URL's like so:
url_for([user, comment]) # => /users/1/comment/1
Passing :edit allows me to create something like this:
url_for([:edit, user, comment]) # => /users/1/comment/1/edit
but is there some way to do following?
url_for([:new, user, comments]) # => NoMethodError: undefined method `new_user_comments_url'
UPDATE: Added more information.
My routes.rb:
resources :users do
resources :comments
end
resources :posts do
resources :comments
end
My problem here is, that I can't use Rails auto-generated url helper (user_comments_url), because I'm sharing the views for both user comments and post comments.
There are two workarounds (but no one feels like the "Rails"-way) for my problem:
Adding logic to the view, e.g. some if conditions.
Defining my own url helpers like new_parent_comment(user_or_blog).
Ok, found a solution, but I'm not sure if this is the intended one:
url_for([:new, user, :comment]) # => '/users/1/comments/new'
url_for([:new, post, :comment]) # => '/posts/1/comments/new'
Stuck with the same problem, and found next solution (tested on Rails 5.2):
url_for([user, Comment, action: :new])
where Comment model class name.
By the way, action also could be :edit.
According to the Rails Docs url_for uses the class name of the object passed to generate the RESTful route. It also states that with nested routes it can not make this assumption correctly:
If you have a nested route, such as admin_workshop_path you’ll have to call that explicitly (it’s impossible for url_for to guess that route).
I would suggest using a named route here something like new_user_comment_path(). I am assuming you have set up your routes.rb something like:
resources :users do
resources :comments do
end
end
Additionally you can run rake routes to print out the proper names for all your routes.
Hope this helps,
/Salernost
Could this simply be a typo? I think the last line should read comment, not comments:
url_for([:new, user, comment])
(Assuming your comment variable has been defined.)

Nested Routing for Single Table Inheritance model rails 3.1

I created a Single table inheritance model in my model file and am having difficulty with the routing. When I use :as in my resource, it renames my named path.
Model file:
class Account < ActiveRecord::Base
belongs_to :user
end
class AdvertiserAccount < Account
end
class PublisherAccount < Account
end
Routes.rb
resources :advertiser_accounts, :as => "accounts" do
resources :campaigns
end
I used :as in my routes because it is a single table inheritance and I want to pass the account_id and not the advertiser_account_id. My link is http://127.0.0.1:3000/advertiser_accounts/1/campaigns
/advertiser_accounts/:account_id/campaigns/:id(.:format)
However, using :as renames my named path from advertiser_account_campaigns to account_campaigns. My route looks like
account_campaigns GET /advertiser_accounts/:account_id/campaigns(.:format) campaigns#index
So when I create a new item using form_for, I would get "undefined method `advertiser_account_campaigns_path'"
Edited: current hacked solution
A hack around way that I am using is to duplicate the code in the routes file. Anyone have suggestions?
resources :advertiser_accounts, :as => "accounts" do
resources :campaigns
end
resources :advertiser_accounts do
resources :campaigns
end
If you run "rake routes" with your setup you'll see this:
account_campaigns GET /advertiser_accounts/:account_id/campaigns(.:format) campaigns#index
POST /advertiser_accounts/:account_id/campaigns(.:format) campaigns#create
new_account_campaign GET /advertiser_accounts/:account_id/campaigns/new(.:format) campaigns#new
edit_account_campaign GET /advertiser_accounts/:account_id/campaigns/:id/edit(.:format) campaigns#edit
account_campaign GET /advertiser_accounts/:account_id/campaigns/:id(.:format) campaigns#show
PUT /advertiser_accounts/:account_id/campaigns/:id(.:format) campaigns#update
DELETE /advertiser_accounts/:account_id/campaigns/:id(.:format) campaigns#destroy
accounts GET /advertiser_accounts(.:format) advertiser_accounts#index
POST /advertiser_accounts(.:format) advertiser_accounts#create
new_account GET /advertiser_accounts/new(.:format) advertiser_accounts#new
edit_account GET /advertiser_accounts/:id/edit(.:format) advertiser_accounts#edit
account GET /advertiser_accounts/:id(.:format) advertiser_accounts#show
PUT /advertiser_accounts/:id(.:format) advertiser_accounts#update
DELETE /advertiser_accounts/:id(.:format) advertiser_accounts#destroy
So you should use "account_campaingns_path" in this setup, the ":as" actually changes the calls in the code not the paths in the url. If you want to change the paths you should use ":path =>" rather than ":as =>".
The Rails guide on routing also shows some examples with ":as" and ":path" and the resulting paths and helpers, you'll need to search a bit because think they only use in in examples explaining other cases.
Edit: rereading your question, I think you may also want to look at member routes, I'm not sure if that's what you want to mean with it being a single inheritance and not wanting to pass the advertiser_account's ':account_id'?

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

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!
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)
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 :]
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.