guillaumegentil-import_fu 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -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