Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
普通のCSVアップロードフォームを作りたい
Search
TAKAHASHI Kazunari
July 12, 2014
3k
17
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
普通のCSVアップロードフォームを作りたい
TAKAHASHI Kazunari
July 12, 2014
More Decks by TAKAHASHI Kazunari
See All by TAKAHASHI Kazunari
fat-settings-yml
1syo
0
890
雑につくるKPIツールのススメ
1syo
0
510
みなとRuby会議02やりたい
1syo
1
330
Testing Wercker plugin with bats
1syo
0
590
私を変えた1冊の本
1syo
0
860
普通のCSVアップロードフォームを作りたい(改)
1syo
9
1.1k
kaja-2013
1syo
0
540
untestable production code
1syo
0
560
authorization-for-buktorg
1syo
0
380
Featured
See All Featured
Imperfection Machines: The Place of Print at Facebook
scottboms
270
14k
Ten Tips & Tricks for a 🌱 transition
stuffmc
0
130
Distributed Sagas: A Protocol for Coordinating Microservices
caitiem20
333
22k
How Fast Is Fast Enough? [PerfNow 2025]
tammyeverts
3
610
Six Lessons from altMBA
skipperchong
29
4.3k
WENDY [Excerpt]
tessaabrams
11
38k
Building a A Zero-Code AI SEO Workflow
portentint
PRO
0
580
Marketing to machines
jonoalderson
1
5.4k
Applied NLP in the Age of Generative AI
inesmontani
PRO
4
2.3k
Mind Mapping
helmedeiros
PRO
1
250
Put a Button on it: Removing Barriers to Going Fast.
kastner
60
4.3k
Learning to Love Humans: Emotional Interface Design
aarron
275
41k
Transcript
ී௨ͷ $47Ξοϓϩʔυ ϑΥʔϜΛ࡞Γ͍ͨ !TZP :PLPIBNBSC
࠷ۙͷϚΠɾϒʔϜ 㾎IVCPUTDSJQU 㾎"JSCSBLF 㾎5SBWJT$* 㾎1JWPUBM5SBDLFS
Ͳ͏͖ 㾎ೖॻʹͬͯͳ͍ 㾎ೖॻͷ͕࣍গͳ͍ 㾎3BJMT$BTU͘Β͍ 㾎ྑ͍։ൃݱͰͷΈڞ༗͞Ε͍ͯΔ
Θ͔Δ͜ͱ 㾎'BU$POUSPMMFSͱͳʹ͔ʁ 㾎Ͳ͏͢Ε4LJOOZ$POUSPMMFSʹͳΔͷ͔ʁ
γφϦΦ 㾎ཧऀ͕Ұׅͯ͠ొ 㾎ෳͷϢʔβʔใ͕$47ϑΝΠϧʹ͋Δ 㾎$47ϑΝΠϧͷதࢯ໊ͱFNBJM
None
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
͋Γ͕ͪͳ࣮
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
<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> JOEFYIUNMFSC
ϦϦʔεͯ͠ΈͨΒ 㾎ϑΝΠϧࢦఆ͠ͳ͍ͱΤϥʔʹͳΓ·͢ 㾎ը૾͕Ξοϓϩʔυ͢ΔͱΤϥʔʹͳΓ·͢ 㾎˓˓͞ΜͷFNBJMΞυϨε͕͓͔͍͠Ͱ͢ 㾎ΤΫηϧ͔Β࡞ͬͨ$47͕औΓࠐΊ·ͤΜ 㾎ࡢొͨ͠ϑΝΠϧΛΞοϓϩʔυͪ͠Ό͍·ͨ͠
σϞ
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
ཁ͕݅Γͯͳ͍ 㾎̎࣠ͷόϦσʔγϣϯ 㾎ϑΝΠϧϑΥʔϚοτ 㾎Ϩίʔυݸผ 㾎Ξϥʔτը໘ 㾎ςετ
Ͳ͏͢Εྑ͍ͷ͔ʁ
Ϟσϧʹ͢Ε͍͍ IUUQTHJTUHJUIVCDPNTZPGDGDDG
[email protected]
ۭ ဘॿ
[email protected]
ՖژӃ య໌ શମ6TFS*NQPSUϞσϧ ֤ߦ6TFSϞσϧ ΛΘ͚ͯΈΔ
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
<div class="container">! <% if flash[:notice] %>! <div class="alert alert-success">! <%=
flash[:notice] %>! </div>! <% end %>! ! <h1>Users</h1>! <%= 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? %>! <div class="alert alert-danger">! <ul>! <% @user_import.errors.full_messages.each do |msg| %>! <li><%= msg %></li>! <% end %>! </ul>! </div>! <% end %>! ! <div class="form-group">! <%= f.file_field :file, class: "form-control" %>! </div>! <div class="form-group">! <%= f.button "Upload", class: ["btn","btn-default"] %>! </div>! <% end %>! </div> JOEFYIUNMFSC
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
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
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
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
σϞ
ςετΛͲ͏ॻ͘ʁ
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
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
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
࣭ͳͲ
ײر