Khắc phục DRY code trong Rails
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 ...
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