0

I have the Comment model, which is polymorphic associated to commentable models like Project, User, Update etc. And I have a page where a user can see every User's comment. I want a link near each comment with an address of an object this comment is associated with. I could write something like that:

link_to 'show on page', Object.const_get(c.commentable_type).find(c.commentable_id)

But this will work only for not nested routes (like User). Here's how my routes look like:

resources :users do
  resources :projects, only: [:show, :edit, :update, :destroy]
end

So when I need a link to a Project page, I will get an error, because I need a link like user_project_path. How can I make Rails to generate a proper link? Somehow I have to find out if this object's route is nested or not and find a parent route for nested ones

1 Answers1

2

You could use a bit of polymophic routing magic.

module CommentsHelper
  def path_to_commentable(commentable)
    resources = [commentable]
    resources.unshift(commentable.parent) if commentable.respond_to?(:parent)
    polymorphic_path(resources) 
  end

  def link_to_commentable(commentable)
     link_to(
       "Show # {commentable.class.model_name.human}",
       path_to_commentable(commentable)
     )
  end
end

class Project < ActiveRecord::Base
  # ...
  def parent
    user
  end
end

link_to_commentable(c.commentable)

But it feels dirty. Your model should not be aware of routing concerns.

But a better way to solve this may be to de-nest the routes.

Unless a resource is purely nested and does not make sense outside its parent context it is often better to employ a minimum of nesting and consider that resources may have different representations.

/users/:id/projects may show the projects belonging to a user. While /projects would display all the projects in the app.

Since each project has a unique identifier on its own we can route the individual routes without nesting:

GET /projects/:id - projects#show
PATCH /projects/:id - projects#update
DELETE /projects/:id - projects#destroy

This lets us use polymorphic routing without any knowledge of the "parent" resource and ofter leads to better API design.

Consider this example:

Rails.application.routes.draw do
  # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html

  resources :projects

  resources :users do
    # will route to User::ProjectsController#index
    resources :projects, module: 'user', only: [:index]
  end
end

class ProjectsController < ApplicationController
  def index
    @projects = Project.all
  end
  # show, edit, etc
end

class User::ProjectsController < ApplicationController
  def index
    @user = User.joins(:projects).find(params[:user_id])
    @projects = @user.comments
  end
end

This would let us link to any project from a comment by:

link_to 'show on page', c.commentable

And any users projects by:

link_to "#{@user.name}'s projects", polymorphic_path(@user, :projects)
Hipjea
  • 391
  • 5
  • 14
max
  • 96,212
  • 14
  • 104
  • 165
  • I will have a commentable model Update which belongs to a Project and I think it's better for Updates to be nested. So I'll have to use this "polymorphic routing magic" anyway. Thanks – Juke Seroon Sep 10 '16 at 14:08