simple_importer 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,2 @@
1
+ .DS_Store
2
+ pkg/
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Justin Marney
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.
@@ -0,0 +1,160 @@
1
+ = simple_importer
2
+
3
+ Simple API for importing csv files.
4
+
5
+ == Defining importers
6
+
7
+ The basic importer has a name, file, and a foreach block.
8
+
9
+ importer :items do
10
+ file 'items.csv'
11
+
12
+ foreach do |row|
13
+ Item.create(:name => row[:name])
14
+ end
15
+ end
16
+
17
+ simple_importer uses fastercsv, which replaces the csv standard lib in ruby1.9. Importers have configuration options that match those of fastercsv. The foreach block is called for each row in the csv file. The argument passed into the foreach block is a fastercsv row created with the specified configuration.
18
+
19
+ importer :items do
20
+ file 'items.csv'
21
+
22
+ # all fastercsv options are supported
23
+ headers false
24
+ converters :numeric
25
+ # etc..
26
+
27
+ foreach do |row|
28
+ Item.create(:name => row[:name])
29
+ end
30
+ end
31
+
32
+ Importers have a default configuration that matches the following importer.
33
+
34
+ importer :items do
35
+ file 'items.csv'
36
+
37
+ # default configuration
38
+ col_sep ","
39
+ row_sep :auto
40
+ quote_char '"'
41
+ field_size_limit nil
42
+ converters :all
43
+ unconverted_fields nil
44
+ headers true
45
+ return_headers false
46
+ header_converters :symbol
47
+ skip_blanks false
48
+ force_quotes false
49
+ # end default configuration
50
+
51
+ foreach do |row|
52
+ Item.create(:name => row[:name])
53
+ end
54
+ end
55
+
56
+ == Before callback
57
+
58
+ Define before_filter-style callbacks in the form of the before block. This block is run once before the importer runs.
59
+
60
+ importer :items do
61
+ file 'items.csv'
62
+
63
+ before do
64
+ Item.delete_all
65
+ end
66
+
67
+ foreach do |row|
68
+ Item.create(:name => row[:name])
69
+ end
70
+ end
71
+
72
+ == File processing
73
+
74
+ Passing an array of file paths to the file configuration field will process all the rows in each file.
75
+
76
+ importer :items do
77
+ file Dir.glob("data/*.csv")
78
+
79
+ foreach do |row|
80
+ Item.create(:name => row[:name])
81
+ end
82
+ end
83
+
84
+ Use the foreach_file block to add per-file behavior.
85
+
86
+ importer :items do
87
+ file Dir.glob("data/*.csv")
88
+
89
+ foreach_file do |file|
90
+ List.create(:name => File.basename(file, ".csv"))
91
+ end
92
+
93
+ foreach do |row|
94
+ Item.create(:name => row[:name])
95
+ end
96
+ end
97
+
98
+ Nest the foreach block to give your row processing block access to foreach_file block variables.
99
+
100
+ importer :items do
101
+ file Dir.glob("data/*.csv")
102
+
103
+ foreach_file do |file|
104
+ list = List.create(:name => File.basename(file, ".csv"))
105
+
106
+ foreach do |row|
107
+ Item.create(:name => row[:name], :list => list)
108
+ end
109
+ end
110
+ end
111
+
112
+ == Loading and running importers
113
+
114
+ simple_importer provides a method that will find and load importer definitions in the importers, lib/importers, and app/importers directories.
115
+
116
+ # importer definition is in lib/importers/item.rb
117
+ SimpleImporter.find_importers
118
+
119
+ SimpleImporter now has a collection of importers and each importer can be run now that it has been loaded.
120
+
121
+ # the following will fire off the actual importers
122
+ SimpleImporter.importers.each do |importer|
123
+ importer.run
124
+ end
125
+
126
+ == Rake tasks and autoloaded importers
127
+
128
+ simple_importer provides rake tasks to run importers individually or all at once.
129
+
130
+ # add this to your rakefile
131
+ # lib/tasks/simple_importer.rake is a good place in your rails app
132
+ require 'simple_importer/tasks'
133
+
134
+ rake -T simple_importer will show a rake task for each importer as well as an import task which runs all of the importers. Descriptions defined in the importer will be used for the rake task description.
135
+
136
+ importer :items do
137
+ file 'items.csv'
138
+ desc 'Import all of the cool items'
139
+
140
+ ...
141
+ end
142
+
143
+ == Requirements
144
+
145
+ * fastercsv (if you are not using ruby 1.9)
146
+
147
+ == Note on Patches/Pull Requests
148
+
149
+ * Fork the project.
150
+ * Make your feature addition or bug fix.
151
+ * Add tests for it. This is important so I don't break it in a
152
+ future version unintentionally.
153
+ * Commit, do not mess with rakefile, version, or history.
154
+ (if you want to have your own version, that is fine but
155
+ bump version in a commit by itself I can ignore when I pull)
156
+ * Send me a pull request. Bonus points for topic branches.
157
+
158
+ == Copyright
159
+
160
+ Copyright (c) 2009 Justin Marney. See LICENSE for details.
@@ -0,0 +1,54 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "simple_importer"
8
+ gem.summary = %Q{Simple api for importing from csv.}
9
+ gem.description = %Q{Provides a simple dsl for creating csv import tasks.}
10
+ gem.email = "gotascii@gmail.com"
11
+ gem.homepage = "http://github.com/vigetlabs/simple_importer"
12
+ gem.authors = ["Justin Marney"]
13
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
14
+ end
15
+ Jeweler::GemcutterTasks.new
16
+ rescue LoadError
17
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
18
+ end
19
+
20
+ require 'rake/testtask'
21
+ Rake::TestTask.new(:test) do |test|
22
+ test.libs << 'lib' << 'test'
23
+ test.pattern = 'test/**/test_*.rb'
24
+ test.verbose = true
25
+ end
26
+
27
+ begin
28
+ require 'rcov/rcovtask'
29
+ Rcov::RcovTask.new do |test|
30
+ test.libs << 'test'
31
+ test.pattern = 'test/**/test_*.rb'
32
+ test.verbose = true
33
+ end
34
+ rescue LoadError
35
+ task :rcov do
36
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
37
+ end
38
+ end
39
+
40
+ task :test => :check_dependencies
41
+
42
+ task :default => :test
43
+
44
+ require 'rake/rdoctask'
45
+ Rake::RDocTask.new do |rdoc|
46
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
47
+
48
+ rdoc.rdoc_dir = 'rdoc'
49
+ rdoc.title = "simple_importer #{version}"
50
+ rdoc.rdoc_files.include('README*')
51
+ rdoc.rdoc_files.include('lib/**/*.rb')
52
+ end
53
+
54
+ require 'lib/simple_importer/tasks'
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,2 @@
1
+ name,lvl,screenname
2
+ justin,11,gotascii
@@ -0,0 +1,11 @@
1
+ importer :example do
2
+ file 'data/example.csv'
3
+
4
+ before do
5
+ puts 'before callback'
6
+ end
7
+
8
+ foreach do |row|
9
+ puts "#{row[:name]}, #{row[:lvl]}, #{row[:screenname]}"
10
+ end
11
+ end
@@ -0,0 +1,106 @@
1
+ require "csv"
2
+
3
+ if CSV.const_defined? :Reader
4
+ require 'fastercsv'
5
+ Object.send(:remove_const, :CSV)
6
+ CSV = FasterCSV
7
+ end
8
+
9
+ module SimpleImporter
10
+ def self.included(base)
11
+ config_meths.each do |meth|
12
+ base.class_eval <<-METH
13
+ def #{meth}(val = nil)
14
+ config[:#{meth}] = val unless val.nil?
15
+ config[:#{meth}]
16
+ end
17
+ METH
18
+ end
19
+
20
+ [:before, :foreach, :foreach_file].each do |meth|
21
+ base.class_eval <<-METH
22
+ def #{meth}(&block)
23
+ config[:#{meth}] = block if block_given?
24
+ config[:#{meth}]
25
+ end
26
+ METH
27
+ end
28
+
29
+ base.class_eval do
30
+ attr_accessor :name
31
+ end
32
+ end
33
+
34
+ def self.importer_paths
35
+ %w{importers app/importers lib/importers}
36
+ end
37
+
38
+ def self.find_importers
39
+ importer_paths.each do |path|
40
+ Dir[File.join(path, '*.rb')].each do |file|
41
+ coedz = File.open(file).read
42
+ SimpleImporter.class_eval(coedz)
43
+ end
44
+ end
45
+ end
46
+
47
+ def self.csv_config_meths
48
+ CSV::DEFAULT_OPTIONS.keys
49
+ end
50
+
51
+ def self.config_meths
52
+ csv_config_meths + [:file, :callbacks, :desc]
53
+ end
54
+
55
+ def self.importers
56
+ @importers ||= []
57
+ end
58
+
59
+ def self.importer(name, &block)
60
+ importers << Importer.new(name, &block)
61
+ end
62
+
63
+ def self.[](name)
64
+ importers.select{|i| i.name == name}.first
65
+ end
66
+
67
+ def config
68
+ @config ||= {
69
+ :callbacks => true,
70
+ :headers => true,
71
+ :header_converters => :symbol,
72
+ :converters => :all,
73
+ :desc => 'a simple_import importer'
74
+ }
75
+ end
76
+
77
+ def csv_config
78
+ ret = {}
79
+ config.each do |k, v|
80
+ ret[k] = config[k] if SimpleImporter.csv_config_meths.include?(k)
81
+ end
82
+ ret
83
+ # config.select{|k,v| SimpleImporter.csv_config_meths.include?(k)}
84
+ end
85
+
86
+ def run
87
+ run_callbacks
88
+ [file].flatten.each do |f|
89
+ foreach_file.call(f) if foreach_file
90
+ CSV.foreach(f, csv_config, &foreach) if foreach
91
+ end
92
+ end
93
+
94
+ def run_callbacks
95
+ self.before.call if callbacks && before
96
+ end
97
+
98
+ class Importer
99
+ include SimpleImporter
100
+
101
+ def initialize(name, &block)
102
+ self.name = name
103
+ instance_eval(&block)
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,3 @@
1
+ Dir[File.join(File.dirname(__FILE__), 'tasks', '*.rake')].each do |f|
2
+ load f
3
+ end
@@ -0,0 +1,24 @@
1
+ namespace :simple_importer do
2
+ def importers
3
+ require 'simple_importer'
4
+ SimpleImporter.find_importers
5
+ SimpleImporter.importers
6
+ end
7
+
8
+ importers.each do |i|
9
+ desc i.desc
10
+ task i.name do
11
+ Rake::Task[:environment].invoke if Rake::Task.task_defined?(:environment)
12
+ puts "Importing #{i.name}"
13
+ i.run
14
+ puts "Finished importing #{i.name}"
15
+ end
16
+ end
17
+
18
+ desc "run all importers"
19
+ task :import do
20
+ importers.each do |i|
21
+ Rake::Task["simple_importer:#{i.name}"].invoke
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,54 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{simple_importer}
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Justin Marney"]
12
+ s.date = %q{2009-10-22}
13
+ s.description = %q{Provides a simple dsl for creating csv import tasks.}
14
+ s.email = %q{gotascii@gmail.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".gitignore",
21
+ "LICENSE",
22
+ "README.rdoc",
23
+ "Rakefile",
24
+ "VERSION",
25
+ "data/example.csv",
26
+ "importers/example.rb",
27
+ "lib/simple_importer.rb",
28
+ "lib/simple_importer/tasks.rb",
29
+ "lib/simple_importer/tasks/simple_importer.rake",
30
+ "simple_importer.gemspec",
31
+ "test/test_helper.rb",
32
+ "test/test_simple_importer.rb"
33
+ ]
34
+ s.homepage = %q{http://github.com/vigetlabs/simple_importer}
35
+ s.rdoc_options = ["--charset=UTF-8"]
36
+ s.require_paths = ["lib"]
37
+ s.rubygems_version = %q{1.3.5}
38
+ s.summary = %q{Simple api for importing from csv.}
39
+ s.test_files = [
40
+ "test/test_helper.rb",
41
+ "test/test_simple_importer.rb"
42
+ ]
43
+
44
+ if s.respond_to? :specification_version then
45
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
46
+ s.specification_version = 3
47
+
48
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
49
+ else
50
+ end
51
+ else
52
+ end
53
+ end
54
+
@@ -0,0 +1,5 @@
1
+ require 'rubygems'
2
+ require 'shoulda'
3
+ require 'matchy'
4
+ require 'mocha'
5
+ require 'simple_importer'
@@ -0,0 +1,164 @@
1
+ require 'test_helper'
2
+
3
+ class CrazyImporter
4
+ include SimpleImporter
5
+ end
6
+
7
+ class SimpleImporterTest < Test::Unit::TestCase
8
+ context "The SimpleImporter module" do
9
+ setup do
10
+ SimpleImporter.importers.clear
11
+ end
12
+
13
+ should "have an importers hash field" do
14
+ SimpleImporter.importers.should == []
15
+ end
16
+
17
+ should "create a new importer" do
18
+ SimpleImporter::Importer.expects(:new).with(:bob)
19
+ SimpleImporter.importer(:bob)
20
+ end
21
+
22
+ should "add new importer to importers" do
23
+ SimpleImporter::Importer.stubs(:new).returns('hi')
24
+ SimpleImporter.importer(:bob)
25
+ SimpleImporter.importers.should == ['hi']
26
+ end
27
+
28
+ should "look in importer, app/importers, and lib/importers for importers" do
29
+ SimpleImporter.importer_paths.should == %w{importers app/importers lib/importers}
30
+ end
31
+
32
+ should "return csv default options for csv_config_methods" do
33
+ SimpleImporter.csv_config_meths.should == CSV::DEFAULT_OPTIONS.keys
34
+ end
35
+
36
+ should "return csv_config_meths plus file and callbacks for config_meths" do
37
+ SimpleImporter.stubs(:csv_config_meths).returns([:yo])
38
+ SimpleImporter.config_meths.should == [:yo, :file, :callbacks, :desc]
39
+ end
40
+
41
+ should "return first importer whos name matches arg for []" do
42
+ importer = stub(:name => :omg)
43
+ SimpleImporter.stubs(:importers).returns([importer])
44
+ SimpleImporter[:omg].should == importer
45
+ end
46
+ end
47
+
48
+ context "An instance of the CrazyImporter class" do
49
+ setup do
50
+ @importer = CrazyImporter.new
51
+ end
52
+
53
+ should "return kv pairs where key is in SimpleImporter.csv_config_meths for csv_config" do
54
+ @importer.csv_config.keys.each do |k|
55
+ SimpleImporter.csv_config_meths.include?(k).should be(true)
56
+ end
57
+ end
58
+
59
+ should "return the right gd hash for csv_config" do
60
+ SimpleImporter.stubs(:csv_config_meths).returns([:one, :two])
61
+ @importer.stubs(:config).returns({:one => 1, :two => 2, :three => 3})
62
+ @importer.csv_config.should == {:one => 1, :two => 2}
63
+ end
64
+
65
+ should "have fields for every CSV::DEFAULT_OPTIONS" do
66
+ CSV::DEFAULT_OPTIONS.keys.each do |meth|
67
+ @importer.send(meth, 'val')
68
+ @importer.send(meth).should == 'val'
69
+ end
70
+ end
71
+
72
+ should "have a file field" do
73
+ @importer.file 'file'
74
+ @importer.file.should == 'file'
75
+ end
76
+
77
+ should "have a callbacks field" do
78
+ @importer.callbacks true
79
+ @importer.callbacks.should be(true)
80
+ end
81
+
82
+ should "execute callbacks by default" do
83
+ @importer.callbacks.should be(true)
84
+ end
85
+
86
+ should "create headers by default" do
87
+ @importer.headers.should be(true)
88
+ end
89
+
90
+ should "execute symbol header_converters by default" do
91
+ @importer.header_converters.should == :symbol
92
+ end
93
+
94
+ should "execute all converters by default" do
95
+ @importer.converters.should == :all
96
+ end
97
+
98
+ should "save block passed to before in before field" do
99
+ @importer.before { 'blazer' }
100
+ @importer.before.call.should == 'blazer'
101
+ end
102
+
103
+ should "save block passed to foreach in foreach field" do
104
+ @importer.foreach { 'blazer' }
105
+ @importer.foreach.call.should == 'blazer'
106
+ end
107
+
108
+ should "not run callbacks if callbacks is false" do
109
+ before = mock { expects(:call).never }
110
+ @importer.stubs(:before => before)
111
+ @importer.callbacks false
112
+ @importer.run_callbacks
113
+ end
114
+
115
+ should "run callbacks if callbacks is true" do
116
+ before = mock { expects(:call) }
117
+ @importer.stubs(:before => before)
118
+ @importer.callbacks true
119
+ @importer.run_callbacks
120
+ end
121
+
122
+ should "not run callbacks if callbacks is true and there are no callbacks" do
123
+ @importer.config[:before] = nil
124
+ @importer.callbacks true
125
+ @importer.run_callbacks
126
+ end
127
+
128
+ should "run callbacks when run" do
129
+ CSV.stubs(:foreach)
130
+ @importer.expects(:run_callbacks)
131
+ @importer.run
132
+ end
133
+
134
+ should "call CSV.foreach with file, csv_config when run" do
135
+ @importer.stubs(:foreach).returns(lambda{})
136
+ @importer.stubs(:file).returns('file')
137
+ @importer.stubs(:csv_config).returns('csv_config')
138
+ CSV.expects(:foreach).with('file', 'csv_config')
139
+ @importer.run
140
+ end
141
+
142
+ should "call CSV.foreach with each file if file is an array" do
143
+ @importer.stubs(:foreach).returns(lambda{})
144
+ @importer.stubs(:file).returns(['file1', 'file2'])
145
+ @importer.stubs(:csv_config).returns('csv_config')
146
+ CSV.expects(:foreach).with('file1', 'csv_config')
147
+ CSV.expects(:foreach).with('file2', 'csv_config')
148
+ @importer.run
149
+ end
150
+ end
151
+
152
+ context "The Importer class" do
153
+ should "include SimpleImporter" do
154
+ SimpleImporter::Importer.ancestors.include?(SimpleImporter)
155
+ end
156
+
157
+ should "instance eval block on initialize" do
158
+ SimpleImporter::Importer.any_instance.expects(:bobo)
159
+ SimpleImporter::Importer.new(:hi) do
160
+ bobo
161
+ end
162
+ end
163
+ end
164
+ end
metadata ADDED
@@ -0,0 +1,69 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: simple_importer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Justin Marney
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-10-22 00:00:00 -04:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: Provides a simple dsl for creating csv import tasks.
17
+ email: gotascii@gmail.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - LICENSE
24
+ - README.rdoc
25
+ files:
26
+ - .gitignore
27
+ - LICENSE
28
+ - README.rdoc
29
+ - Rakefile
30
+ - VERSION
31
+ - data/example.csv
32
+ - importers/example.rb
33
+ - lib/simple_importer.rb
34
+ - lib/simple_importer/tasks.rb
35
+ - lib/simple_importer/tasks/simple_importer.rake
36
+ - simple_importer.gemspec
37
+ - test/test_helper.rb
38
+ - test/test_simple_importer.rb
39
+ has_rdoc: true
40
+ homepage: http://github.com/vigetlabs/simple_importer
41
+ licenses: []
42
+
43
+ post_install_message:
44
+ rdoc_options:
45
+ - --charset=UTF-8
46
+ require_paths:
47
+ - lib
48
+ required_ruby_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: "0"
53
+ version:
54
+ required_rubygems_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: "0"
59
+ version:
60
+ requirements: []
61
+
62
+ rubyforge_project:
63
+ rubygems_version: 1.3.5
64
+ signing_key:
65
+ specification_version: 3
66
+ summary: Simple api for importing from csv.
67
+ test_files:
68
+ - test/test_helper.rb
69
+ - test/test_simple_importer.rb