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
350
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
570
authorization-for-buktorg
1syo
0
380
Featured
See All Featured
Refactoring Trust on Your Teams (GOTO; Chicago 2020)
rmw
35
3.7k
Documentation Writing (for coders)
carmenintech
77
5.4k
Building Better People: How to give real-time feedback that sticks.
wjessup
370
20k
How To Speak Unicorn (iThemes Webinar)
marktimemedia
1
520
Building a Modern Day E-commerce SEO Strategy
aleyda
45
9.2k
Efficient Content Optimization with Google Search Console & Apps Script
katarinadahlin
PRO
1
760
Conquering PDFs: document understanding beyond plain text
inesmontani
PRO
4
2.9k
Claude Code どこまでも/ Claude Code Everywhere
nwiizo
66
57k
Typedesign – Prime Four
hannesfritz
42
3.1k
[RailsConf 2023 Opening Keynote] The Magic of Rails
eileencodes
31
10k
Fashionably flexible responsive web design (full day workshop)
malarkey
408
67k
Design and Strategy: How to Deal with People Who Don’t "Get" Design
morganepeng
133
19k
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Λ͏
͋ͳͨͳΒͲ͏࣮͢Δ͔ ڭ͍͑ͯͩ͘͞