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
Sponsored
·
Your Podcast. Everywhere. Effortlessly.
Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
→
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
Future Trends and Review - Lecture 12 - Web Technologies (1019888BNR)
signer
PRO
0
3.6k
Cheating the UX When There Is Nothing More to Optimize - PixelPioneers
stephaniewalter
287
14k
CSS Pre-Processors: Stylus, Less & Sass
bermonpainter
360
30k
From Legacy to Launchpad: Building Startup-Ready Communities
dugsong
0
230
Design in an AI World
tapps
1
240
Why Mistakes Are the Best Teachers: Turning Failure into a Pathway for Growth
auna
0
160
YesSQL, Process and Tooling at Scale
rocio
174
15k
How to audit for AI Accessibility on your Front & Back End
davetheseo
0
420
How to Talk to Developers About Accessibility
jct
2
230
My Coaching Mixtape
mlcsv
0
140
For a Future-Friendly Web
brad_frost
183
10k
Building Flexible Design Systems
yeseniaperezcruz
330
40k
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
࣭ͳͲ
ײر