$30 off During Our Annual Pro Sale. View Details »

普通のCSVアップロードフォームを作りたい(改)

TAKAHASHI Kazunari
August 20, 2014
930

 普通のCSVアップロードフォームを作りたい(改)

TAKAHASHI Kazunari

August 20, 2014
Tweet

Transcript

  1. ී௨ͷ CSVΞοϓϩʔυ ϑΥʔϜΛ࡞Γ͍ͨ վ

  2. ✓ Kazunari Takahashi ✓ Yokohama.rb ✓ 2012 Ruby kaja award

    winner ✓ MinatoRubyKaigi01 organizer ✓ TDDBC Yokohama TA @1syo
  3. νΣοΫ ✓ Anvil͸ىಈ͍ͯ͠Δ͔ ✓ http://bad.dev/admin/usersͷλϒ͸͋Δ͔ ✓ http://good.dev/admin/usersͷλϒ͸͋Δ͔ ✓ gist(bad/good)͸tweet͔ͨ͠ ✓

    ֤؀ڥͰdb:setup͸࣮ߦ͔ͨ͠ʁ
  4. formͷઃܭΛͲ͏͢Ε͹͍͍͔ʁ ✓ ೖ໳ॻʹࡌͬͯͳ͍ ✓ ೖ໳ॻͷ͕࣍গͳ͍ ✓ Rails Cast͘Β͍? ✓ ྑ͍։ൃݱ৔Ͱڞ༗͞Ε͍ͯΔ

  5. ྑ͍։ൃݱ৔Ͱ ڞ༗͞Ε͍ͯΔϊ΢ϋ΢Λ ίϛϡχςΟʔͰڞ༗͍ͨ͠

  6. γφϦΦ ✓ ؅ཧऀ͕Ϣʔβʔ৘ใҰׅొ࿥͢Δ ✓ ෳ਺ͷϢʔβʔ৘ใ͕CSVϑΝΠϧʹ͋Δ ✓ CSVϑΝΠϧͷத਎͸ࢯ໊ͱemail

  7. None
  8. class CreateUsers < ActiveRecord::Migration! def change! create_table :users do |t|!

    t.string :first_name, null:false! t.string :last_name, null:false! t.string :email, null:false! t.timestamps! end! ! add_index :users, :email, unique: true! end! end Users
  9. ͋ΔγεςϜͷ࣮૷

  10. class Admin::UsersController < ApplicationController! def index! end! ! def upload!

    f = params[:data_file]! flash[:notice] = 'Upload successful'! CSV.open(f.path, 'rb', encoding: 'UTF-8').each do |row|! User.create(! ! ! ! email: row[0], ! ! ! ! first_name: row[1], ! ! ! ! last_name: row[2]! ! ! )! end! redirect_to action: :index! end! end Admin:UsersController
  11. <div class="container">! <h1>Users</h1>! <% if flash[:notice].present? %>! <div class="bg-success"><%= flash[:notice]

    %></div>! <% end %>! <%= form_tag(upload_admin_users_path, ! method: :post, multipart: true, role: "form", ! ! ! ! class: "form-inline") do %>! <div class="form-group">! <%= file_field_tag :data_file, class: "form-control" %>! </div>! <div class="form-group">! <%= button_tag "Upload", class: ["btn","btn-default"] %>! </div>! </div>! <% end %>! </div> index.html.erb
  12. ϦϦʔεͯ͠ΈͨΒ ✓ ϑΝΠϧࢦఆ͠ͳ͍ͱΤϥʔʹͳΓ·͢ ✓ ը૾͕Ξοϓϩʔυ͢ΔͱΤϥʔʹͳΓ·͢ ✓ ◦◦͞ΜͷemailΞυϨε͕͓͔͍͠Ͱ͢ ✓ ΤΫηϧ͔Β࡞ͬͨCSV͕औΓࠐΊ·ͤΜ ✓

    ࡢ೔ొ࿥ͨ͠ϑΝΠϧΛΞοϓϩʔυͪ͠Ό͍·ͨ͠
  13. demo

  14. ཁ͕݅଍Γͳ͍ ✓ ̎࣠ͷόϦσʔγϣϯ ✓ ϑΝΠϧϑΥʔϚοτ ✓ Ϩίʔυݸผ ✓ Ξϥʔτը໘ ✓

    ςετ..
  15. def upload! f = params[:data_file]! if f.blank?! flash[:notice] = 'File

    not found'! redirect_to action: :index! return! end! ! flash[:notice] = 'Upload successful'! CSV.open(f.path, 'rb', encoding: 'UTF-8').each do |row|! unless row[0] =~ /\A[a-z.-_]+@[a-z.-_]+\z/! next! end! ! User.create(! email: row[0], first_name: row[1], last_name: row[2])! end! redirect_to action: :index! end Admin:UsersController Added!
  16. f = params[:data_file]! if f.blank?! ! flash[:notice] = 'File not

    found'! ! redirect_to action: :index! return! end Admin:UsersController
  17. CSV.open(f.path, 'rb', encoding: 'UTF-8').each do |row|! unless row[0] =~ /\A[a-z.-_]+@[a-z.-_]+\z/!

    next! end! end Admin:UsersController
  18. ✓ Controllerʹ࣮૷͕௥Ճ͞ΕΔ ✓ Validationʹແཧ͕͋Δ

  19. Ͳ͏͢Ε͹ྑ͍ͷ͔ʁ

  20. Ϟσϧʹ͢Δ https://gist.github.com/1syo/319f237637cf522c8c9f

  21. jojo@example.com ۭ৚ ঝଠ࿠ kakyoin@example.com ՖژӃ య໌ CSVϑΝΠϧશମ͸UserImportϞσϧ Ϟσϧ͸ͭ ֤ߦ͸UserϞσϧ

  22. class Admin::UsersController < ApplicationController! def index! @user_import = UserImport.new! end!

    ! def import! @user_import = UserImport.new(params[:user_import])! if @user_import.save! redirect_to url_for(action: :index), ! ! ! ! ! notice: 'Upload successful'! else! render :index! end! end! end admin/users_controller.rb
  23. ScaffoldͬΆ͍Controller

  24. <%= form_for(@user_import, url: import_admin_users_path,! html: {method: :post, multipart: true}) do

    |f| %>! <% if @user_import.errors.any? %>! <div class="alert alert-danger">! <ul>! <% @user_import.errors.full_messages.each do |msg| %>! <li><%= msg %></li>! <% end %>! </ul>! </div>! <% end %>! <% end %>! … index.html.erb
  25. form_forΛ࢖͏

  26. ΤοϥʔϝοηʔδΛ දࣔ͢Δ

  27. class UserImport! CSV_OPTIONS = {encoding: 'UTF-8'}.freeze! include ActiveModel::Model! ! attr_accessor

    :file! ! validates :file, presence: true! validate :csv_format, ! ! ! ! if: Proc.new { |m| m.file.present? }! ! def save; end! ! private! def csv_format;end! def path; end! end user_import.rb
  28. class User < ActiveRecord::Base! validates :first_name, ! ! ! length:

    { maximum: 100 }, presence: true! validates :last_name, ! ! ! length: { maximum: 100 }, presence: true! validates :email, ! ! ! length: { maximum: 100 }, presence: true, uniqueness: true! end user.rb
  29. ActiveModel::ModelΛ࢖͏

  30. Custom ValidationΛ࡞Δ

  31. def save! return false unless valid?! ! User.transaction do! CSV.read(path,

    "r", CSV_OPTIONS).each_with_index do |row, index|! user = User.new(! ! ! ! email: row[0], ! ! ! ! first_name: row[1], ! ! ! ! last_name: row[2]! ! ! )! unless user.save! errors.add(! ! ! ! ! "line #{index+1} : ", ! ! ! ! ! user.errors.full_messages.join(" , “)! ! ! ! )! end! end! ! if errors.any?! raise ActiveRecord::Rollback! end! end! ! !errors.any?! end user_import.rb
  32. UserϞσϧͷerrorsΛ UserImportϞσϧͷerrorsʹ add͢Δ

  33. private! def csv_format! CSV.open(path, "r", CSV_OPTIONS) { |csv| }! rescue!

    errors.add(:file, "is invalid csv format")! end! ! def path! if file.respond_to? :path! file.path! else! file! end! end user_import.rb
  34. demo

  35. ςετΛͲ͏ॻ͘ʁ

  36. describe "#save" do! subject { UserImport.new(file: file) }! context "༗ޮͳϑΥʔϚοτͷ࣌"

    do! let(:file) do! ! ! Rails.root.join(“spec/fixtures/valid.csv")! ! ! end! let(:error_count) { CSV.read(file).count }! it { expect(subject.save).to be_truthy }! it do! ! ! expect { subject.save }.to! ! ! ! ! change(User, :count).by(error_count)! ! ! end! end! end user_import_spec.rb
  37. RSpec.describe UserImport do! describe "#save" do! subject { UserImport.new(file: file)

    }! context "UserϞσϧͰΤϥʔͰ͕͋Δ࣌" do! let(:file) do! ! ! Rails.root.join(“spec/fixtures/valid.csv")! ! ! end! let(:errors) { double(add: nil, clear: nil, empty?: nil) }! before do! allow_any_instance_of(User).to receive(:save) { false }! expect(subject).to receive(:errors) { errors }.twice! end! it { expect(subject.save).to be_falsey }! it do! ! ! expect { subject.save }.not_to ! ! ! ! change(User, :count)! ! ! end! end! end! end user_import_spec.rb
  38. RSpec.describe Admin::UsersController do! describe "POST import" do! let(:attributes) { {

    file: Rack::Test::UploadedFile.new(file, "text/plan") } }! ! describe "with valid params" do! let(:file) { Rails.root.join("spec/fixtures/valid.csv") }! let(:record_count) { CSV.read(file).count }! ! it "creates new Users" do! expect {! post :import, {user_import: attributes}! }.to change(User, :count).by(record_count)! end! ! it "redirects to the created user" do! post :import, {user_import: attributes}! expect(response).to redirect_to(action: :index)! end! end! end! end admin/users_controller_spec.rb
  39. ·ͱΊ ✓ form_forͰݕ౼ͯ͠ΈΔ ✓ ScaffoldͰͰ͖ͨControllerͷઃܭΛ·ͶΔ ✓ ActiveModel::ModelΛ࢖͏ ✓ Custom ValidationΛ࢖͏

  40. ͋ͳͨͳΒͲ͏࣮૷͢Δ͔ ڭ͍͑ͯͩ͘͞