Import CSV file dùng cho nested attributes trong rails sử dụng CSV framework
Bài đăng này đã không được cập nhật trong 8 năm
Mình xin giới thiệu cách để import csv file dùng cho nested attributes sử dụng csv framework.
Mục đích của việc import là có thể thuận tiên hơn cho việc chuẩn bị data đầu vào trong các hệ thống hoặc nhập báo cáo thông kê tài chính hàng tháng chẳng hạn... Nó sẽ nhanh và thuận tiện hơn rất nhiều so với các bạn nhập thủ công bằng tay. Vì với số lượng bản ghi lớn, nhập bằng tay sẽ mất rất nhiều thời gian, chưa kể còn có thể có sai sót trong quá trình nhập liệu.
Mình sẽ lấy ví dụ 1 bài toán đặt ra: bạn có 2 model question(id,content) và answer(id,content,is_correct,question_id), 1 question có thể có nhiều answer (từ 2 tới 5), và bạn muốn import question và answer bằng file CSV. Mình xin giới thiệu một cách làm đơn giản như sau:
- Đầu tiên trong file
config/application.rb
bạn thêmrequire "csv"
require "csv"
- Tiếp theo bạn tạo thêm controller Csv để import và export file CSV. Sau đó khai báo trong routes:
csv_controller.rb
class CsvController < ApplicationController
def create
Question.import params[:file]
redirect_to root_url, notice: "Question imported"
end
end
routes.rb
resources :csv, only: [:create]
- Trong
question.rb
khai báo Answers là nested attributes:
has_many :answers, dependent: :destroy, inverse_of: :question
accepts_nested_attributes_for :answers, allow_destroy: true,
reject_if: proc{|attributes| attributes["content"].blank?}
Trong answer.rb
khai báo thêm điều kiện:
belongs_to :question
Các bạn lưu ý là thêm inverse_of: :question
nếu không khi bạn import sẽ báo lỗi là "question must exist" do khi đó question chưa được thêm vào database.
- Trong file
question.rb
chúng ta thêm methodimport
:
class << self
def import file
CSV.foreach(file.path, headers: true, col_sep: "|", header_converters: :symbol) do |row|
row = row.to_hash
answers_attributes = []
row[:answers].split(";").each do |answer|
answer_hash = Hash.new
arr_answer = answer.split(",")
answer_hash[:content] = arr_answer[0]
answer_hash[:is_correct] = arr_answer[1]
answers_attributes.push answer_hash
end
row[:answers_attributes] = answers_attributes
row.delete :answers
Question.create! row
end
end
Mình xin giải thích qua một chút về đoạn code trên:
headers: true
bỏ qua dòng đầu tiên trong file csv (column_names), với mỗi dòng tiếp theo là tương ứng 1 record trong databaseheader_converters: :symbol
convert header: The header String is downcased, spaces are replaced with underscores, non-word characters are dropped, and finally to_sym() is called. (viết tiếng anh này mọi người sẽ dễ hiểu hơn)- col_sep: kí hiệu ngăn cách giữa các column trong csv file, ở đây mình dùng "|"
- mình tạo ra 1 hash là answers_attributes để lưu nested answers. file csv template:
content|answers
math|toan,true;ly,false
physical|toan,false;van,false;ly:true
ở đây các answer được ngăn cách nhau bằng đâu ";", và các attributes của answer được ngăn cách nhau bởi dấu "," nên mình xử lý như trên, cuối cùng mình xóa phần tử answers đi (vì thừa) và thực hiện create.
- Cuối cùng là tạo button để import:
<%= form_tag csv_create_path, multipart: true do %>
<%= file_field_tag :file %>
<%= submit_tag "Import" %>
<% end %>
Trên đây là phần hướng dẫn của mình. Nó còn nhiều hạn chế ví dụ như chưa validate được trong lúc import,... Mong các bạn góp ý thêm.
All rights reserved