У кого-нибудь есть советы по управлению полиморфными вложенными ресурсами в Rails 3? - PullRequest
7 голосов
/ 10 сентября 2010

В config / rout.Rb:

resources :posts do
  resources :comments
end

resources :pictures do
  resources :comments
end

Я хотел бы также разрешить комментировать больше вещей.

В настоящее время я использую mongoid (mongomapper пока не так совместим с Rails 3, как хотелось бы), а комментарии являются встроенным ресурсом (mongoid еще не может обрабатывать полиморфные реляционныересурсы), что означает, что мне нужен родительский ресурс, чтобы найти комментарий.

Существуют ли элегантные способы решения некоторых из следующих проблем:

В моем контроллере мне нужночтобы найти родителя, прежде чем найти комментарий:

if params[:post_id]
  parent = Post.find(params[:post_id]
else if params[:picture_id]
  parent = Picture.find(params[:picture_id]
end

, который может стать грязным, если я начну добавлять дополнительные элементы, которые будут комментируемыми.

Также url_for([comment.parent, comment]) не работает, поэтомуМне нужно будет что-то определить в моей модели Comment, но я думаю, что мне также понадобится определить индексный маршрут в модели Comment, а также, возможно, изменение и определение нового маршрута.

Могут возникнуть и другие проблемы, с которыми мне придется столкнуться по мере продвижения вперед.

Не могу себе представить, что я первый человек, который попытается решить эту проблему, есть ли какие-то решения там?сделатьболее управляемым?

Ответы [ 3 ]

4 голосов
/ 10 сентября 2010

Я должен был сделать нечто подобное в моем приложении. Я взял то, что придумал, и немного изменил, но я не проверял, так что будьте осторожны. Это не красиво, но лучше, чем все остальное, что я мог придумать.

В маршрутах .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

В комментариях.рб:

embedded_in :commentable, :inverse_of => :comments

def to_param
  [commentable.class.to_s.downcase.pluralize, commentable.id, 'comments', id].join '/'
end

В фильтре before в 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)

Хорошо, уродство прошло. Теперь вы можете добавлять комментарии к любым моделям и просто делать:

edit_comment_path @comment
url_for @comment
redirect_to @comment

и т. Д.

Редактировать: я не реализовывал никаких других путей в своем собственном приложении, потому что все, что мне было нужно, это редактировать и обновлять, но я думаю, что они будут выглядеть примерно так:

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

Другие действия будут хитрее. Вам, вероятно, нужно сделать что-то вроде:

  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

Затем вы получите доступ к родительской модели в контроллере, используя params [: parent_type] и params [: parent_id]. Вам также необходимо передать правильные параметры помощникам URL:

comments_path('pictures', 7)
2 голосов
/ 23 декабря 2010

Райан Бейтс рассмотрел полиморфные ассоциации в Railscasts # 154 , но пример был для Rails 2 и Active Record. Мне удалось заставить его пример работать с использованием Rails 3 и Mongoid, внеся несколько изменений.

В моделях Post и Picture добавьте следующую строку:

embeds_many :comments, :as => :commentable

Согласно документации Mongoid все embedded_in ассоциации являются полиморфными. Вам не нужны столбцы commentable_id и commentable_type, упомянутые в Railscast при использовании Mongoid, потому что комментарий является дочерним по отношению к комментируемому. В модели комментариев добавьте следующую строку:

embedded_in :commentable, :inverse_of => :comment

Настройте маршруты в config / rout.rb следующим образом:

resources posts do
  resources comments
end

resources pictures do
  resources comments
end

Добавьте следующий метод к вашему контроллеру комментариев как private метод. Это идентично методу Райана:

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

В каждом из ваших действий контроллера комментариев, где вам нужно найти комментарий, сначала вызывайте метод find_commentable, чтобы получить родителя. Как только родитель найден, вы можете найти комментарий по идентификатору, выполнив поиск по комментариям комментируемого. Например, в действии редактирования код для поиска комментария будет выглядеть так:

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

Чтобы уменьшить количество повторных вызовов find_commentable в начале каждого действия, вы можете поместить фильтр before в верхнюю часть контроллера следующим образом:

class CommentsController < ApplicationController
  before_filter :find_commentable
  ...

А затем измените обратный вызов в методе find_commentable на:

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

Я не сталкивался с какими-либо проблемами при использовании этого метода, но если вы столкнетесь с какими-либо проблемами, пожалуйста, укажите их.

0 голосов
/ 30 января 2011

Опираясь на ответ Uriptical, я обнаружил, что отношения работают, но именованные маршруты все еще нет.

Я все еще довольно новичок в рельсах 3, но я нашел простое решение с помощью eval.

Например, в моем проекте полиморфные родители (представленные в моем приложении как mongoid-объекты Product и Category) определяются как @imagable с использованием модификации find_comentable, а редактируемый дочерний элемент называется @ image.

URL-адрес, такой как product_image_path(@imagable, @image), который делает GET => products/:product_id/images/ можно заменить на:

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

Это работает для всех именованных путей. Например:

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

Недостатком этого является то, что он оставляет посылки по всем вашим представлениям и в контроллерах, куда бы вы ни перенаправляли.

Есть ли более элегантное решение для этого по маршрутам?

* заменен eval с отправкой

...