Привет.
Сегодня рассмотрим как использовтаь полиморфные отношения в rails.
С чем это можно сравнить? Помните в детсвет у вас был карандаш\фломастер, которым
вы изрисовали пол\обои\стол\руки. Так вот карандаш -- это и есть полиморф, то есть рисует и на полу и на столе.
Так и здесь это такое отношение, которое может быть для многих типов.
Там во второй части я сделал модель для голосования, помните?
Вот это и будет наша полимофрная модель Vote.
и так сперва делаем миграцию:
rails g migration add_type_into_voteПишем в получившийся класс:
class AddTypeIntoVote < ActiveRecord::Migration def change change_table :votes do |f| f.string :votable_type end rename_column :votes, :post_id, :votable_id end endЗдесь как видно добавляем колонку для типа, здесь будет записан тип к которому применяется
модель. У нас это Post и Comment. То есть позволяет ставить голоса за комментарии и за посты.
Теперь исправим классы моделей.
#models/vote.rb class Vote < ActiveRecord::Base belongs_to :votable, :polymorphic => true #.... endЭто описывает полиморфную модель, указываем строчкой polymorphic => true, то что это полиморфная модель,
а также смотрите belongs_to, не к post\comment, а к votable.
Затем мы могли бы добавить в каждую модель (Post, Comment) одинаковый код:
after_create :create_vote has_one :vote, :dependent => :destroy, :as => :votable def create_vote self.vote = Vote.create(:score => 0) endВидите, это должно быть в каждой модели, но это нарушает принцип DRY. Поэтому создаём модуль в директории с моделями:
#models/vote_module.rb module VoteModule def self.included(base) base.module_eval { after_create :create_vote has_one :vote, :dependent => :destroy, :as => :votable } end protected def create_vote self.vote = Vote.create(:score => 0) end endТут применено метапрограммирование self.included(base)
base - это класс в который подмешивается модуль.
self.included будет срабатывать после вставки в класс, и включать макросы after_create и has_one.
Смотрите в has_one указывается не votable, а vote, но то что это полиморфная модель говорит опция :as
Теперь это нужно включить в классы Post и Comment
#models/post.rb class Post < ActiveRecord::Base include VoteModule #.... end-----
#models/comment.rb class Comment < ActiveRecord::Base include VoteModule #... endХорошая техника, правда?
Так хорошо, исправляем контроллер:
#controllers/vote_controller.rb class VoteController < ApplicationController load_and_authorize_resource before_filter :type def update vote = @type.vote vote.score += 1 if params[:vote] == "Up" vote.score -= 1 if params[:vote] == "Down" vote.users << current_user vote.save message = "You vote counted!" redirect(message) end def clear vote.update_attributes(:score => 0) message = "Clear!" redirect(message) end rescue_from CanCan::AccessDenied do |exception| message = "You have already voted" redirect(message) end private def type @type = Post.find(params[:post_id]) @type = Comment.find(params[:comment_id]) if params.include?(:comment_id) end def redirect(msg) respond_to do |format| format.text { render text: msg } end end endКак-то так, получилось (я уже забыл на самом деле, что там, и этот контроллер уже переписан, но про это в след. постах)
Однако тут мы сперва используем фильт before_filter, чтобы установить тип модели, или это пост или комментарий. Вот и всё
что важно в этом контроллере.
Представление исправте сами (ДЗ, ага). Не забывайте про роутинг:
#config/routes.rb resources :posts do resources :vote, :only => [:update] do delete 'clear' end resources :comments, :only => [:create, :destroy] do resources :vote, :only => [:update] do delete 'clear' end end endПолучается Vote вложен в post и comment.
Когда будете формировать линки для учитывайте это, посмотрите, что должно быть rake routes.
Вот как бы и всё про полиморфные модели, больше информации в api.
Комментариев нет:
Отправить комментарий