Tạo Multistep Forms với gem wicked trong Rails
Chào các bạn mình là Bình, một sinh viên vừa mới tốt nghiệp, hôm nay mình xin chia sẻ với các bạn cách tạo một multistep form với gem wicked trong lập trình Ruby On Rails. Do chỉ là sinh viên mới tốt nghiệp, kinh nghiệm chưa có nhiều, lại là lần đầu viết bài trên viblo nên rất mong nhận được sự ...
Chào các bạn mình là Bình, một sinh viên vừa mới tốt nghiệp, hôm nay mình xin chia sẻ với các bạn cách tạo một multistep form với gem wicked trong lập trình Ruby On Rails. Do chỉ là sinh viên mới tốt nghiệp, kinh nghiệm chưa có nhiều, lại là lần đầu viết bài trên viblo nên rất mong nhận được sự đóng góp của mọi người.
Có lẽ mình sẽ không lan man nữa mà sẽ đi vào chủ đề. Như các bạn biết thì form là một đối tượng rất hay gặp trong lập trình web, thường thì form được sử dụng để tạo hoặc thay đổi dữ liệu. Thông thường việc tạo mới một đối tượng hay sử dụng duy nhất một form để nạp thông tin. Tuy nhiên có một số đối tượng có quá nhiều thông tin, nếu yêu cầu người dùng nhập quá nhiều thông tin trên một form thì đó là một điều không nên trong UX, một cách giải quyết đơn giản là chúng ta hãy chia nhỏ đối tượng đó ra thành nhiều phần nhỏ và cho phép người dùng cập nhật từng phần vào cơ sở dữ liệu. Kỹ thuật đó gọi là tạo multistep form.
Trong Rails có một gem khá hay giúp bạn nhanh chóng tạo được multistep form đó chính là gem wicked.
Bài toán: Giả sử mình có một product với 3 thông tin name, price, category. Giờ mình không muốn nhập cùng một lúc trên cùng một form mà mình muốn nhập name trước sau đó nhập tiếp price rồi cuối cùng nhập category. Lưu ý là name, price và category đều không được bỏ trống.
Khởi tạo project
Mở terminal và tạo một project Rails đơn giản.
rails new myapp
Tạo nhanh một products table
rails g scaffold product name:string price:string category:string
Nhớ đừng quên migrate data.
rake db:migrate
Trong file Gemfile thêm
gem "wicked"
Sau đó bundle install lại
bundle install
Các bước setup dự án coi như đã xong. Giờ tiến hành giải quyết bài toán.
Giải quyết bài toán
Ý tưởng đơn giản của gem wicked là khi tạo mới một product chúng ta sẽ tạo trước một product với dữ liệu trống (chỉ có id được tự sinh ra). Sau đó việc thêm name, price hay category chỉ đơn giản là việc cập nhật product vừa được tạo ra. Chúng ta sẽ validate dữ liệu từng phần, ví dụ phần thêm name thì sẽ chỉ validate cho name...Nào code thôi.
Đầu tiên mình sẽ tạo ra một controller cho các multiple step form.
rails g controller product/steps
Định nghĩa trong router.rb
resources :products, only: [:new, :create, :index, :destroy] do resources :steps, only: [:show, :update], controller: "product/steps" end root to: "products#index"
Tại model product.rb
Đầu tiên chúng ta sẽ định nghĩa một class attribute mô tả các steps sẽ có. Ở đây mình sẽ có 3 steps là add_name tương ứng với form thêm name vào product, tương tự với price và category.
cattr_accessor :form_steps do %w(add_name add_price add_category) end
Định nghĩa một attribute giả để đánh dấu vị trí của step
attr_accessor :form_step
Validates dữ liệu
validates :name, presence: true, if: -> { required_for_step?(:add_name) } validates :price, presence: true, if: -> { required_for_step?(:add_price) } validates :category, presence: true, if: -> { required_for_step?(:add_category) } def required_for_step? step return true if self.form_steps.index(step.to_s) == self.form_steps.index(form_step) end
Ý nghĩa của hàm required_for_step?(step) là so sánh vị trí của form_steps với vị trí attribute cần validate.
Trong steps_controller.rb
Khai báo include Wicked::Wizard và các steps form
class Product::StepsController < ApplicationController include Wicked::Wizard steps *Product.form_steps def show @product = Product.find params[:product_id] render_wizard end def update @product = Product.find params[:product_id] @product.update_attributes product_params(step) render_wizard @product end private def product_params step permitted_attributes = case step when "add_name" [:name] when "add_price" [:price] when "add_category" [:category] end params.require(:product).permit(permitted_attributes).merge(form_step: step) end end`
Ở đây thì có một vài chú ý là chúng ta sẽ bắt buộc phải khai báo các steps, trong hàm show và update chúng ta lấy product thông qua params[product_id]. Trong params chúng ta sẽ định nghĩa các params được permit theo từng step, thông qua biến step chúng ta có thể biết được chúng ta đang ở form nào. Ứng với mỗi một step sẽ có một view tương ứng. Giả sử chúng ta đang có 3 steps là :add_name, :add_price, :add_category tương ứng trong thư mục views/product/steps sẽ có 3 view add_name.html.erb, add_price.html.erb, add_category.html.erb.
Trong add_name.html.erb
<%= form_for @product, method: :put, url: wizard_path do |f| %> <% if @product.errors.any? %> <div id="error_explanation"> <h2><%= pluralize(@product.errors.count, "error") %> prohibited this product from being saved:</h2> <ul> <% @product.errors.full_messages.each do |message| %> <li><%= message %></li> <% end %> </ul> </div> <% end %> <div class="field"> <%= f.label :name %><br> <%= f.text_field :name %> </div> <div class="actions"> <%= f.submit "Next Step" %> </div> <% end %>
Tương tự với add_price.html.erb, add_category.html.erb.
Tại products_controller.rb, định nghĩa lại hàm create
def create @product = Product.new @product.save validate: false redirect_to product_step_path(@product, Product.form_steps.first) end
Kết luận
Như vậy mình đã kết thúc bài hướng dẫn tạo multi steps form với gem wicked. Tất nhiên trong khuôn khổ của 1 tutorial mình không thể giải thích quá chi tiết tất cả mọi thứ . Để hiểu rõ hơn về gem wicked, mời bạn đọc tại đây. Mã nguồn của mình ở đây.
Ngoài ra còn một vấn đề nữa mình muốn nói thêm đó là khi người dùng tạo product với chỉ id mà không tạo name, price hay category. Như vậy hệ thống của chúng ta sẽ dư thừa dữ liệu trống rất nhiều. Một giải pháp là sử dụng các tác vụ background như whenever, delayed job để xóa các dữ liệu đó.
Nhiệm vụ: Sau khi đọc qua tutorial này bạn hãy thử làm một vài thử thách sau :
1.Tạo trang edit cho product cũng multi steps form. 2.Thử tạo nút back giữa các form. 3.Thử tạo multi steps form mà không cần tạo trước object. 4.Thử tạo multi steps form mà không cần dùng gem.