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

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

TAKAHASHI Kazunari
July 12, 2014
2.7k

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

TAKAHASHI Kazunari

July 12, 2014
Tweet

Transcript

  1. ී௨ͷ
    $47Ξοϓϩʔυ
    ϑΥʔϜΛ࡞Γ͍ͨ
    !TZP
    :PLPIBNBSC

    View Slide

  2. ࠷ۙͷϚΠɾϒʔϜ
    㾎IVCPUTDSJQU
    㾎"JSCSBLF
    㾎5SBWJT$*
    㾎1JWPUBM5SBDLFS

    View Slide

  3. Ͳ͏͖
    㾎ೖ໳ॻʹ৐ͬͯͳ͍
    㾎ೖ໳ॻͷ͕࣍গͳ͍
    㾎3BJMT$BTU͘Β͍
    㾎ྑ͍։ൃݱ৔ͰͷΈڞ༗͞Ε͍ͯΔ

    View Slide

  4. Θ͔Δ͜ͱ
    㾎'BU$POUSPMMFSͱ͸ͳʹ͔ʁ
    㾎Ͳ͏͢Ε͹4LJOOZ$POUSPMMFSʹͳΔͷ͔ʁ

    View Slide

  5. γφϦΦ
    㾎؅ཧऀ͕Ұׅͯ͠ొ࿥
    㾎ෳ਺ͷϢʔβʔ৘ใ͕$47ϑΝΠϧʹ͋Δ
    㾎$47ϑΝΠϧͷத਎͸ࢯ໊ͱFNBJM

    View Slide

  6. View Slide

  7. 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
    6TFST

    View Slide

  8. ͋Γ͕ͪͳ࣮૷

    View Slide

  9. 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
    "ENJO6TFST$POUSPMMFS

    View Slide

  10. !
    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 %>!

    JOEFYIUNMFSC

    View Slide

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

    View Slide

  12. σϞ

    View Slide

  13. class Admin::UsersController < ApplicationController!
    def index!
    end!
    !
    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!
    end
    "ENJO6TFST$POUSPMMFS

    View Slide

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

    View Slide

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

    View Slide

  16. Ϟσϧʹ͢Ε͹͍͍
    IUUQTHJTUHJUIVCDPNTZPGDGDDG

    View Slide

  17. [email protected] ۭ৚ ဘॿ
    [email protected] ՖژӃ య໌
    શମ͸6TFS*NQPSUϞσϧ
    ֤ߦ͸6TFSϞσϧ
    ੹຿ΛΘ͚ͯΈΔ

    View Slide

  18. 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
    BENJOVTFST@DPOUSPMMFSSC

    View Slide

  19. !
    <% if flash[:notice] %>!
    !
    <%= flash[:notice] %>!
    !
    <% end %>!
    !
    Users!
    <%= form_for(@user_import, url: import_admin_users_path,!
    html: {method: :post, multipart: true, role: "form", class: "form-inline"}) do |f| %>!
    <% if @user_import.errors.any? %>!
    !
    !
    <% @user_import.errors.full_messages.each do |msg| %>!
    <%= msg %>!
    <% end %>!
    !
    !
    <% end %>!
    !
    !
    <%= f.file_field :file, class: "form-control" %>!
    !
    !
    <%= f.button "Upload", class: ["btn","btn-default"] %>!
    !
    <% end %>!

    JOEFYIUNMFSC

    View Slide

  20. 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
    VTFSSC

    View Slide

  21. 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
    VTFS@JNQPSUSC

    View Slide

  22. 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
    VTFS@JNQPSUSC

    View Slide

  23. 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
    VTFS@JNQPSUSC

    View Slide

  24. σϞ

    View Slide

  25. ςετΛͲ͏ॻ͘ʁ

    View Slide

  26. RSpec.describe UserImport do!
    describe "#file" do!
    ! …!
    end!
    !
    describe "#save" do!
    subject { UserImport.new(file: file) }!
    context "༗ޮͳϑΥʔϚοτͷ࣌" do!
    let(:file) { Rails.root.join("spec/fixtures/valid.csv") }!
    let(:error_count) { CSV.read(file).count }!
    it { expect(subject.save).to be_truthy }!
    it { expect { subject.save }.to change(User, :count).by(error_count) }!
    end!
    !
    context "ແޮͳϑΥʔϚοτͷ࣌" do!
    let(:file) { Rails.root.join("spec/fixtures/invalid.csv") }!
    it { expect(subject.save).to be_falsey }!
    end!
    !
    context "UserϞσϧͰΤϥʔͰ͕͋Δ࣌" do!
    let(:file) { Rails.root.join("spec/fixtures/valid.csv") }!
    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 { expect { subject.save }.not_to change(User, :count) }!
    end!
    end!
    end
    VTFS@JNQPSU@TQFDSC

    View Slide

  27. RSpec.describe UserImport do!
    describe "#save" do!
    subject { UserImport.new(file: file) }!
    context "UserϞσϧͰΤϥʔͰ͕͋Δ࣌" do!
    let(:file) { Rails.root.join("spec/fixtures/valid.csv") }!
    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 { expect { subject.save }.not_to change(User, :count) }!
    end!
    end!
    end
    VTFS@JNQPSU@TQFDSC

    View Slide

  28. 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
    BENJOVTFST@DPOUSPMMFS@TQFDSC

    View Slide

  29. ࣭໰ͳͲ

    View Slide

  30. ײ૝ر๬

    View Slide