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 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