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
August 20, 2014
1.1k
9
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
普通のCSVアップロードフォームを作りたい(改)
TAKAHASHI Kazunari
August 20, 2014
More Decks by TAKAHASHI Kazunari
See All by TAKAHASHI Kazunari
fat-settings-yml
1syo
0
900
雑につくるKPIツールのススメ
1syo
0
520
みなとRuby会議02やりたい
1syo
1
370
Testing Wercker plugin with bats
1syo
0
600
私を変えた1冊の本
1syo
0
870
普通のCSVアップロードフォームを作りたい
1syo
17
3k
kaja-2013
1syo
0
550
untestable production code
1syo
0
580
authorization-for-buktorg
1syo
0
380
Featured
See All Featured
What’s in a name? Adding method to the madness
productmarketing
PRO
24
4.2k
Game over? The fight for quality and originality in the time of robots
wayneb77
1
260
Avoiding the “Bad Training, Faster” Trap in the Age of AI
tmiket
0
230
Darren the Foodie - Storyboard
khoart
PRO
3
3.9k
The Curious Case for Waylosing
cassininazir
1
490
Groundhog Day: Seeking Process in Gaming for Health
codingconduct
0
330
DBのスキルで生き残る技術 - AI時代におけるテーブル設計の勘所
soudai
PRO
68
57k
Mobile First: as difficult as doing things right
swwweet
225
10k
A better future with KSS
kneath
240
18k
Fantastic passwords and where to find them - at NoRuKo
philnash
52
3.8k
Connecting the Dots Between Site Speed, User Experience & Your Business [WebExpo 2025]
tammyeverts
11
1k
svc-hook: hooking system calls on ARM64 by binary rewriting
retrage
2
560
Transcript
ී௨ͷ CSVΞοϓϩʔυ ϑΥʔϜΛ࡞Γ͍ͨ վ
✓ Kazunari Takahashi ✓ Yokohama.rb ✓ 2012 Ruby kaja award
winner ✓ MinatoRubyKaigi01 organizer ✓ TDDBC Yokohama TA @1syo
νΣοΫ ✓ Anvilىಈ͍ͯ͠Δ͔ ✓ http://bad.dev/admin/usersͷλϒ͋Δ͔ ✓ http://good.dev/admin/usersͷλϒ͋Δ͔ ✓ gist(bad/good)tweet͔ͨ͠ ✓
֤ڥͰdb:setup࣮ߦ͔ͨ͠ʁ
formͷઃܭΛͲ͏͢Ε͍͍͔ʁ ✓ ೖॻʹࡌͬͯͳ͍ ✓ ೖॻͷ͕࣍গͳ͍ ✓ Rails Cast͘Β͍? ✓ ྑ͍։ൃݱͰڞ༗͞Ε͍ͯΔ
ྑ͍։ൃݱͰ ڞ༗͞Ε͍ͯΔϊϋΛ ίϛϡχςΟʔͰڞ༗͍ͨ͠
γφϦΦ ✓ ཧऀ͕ϢʔβʔใҰׅొ͢Δ ✓ ෳͷϢʔβʔใ͕CSVϑΝΠϧʹ͋Δ ✓ CSVϑΝΠϧͷதࢯ໊ͱemail
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 Users
͋ΔγεςϜͷ࣮
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
<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
ϦϦʔεͯ͠ΈͨΒ ✓ ϑΝΠϧࢦఆ͠ͳ͍ͱΤϥʔʹͳΓ·͢ ✓ ը૾͕Ξοϓϩʔυ͢ΔͱΤϥʔʹͳΓ·͢ ✓ ◦◦͞ΜͷemailΞυϨε͕͓͔͍͠Ͱ͢ ✓ ΤΫηϧ͔Β࡞ͬͨCSV͕औΓࠐΊ·ͤΜ ✓
ࡢొͨ͠ϑΝΠϧΛΞοϓϩʔυͪ͠Ό͍·ͨ͠
demo
ཁ͕݅Γͳ͍ ✓ ̎࣠ͷόϦσʔγϣϯ ✓ ϑΝΠϧϑΥʔϚοτ ✓ Ϩίʔυݸผ ✓ Ξϥʔτը໘ ✓
ςετ..
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!
f = params[:data_file]! if f.blank?! ! flash[:notice] = 'File not
found'! ! redirect_to action: :index! return! end Admin:UsersController
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
✓ Controllerʹ࣮͕Ճ͞ΕΔ ✓ Validationʹແཧ͕͋Δ
Ͳ͏͢Εྑ͍ͷ͔ʁ
Ϟσϧʹ͢Δ https://gist.github.com/1syo/319f237637cf522c8c9f
[email protected]
ۭ ঝଠ
[email protected]
ՖژӃ య໌ CSVϑΝΠϧશମUserImportϞσϧ Ϟσϧͭ ֤ߦUserϞσϧ
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
ScaffoldͬΆ͍Controller
<%= 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
form_forΛ͏
ΤοϥʔϝοηʔδΛ දࣔ͢Δ
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
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
ActiveModel::ModelΛ͏
Custom ValidationΛ࡞Δ
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
UserϞσϧͷerrorsΛ UserImportϞσϧͷerrorsʹ add͢Δ
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
demo
ςετΛͲ͏ॻ͘ʁ
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
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
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
·ͱΊ ✓ form_forͰݕ౼ͯ͠ΈΔ ✓ ScaffoldͰͰ͖ͨControllerͷઃܭΛ·ͶΔ ✓ ActiveModel::ModelΛ͏ ✓ Custom ValidationΛ͏
͋ͳͨͳΒͲ͏࣮͢Δ͔ ڭ͍͑ͯͩ͘͞