Question

I have a Profile model, which is inherited by a Customer and Vendor model. Profile has_many :posts.

When I do

form_for [ @profile, @post ] do |f|

Instead of callind profile_posts_path, form_for will actually call customer_posts_path or vendor_posts_path.

Since I want to have URLs like '/foo', '/bar', where foo and bar are usernames I wrote the routes like this:

resources :profiles, path: '/', constraints: { id: /[A-Z0-9\-\+\.]+/i } do
    resources :posts
end

resources :customers, path: '/', constraints: { id: /[A-Z0-9\-\+\.]+/i } do
    resources :posts
end

resources :vendors, path: '/', constraints: { id: /[A-Z0-9\-\+\.]+/i } do
    resources :posts
end

This way all requestes to '/foo' will be handled by the ProfilesController (it's the first in the list) and path helpers will be generated so that form_for will work.

However even if it works, this is far from ideal. There is evident repetition. If I need to add another nested resource I would have to add it trice.

So I refactored it like this (I know that this is horrible, but it works):

profile_block = Proc.new do
    resources :posts
end
resources :profiles, path: '/', constraints: { id: /[A-Z0-9\-\+\.]+/i }, &profile_block
resources :customer, path: '/', constraints: { id: /[A-Z0-9\-\+\.]+/i }, &profile_block
resources :vendor, path: '/', constraints: { id: /[A-Z0-9\-\+\.]+/i }, &profile_block

But this is still horrible. What I would really love to have is the as parameter to be an array, so that I could do:

resources :profiles, path: '/', constraints: { id: /[A-Z0-9\-\+\.]+/i }, as: [ 'profiles', 'customers', 'vendors' ] do
    ...
end

Is it possible to achieve something similar? Everything would be mapped to the same controller. And no repetition is in act. Any way to create named routes without having to call resources or match or anything...

Thanks in advance

edit: The relation between posts and profiles might become polymorphic in the near future. So the last solution proposed by AJcodex will break.

I find this frustrating, I will probably request this feature for the next rails

Was it helpful?

Solution

Some options:

  1. use a symbol form_for([:profile, @post]) do |f|

  2. alias the other methods. See this for adding routes: How to define own routing helpers in rails 3?

    alias_method :customers_path, :profiles_path
    
  3. iterate over the symbols in routes

in routes.rb

[ :profiles, :customers, :vendors ].each do |name|
  resources name, path: '/', constraints: { id: /[A-Z0-9\-\+\.]+/i }, as: name do
    resources :posts
  end
end

You should consider using a client-side framework though, because then you can separate the client-facing routes from the API routes. RESTful requests with the server, vanity URLs for the user.

edit:

Is there a reason you need routes for customers and vendors for posts? Have all posts handled by the profiles controller. Get rid of the vendors and customers fancy url's all together.

Depending on if it's a customer or vendor, render a different view if necessary.

edit 2:

do it by hand:

<%= form_for @post, as: :post, url: profile_path(@customer, @post) do |f| %>
  ...
<% end %>
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top