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
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
890
雑につくるKPIツールのススメ
1syo
0
510
みなとRuby会議02やりたい
1syo
1
330
Testing Wercker plugin with bats
1syo
0
590
私を変えた1冊の本
1syo
0
860
普通のCSVアップロードフォームを作りたい
1syo
17
3k
kaja-2013
1syo
0
540
untestable production code
1syo
0
560
authorization-for-buktorg
1syo
0
380
Featured
See All Featured
Prompt Engineering for Job Search
mfonobong
0
340
Speed Design
sergeychernyshev
33
1.8k
Amusing Abliteration
ianozsvald
1
200
Performance Is Good for Brains [We Love Speed 2024]
tammyeverts
12
1.7k
Dominate Local Search Results - an insider guide to GBP, reviews, and Local SEO
greggifford
PRO
0
190
The Invisible Side of Design
smashingmag
302
52k
The #1 spot is gone: here's how to win anyway
tamaranovitovic
2
1.1k
Side Projects
sachag
455
43k
YesSQL, Process and Tooling at Scale
rocio
174
15k
Learning to Love Humans: Emotional Interface Design
aarron
275
41k
Paper Plane (Part 1)
katiecoart
PRO
0
8.8k
Ten Tips & Tricks for a 🌱 transition
stuffmc
0
130
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Λ͏
͋ͳͨͳΒͲ͏࣮͢Δ͔ ڭ͍͑ͯͩ͘͞