Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

✓ Kazunari Takahashi ✓ Yokohama.rb ✓ 2012 Ruby kaja award winner ✓ MinatoRubyKaigi01 organizer ✓ TDDBC Yokohama TA @1syo

Slide 3

Slide 3 text

νΣοΫ ✓ Anvil͸ىಈ͍ͯ͠Δ͔ ✓ http://bad.dev/admin/usersͷλϒ͸͋Δ͔ ✓ http://good.dev/admin/usersͷλϒ͸͋Δ͔ ✓ gist(bad/good)͸tweet͔ͨ͠ ✓ ֤؀ڥͰdb:setup͸࣮ߦ͔ͨ͠ʁ

Slide 4

Slide 4 text

formͷઃܭΛͲ͏͢Ε͹͍͍͔ʁ ✓ ೖ໳ॻʹࡌͬͯͳ͍ ✓ ೖ໳ॻͷ͕࣍গͳ͍ ✓ Rails Cast͘Β͍? ✓ ྑ͍։ൃݱ৔Ͱڞ༗͞Ε͍ͯΔ

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

͋ΔγεςϜͷ࣮૷

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

!

Users

! <% if flash[:notice].present? %>!
<%= flash[:notice] %>
! <% end %>! <%= form_tag(upload_admin_users_path, ! method: :post, multipart: true, role: "form", ! ! ! ! class: "form-inline") do %>!
! <%= file_field_tag :data_file, class: "form-control" %>!
!
! <%= button_tag "Upload", class: ["btn","btn-default"] %>!
!
! <% end %>! index.html.erb

Slide 12

Slide 12 text

ϦϦʔεͯ͠ΈͨΒ ✓ ϑΝΠϧࢦఆ͠ͳ͍ͱΤϥʔʹͳΓ·͢ ✓ ը૾͕Ξοϓϩʔυ͢ΔͱΤϥʔʹͳΓ·͢ ✓ ○○͞ΜͷemailΞυϨε͕͓͔͍͠Ͱ͢ ✓ ΤΫηϧ͔Β࡞ͬͨCSV͕औΓࠐΊ·ͤΜ ✓ ࡢ೔ొ࿥ͨ͠ϑΝΠϧΛΞοϓϩʔυͪ͠Ό͍·ͨ͠

Slide 13

Slide 13 text

demo

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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!

Slide 16

Slide 16 text

f = params[:data_file]! if f.blank?! ! flash[:notice] = 'File not found'! ! redirect_to action: :index! return! end Admin:UsersController

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

✓ Controllerʹ࣮૷͕௥Ճ͞ΕΔ ✓ Validationʹແཧ͕͋Δ

Slide 19

Slide 19 text

Ͳ͏͢Ε͹ྑ͍ͷ͔ʁ

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

[email protected] ۭ৚ ঝଠ࿠ [email protected] ՖژӃ య໌ CSVϑΝΠϧશମ͸UserImportϞσϧ Ϟσϧ͸ͭ ֤ߦ͸UserϞσϧ

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

ScaffoldͬΆ͍Controller

Slide 24

Slide 24 text

<%= form_for(@user_import, url: import_admin_users_path,! html: {method: :post, multipart: true}) do |f| %>! <% if @user_import.errors.any? %>!
!
    ! <% @user_import.errors.full_messages.each do |msg| %>!
  • <%= msg %>
  • ! <% end %>!
!
! <% end %>! <% end %>! … index.html.erb

Slide 25

Slide 25 text

form_forΛ࢖͏

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

ActiveModel::ModelΛ࢖͏

Slide 30

Slide 30 text

Custom ValidationΛ࡞Δ

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

UserϞσϧͷerrorsΛ UserImportϞσϧͷerrorsʹ add͢Δ

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

demo

Slide 35

Slide 35 text

ςετΛͲ͏ॻ͘ʁ

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

·ͱΊ ✓ form_forͰݕ౼ͯ͠ΈΔ ✓ ScaffoldͰͰ͖ͨControllerͷઃܭΛ·ͶΔ ✓ ActiveModel::ModelΛ࢖͏ ✓ Custom ValidationΛ࢖͏

Slide 40

Slide 40 text

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