Popular Posts

суббота, 31 марта 2012 г.

Полиморфные отношения. (Polymorphic Association)


Привет.
Сегодня рассмотрим как использовтаь полиморфные отношения в 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.

Комментариев нет:

Отправить комментарий