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
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
Agile Leadership in an Agile Organization
kimpetersen
PRO
0
170
Visual Storytelling: How to be a Superhuman Communicator
reverentgeek
2
560
Save Time (by Creating Custom Rails Generators)
garrettdimon
PRO
32
3.5k
Are puppies a ranking factor?
jonoalderson
1
3.6k
Dominate Local Search Results - an insider guide to GBP, reviews, and Local SEO
greggifford
PRO
0
200
How to audit for AI Accessibility on your Front & Back End
davetheseo
0
430
Distributed Sagas: A Protocol for Coordinating Microservices
caitiem20
333
22k
What does AI have to do with Human Rights?
axbom
PRO
1
2.2k
Utilizing Notion as your number one productivity tool
mfonobong
4
320
The B2B funnel & how to create a winning content strategy
katarinadahlin
PRO
1
390
Impact Scores and Hybrid Strategies: The future of link building
tamaranovitovic
0
310
Data-driven link building: lessons from a $708K investment (BrightonSEO talk)
szymonslowik
1
1.1k
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
࣭ͳͲ
ײر