csv_import 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +8 -0
- data/Gemfile +4 -0
- data/MIT-LICENSE +21 -0
- data/README.md +43 -0
- data/Rakefile +1 -0
- data/app/helpers/csv_helper.rb +12 -0
- data/app/views/csv_import/_csv.html.haml +34 -0
- data/config/locales/en.yml +14 -0
- data/config/locales/ja.yml +14 -0
- data/csv_import.gemspec +28 -0
- data/lib/csv_import.rb +55 -0
- data/lib/csv_import/version.rb +3 -0
- data/spec/csv_import_spec.rb +20 -0
- data/spec/fake/app.rb +27 -0
- data/spec/fake/app/controllers/application_controller.rb +2 -0
- data/spec/fake/app/controllers/members_controller.rb +15 -0
- data/spec/fake/app/models/member.rb +2 -0
- data/spec/fake/app/views/members/index.html.haml +9 -0
- data/spec/fixtures/ascii.csv +2 -0
- data/spec/fixtures/utf-8.csv +2 -0
- data/spec/spec_helper.rb +11 -0
- metadata +141 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
Copyright (c) 2009 jarrett (https://github.com/jarrett)
|
2
|
+
Copyright (c) 2010-2011 mobalean LLC
|
3
|
+
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
a copy of this software and associated documentation files (the
|
6
|
+
"Software"), to deal in the Software without restriction, including
|
7
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
# CsvImport
|
2
|
+
|
3
|
+
This Rails plugin provides a controller method called each_csv_row and
|
4
|
+
an accompanying helper called csv_form.
|
5
|
+
|
6
|
+
By default, when each_csv_row is called, any exceptions raised in the
|
7
|
+
block will be rescued. The rows that triggered the exceptions will
|
8
|
+
then be displayed in a table as part of the output from csv_form.
|
9
|
+
|
10
|
+
You can turn off rescuing by calling each_csv_row(false).
|
11
|
+
|
12
|
+
Note that we have adapted this version to our needs for [Doorkeeper](http://www.doorkeeperhq.com/), and we welcome any additions to make it more generic.
|
13
|
+
|
14
|
+
[ドアキーパー](http://www.doorkeeper.jp/)でも利用するので、日本語対応もできます!
|
15
|
+
|
16
|
+
## Compatibility
|
17
|
+
|
18
|
+
* Ruby 1.9
|
19
|
+
* Rails 3
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
# app/controllers/members_controller.rb
|
24
|
+
|
25
|
+
class MembersController < ActionController::Base
|
26
|
+
include CsvImport
|
27
|
+
|
28
|
+
def create
|
29
|
+
each_csv_row do |row|
|
30
|
+
Member.create!(row)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# app/views/members/index.html.erb
|
36
|
+
|
37
|
+
<h1>Import Members</h1>
|
38
|
+
|
39
|
+
<%= csv_form members_path do %>
|
40
|
+
|
41
|
+
<!-- whatever you put in the optional
|
42
|
+
block will appear at the end of the form -->
|
43
|
+
<% end %>
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'haml'
|
2
|
+
module CsvHelper
|
3
|
+
# The optional block allows you to pass in extra HTML that will be inserted after the CSV form fields but before the submit button.
|
4
|
+
def csv_form(url_for_options = {}, &block)
|
5
|
+
if block_given?
|
6
|
+
other_html = capture(&block)
|
7
|
+
else
|
8
|
+
other_html = nil
|
9
|
+
end
|
10
|
+
render :partial => 'csv_import/csv', :locals => {:url_for_options => url_for_options, :other_html => other_html}
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
= form_tag url_for_options, {:multipart => true} do
|
2
|
+
%p
|
3
|
+
= I18n.t :upload_label, :scope => :csv_import
|
4
|
+
= file_field_tag 'csv'
|
5
|
+
%p
|
6
|
+
= check_box_tag :csv_ignore_unknown_columns, true, false
|
7
|
+
%label{:for => 'csv_ignore_unknown_columns'}= I18n.t :ignore_unknown_columns_label, :scope => :csv_import
|
8
|
+
%div.columns= other_html
|
9
|
+
%p= submit_tag I18n.t(:submit_button, :scope => :csv_import), :class => 'button'
|
10
|
+
|
11
|
+
- if @rows_imported && ! @imported
|
12
|
+
%h2= I18n.t :import_faild, :scope => 'csv_import.errors'
|
13
|
+
- if @bad_csv
|
14
|
+
%p= I18n.t :bad_csv, :scope => 'csv_import.errors'
|
15
|
+
- else
|
16
|
+
%p= I18n.t :bad_rows, :scope => 'csv_import.errors', :bad_rows_length => @bad_rows.length, :rows_imported => @rows_imported
|
17
|
+
%p= I18n.t :exist_bad_rows, :scope => 'csv_import.errors'
|
18
|
+
- if @unknown_headers.any?
|
19
|
+
%h3= I18n.t :unknown_columns, :scope => 'csv_import.errors'
|
20
|
+
%p
|
21
|
+
= I18n.t :following_unknown_columns, :scope => 'csv_import.errors'
|
22
|
+
= @unknown_headers.map{|header| ""#{h(header)}""}.join(", ").html_safe
|
23
|
+
|
24
|
+
- if @bad_rows.any?
|
25
|
+
%h3= I18n.t :bad_rows_in_uploaded_csv, :scope => 'csv_import.errors'
|
26
|
+
- @headers = @bad_rows.first.headers
|
27
|
+
%table
|
28
|
+
%tr
|
29
|
+
- @headers.each do |field_name|
|
30
|
+
%th= field_name
|
31
|
+
- @bad_rows.each_with_index do |row, row_number|
|
32
|
+
%tr{:class => cycle('even', 'odd')}
|
33
|
+
- @headers.each do |field_name|
|
34
|
+
%td= row[field_name].to_s
|
@@ -0,0 +1,14 @@
|
|
1
|
+
en:
|
2
|
+
csv_import:
|
3
|
+
upload_label: 'Upload CSV file:'
|
4
|
+
ignore_unknown_columns_label: 'Ignore all unknown columns in uploaded CSV'
|
5
|
+
submit_button: 'Import'
|
6
|
+
|
7
|
+
errors:
|
8
|
+
import_faild: 'Import Failed'
|
9
|
+
bad_csv: 'The uploaded file contained severe errors and could not be processed. Please make sure it is formatted correctly and contains headers.'
|
10
|
+
bad_rows: '%{bad_rows_length} rows failed to import. %{rows_imported} rows would have been imported successfully.'
|
11
|
+
exist_bad_rows: 'Anytime there are bad rows, the entire import gets cancelled. This is so that you can make corrections and re-run the import without manually rolling back the previous attempt.'
|
12
|
+
unknown_columns: 'Unknown columns in CSV'
|
13
|
+
following_unknown_columns: 'The following columns are not known: '
|
14
|
+
bad_rows_in_uploaded_csv: 'Bad rows in uploaded CSV'
|
@@ -0,0 +1,14 @@
|
|
1
|
+
ja:
|
2
|
+
csv_import:
|
3
|
+
upload_label: 'CSVファイルのアップロード:'
|
4
|
+
ignore_unknown_columns_label: 'アップロードされたCSVファイルに、不明な欄があった場合、無視する'
|
5
|
+
submit_button: 'インポート'
|
6
|
+
|
7
|
+
errors:
|
8
|
+
import_faild: 'インポートに失敗しました'
|
9
|
+
bad_csv: 'CSVファイルにエラーがあったため、アップロードに失敗しました。CSVのフォーマット、値などをご確認のうえ、再度アップロードお願いいたします。'
|
10
|
+
bad_rows: '%{bad_rows_length}列のインポートに失敗し、%{rows_imported}列のインポートに成功しました。'
|
11
|
+
exist_bad_rows: 'インポートに失敗した場合、全てのインポートはキャンセルされます。CSVファイルの修正後、再度アップロードしてください。'
|
12
|
+
unknown_columns: '不明な欄'
|
13
|
+
following_unknown_columns: '次の欄は不明です: '
|
14
|
+
bad_rows_in_uploaded_csv: '不正な列'
|
data/csv_import.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "csv_import/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "csv_import"
|
7
|
+
s.version = CsvImport::VERSION
|
8
|
+
s.authors = ["Paul McMahon"]
|
9
|
+
s.email = ["paul@mobalean.com"]
|
10
|
+
s.homepage = "http://www.mobalean.com"
|
11
|
+
s.summary = %q{Easy CSV importing in Rails}
|
12
|
+
s.description = %q{Use this Rails 3 plugin to easily import data via csv files}
|
13
|
+
|
14
|
+
s.rubyforge_project = "csv_import"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
s.add_dependency 'rails', '~> 3.0'
|
22
|
+
s.add_dependency 'haml', '~> 3.1'
|
23
|
+
|
24
|
+
s.add_development_dependency 'rspec', '~> 2.5.0'
|
25
|
+
s.add_development_dependency 'steak'
|
26
|
+
s.add_development_dependency 'sqlite3'
|
27
|
+
s.add_development_dependency 'launchy'
|
28
|
+
end
|
data/lib/csv_import.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'csv'
|
2
|
+
require 'rails'
|
3
|
+
|
4
|
+
# Include this in a controller
|
5
|
+
module CsvImport
|
6
|
+
class Engine < Rails::Engine
|
7
|
+
end
|
8
|
+
DEFAULT_PARSE_OPTS = { :headers => true, :header_converters => :symbol }
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def each_csv_row(opts = {})
|
13
|
+
@rows_imported = 0
|
14
|
+
@bad_csv = false
|
15
|
+
@bad_rows = []
|
16
|
+
@unknown_headers = Set.new
|
17
|
+
@imported = false
|
18
|
+
|
19
|
+
if request.post?
|
20
|
+
ActiveRecord::Base.transaction do
|
21
|
+
::Rails.logger.info("starting csv import")
|
22
|
+
valid_headers = opts.delete(:valid_headers)
|
23
|
+
|
24
|
+
begin
|
25
|
+
CSV.parse(params[:csv].read.force_encoding("UTF-8"), DEFAULT_PARSE_OPTS.merge(opts)) do |row|
|
26
|
+
begin
|
27
|
+
row_data = row.to_hash
|
28
|
+
if valid_headers
|
29
|
+
row_data.delete_if {|header, value| ! valid_headers.include?(header) }
|
30
|
+
@unknown_headers.merge(row.headers - row_data.keys)
|
31
|
+
end
|
32
|
+
obj = yield row_data
|
33
|
+
obj.save! if obj && (obj.new_record? || obj.changed?)
|
34
|
+
@rows_imported += 1
|
35
|
+
rescue ActiveRecord::ActiveRecordError, ActiveRecord::UnknownAttributeError => e
|
36
|
+
row[:error] = e
|
37
|
+
@bad_rows << row
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
if @bad_rows.empty? && (@unknown_headers.empty? || params[:csv_ignore_unknown_columns])
|
42
|
+
@imported = true
|
43
|
+
end
|
44
|
+
rescue => e
|
45
|
+
@bad_csv = true
|
46
|
+
end
|
47
|
+
|
48
|
+
unless @imported
|
49
|
+
::Rails.logger.info("rolling back csv import, contained errors")
|
50
|
+
raise ActiveRecord::Rollback
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require "spec_helper"
|
3
|
+
|
4
|
+
feature "import csv" do
|
5
|
+
scenario "with only ascii" do
|
6
|
+
visit members_path
|
7
|
+
attach_file 'csv', File.join(File.dirname(__FILE__), 'fixtures/ascii.csv')
|
8
|
+
click_button "Import"
|
9
|
+
page.find("td:first").text.should == "Paul McMahon"
|
10
|
+
page.find("td:last").text.should == "paul@mobalean.com"
|
11
|
+
end
|
12
|
+
|
13
|
+
scenario "with only utf-8" do
|
14
|
+
visit members_path
|
15
|
+
attach_file 'csv', File.join(File.dirname(__FILE__), 'fixtures/utf-8.csv')
|
16
|
+
click_button "Import"
|
17
|
+
page.find("td:first").text.should == "ポール"
|
18
|
+
page.find("td:last").text.should == "paul@mobalean.com"
|
19
|
+
end
|
20
|
+
end
|
data/spec/fake/app.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'rails'
|
2
|
+
require 'active_record'
|
3
|
+
require 'action_controller/railtie'
|
4
|
+
require 'action_view/railtie'
|
5
|
+
|
6
|
+
ActiveRecord::Base.configurations = {'test' => {:adapter => 'sqlite3', :database => ':memory:'}}
|
7
|
+
ActiveRecord::Base.establish_connection('test')
|
8
|
+
|
9
|
+
app = Class.new(Rails::Application)
|
10
|
+
app.config.root = File.dirname(__FILE__)
|
11
|
+
app.config.secret_token = "3b7cd727ee24e8444053437c36cc66c4"
|
12
|
+
app.config.session_store :cookie_store, :key => "_myapp_session"
|
13
|
+
app.config.active_support.deprecation = :log
|
14
|
+
app.config.action_dispatch.show_exceptions = false
|
15
|
+
|
16
|
+
app.initialize!
|
17
|
+
|
18
|
+
app.routes.draw do
|
19
|
+
resources :members
|
20
|
+
end
|
21
|
+
Class.new(ActiveRecord::Migration) do
|
22
|
+
def self.up
|
23
|
+
create_table(:members) {|t| t.string :email; t.string :name }
|
24
|
+
add_index :members, :email, :unique => true
|
25
|
+
end
|
26
|
+
end.up
|
27
|
+
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class MembersController < ApplicationController
|
2
|
+
include CsvImport
|
3
|
+
def index
|
4
|
+
@members = Member.all
|
5
|
+
end
|
6
|
+
def create
|
7
|
+
each_csv_row do |row|
|
8
|
+
email = row[:email].to_s.strip
|
9
|
+
Member.find_or_create_by_email(email).tap do |member|
|
10
|
+
member.update_attributes!(row)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
redirect_to action: :index
|
14
|
+
end
|
15
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
$:.unshift File.expand_path('../../lib', __FILE__)
|
2
|
+
|
3
|
+
require 'csv_import'
|
4
|
+
require File.expand_path(File.dirname(__FILE__) + "/fake/app")
|
5
|
+
|
6
|
+
require "steak"
|
7
|
+
require 'capybara/rspec'
|
8
|
+
|
9
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
10
|
+
|
11
|
+
require 'rspec/rails'
|
metadata
ADDED
@@ -0,0 +1,141 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: csv_import
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Paul McMahon
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-12-15 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rails
|
16
|
+
requirement: &2156048980 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '3.0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *2156048980
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: haml
|
27
|
+
requirement: &2156045560 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ~>
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '3.1'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *2156045560
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: rspec
|
38
|
+
requirement: &2156044420 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ~>
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 2.5.0
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *2156044420
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: steak
|
49
|
+
requirement: &2156043400 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *2156043400
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: sqlite3
|
60
|
+
requirement: &2156042300 !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ! '>='
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
66
|
+
type: :development
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: *2156042300
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: launchy
|
71
|
+
requirement: &2156041660 !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ! '>='
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: *2156041660
|
80
|
+
description: Use this Rails 3 plugin to easily import data via csv files
|
81
|
+
email:
|
82
|
+
- paul@mobalean.com
|
83
|
+
executables: []
|
84
|
+
extensions: []
|
85
|
+
extra_rdoc_files: []
|
86
|
+
files:
|
87
|
+
- .gitignore
|
88
|
+
- Gemfile
|
89
|
+
- MIT-LICENSE
|
90
|
+
- README.md
|
91
|
+
- Rakefile
|
92
|
+
- app/helpers/csv_helper.rb
|
93
|
+
- app/views/csv_import/_csv.html.haml
|
94
|
+
- config/locales/en.yml
|
95
|
+
- config/locales/ja.yml
|
96
|
+
- csv_import.gemspec
|
97
|
+
- lib/csv_import.rb
|
98
|
+
- lib/csv_import/version.rb
|
99
|
+
- spec/csv_import_spec.rb
|
100
|
+
- spec/fake/app.rb
|
101
|
+
- spec/fake/app/controllers/application_controller.rb
|
102
|
+
- spec/fake/app/controllers/members_controller.rb
|
103
|
+
- spec/fake/app/models/member.rb
|
104
|
+
- spec/fake/app/views/members/index.html.haml
|
105
|
+
- spec/fixtures/ascii.csv
|
106
|
+
- spec/fixtures/utf-8.csv
|
107
|
+
- spec/spec_helper.rb
|
108
|
+
homepage: http://www.mobalean.com
|
109
|
+
licenses: []
|
110
|
+
post_install_message:
|
111
|
+
rdoc_options: []
|
112
|
+
require_paths:
|
113
|
+
- lib
|
114
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
115
|
+
none: false
|
116
|
+
requirements:
|
117
|
+
- - ! '>='
|
118
|
+
- !ruby/object:Gem::Version
|
119
|
+
version: '0'
|
120
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ! '>='
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
requirements: []
|
127
|
+
rubyforge_project: csv_import
|
128
|
+
rubygems_version: 1.8.10
|
129
|
+
signing_key:
|
130
|
+
specification_version: 3
|
131
|
+
summary: Easy CSV importing in Rails
|
132
|
+
test_files:
|
133
|
+
- spec/csv_import_spec.rb
|
134
|
+
- spec/fake/app.rb
|
135
|
+
- spec/fake/app/controllers/application_controller.rb
|
136
|
+
- spec/fake/app/controllers/members_controller.rb
|
137
|
+
- spec/fake/app/models/member.rb
|
138
|
+
- spec/fake/app/views/members/index.html.haml
|
139
|
+
- spec/fixtures/ascii.csv
|
140
|
+
- spec/fixtures/utf-8.csv
|
141
|
+
- spec/spec_helper.rb
|