Khắc phục DRY code trong Rails
Bài đăng này đã không được cập nhật trong 7 năm
Khi bạn làm việc với Rails có rất nhiều đoạn code xử lý giống nhau trong các controllers hay models khiến việc DRY code khá nhiều và đọc thấy rất khó chịu. Bạn có bao giờ để ý đến thư mục concerns không. Folder này nằm trong app/controllers và app/models nó sẽ là cứu cánh để xử lý DRY code Trong bài viết này mình sẽ khắc phục bằng ActiveSupport::Concern (concerns)
Build một app demo
Trong app này mình sẽ sử dụng bootstrap và devise. Thực hiện các bước sau nhé.
rails new demo
thêm hai gem và chạy câu lệnh bundle
gem "devise"
gem "bootstrap-sass"
Tiếp theo cài đặt Devise rails g devise:install
giờ hãy create một model Admin với devise rails g devise Admin
Giờ tạo một partion app/views/layouts/_navigation.html.erb với nội dung
#app/views/layouts/_navigation.html.erb
<nav class="navbar navbar-default">
<div class="container-fluid">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar-collapse" aria-expanded="false">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<% if admin_signed_in? %>
<%= link_to "Twik", admin_products_path, class: "navbar-brand" %>
<% else %>
<%= link_to "Twik", root_path, class: "navbar-brand" %>
<% end %>
</div>
<div class="collapse navbar-collapse" id="navbar-collapse">
<ul class="nav navbar-nav navbar-right">
<li><%= link_to 'Home', root_path %></li>
<% if admin_signed_in? %>
<li><%= link_to "My Account", edit_admin_registration_path %></li>
<li><%= link_to "Logout", destroy_admin_session_path, method: :delete %></li>
<% else %>
<li><%= link_to "Login", new_admin_session_path %></li>
<% end %>
</ul>
</div>
</div>
</nav>
Trong file application.html.erb bạn phải render menu chúng ta vừa tạo bằng cách thêm đoạn code này vào
<%= render "layouts/navigation" %>
products Controllers and Concerns
Chúng ta muôn viết ít code nhất có thể và chắc chắn một điều là chúng không bị lăp lại. Concern sẽ gom những đoạn code thực thi chức năng giống nhau trong mỗi controllers lại. Oke giờ đi vào cụ thể.
- tạo một product modle:
rails g model product description:text
- tiếp theo là controller cho nó
rails g controller productsController.
- create một file twiable.rb trong app/controllers/concerns với nọi dung
#app/controllers/concerns/productable.rb
module productable
extend ActiveSupport::Concern
included do
before_action :set_product, only: [:show, :edit, :destroy, :update]
end
def index
@products = product.all
end
def new
@product = product.new
end
def show
end
def create
@product = product.new product_params
if @product.save
flash[:notice] = "Successfully created product."
redirect_to @product
else
flash[:alert] = "Error creating product."
render :new
end
end
private
def product_params
params.require(:product).permit :description
end
def set_product
@product = product.find params[:id]
end
end
Đoạn code trên extend ActiveSupport::Concern có nghĩa là chúng ta đang tạo một concern. Trong include block sẽ được thực thi mỗi khi module này được include vào trong mỗi controller cần dung. Đây nó như là các function bên thứ 3. Giờ mình sẽ tiến hành include Twiable vào trong productsController.
class productsController < ApplicationController
include productable
end
Chúng ta cũng cần mộ namespace cho admin cho product. Để chỉ có admin mới có quyền them sửa xóa product
rails g controller admin/product
.
class Admin::productsController < ApplicationController
include productable
def edit
end
def update
if @product.update_attributes product_params
flash[:notice] = "Successfully updated product."
redirect_to admin_product_path
else
flash[:alert] = "Error creating product."
render :edit
end
end
def destroy
if @product.destroy
flash[:notice] = "Successfully deleted product."
redirect_to products_path
else
flash[:alert] = "Error deleting product."
end
end
end
đấy chúng ta đã không con lặp code nữa rồi. giờ hãy tạo view để thấy nó làm việc
mkdir -p app/views/admin/products
touch app/views/admin/products/index.html.erb
touch app/views/admin/products/new.html.erb
touch app/views/admin/products/show.html.erb
touch app/views/admin/products/edit.html.erb
touch app/views/products/new.html.erb
touch app/views/products/show.html.erb
touch app/views/products/index.html.erb
paste lần lượt các đoạn dưới đây vào: Admin product Edit Page
#app/views/admin/products/edit.html.erb
<div class="container-fluid">
<div class="row">
<div class="col-sm-offset-4 col-sm-4 col-xs-12">
<%= form_for @product, :url => {:controller => "products", :action => "update" } do |f| %>
<div class="form-group">
<%= f.label :description %>
<%= f.text_field :description, class: "form-control" %>
</div>
<div class="form-group">
<%= f.submit "Update", class: "btn btn-primary" %>
<%= link_to "Cancel", :back, class: "btn btn-default" %>
</div>
<% end %>
</div>
</div>
</div>
Admin product Index Page
#app/views/admin/products/index.html.erb
<div class="container-fluid">
<p id="notice"><%= notice %></p>
<h1>Listing products</h1>
<div class="row">
<div class="col-sm-12 col-xs-12">
<%= link_to "New description", new_admin_product_path, class: "btn btn-primary pull-right" %>
</div>
</div>
<div class="row">
<div class="col-sm-12 col-xs-12">
<div class="table-responsive">
<table class="table table-striped table-bordered table-hover">
<tbody>
<% @products.each do |product| %>
<tr>
<td class="col-sm-8 col-xs-8"><%= product.description %></td>
<td class="col-sm-4 col-xs-4"><%= link_to 'Show', admin_product_path(product), class: "btn btn-primary" %>
<%= link_to 'Edit', edit_admin_product_path(product), class: "btn btn-default" %>
<%= link_to "Delete", admin_product_path(product), class: "btn btn-danger", data: {:confirm => "Are you sure?"}, method: :delete %>
</td>
</tr>
<% end %>
</tbody>
</table>
</div>
</div>
</div>
</div>
Admin New product Page
#app/views/admin/products/new.html.erb
<div class="container-fluid">
<div class="row">
<div class="col-sm-offset-4 col-sm-4 col-xs-12">
<%= form_for @product do |f| %>
<div class="form-group">
<%= f.label :description %>
<%= f.text_field :description, class: "form-control" %>
</div>
<div class="form-group">
<%= f.submit "Submit", class: "btn btn-primary" %>
<%= link_to "Cancel", :back, class: "btn btn-default" %>
</div>
<% end %>
</div>
</div>
</div>
Admin product Show Page
#app/views/admin/products/show.html.erb
<div>
<h2><%= @product.description %></h2>
</div>
product Index Page
#app/views/products/index.html.erb
<div class="container-fluid">
<p id="notice"><%= notice %></p>
<h1>Listing products</h1>
<div class="row">
<div class="col-sm-12 col-xs-12">
<%= link_to "New description", new_product_path, class: "btn btn-primary pull-right" %>
</div>
</div>
<div class="row">
<div class="col-sm-12 col-xs-12">
<div class="table-responsive">
<table class="table table-striped table-bordered table-hover">
<tbody>
<% @products.each do |product| %>
<tr>
<td><%= product.description %></td>
<td><%= link_to 'Show', product, class: "btn btn-primary" %></td>
</tr>
<% end %>
</tbody>
</table>
</div>
</div>
</div>
</div>
New product Page
#app/views/products/new.html.erb
<div class="container-fluid">
<div class="row">
<div class="col-sm-offset-4 col-sm-4 col-xs-12">
<%= form_for @product do |f| %>
<div class="form-group">
<%= f.label :description %>
<%= f.text_field :description, class: "form-control" %>
</div>
<div class="form-group">
<%= f.submit "Submit", class: "btn btn-primary" %>
<%= link_to "Cancel", :back, class: "btn btn-default" %>
</div>
<% end %>
</div>
</div>
</div>
product Show Page
#app/views/products/show.html.erb
<div>
<h2><%= @product.description %></h2>
</div>
Giờ bạn chạy rails s lên và thấy mọi thứ làm việc thật sự tốt. Code trong controller điều hướng của bạn thật sự gọn gang nhờ concern.
Concerns in Models
Phần trước ta đã thấy thực hiện viết code trong controller. Giờ là đến Models nó cũng tương tự vậy thôi. Nếu bạn thực sự hiểu cách implement trong controller thì bạn sẽ biết cách thực hiện nó trong model vậy. Hãy nhóm những đoạn thực thi các function dung chung và viết chúng vào block include. Cuối cũng nhưng model nào sử dụng thì include modul concern này vào
Kết luận
Nhờ ActiveSupport::Concern mà bạn có thể refactor lại code của mình tránh được DRY code, trông có vẻ sang sủa và tối ưu dễ đọc. Hy vọng sẽ giúp ích được bạn
All rights reserved