guillaumegentil-import_fu 0.1.0
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/MIT-LICENSE +20 -0
- data/Rakefile +14 -0
- data/import_fu.gemspec +38 -0
- data/init.rb +1 -0
- data/lib/import_fu.rb +75 -0
- data/test/import_fu_test.rb +118 -0
- data/test/test_helper.rb +29 -0
- metadata +85 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2008 Thibaud Guillaume-Gentil
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/testtask'
|
4
|
+
require 'rake/rdoctask'
|
5
|
+
require 'echoe'
|
6
|
+
|
7
|
+
Echoe.new('import_fu', '0.1.0') do |p|
|
8
|
+
p.description = "Add quick mass data import from CSV or Array to Active Record model"
|
9
|
+
p.url = "http://github.com/guillaumegentil/import_fu/tree"
|
10
|
+
p.author = "Thibaud Guillaume-Gentil"
|
11
|
+
p.email = "guillaumegentil@gmail.com"
|
12
|
+
p.ignore_pattern = ["tmp/*", "script/*"]
|
13
|
+
p.development_dependencies = ["faster_csv", "active_record"]
|
14
|
+
end
|
data/import_fu.gemspec
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{import_fu}
|
5
|
+
s.version = "0.1.0"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["Thibaud Guillaume-Gentil"]
|
9
|
+
s.date = %q{2008-12-11}
|
10
|
+
s.description = %q{Add quick mass data import from CSV or Array to Active Record model}
|
11
|
+
s.email = %q{guillaumegentil@gmail.com}
|
12
|
+
s.extra_rdoc_files = ["lib/import_fu.rb", "README"]
|
13
|
+
s.files = ["init.rb", "lib/import_fu.rb", "Manifest", "MIT-LICENSE", "Rakefile", "README", "test/import_fu_test.rb", "test/test_helper.rb", "import_fu.gemspec"]
|
14
|
+
s.has_rdoc = true
|
15
|
+
s.homepage = %q{http://github.com/guillaumegentil/import_fu/tree}
|
16
|
+
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Import_fu", "--main", "README.markdown"]
|
17
|
+
s.require_paths = ["lib"]
|
18
|
+
s.rubyforge_project = %q{import_fu}
|
19
|
+
s.rubygems_version = %q{1.3.1}
|
20
|
+
s.summary = %q{Add quick mass data import from CSV or Array to Active Record model}
|
21
|
+
s.test_files = ["test/import_fu_test.rb", "test/test_helper.rb"]
|
22
|
+
|
23
|
+
if s.respond_to? :specification_version then
|
24
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
25
|
+
s.specification_version = 2
|
26
|
+
|
27
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
28
|
+
s.add_development_dependency(%q<faster_csv>, [">= 0"])
|
29
|
+
s.add_development_dependency(%q<active_record>, [">= 0"])
|
30
|
+
else
|
31
|
+
s.add_dependency(%q<faster_csv>, [">= 0"])
|
32
|
+
s.add_dependency(%q<active_record>, [">= 0"])
|
33
|
+
end
|
34
|
+
else
|
35
|
+
s.add_dependency(%q<faster_csv>, [">= 0"])
|
36
|
+
s.add_dependency(%q<active_record>, [">= 0"])
|
37
|
+
end
|
38
|
+
end
|
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'import_fu'
|
data/lib/import_fu.rb
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'fastercsv'
|
2
|
+
|
3
|
+
# Adds a import_with_load_data_infile class method.
|
4
|
+
# this lets you import data using mysql "LOAD DATA INFILE"
|
5
|
+
# This is about 30% faster than using ar-extensions bulk import
|
6
|
+
#
|
7
|
+
# be careful, there's no validation or escaping here!
|
8
|
+
module ImportFu
|
9
|
+
|
10
|
+
def self.included(base)
|
11
|
+
base.extend(ClassMethods)
|
12
|
+
end
|
13
|
+
|
14
|
+
module ClassMethods
|
15
|
+
|
16
|
+
def import(*args)
|
17
|
+
options = { :local => false, :replace => false, :timestamps => true, :format => false }
|
18
|
+
options.merge!(args.pop) if args.last.is_a? Hash
|
19
|
+
|
20
|
+
columns = args.first
|
21
|
+
if args.last.is_a? Array # Array of values
|
22
|
+
columns += [:created_at, :updated_at] if options[:timestamps]
|
23
|
+
csv_tempfile = create_csv_tempfile_for(args.last, options)
|
24
|
+
csv_path = csv_tempfile.path
|
25
|
+
else # path of an existing csv
|
26
|
+
csv_path = args.last
|
27
|
+
end
|
28
|
+
|
29
|
+
load_data_infile_sql = build_load_data_infile_statement(csv_path, columns, options)
|
30
|
+
ActiveRecord::Base.connection.execute(load_data_infile_sql)
|
31
|
+
|
32
|
+
csv_tempfile.close! if csv_tempfile
|
33
|
+
end
|
34
|
+
|
35
|
+
protected
|
36
|
+
|
37
|
+
def create_csv_tempfile_for(values, options = {})
|
38
|
+
# change permissions on tmpdir
|
39
|
+
File.new(Dir::tmpdir).chmod(0755) rescue nil
|
40
|
+
# create tempfile
|
41
|
+
csv_tempfile = Tempfile.new('ImportFu')
|
42
|
+
csv_tempfile.chmod(0644)
|
43
|
+
# write csv in tempfile
|
44
|
+
FasterCSV.open(csv_tempfile.path, "w") do |csv|
|
45
|
+
values.each do |column_values|
|
46
|
+
column_values.map! { |value| format_value(value) } if options[:format]
|
47
|
+
if options[:timestamps]
|
48
|
+
csv << (column_values + [Time.now.utc.to_s(:db), Time.now.utc.to_s(:db)])
|
49
|
+
else
|
50
|
+
csv << column_values
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
csv_tempfile
|
55
|
+
end
|
56
|
+
|
57
|
+
def format_value(value)
|
58
|
+
case value.class.to_s
|
59
|
+
when "Time", "Date"
|
60
|
+
value.to_s(:db)
|
61
|
+
else
|
62
|
+
value.to_s
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def build_load_data_infile_statement(csv_path, columns, options = {})
|
67
|
+
local = options[:local] ? 'LOCAL' : ''
|
68
|
+
replace = options[:replace] ? 'REPLACE' : 'IGNORE'
|
69
|
+
column_list = columns.map(&:to_s).join(',')
|
70
|
+
|
71
|
+
"LOAD DATA #{local} INFILE '#{csv_path}' #{replace} INTO TABLE #{table_name} FIELDS TERMINATED BY ',' ENCLOSED BY '\"' (#{column_list});"
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/test_helper')
|
2
|
+
|
3
|
+
# reopen the module and make methods public for testing
|
4
|
+
module ImportFu
|
5
|
+
module ClassMethods
|
6
|
+
public :create_csv_tempfile_for
|
7
|
+
public :build_load_data_infile_statement
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class ImportFuTest < Test::Unit::TestCase
|
12
|
+
|
13
|
+
def setup
|
14
|
+
rebuild_model
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_should_respond_to_import
|
18
|
+
assert Foo.respond_to?(:import)
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_should_create_a_temp_file_with_the_csv_content
|
22
|
+
# columns = [:name, :size]
|
23
|
+
values = [["toto", "32"],
|
24
|
+
["tata", "33"]]
|
25
|
+
|
26
|
+
file = Foo.create_csv_tempfile_for(values)
|
27
|
+
|
28
|
+
assert_equal IO.read(file.path), IO.read(fixtures('foos.csv'))
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_should_import_data_form_array
|
32
|
+
columns = [:name, :size]
|
33
|
+
values = [["toto", "32"],
|
34
|
+
["tata", "33"]]
|
35
|
+
|
36
|
+
Foo.import columns, values
|
37
|
+
|
38
|
+
assert_equal 2, Foo.count
|
39
|
+
assert_not_nil Foo.find_by_name_and_size('toto', 32)
|
40
|
+
assert_not_nil Foo.find_by_name_and_size('tata', 33)
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_should_import_data_form_array_with_timestamps
|
44
|
+
columns = [:name, :size]
|
45
|
+
values = [["toto", "32"]]
|
46
|
+
|
47
|
+
Foo.import columns, values
|
48
|
+
|
49
|
+
assert_kind_of Time, Foo.find_by_name_and_size('toto', 32).created_at
|
50
|
+
assert_kind_of Time, Foo.find_by_name_and_size('toto', 32).updated_at
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_should_import_data_form_array_without_timestamps
|
54
|
+
columns = [:name, :size]
|
55
|
+
values = [["toto", "32"]]
|
56
|
+
|
57
|
+
Foo.import columns, values, :timestamps => false
|
58
|
+
|
59
|
+
assert_nil Foo.find_by_name_and_size('toto', 32).created_at
|
60
|
+
assert_nil Foo.find_by_name_and_size('toto', 32).updated_at
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_should_import_data_form_array_and_format_value
|
64
|
+
columns = [:name, :size, :created_at, :updated_at]
|
65
|
+
values = [["toto", "32", Time.now.utc, Time.now.utc]]
|
66
|
+
|
67
|
+
Foo.import columns, values, :timestamps => false, :format => true
|
68
|
+
|
69
|
+
assert_kind_of Time, Foo.find_by_name_and_size('toto', 32).created_at
|
70
|
+
assert_kind_of Time, Foo.find_by_name_and_size('toto', 32).updated_at
|
71
|
+
end
|
72
|
+
|
73
|
+
def test_should_replace_imported_data_form_array
|
74
|
+
Foo.create(:name => 'toto', :size => '32')
|
75
|
+
Foo.create(:name => 'tata', :size => '33')
|
76
|
+
|
77
|
+
columns = [:id, :name, :size]
|
78
|
+
values = [["1", "toto", "1000"],
|
79
|
+
["2", "tata", "1001"]]
|
80
|
+
|
81
|
+
Foo.import columns, values, :replace => true
|
82
|
+
|
83
|
+
assert_equal 2, Foo.count
|
84
|
+
|
85
|
+
assert_nil Foo.find_by_name_and_size('toto', 32)
|
86
|
+
assert_nil Foo.find_by_name_and_size('tata', 33)
|
87
|
+
assert_not_nil Foo.find_by_name_and_size('toto', 1000)
|
88
|
+
assert_not_nil Foo.find_by_name_and_size('tata', 1001)
|
89
|
+
end
|
90
|
+
|
91
|
+
def test_should_import_data_form_csv
|
92
|
+
columns = [:name, :size]
|
93
|
+
values = [["toto", "32"],
|
94
|
+
["tata", "33"]]
|
95
|
+
|
96
|
+
Foo.import columns, fixtures('foos.csv')
|
97
|
+
|
98
|
+
assert_equal 2, Foo.count
|
99
|
+
assert_not_nil Foo.find_by_name_and_size('toto', 32)
|
100
|
+
assert_not_nil Foo.find_by_name_and_size('tata', 33)
|
101
|
+
end
|
102
|
+
|
103
|
+
def test_should_generate_the_correct_default_import_statement
|
104
|
+
expected = "LOAD DATA INFILE 'fake.csv' IGNORE INTO TABLE foos FIELDS TERMINATED BY ',' ENCLOSED BY '\"' (col1,col2);"
|
105
|
+
assert_equal expected, Foo.build_load_data_infile_statement('fake.csv', [:col1,:col2])
|
106
|
+
end
|
107
|
+
|
108
|
+
def test_should_generate_the_correct_import_statement_with_locale
|
109
|
+
expected = "LOAD DATA LOCAL INFILE 'fake.csv' IGNORE INTO TABLE foos FIELDS TERMINATED BY ',' ENCLOSED BY '\"' (col1,col2);"
|
110
|
+
assert_equal expected, Foo.build_load_data_infile_statement('fake.csv', [:col1,:col2], :local => true)
|
111
|
+
end
|
112
|
+
|
113
|
+
def test_should_generate_the_correct_import_statement_with_replace
|
114
|
+
expected = "LOAD DATA INFILE 'fake.csv' REPLACE INTO TABLE foos FIELDS TERMINATED BY ',' ENCLOSED BY '\"' (col1,col2);"
|
115
|
+
assert_equal expected, Foo.build_load_data_infile_statement('fake.csv', [:col1,:col2], :replace => true)
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'test/unit'
|
3
|
+
require 'active_record'
|
4
|
+
require 'import_fu'
|
5
|
+
|
6
|
+
# gem install redgreen for colored test output
|
7
|
+
begin require 'redgreen'; rescue LoadError; end
|
8
|
+
|
9
|
+
config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml'))
|
10
|
+
ActiveRecord::Base.establish_connection(config['test'])
|
11
|
+
|
12
|
+
def rebuild_model
|
13
|
+
ActiveRecord::Base.connection.create_table :foos, :force => true do |t|
|
14
|
+
t.string :name
|
15
|
+
t.integer :size
|
16
|
+
t.timestamps
|
17
|
+
end
|
18
|
+
|
19
|
+
ActiveRecord::Base.send(:include, ImportFu)
|
20
|
+
Object.send(:remove_const, "Foo") rescue nil
|
21
|
+
Object.const_set("Foo", Class.new(ActiveRecord::Base))
|
22
|
+
Foo.class_eval do
|
23
|
+
include ImportFu
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def fixtures(path)
|
28
|
+
"#{File.expand_path(File.dirname(__FILE__))}/fixtures/#{path}"
|
29
|
+
end
|
metadata
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: guillaumegentil-import_fu
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Thibaud Guillaume-Gentil
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-12-11 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: faster_csv
|
17
|
+
version_requirement:
|
18
|
+
version_requirements: !ruby/object:Gem::Requirement
|
19
|
+
requirements:
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: "0"
|
23
|
+
version:
|
24
|
+
- !ruby/object:Gem::Dependency
|
25
|
+
name: active_record
|
26
|
+
version_requirement:
|
27
|
+
version_requirements: !ruby/object:Gem::Requirement
|
28
|
+
requirements:
|
29
|
+
- - ">="
|
30
|
+
- !ruby/object:Gem::Version
|
31
|
+
version: "0"
|
32
|
+
version:
|
33
|
+
description: Add quick mass data import from CSV or Array to Active Record model
|
34
|
+
email: guillaumegentil@gmail.com
|
35
|
+
executables: []
|
36
|
+
|
37
|
+
extensions: []
|
38
|
+
|
39
|
+
extra_rdoc_files:
|
40
|
+
- lib/import_fu.rb
|
41
|
+
- README
|
42
|
+
files:
|
43
|
+
- init.rb
|
44
|
+
- lib/import_fu.rb
|
45
|
+
- Manifest
|
46
|
+
- MIT-LICENSE
|
47
|
+
- Rakefile
|
48
|
+
- README
|
49
|
+
- test/import_fu_test.rb
|
50
|
+
- test/test_helper.rb
|
51
|
+
- import_fu.gemspec
|
52
|
+
has_rdoc: true
|
53
|
+
homepage: http://github.com/guillaumegentil/import_fu/tree
|
54
|
+
post_install_message:
|
55
|
+
rdoc_options:
|
56
|
+
- --line-numbers
|
57
|
+
- --inline-source
|
58
|
+
- --title
|
59
|
+
- Import_fu
|
60
|
+
- --main
|
61
|
+
- README.markdown
|
62
|
+
require_paths:
|
63
|
+
- lib
|
64
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: "0"
|
69
|
+
version:
|
70
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: "1.2"
|
75
|
+
version:
|
76
|
+
requirements: []
|
77
|
+
|
78
|
+
rubyforge_project: import_fu
|
79
|
+
rubygems_version: 1.2.0
|
80
|
+
signing_key:
|
81
|
+
specification_version: 2
|
82
|
+
summary: Add quick mass data import from CSV or Array to Active Record model
|
83
|
+
test_files:
|
84
|
+
- test/import_fu_test.rb
|
85
|
+
- test/test_helper.rb
|