Rails中的多态关联polymorphic associations

多态关联(polymorphic associations)算是个挖了很久的坑,一直没填,这次填了这个坑。

是什么?

Polymorphic Associations help when one table must belongs_to multiple other tables

大白话可以这样理解:当存在多个model 同时 has_many 一个model时,可以使用多态关联,来实现model层面的DRY。【我是这样理解的……】

官方给到的图解案例是这样滴:

Employee和Product同时拥有多个pictures,Picture里面必须包含两个字段:imageable_id,imageable_type。这样就不需要新增EmployeePicture和ProductPicture两个model了,只需要一个Picture就可以了。

这么说好像没啥感觉,通过一个小例子来实战一下。

怎么玩?

  • 新建一个rails项目 poly, 有article,event和comment,用户可以对article或者event进行评论,单个article,event可以拥有多个comments。

    rails new poly
    cd poly
    rails g model article title:string content:text
    rails g model event title:string start_at:datetime end_at:datetime description:text
    rake db:migrate
    

    记得重启服务器:rails s

    建controller:

    rails g controller articles
    rails g controller events
    

    完成ArticlesController,EventsController中的7个action,这里就不一一列出了。

    修改routes.rb

    Rails.application.routes.draw do
      resources :events
      resources :articles
      root "articles#index"
    end
    

    使用seeds或者task,生成一些测试数据,这里我用了seeds。

    添加articles和events的index,show页面。

    浏览器打开localhost:3000,类似这样:

    现在请上comment。

  • 建model comment

    运行:

    rails g model comment
    

    修改生成的migrate文件如下:

    class CreateComments < ActiveRecord::Migration[5.1]
      def change
        create_table :comments do |t|
          t.text :content
          t.belongs_to :commentable, polymorphic: true
          t.timestamps
        end
        add_index :comments, [:commentable_id, :commentable_type]
      end
    end
    

    这里t.belongs_to :commentable, polymorphic: true等同与

          t.integer :commentable_id
          t.string :commentable_type
    

    运行rake db:migrate, 重启服务器。

  • 给article,event,comment建立联系:

    修改app/models/article.rb, app/models/event.rb,添加上:

    has_many :comments, as: :commentable
    

    好,我们去rails console 看看是不是可以给article和event添加comments。

    可以看到成功为article创建了comment。

    修改app/controllers/articles_controller.rb的show部分:

      def show
        @article = Article.find(params[:id])
        @comments = @article.comments
      end
    

    修改app/views/articles/show.html.erb

    <div class="container-fluid">
      <h1><%= @article.title %></h1>
      <p><%= @article.content %></p>
      <ul>
        <% if @comments %>
            <% @comments.each do |comment| %>
                <li><%= comment.content %></li>
            <% end %>
        <% end %>
      </ul>
    </div>
    

    打开http://localhost:3000/articles/0,可以看到刚刚添加的那个comment:

  • DRY comment view

    新建comments controller:

    rails g controller comments
    

    修改app/controllers/comments_controller.rb, 内容如下:

    class CommentsController < ApplicationController
      before_action :load_commentable
    
      def index
        @comments = @commentable.comments
      end
    
      def new
        @comment = @commentable.comments.new
      end
    
      def create
        @comment = @commentable.comments.new(comment_params)
        if @comment.save
          redirect_to @commentable
        else
          render :new
        end
      end
    
      private
    
      def load_commentable
        resource, id = request.path.split('/')[1,2]
        @commetable = resource.singularize.classify.constantize.find(id)
      end
    
      def comment_params
        params.require(:comment).permit(:content)
      end
    end
    

    修改routes.rb

    Rails.application.routes.draw do
      # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
    
      resources :events do
        resources :comments
      end
      resources :articles do
        resources :comments
      end
      root "articles#index"
    end
    

    新建 app/views/comments/_form.html.erb:

    内容如下:

    <%= form_for [@commentable, @comment] do |f| %>
        <div class="field">
          <%= f.text_area :content %>
        </div>
        <div class="action">
          <%= f.submit "sumbit" %>
        </div>
    <% end %>
    

    修改app/controllers/articles_controller.rb的show部分:

      def show
        @commentable = Article.find(params[:id])
        @comments = @commentable.comments
        @comment =  @commentable.comments.new
      end
    

    修改app/views/articles/show.html.erb:

    <div class="container-fluid">
      <h1><%= @article.title %></h1>
      <p><%= @article.content %></p>
      <ul>
        <% if @comments %>
            <% @comments.each do |comment| %>
                <li><%= comment.content %></li>
            <% end %>
        <% end %>
      </ul>
    
      <%= render "comments/form" %>
    </div>
    

    针对event做同样的处理,这样create comment就可以重复使用了。

    看看events:

OK!! 基本功能实现,想要美观地码楼的话,就要用到Ajax部分了,继续写就没完没了了 ,下次在码字: P

参考

Polymorphic Associations in Rails 5

Polymorphic Association in Rails 5