Does anybody have any tips for managing polymorphic nested resources in Rails 3?

前端 未结 3 1852
别那么骄傲
别那么骄傲 2021-02-09 08:30

In config/routes.rb:

resources :posts do
  resources :comments
end

resources :pictures do
  resources :comments
end

I would like to allow for

相关标签:
3条回答
  • 2021-02-09 08:46

    I had to do something similar in an app of mine. I took what I came up with and changed it around a bit, but I haven't tested it, so use with care. It's not pretty, but it's better than anything else I was able to think of.

    In routes.rb:

    resources :posts, :pictures
    
    controller :comments do
      get '*path/edit' => :edit, :as => :edit_comment
      get '*path'      => :show, :as => :comment
      # etc. The order of these is important. If #show came first, it would direct /edit to #show and simply tack on '/edit' to the path param.
    end
    

    In comment.rb:

    embedded_in :commentable, :inverse_of => :comments
    
    def to_param
      [commentable.class.to_s.downcase.pluralize, commentable.id, 'comments', id].join '/'
    end
    

    In a before filter in comments_controller.rb:

    parent_type, parent_id, scrap, id = params[:path].split '/'
    
    # Security: Make sure people can't just pass in whatever models they feel like
    raise "Uh-oh!" unless %w(posts pictures).include? parent_type
    
    @parent = parent_type.singularize.capitalize.constantize.find(parent_id)
    @comment = @parent.comments.find(id)
    

    Ok, ugliness over. Now you can add comments to whatever models you want, and simply do:

    edit_comment_path @comment
    url_for @comment
    redirect_to @comment
    

    And so on.

    Edit: I didn't implement any other paths in my own app, because all I needed was edit and update, but I'd imagine they'd look something like:

    controller :comments do
      get    '*path/edit' => :edit, :as => :edit_comment
      get    '*path'      => :show, :as => :comment
      put    '*path'      => :update
      delete '*path'      => :destroy
    end
    

    The other actions will be trickier. You'll probably need to do something like:

      get  ':parent_type/:parent_id/comments'     => :index, :as => :comments
      post ':parent_type/:parent_id/comments'     => :create
      get  ':parent_type/:parent_id/comments/new' => :new,   :as => :new_comment
    

    You'd then access the parent model in the controller using params[:parent_type] and params[:parent_id]. You'd also need to pass the proper parameters to the url helpers:

    comments_path('pictures', 7)
    
    0 讨论(0)
  • 2021-02-09 08:46

    Ryan Bates covered polymorphic associations in Railscasts #154, but the example was for Rails 2 and Active Record. I managed to get his example working using Rails 3 and Mongoid by making a few changes.

    In the Post and Picture models, add the following line:

    embeds_many :comments, :as => :commentable
    

    According to the Mongoid associations documentation, all embedded_in associations are polymorphic. You don't need the commentable_id and commentable_type columns mentioned in the Railscast when using Mongoid, because the comment is a child of the commentable. In the Comment model, add the following line:

    embedded_in :commentable, :inverse_of => :comment
    

    Setup the routes in config/routes.rb like this:

    resources posts do
      resources comments
    end
    
    resources pictures do
      resources comments
    end
    

    Add the following method to your comments controller as a private method. This is identical to Ryan's method:

    def find_commentable  
      params.each do |name, value|  
        if name =~ /(.+)_id$/  
          return $1.classify.constantize.find(value)  
        end  
      end  
      nil  
    end
    

    In each of your comments controller actions where you need to find the comment, call the find_commentable method first to get the parent. Once the parent has been found, you can find the comment by ID, by searching through the commentable's comments. For example, in the edit action the code to find the comment would look like this:

    @commentable = find_commentable  
    @comment = @commentable.comments.find(params[:id])
    

    To reduce the repetition of calling find_commentable at the start of every action, you could put a before filter at the top of the controller like this:

    class CommentsController < ApplicationController
      before_filter :find_commentable
      ...
    

    And then change the return call in the find_commentable method to:

    return @commentable = $1.classify.constantize.find(value)
    

    I haven't encountered any problems using this method, but if you come across any issues please point them out.

    0 讨论(0)
  • 2021-02-09 08:52

    Drawing on Uriptical's answer, I found the relationships to work but named routes still did not.

    I'm still pretty new at rails 3 but I found a simple solution using eval.

    For instance, in my project, the polymorphic parents (represented in my app as the mongoid objects Product and Category) are defined as @imagable using a modification of find_comentable and the child being edited is referred to as @image.

    a url such as product_image_path(@imagable, @image) which does GET => products/:product_id/images/ can be replaced with:

    send("#{@imagable.class.name.downcase}_image_url", @imagable, image )
    

    This works for all named paths. For instance:

    link_to 'Edit', send("edit_#{@imagable.class.name.downcase}_image_path", @imagable, image ) 
    link_to 'Destroy', send("#{@imagable.class.name.downcase}_image_url", @imagable, image), :confirm => 'Are you sure?', :method => :delete
    

    The downside to this is it leaves sends all over your views and in controllers wherever you have redirects.

    Is there a more elegant solution to do this via routes?

    *replaced eval with send

    0 讨论(0)
提交回复
热议问题