Polymorphic Association in Rails 5
Bài đăng này đã không được cập nhật trong 3 năm
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.
All rights reserved