datashift 0.8.0 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +12 -0
- data/Rakefile +4 -2
- data/VERSION +1 -1
- data/datashift.gemspec +13 -12
- data/lib/datashift/delimiters.rb +87 -0
- data/lib/datashift/method_detail.rb +5 -0
- data/lib/datashift/method_details_manager.rb +5 -1
- data/lib/datashift/method_dictionary.rb +3 -1
- data/lib/exporters/csv_exporter.rb +158 -10
- data/lib/exporters/excel_exporter.rb +6 -2
- data/lib/exporters/exporter_base.rb +6 -0
- data/lib/helpers/spree_helper.rb +2 -1
- data/lib/loaders/paperclip/image_loader.rb +32 -2
- data/lib/loaders/spree/product_loader.rb +3 -2
- data/lib/thor/export.thor +111 -0
- data/lib/thor/{import_excel.thor → import.thor} +40 -2
- data/lib/thor/spree/bootstrap_cleanup.thor +7 -4
- data/lib/thor/spree/products_images.thor +26 -17
- data/lib/thor/spree/reports.thor +30 -40
- data/lib/thor/tools.thor +63 -0
- data/spec/Gemfile +3 -2
- data/spec/csv_exporter_spec.rb +101 -0
- data/spec/excel_exporter_spec.rb +1 -1
- data/spec/fixtures/datashift_Spree_db.sqlite +0 -0
- data/spec/fixtures/datashift_test_models_db.sqlite +0 -0
- data/spec/fixtures/test_model_defs.rb +5 -0
- data/spec/spree_loader_spec.rb +28 -131
- data/spec/spree_method_mapping_spec.rb +1 -1
- data/spec/spree_variants_loader_spec.rb +189 -0
- metadata +193 -167
- data/lib/thor/export_excel.thor +0 -74
- data/sandbox/app/controllers/application_controller.rb +0 -3
- data/sandbox/config/application.rb +0 -59
- data/sandbox/config/database.yml +0 -20
- data/sandbox/config/environment.rb +0 -5
- data/sandbox/config/environments/development.rb +0 -37
- data/tasks/import/csv.rake +0 -51
data/README.markdown
CHANGED
@@ -43,11 +43,23 @@ To use :
|
|
43
43
|
|
44
44
|
To pull the tasks in, add this call to your Rakefile :
|
45
45
|
|
46
|
+
<<<<<<< HEAD
|
47
|
+
```ruby
|
48
|
+
DataShift::load_tasks
|
49
|
+
```
|
50
|
+
|
51
|
+
To keep the availability to only development mode use
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
DataShift::load_tasks if(Rails.env.development?)
|
55
|
+
```
|
56
|
+
=======
|
46
57
|
```ruby DataShift::load_tasks```
|
47
58
|
|
48
59
|
To keep the availability to only development mode use
|
49
60
|
|
50
61
|
```ruby DataShift::load_tasks if(Rails.env.development?)```
|
62
|
+
>>>>>>> a6d5d492fd9df38a4acb8c50423219994b4b5601
|
51
63
|
|
52
64
|
To use the Thor command line applications :
|
53
65
|
|
data/Rakefile
CHANGED
@@ -34,13 +34,15 @@ Jeweler::Tasks.new do |gem|
|
|
34
34
|
gem.homepage = "http://github.com/autotelik/datashift"
|
35
35
|
gem.license = "MIT"
|
36
36
|
gem.summary = %Q{ Shift data betwen applications and Active Record}
|
37
|
-
gem.description = %Q{
|
37
|
+
gem.description = %Q{Comprehensive Excel and CSV import/export tools. Shift data between ActiveRecord databases, applications, and projects like Spree}
|
38
38
|
gem.email = "rubygems@autotelik.co.uk"
|
39
39
|
gem.authors = ["Thomas Statter"]
|
40
40
|
# dependencies defined in Gemfile
|
41
41
|
gem.files.exclude ['sandbox']
|
42
42
|
|
43
|
-
gem.add_dependency
|
43
|
+
gem.add_dependency 'spreadsheet'
|
44
|
+
gem.add_dependency 'rubyzip'
|
45
|
+
|
44
46
|
end
|
45
47
|
Jeweler::RubygemsDotOrgTasks.new
|
46
48
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.9.0
|
data/datashift.gemspec
CHANGED
@@ -5,12 +5,12 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = "datashift"
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.9.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Thomas Statter"]
|
12
|
-
s.date = "2012-
|
13
|
-
s.description = "
|
12
|
+
s.date = "2012-09-03"
|
13
|
+
s.description = "Comprehensive Excel and CSV import/export tools. Shift data between ActiveRecord databases, applications, and projects like Spree"
|
14
14
|
s.email = "rubygems@autotelik.co.uk"
|
15
15
|
s.extra_rdoc_files = [
|
16
16
|
"LICENSE.txt",
|
@@ -28,6 +28,7 @@ Gem::Specification.new do |s|
|
|
28
28
|
"lib/applications/jruby/jexcel_file.rb",
|
29
29
|
"lib/applications/jruby/word.rb",
|
30
30
|
"lib/datashift.rb",
|
31
|
+
"lib/datashift/delimiters.rb",
|
31
32
|
"lib/datashift/exceptions.rb",
|
32
33
|
"lib/datashift/file_definitions.rb",
|
33
34
|
"lib/datashift/logging.rb",
|
@@ -69,12 +70,13 @@ Gem::Specification.new do |s|
|
|
69
70
|
"lib/loaders/spreadsheet_loader.rb",
|
70
71
|
"lib/loaders/spree/image_loader.rb",
|
71
72
|
"lib/loaders/spree/product_loader.rb",
|
72
|
-
"lib/thor/
|
73
|
+
"lib/thor/export.thor",
|
73
74
|
"lib/thor/generate_excel.thor",
|
74
|
-
"lib/thor/
|
75
|
+
"lib/thor/import.thor",
|
75
76
|
"lib/thor/spree/bootstrap_cleanup.thor",
|
76
77
|
"lib/thor/spree/products_images.thor",
|
77
78
|
"lib/thor/spree/reports.thor",
|
79
|
+
"lib/thor/tools.thor",
|
78
80
|
"public/spree/products/large/DEMO_001_ror_bag.jpeg",
|
79
81
|
"public/spree/products/large/DEMO_002_Powerstation.jpg",
|
80
82
|
"public/spree/products/large/DEMO_003_ror_mug.jpeg",
|
@@ -90,12 +92,8 @@ Gem::Specification.new do |s|
|
|
90
92
|
"public/spree/products/small/DEMO_001_ror_bag.jpeg",
|
91
93
|
"public/spree/products/small/DEMO_002_Powerstation.jpg",
|
92
94
|
"public/spree/products/small/DEMO_003_ror_mug.jpeg",
|
93
|
-
"sandbox/app/controllers/application_controller.rb",
|
94
|
-
"sandbox/config/application.rb",
|
95
|
-
"sandbox/config/database.yml",
|
96
|
-
"sandbox/config/environment.rb",
|
97
|
-
"sandbox/config/environments/development.rb",
|
98
95
|
"spec/Gemfile",
|
96
|
+
"spec/csv_exporter_spec.rb",
|
99
97
|
"spec/csv_loader_spec.rb",
|
100
98
|
"spec/datashift_spec.rb",
|
101
99
|
"spec/db/migrate/20110803201325_create_test_bed.rb",
|
@@ -147,13 +145,13 @@ Gem::Specification.new do |s|
|
|
147
145
|
"spec/spree_images_loader_spec.rb",
|
148
146
|
"spec/spree_loader_spec.rb",
|
149
147
|
"spec/spree_method_mapping_spec.rb",
|
148
|
+
"spec/spree_variants_loader_spec.rb",
|
150
149
|
"spec/thor_spec.rb",
|
151
150
|
"tasks/config/seed_fu_product_template.erb",
|
152
151
|
"tasks/config/tidy_config.txt",
|
153
152
|
"tasks/db_tasks.rake",
|
154
153
|
"tasks/export/excel_generator.rake",
|
155
154
|
"tasks/file_tasks.rake",
|
156
|
-
"tasks/import/csv.rake",
|
157
155
|
"tasks/import/excel.rake",
|
158
156
|
"tasks/word_to_seedfu.rake",
|
159
157
|
"test/helper.rb",
|
@@ -162,7 +160,7 @@ Gem::Specification.new do |s|
|
|
162
160
|
s.homepage = "http://github.com/autotelik/datashift"
|
163
161
|
s.licenses = ["MIT"]
|
164
162
|
s.require_paths = ["lib"]
|
165
|
-
s.rubygems_version = "1.8.
|
163
|
+
s.rubygems_version = "1.8.24"
|
166
164
|
s.summary = "Shift data betwen applications and Active Record"
|
167
165
|
|
168
166
|
if s.respond_to? :specification_version then
|
@@ -170,11 +168,14 @@ Gem::Specification.new do |s|
|
|
170
168
|
|
171
169
|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
172
170
|
s.add_runtime_dependency(%q<spreadsheet>, [">= 0"])
|
171
|
+
s.add_runtime_dependency(%q<rubyzip>, [">= 0"])
|
173
172
|
else
|
174
173
|
s.add_dependency(%q<spreadsheet>, [">= 0"])
|
174
|
+
s.add_dependency(%q<rubyzip>, [">= 0"])
|
175
175
|
end
|
176
176
|
else
|
177
177
|
s.add_dependency(%q<spreadsheet>, [">= 0"])
|
178
|
+
s.add_dependency(%q<rubyzip>, [">= 0"])
|
178
179
|
end
|
179
180
|
end
|
180
181
|
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# Copyright:: (c) Autotelik Media Ltd 2011
|
2
|
+
# Author :: Tom Statter
|
3
|
+
# Date :: Aug 2010
|
4
|
+
# License:: MIT
|
5
|
+
#
|
6
|
+
# Details:: Module providing standard location for delimiters used in both export/import
|
7
|
+
#
|
8
|
+
# For example we support multiple entries in a single column, so the string
|
9
|
+
# needs to be formatted with recognisable delimiters seperating each of the multiple values.
|
10
|
+
#
|
11
|
+
module DataShift
|
12
|
+
|
13
|
+
|
14
|
+
module Delimiters
|
15
|
+
|
16
|
+
|
17
|
+
# Support multiple associations being added to a base object to be specified in a single column.
|
18
|
+
#
|
19
|
+
# Entry represents the association to find via supplied name, value to use in the lookup.
|
20
|
+
# Can contain multiple lookup name/value pairs, separated by multi_assoc_delim ( | )
|
21
|
+
#
|
22
|
+
# Default syntax :
|
23
|
+
#
|
24
|
+
# Name1:value1, value2|Name2:value1, value2, value3|Name3:value1, value2
|
25
|
+
#
|
26
|
+
# E.G.
|
27
|
+
# Association Properties, has a column named Size, and another called Colour,
|
28
|
+
# and this combination could be used to lookup multiple associations to add to the main model Jumper
|
29
|
+
#
|
30
|
+
# Size:small # => generates find_by_size( 'small' )
|
31
|
+
# Size:large # => generates find_by_size( 'large' )
|
32
|
+
# Colour:red,green,blue # => generates find_all_by_colour( ['red','green','blue'] )
|
33
|
+
#
|
34
|
+
# Size:large|Size:medium|Size:large
|
35
|
+
# => Find 3 different associations, perform lookup via column called Size
|
36
|
+
# => Jumper.properties << [ small, medium, large ]
|
37
|
+
#
|
38
|
+
def self.name_value_delim
|
39
|
+
@name_value_delim ||= ':'
|
40
|
+
@name_value_delim
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.set_name_value_delim(x) @name_value_delim = x; end
|
44
|
+
# TODO - support embedded object creation/update via hash (which hopefully we should be able to just forward to AR)
|
45
|
+
#
|
46
|
+
# |Category|
|
47
|
+
# name:new{ :date => '20110102', :owner = > 'blah'}
|
48
|
+
#
|
49
|
+
|
50
|
+
|
51
|
+
def self.multi_value_delim
|
52
|
+
@multi_value_delim ||= ','
|
53
|
+
@multi_value_delim
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.set_multi_value_delim(x) @multi_value_delim = x; end
|
57
|
+
|
58
|
+
# Multiple objects can be embedded in single columns.
|
59
|
+
# In this example a single Category column contains 3 separate entries, New, SecondHand, Retro
|
60
|
+
# object creation/update via hash (which hopefully we should be able to just forward to AR)
|
61
|
+
#
|
62
|
+
# | Category |
|
63
|
+
# 'name =>New, :a => 1, :b => 2|name => SecondHand, :a => 6, :b => 34|Name:Old, :a => 12, :b => 67', 'Next Column'
|
64
|
+
#
|
65
|
+
def self.multi_assoc_delim
|
66
|
+
@multi_assoc_delim ||= '|'
|
67
|
+
@multi_assoc_delim
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
def self.set_multi_assoc_delim(x) @multi_assoc_delim = x; end
|
72
|
+
|
73
|
+
|
74
|
+
def self.csv_delim
|
75
|
+
@csv_delim ||= ','
|
76
|
+
@csv_delim
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.set_csv_delim(x) @csv_delim = x; end
|
80
|
+
|
81
|
+
def self.eol
|
82
|
+
"\n"
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
@@ -25,6 +25,11 @@ module DataShift
|
|
25
25
|
@type_enum
|
26
26
|
end
|
27
27
|
|
28
|
+
def self.association_types_enum
|
29
|
+
@assoc_type_enum ||= Set[:belongs_to, :has_one, :has_many]
|
30
|
+
@assoc_type_enum
|
31
|
+
end
|
32
|
+
|
28
33
|
# When looking up an association, try each of these in turn till a match
|
29
34
|
# i.e find_by_name .. find_by_title and so on, lastly try the raw id
|
30
35
|
@@insistent_find_by_list ||= [:name, :title, :id]
|
@@ -47,9 +47,13 @@ module DataShift
|
|
47
47
|
end
|
48
48
|
|
49
49
|
def get_list( type )
|
50
|
-
@method_details_list[type.to_sym]
|
50
|
+
@method_details_list[type.to_sym] || []
|
51
51
|
end
|
52
52
|
|
53
|
+
def get_operators( op_type )
|
54
|
+
get_list(op_type).collect { |md| md.operator }
|
55
|
+
end
|
56
|
+
|
53
57
|
def all_available_operators
|
54
58
|
method_details_list.values.flatten.collect(&:operator)
|
55
59
|
end
|
@@ -59,7 +59,9 @@ module DataShift
|
|
59
59
|
# the raw form i.e without the '=' for consistency
|
60
60
|
if( options[:reload] || assignments[klass].nil? )
|
61
61
|
|
62
|
-
|
62
|
+
# TODO investigate difference with attribute_names - maybe column names can be assigned to an attribute
|
63
|
+
# so in terms of method calls on klass attribute_names might be safer
|
64
|
+
assignments[klass] = klass.column_names
|
63
65
|
|
64
66
|
if(options[:instance_methods] == true)
|
65
67
|
setters = klass.instance_methods.grep(/\w+=/).collect {|x| x.to_s }
|
@@ -7,30 +7,178 @@
|
|
7
7
|
#
|
8
8
|
#
|
9
9
|
require 'exporter_base'
|
10
|
+
require 'csv'
|
10
11
|
|
11
12
|
module DataShift
|
12
13
|
|
13
14
|
class CsvExporter < ExporterBase
|
14
15
|
|
15
|
-
attr_accessor :
|
16
|
-
|
16
|
+
attr_accessor :text_delim
|
17
|
+
|
17
18
|
def initialize(filename)
|
18
|
-
|
19
|
-
@
|
19
|
+
super(filename)
|
20
|
+
@text_delim = "\'"
|
20
21
|
end
|
21
22
|
|
22
|
-
# Create CSV file representing supplied Model
|
23
23
|
|
24
|
-
|
25
|
-
|
26
|
-
@
|
24
|
+
# Return opposite of text delim - "hello, 'barry'" => '"hello, "barry""'
|
25
|
+
def escape_text_delim
|
26
|
+
return '"' if @text_delim == "\'"
|
27
|
+
"\'"
|
28
|
+
end
|
29
|
+
|
30
|
+
# Create CSV file from set of ActiveRecord objects
|
31
|
+
# Options :
|
32
|
+
# => :filename
|
33
|
+
# => :text_delim => Char to use to delim columns, useful when data contain embedded ','
|
34
|
+
# => ::methods => List of methods to additionally call on each record
|
35
|
+
#
|
36
|
+
def export(records, options = {})
|
37
|
+
|
38
|
+
raise ArgumentError.new('Please supply array of records to export') unless records.is_a? Array
|
39
|
+
|
40
|
+
first = records[0]
|
41
|
+
|
42
|
+
return unless(first.is_a?(ActiveRecord::Base))
|
43
|
+
|
44
|
+
f = options[:filename] || filename()
|
45
|
+
|
46
|
+
@text_delim = options[:text_delim] if(options[:text_delim])
|
47
|
+
|
48
|
+
File.open(f, "w") do |csv|
|
49
|
+
|
50
|
+
headers = first.serializable_hash.keys.collect
|
51
|
+
|
52
|
+
[*options[:methods]].each do |c| headers << c if(first.respond_to?(c)) end if(options[:methods])
|
53
|
+
|
54
|
+
csv << headers.join(Delimiters::csv_delim) << Delimiters::eol
|
55
|
+
|
56
|
+
records.each do |r|
|
57
|
+
next unless(r.is_a?(ActiveRecord::Base))
|
58
|
+
|
59
|
+
csv << record_to_csv(r, options) << Delimiters::eol
|
60
|
+
end
|
61
|
+
end
|
27
62
|
end
|
63
|
+
|
64
|
+
# Create an Excel file from list of ActiveRecord objects
|
65
|
+
# Specify which associations to export via :with or :exclude
|
66
|
+
# Possible values are : [:assignment, :belongs_to, :has_one, :has_many]
|
67
|
+
#
|
68
|
+
def export_with_associations(klass, records, options = {})
|
69
|
+
|
70
|
+
f = options[:filename] || filename()
|
71
|
+
|
72
|
+
@text_delim = options[:text_delim] if(options[:text_delim])
|
73
|
+
|
74
|
+
MethodDictionary.find_operators( klass )
|
75
|
+
|
76
|
+
# builds all possible operators
|
77
|
+
MethodDictionary.build_method_details( klass )
|
78
|
+
|
79
|
+
work_list = options[:with] ? Set(options[:with]) : MethodDetail::supported_types_enum
|
80
|
+
|
81
|
+
assoc_work_list = work_list.dup
|
28
82
|
|
83
|
+
details_mgr = MethodDictionary.method_details_mgrs[klass]
|
84
|
+
|
85
|
+
headers, assoc_operators, assignments = [], [], []
|
86
|
+
|
87
|
+
# headers
|
88
|
+
if(work_list.include?(:assignment))
|
89
|
+
assignments << details_mgr.get_list(:assignment).collect( &:operator)
|
90
|
+
assoc_work_list.delete :assignment
|
91
|
+
end
|
92
|
+
|
93
|
+
headers << assignments.flatten!
|
94
|
+
# based on users requested list ... belongs_to has_one, has_many etc ... select only those operators
|
95
|
+
assoc_operators = assoc_work_list.collect do |op_type|
|
96
|
+
details_mgr.get_list(op_type).collect(&:operator).flatten
|
97
|
+
end
|
98
|
+
|
99
|
+
assoc_operators.flatten!
|
100
|
+
|
101
|
+
File.open(f, "w") do |csv|
|
102
|
+
|
103
|
+
csv << headers.join(Delimiters::csv_delim)
|
104
|
+
|
105
|
+
csv << Delimiters::csv_delim << assoc_operators.join(Delimiters::csv_delim) unless(assoc_operators.empty?)
|
106
|
+
|
107
|
+
csv << Delimiters::eol
|
108
|
+
|
109
|
+
records.each do |r|
|
110
|
+
next unless(r.is_a?(ActiveRecord::Base))
|
111
|
+
|
112
|
+
csv_columns = []
|
113
|
+
# need to make sure order matches headers
|
114
|
+
# could look at collection headers via serializable hash.keys and then use
|
115
|
+
# csv << record_to_csv(r) ??
|
116
|
+
assignments.each {|x| csv << escape(r.send(x)) << Delimiters::csv_delim }
|
117
|
+
|
118
|
+
# done records basic attributes now deal with associations
|
119
|
+
|
120
|
+
#assoc_work_list.each do |op_type|
|
121
|
+
# details_mgr.get_operators(op_type).each do |operator|
|
122
|
+
assoc_operators.each do |operator|
|
123
|
+
assoc_object = r.send(operator)
|
124
|
+
|
125
|
+
if(assoc_object.is_a?ActiveRecord::Base)
|
126
|
+
column_text = record_to_column(assoc_object) # belongs_to or has_one
|
127
|
+
|
128
|
+
csv << "#{@text_delim}#{column_text}#{@text_delim}" << Delimiters::csv_delim
|
129
|
+
#csv << record_to_csv(r)
|
130
|
+
|
131
|
+
elsif(assoc_object.is_a? Array)
|
132
|
+
items_to_s = assoc_object.collect {|x| record_to_column(x) }
|
133
|
+
|
134
|
+
# create a single column
|
135
|
+
csv << "#{@text_delim}#{items_to_s.join(Delimiters::multi_assoc_delim)}#{@text_delim}" << Delimiters::csv_delim
|
136
|
+
|
137
|
+
else
|
138
|
+
csv << Delimiters::csv_delim
|
139
|
+
end
|
140
|
+
#end
|
141
|
+
end
|
142
|
+
|
143
|
+
csv << Delimiters::eol # next record
|
144
|
+
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
|
150
|
+
# Convert an AR instance to a single CSV column
|
29
151
|
|
30
|
-
|
152
|
+
def record_to_column(record)
|
153
|
+
|
154
|
+
csv_data = []
|
155
|
+
record.serializable_hash.each do |name, value|
|
156
|
+
value = 'nil' if value.nil?
|
157
|
+
text = value.to_s.gsub(@text_delim, escape_text_delim())
|
158
|
+
csv_data << "#{name.to_sym} => #{text}"
|
159
|
+
end
|
160
|
+
"#{csv_data.join(Delimiters::csv_delim)}"
|
161
|
+
end
|
162
|
+
|
163
|
+
|
164
|
+
# Convert an AR instance to a set of CSV columns
|
165
|
+
def record_to_csv(record, options = {})
|
166
|
+
csv_data = record.serializable_hash.values.collect { |value| escape(value) }
|
31
167
|
|
32
|
-
|
168
|
+
[*options[:methods]].each { |x| csv_data << escape(record.send(x)) if(record.respond_to?(x)) } if(options[:methods])
|
169
|
+
|
170
|
+
csv_data.join( Delimiters::csv_delim )
|
33
171
|
end
|
172
|
+
|
173
|
+
|
174
|
+
private
|
34
175
|
|
176
|
+
def escape(value)
|
177
|
+
text = value.to_s.gsub(@text_delim, escape_text_delim())
|
178
|
+
|
179
|
+
text = "#{@text_delim}#{text}#{@text_delim}" if(text.include?(Delimiters::csv_delim))
|
180
|
+
text
|
181
|
+
end
|
182
|
+
|
35
183
|
end
|
36
184
|
end
|