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
510
みなとRuby会議02やりたい
1syo
1
330
Testing Wercker plugin with bats
1syo
0
590
私を変えた1冊の本
1syo
0
870
普通のCSVアップロードフォームを作りたい
1syo
17
3k
kaja-2013
1syo
0
550
untestable production code
1syo
0
560
authorization-for-buktorg
1syo
0
380
Featured
See All Featured
Code Reviewing Like a Champion
maltzj
528
40k
Joys of Absence: A Defence of Solitary Play
codingconduct
1
410
Facilitating Awesome Meetings
lara
57
7k
Ethics towards AI in product and experience design
skipperchong
2
330
Pawsitive SEO: Lessons from My Dog (and Many Mistakes) on Thriving as a Consultant in the Age of AI
davidcarrasco
0
190
Keith and Marios Guide to Fast Websites
keithpitt
413
23k
Money Talks: Using Revenue to Get Sh*t Done
nikkihalliwell
0
310
Un-Boring Meetings
codingconduct
0
340
Testing 201, or: Great Expectations
jmmastey
46
8.2k
Evolving SEO for Evolving Search Engines
ryanjones
0
240
The Language of Interfaces
destraynor
162
27k
Building Flexible Design Systems
yeseniaperezcruz
330
40k
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Λ͏
͋ͳͨͳΒͲ͏࣮͢Δ͔ ڭ͍͑ͯͩ͘͞