Polymorphic Association in Rails 5
Một trong những chủ đề mà các bạn mới bắt đầu với rails thường gặp khó khăn là Polymorphic Association trong Rails, bài viết sau hướng dẫn các bạn cách viết chức năng comment cho các object khác nhau dùng Polymorphic Association. Polymorphic Associations là gì? Quan hệ đa hình, khái niệm của ...
Một trong những chủ đề mà các bạn mới bắt đầu với rails thường gặp khó khăn là Polymorphic Association trong Rails, bài viết sau hướng dẫn các bạn cách viết chức năng comment cho các object khác nhau dùng Polymorphic Association.
Polymorphic Associations là gì?
Quan hệ đa hình, khái niệm của quan hệ này khá đơn giản: bạn có một model có thể thuộc về nhiều model khác nhau trong một liên kết duy nhất, (a model can belong to more than one other model, on a single association)
Starter Rails 5 App
Giả sử chúng ta có 3 model article, event và photo. Bây giờ chúng ta muốn tạo comment cho article, event và photo. Comment khá tương tự, ngoại trừ việc chúng thuộc vào những model khác nhau. Đây là lúc Polymorphic Associations phát huy tác dụng.
Creat new Rails 5 project
rails new polym_demo
Chúng ta tạo các model (article, event và photo):
rails g model article name content:text
rails g model event name starts_at:datetime ends_at:datetime description:text
rails g model photos name filename
Chúng ta tạo các table bằng cách chạy lệnh:
rails db:migrate
Chúng ta tạo các controller:
rails g controller articles
rails g controller events
rails g controller photos
Define the resources in routes.rb
Rails.application.routes.draw do resources :photos resources :events resources :articles root to: 'articles#index' end
Polymorphic Association Tạo model comment
rails g model comment content:text commentable_id:integer:index commentable_type:index
commentable_id chính là foreign key để thết lập quan hệ với các table khác. Dần dần, commentable_type sẽ chứa tên thật của model (có comment tương ứng). Migration:
class CreateComments < ActiveRecord::Migration[5.0] def change create_table :comments do |t| t.text :content t.integer :commentable_id t.string :commentable_type t.timestamps end add_index :comments, :commentable_id add_index :comments, :commentable_type end end
Có thể viết lại thành:
class CreateComments < ActiveRecord::Migration[5.0] def change create_table :comments do |t| t.text :content t.belongs_to :commentable, polymorphic: true t.timestamps end add_index :comments, :commentable_id add_index :comments, :commentable_type end end
Comment model sẽ có liên kết belongs_to, nhưng với một thay đổi nhỏ:
belongs_to :commentable, polymorphic: true
Add
has_many :comments, as: :commentable
vào các model article, event và photo. :as là một tùy chọn đặc biệt, giải thích rằng “đây là liên kết đa hình. Giờ, hãy boot console và thử chạy:
a = Article.create({name: 'test', content: 'this is test'}) a.comments.create({content: "this is comment"})
kết quả
=> #<Comment id: 1, content: "this is comment", commentable_id: 1, commentable_type: "Article", created_at: "2016-12-29 23:06:18", updated_at: "2016-12-29 23:06:18">
giờ chúng ta đã có thể sử dụng polymorphic association.
Tạo cotroller comments
rails g controller comments index new
Define the nested resources in routes.rb.
Rails.application.routes.draw do resources :photos do resources :comments end resources :events do resources :comments end resources :articles do resources :comments end root to: 'articles#index' end
code file 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, notice: "Comment created." else render :new end end private def load_commentable resource, id = request.path.split('/')[1,2] @commentable = resource.singularize.classify.constantize.find(id) end def comment_params params.require(:comment).permit! end end
code file articles_controller.rb
class ArticlesController < ApplicationController before_action :load_article, only: [:show, :edit, :update, :destroy] def index @articles = Article.all end def show @commentable = @article @comments = @commentable.comments @comment = Comment.new end def new @article = Article.new end def edit end def create @article = Article.new article_params if @article.save redirect_to @article, notice: "Article was successfully created." else render :new end end def update if @article.update_attributes(params[:article]) redirect_to @article, notice: "Article was successfully updated." else render :edit end end def destroy @article.destroy redirect_to articles_url, notice: "Article was destroyed." end private def load_article @article = Article.find(params[:id]) end def article_params binding.pry params.require(:article).permit! end end
Trong view chúng ta sẽ có các file sau file comments/_comments.html.erb
<div id="comments"> <% @comments.each do |comment| %> <div class="comment"> <%= simple_format comment.content %> </div> <% end %> </div>
file comments/_form.html.erb
<%= form_for [@commentable, @comment] do |f| %> <% if @comment.errors.any? %> <div class="error_messages"> <h2>Please correct the following errors.</h2> <ul> <% @comment.errors.full_messages.each do |msg| %> <li><%= msg %></li> <% end %> </ul> </div> <% end %> <div class="field"> <%= f.text_area :content, rows: 8 %> </div> <div class="actions"> <%= f.submit %> </div> <% end %>
file comments/index.html.erb
<h1>Comments</h1> <%= render 'comments' %> <p><%= link_to "New Comment", [:new, @commentable, :comment] %></p>
file comments/new.html.erb
<h1>New Comment</h1> <%= render 'form' %>
file articles/_form.html.erb
<%= form_for(@article) do |f| %> <% if @article.errors.any? %> <div id="error_explanation"> <h2><%= pluralize(@article.errors.count, "error") %> prohibited this article from being saved:</h2> <ul> <% @article.errors.full_messages.each do |msg| %> <li><%= msg %></li> <% end %> </ul> </div> <% end %> <div class="field"> <%= f.label :name %><br /> <%= f.text_field :name %> </div> <div class="field"> <%= f.label :content %><br /> <%= f.text_area :content %> </div> <div class="actions"> <%= f.submit %> </div> <% end %>
file articles/edit.html.erb
<h1>Editing article</h1> <%= render 'form' %> <%= link_to 'Show', @article %> | <%= link_to 'Back', articles_path %>
file articles/index.html.erb
<h1>Articles</h1> <div id="articles"> <% @articles.each do |article| %> <h2><%= link_to article.name, article %></h2> <div class="content"><%= simple_format(article.content) %></div> <% end %> </div> <p><%= link_to "New Article", new_article_path %></p>
file articles/new.html.erb
<h1>New article</h1> <%= render 'form' %> <%= link_to 'Back', articles_path %>
file articles/show.html.erb
<h1><%= @article.name %></h1> <%= simple_format @article.content %> <p><%= link_to "Back to Articles", articles_path %></p> <h2>Comments</h2> <%= render "comments/comments" %> <%= render "comments/form" %>
Chạy rails s để kiểm tra kết quả
- chúng ta tạo new article
- chúng ta test chức năng comment article, kết quả hình bên dưới Các chức năng comment cho Event và Photo tương tự.
Kết Luận
Đây là một ví dụ cơ bản của Polymorphic Associations . Hi vọng nó sẽ giúp được cho các bạn mới bắt đầu để làm việc tốt hơn với Rails.