datashift 0.7.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.markdown CHANGED
@@ -9,9 +9,11 @@ Wiki taking shape with more info here : **https://github.com/autotelik/datashift
9
9
  ### Features
10
10
 
11
11
  Import and Export ActiveRecord models through .xls or CSV files, including
12
- all associations and with configurable defaults.
12
+ all associations and setting configurable defaults or over rides.
13
13
 
14
- Export all data or simply generate a sample template with headers only.
14
+ Generate a sample template with headers only.
15
+
16
+ Export template and populate with model data
15
17
 
16
18
  Create, parse and use Excel/OpenOffice (.xls) documents dynamically from Ruby.
17
19
 
@@ -24,7 +26,7 @@ Specific loaders and command line tasks provided out the box for **Spree E-Comme
24
26
  enabling import/export of Product data including creating Variants with different
25
27
  count on hands and all associations including Properties/Taxons/OptionTypes and Images.
26
28
 
27
- Loaders can be configured via YAML with over ride values, default values and mandatory column settingss.
29
+ Loaders can be configured via YAML with over ride values, default values and mandatory column settings.
28
30
 
29
31
  Many example Spreadsheets/CSV files in spec/fixtures, fully documented with comments for each column.
30
32
 
@@ -41,13 +43,11 @@ To use :
41
43
 
42
44
  To pull the tasks in, add this call to your Rakefile :
43
45
 
44
- DataShift::load_tasks
46
+ ```ruby DataShift::load_tasks```
45
47
 
46
48
  To keep the availability to only development mode use
47
49
 
48
- if(Rails.env.development?)
49
- DataShift::<b>load_tasks</b>
50
- end
50
+ ```ruby DataShift::load_tasks if(Rails.env.development?)```
51
51
 
52
52
  To use the Thor command line applications :
53
53
 
@@ -58,7 +58,7 @@ Edit the file and add the following to pull in the thor commands :
58
58
  require 'thor'
59
59
  require 'datashift'
60
60
 
61
- DataShift::<b>load_commands</b>
61
+ DataShift::load_commands
62
62
  ```
63
63
  To check the available tasks run
64
64
 
@@ -88,6 +88,9 @@ Guards are provided, and used internally, for mixed Ruby setups. Can be used lik
88
88
  Provides high level rake tasks for importing data via ActiveRecord models into a DB,
89
89
  from various sources, currently csv or .xls files (Excel/Open Office)
90
90
 
91
+ N.B This is under active development, moving to thor tasks so maybe out of date
92
+
93
+ Please try rake -T and thor list to get upto date command lines
91
94
 
92
95
  bundle exec rake datashift:import:csv model=BlogPost input=BlogPostImport.csv verbose=true
93
96
 
@@ -102,54 +105,9 @@ The library can be easily extended with Loaders to deal with non trivial cases,
102
105
  for example when multiple lookups required to find right association.
103
106
 
104
107
  Spree loaders are an example, these illustrate over riding processing for specific columns with
105
- complicated lookup requirements.
106
-
107
- A core feature of DataShift is the MethodDictionary and MethodMapper, which provides features for collecting
108
- reflection information from ActiveRecord models (all different associations, including join tables with many-to-many relationships).
109
-
110
- A full picture of all possible operations on a class can be created very easily, for example ona Blog model :
111
-
112
- MethodDictionary.find_operators( Blog )
113
-
114
- This then allows Import/Export to be achieved, by mapping the file's header and column data to the found operators
115
- i.e. the methods to set data on model's attributes and associations.
116
-
117
- Here we retrieve the method details for a column name from a file, "Blog Date"
118
-
119
- MethodDictionary.find_method_detail( Blog, "Blog Date" )
120
-
121
- Loaders can use this method lookup functionality, to find the correct association for a column heading,
122
- and populate AR object with row data.
123
-
124
- This means data can be mapped to any model without any further coding. Generators are also supplied to export
125
- a models attributes and associations to files, thus providing template spreadsheets that any user can fill out.
126
-
127
- MethodMapper also stores column type information so the raw file data can be provided as is,
128
- and whenever possible, under the bonnet the data will be cast to correct DB type.
129
-
130
- Here we show how a column name from a file, "Blog Date", can be mapped to Assign a stringified date, to the blog_date column, on a new Blog object :
131
-
132
- MethodDictionary.find_method_detail( Blog, "Blog Date" ).assign( Blog.new, "Sat Jul 23 2011" )
133
-
134
- Because it's based on reflection against the model, can build complex relationships between tables during import/export,
135
- and extending data files with new columns need not require any additional Ruby coding.
136
-
137
- New columns can simply be added to the Excel/Open Office spreadsheet, or CSV file, setting the new
138
- attribute or association name in the header row.
139
-
140
-
141
- The Loader attempts to handle various human read-able forms of column names.
142
-
143
- For example, given an association on the model called, product_properties, will successfully load
144
- from columns with headings such as 'product_properties', 'Product Properties', 'ProductProperties' 'product properties' etc
145
-
146
- For has_many associations, either multiple columns can be used,
147
- or multiple values can be specified in a single column using suitable delimiters.
148
-
149
- Modular - so complex associations/mappings that require non generic lookups, can be handled by extending the loader engine.
150
-
151
- Original focus was on support for the Open Source Spree e-commerce project, so includes specific loaders and rake tasks
152
- for loading Spree Products, and associated data such as Product Variants, and Images.
108
+ complicated lookup requirements. Spree is the prime Open Source e-commerce project for Rails,
109
+ and the specific loaders and tasks support loading Spree Products, and associated data such as Variants,
110
+ OptionTypes, Properties and Images.
153
111
 
154
112
  ## Template Generation and Export
155
113
 
@@ -256,48 +214,6 @@ So if our Project model has many Categories, we can supply a Category list, whic
256
214
  During loading, a call to find_all_by_reference will be made, picking up the 2 categories with matching references,
257
215
  and our Project model will contain those two i.e project.categories = [category_002,category_003]
258
216
 
259
- ## Spree Suppprt
260
-
261
- ### OptionTypes & Variants
262
-
263
- When loaded with the Spree specific tasks, spree specific over rides are supported, such as direct s
264
- support for OptionTypes with values
265
-
266
- Any 'Option Types' columns can contain the OptionType to associate with the Product, plus a selection of
267
- appropriate OptionValues to go with that Type.
268
-
269
- For example, in a single column/row we could supply 2 OptionTypes (named, size & colour), with a selection values
270
- (such as small, medium etc)
271
-
272
- 'Option Types'
273
- size:small,medium,large|colour:red,white
274
-
275
- If no such OptionType exists, e.g size, then a new one is created with the supplied name.
276
-
277
- Next the OptionValues are also parsed, again if no such OptionValue exists, e.g small, then a new one is created with the supplied name.
278
-
279
- Lastly a Variant is created on each OptionValue, with price and availaable dates being copied from Master.
280
- Currently a unique SKU is created by adding an index to the master's sku.
281
-
282
- TODO - Enable a hash of attributes to be supplied in association columns to enable more control over creation of associated objects.
283
-
284
- ### Properties
285
-
286
- The properties to associate with this product.
287
- Properties are for small snippets of text, shared across many products,
288
- and are for display purposes only.
289
-
290
- An optional display value can be supplied to supplement the displayed text.
291
-
292
- As for all associations can contain multiple name/value sets in default form :
293
-
294
- Property:display_value|Property:display_value
295
-
296
- Example - No values :
297
- manufacturer|standard
298
-
299
- Example - Display values :
300
- manufacturer:somebody else plc|standard:ISOBlah21
301
217
 
302
218
  ## TODO
303
219
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.7.0
1
+ 0.8.0
data/datashift.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "datashift"
8
- s.version = "0.7.0"
8
+ s.version = "0.8.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-05-27"
12
+ s.date = "2012-07-09"
13
13
  s.description = "A suite of tools to move data between ActiveRecord models,databases,applications like Excel/Open Office, files and projects including Spree"
14
14
  s.email = "rubygems@autotelik.co.uk"
15
15
  s.extra_rdoc_files = [
@@ -24,8 +24,6 @@ Gem::Specification.new do |s|
24
24
  "README.rdoc",
25
25
  "Rakefile",
26
26
  "VERSION",
27
- "datashift-0.6.0.gem",
28
- "datashift-0.6.1.gem",
29
27
  "datashift.gemspec",
30
28
  "lib/applications/jruby/jexcel_file.rb",
31
29
  "lib/applications/jruby/word.rb",
@@ -67,6 +65,7 @@ Gem::Specification.new do |s|
67
65
  "lib/loaders/csv_loader.rb",
68
66
  "lib/loaders/excel_loader.rb",
69
67
  "lib/loaders/loader_base.rb",
68
+ "lib/loaders/paperclip/image_loader.rb",
70
69
  "lib/loaders/spreadsheet_loader.rb",
71
70
  "lib/loaders/spree/image_loader.rb",
72
71
  "lib/loaders/spree/product_loader.rb",
@@ -75,6 +74,7 @@ Gem::Specification.new do |s|
75
74
  "lib/thor/import_excel.thor",
76
75
  "lib/thor/spree/bootstrap_cleanup.thor",
77
76
  "lib/thor/spree/products_images.thor",
77
+ "lib/thor/spree/reports.thor",
78
78
  "public/spree/products/large/DEMO_001_ror_bag.jpeg",
79
79
  "public/spree/products/large/DEMO_002_Powerstation.jpg",
80
80
  "public/spree/products/large/DEMO_003_ror_mug.jpeg",
@@ -124,6 +124,7 @@ Gem::Specification.new do |s|
124
124
  "spec/fixtures/simple_export_spec.xls",
125
125
  "spec/fixtures/simple_template_spec.xls",
126
126
  "spec/fixtures/spree/SpreeImages.xls",
127
+ "spec/fixtures/spree/SpreeMultiVariant.csv",
127
128
  "spec/fixtures/spree/SpreeProducts.csv",
128
129
  "spec/fixtures/spree/SpreeProducts.xls",
129
130
  "spec/fixtures/spree/SpreeProductsDefaults.yml",
@@ -149,6 +150,7 @@ Gem::Specification.new do |s|
149
150
  "spec/thor_spec.rb",
150
151
  "tasks/config/seed_fu_product_template.erb",
151
152
  "tasks/config/tidy_config.txt",
153
+ "tasks/db_tasks.rake",
152
154
  "tasks/export/excel_generator.rake",
153
155
  "tasks/file_tasks.rake",
154
156
  "tasks/import/csv.rake",
@@ -25,13 +25,15 @@ if(DataShift::Guards::jruby?)
25
25
  include_class 'org.apache.poi.hssf.usermodel.HSSFDataFormat'
26
26
  include_class 'org.apache.poi.hssf.usermodel.HSSFClientAnchor'
27
27
  include_class 'org.apache.poi.hssf.usermodel.HSSFRichTextString'
28
+
29
+ include_class 'org.apache.poi.hssf.util.HSSFColor'
28
30
 
29
31
  include_class 'java.io.ByteArrayOutputStream'
30
32
  include_class 'java.util.Date'
31
33
  include_class 'java.io.FileInputStream'
32
34
  include_class 'java.io.FileOutputStream'
33
35
 
34
- attr_accessor :book, :row, :date_style
36
+ attr_accessor :workbook, :row, :date_style
35
37
  attr_reader :sheet
36
38
 
37
39
  MAX_COLUMNS = 256.freeze unless defined?(MAX_COLUMNS)
@@ -45,27 +47,25 @@ if(DataShift::Guards::jruby?)
45
47
  return 65535
46
48
  end
47
49
 
48
- # The HSSFWorkbook uses 0 based indexes, whilst our companion jexcel_win32 class
49
- # uses 1 based indexes. So they can be used interchangeably we bring indexes
50
- # inline with JExcel usage in this class, as 1 based maps more intuitively for the user
51
- #
52
- # i.e Row 1 passed to this class, internally means Row 0
50
+ # The HSSFWorkbook uses 0 based indexes,
53
51
 
54
- def initialize()
55
- @book = nil
52
+ def initialize(filename = nil)
53
+ @workbook = nil
56
54
  # The @patriarchs hash is a workaround because HSSFSheet.getDrawingPatriarch()
57
55
  # causes a lot of issues (if it doesn't throw an exception!)
58
56
  @patriarchs = Hash.new
59
57
 
60
58
  @date_style = nil
59
+
60
+ open(filename) if(filename)
61
61
  end
62
62
 
63
63
  def open(filename)
64
64
  inp = FileInputStream.new(filename)
65
65
 
66
- @book = HSSFWorkbook.new(inp)
66
+ @workbook = HSSFWorkbook.new(inp)
67
67
 
68
- @date_style = @book.createCellStyle
68
+ @date_style = @workbook.createCellStyle
69
69
  @date_style.setDataFormat( JExcelFile::date_format )
70
70
 
71
71
  @current_sheet = 0
@@ -75,19 +75,19 @@ if(DataShift::Guards::jruby?)
75
75
  # EXCEL ITEMS
76
76
 
77
77
  def create(sheet_name)
78
- @book = HSSFWorkbook.new() if @book.nil?
78
+ @workbook = HSSFWorkbook.new() if @workbook.nil?
79
79
 
80
80
  acceptable_name = sheet_name.gsub(':', '').gsub(" ", '')
81
81
 
82
82
  # Double check sheet doesn't already exist
83
- if(@book.getSheetIndex(acceptable_name) < 0)
84
- sheet = @book.createSheet(acceptable_name.gsub(" ", ''))
83
+ if(@workbook.getSheetIndex(acceptable_name) < 0)
84
+ sheet = @workbook.createSheet(acceptable_name.gsub(" ", ''))
85
85
 
86
86
  @patriarchs.store(acceptable_name, sheet.createDrawingPatriarch())
87
87
  end
88
- @current_sheet = @book.getSheetIndex(acceptable_name)
88
+ @current_sheet = @workbook.getSheetIndex(acceptable_name)
89
89
 
90
- @date_style = @book.createCellStyle
90
+ @date_style = @workbook.createCellStyle
91
91
  @date_style.setDataFormat( JExcelFile::date_format )
92
92
 
93
93
  self.sheet()
@@ -98,18 +98,18 @@ if(DataShift::Guards::jruby?)
98
98
  # Return the current or specified HSSFSheet
99
99
  def sheet(i = nil)
100
100
  @current_sheet = i if i
101
- @sheet = @book.getSheetAt(@current_sheet)
101
+ @sheet = @workbook.getSheetAt(@current_sheet)
102
102
  end
103
103
 
104
104
  def activate_sheet(sheet)
105
105
  active_sheet = @current_sheet
106
- if(@book)
106
+ if(@workbook)
107
107
  i = sheet if sheet.kind_of?(Integer)
108
- i = @book.getSheetIndex(sheet) if sheet.kind_of?(String)
108
+ i = @workbook.getSheetIndex(sheet) if sheet.kind_of?(String)
109
109
 
110
110
  if( i >= 0 )
111
- @book.setActiveSheet(i) unless @book.nil?
112
- active_sheet = @book.getSheetAt(i)
111
+ @workbook.setActiveSheet(i) unless @workbook.nil?
112
+ active_sheet = @workbook.getSheetAt(i)
113
113
  active_sheet.setActive(true)
114
114
  end unless i.nil?
115
115
  end
@@ -126,11 +126,11 @@ if(DataShift::Guards::jruby?)
126
126
  @sheet.rowIterator.each { |row| @row = row; yield row }
127
127
  end
128
128
 
129
- # Create new row, bring index in line with POI usage (our 1 is their 0)
129
+ # Create new row, index with POI usage starts at 0
130
130
  def create_row(index)
131
131
  return if @sheet.nil?
132
- raise "BAD INDEX: Row indexing starts at 1" if(index == 0)
133
- @row = @sheet.createRow(index - 1)
132
+ raise "BAD INDEX: Row indexing starts at 0" if(index < 0)
133
+ @row = @sheet.createRow(index)
134
134
  @row
135
135
  end
136
136
 
@@ -141,20 +141,34 @@ if(DataShift::Guards::jruby?)
141
141
  # Populate a single cell with data
142
142
  #
143
143
  def set_cell(row, column, datum)
144
- @row = @sheet.getRow(row - 1) || create_row(row)
145
- @row.createCell(column - 1, excel_cell_type(datum)).setCellValue(datum)
144
+ @row = @sheet.getRow(row) || create_row(row)
145
+ @row.createCell(column, excel_cell_type(datum)).setCellValue(datum)
146
146
  end
147
147
 
148
148
  # Convert array into a header row
149
- def set_headers(headers)
150
- create_row(1)
149
+ def set_headers(headers, apply_style = nil)
150
+ create_row(0)
151
151
  return if headers.empty?
152
152
 
153
+ style = apply_style || header_style()
154
+
153
155
  headers.each_with_index do |datum, i|
154
- @row.createCell(i, excel_cell_type(datum)).setCellValue(datum)
156
+ c = @row.createCell(i, excel_cell_type(datum))
157
+ c.setCellValue(datum)
158
+ c.setCellStyle(style)
155
159
  end
156
160
  end
157
161
 
162
+ def header_style
163
+ return @header_style if @header_style
164
+ @header_style = @workbook.createCellStyle();
165
+ @header_style.setBorderTop(6) # double lines border
166
+ @header_style.setBorderBottom(1) # single line border
167
+ @header_style.setFillBackgroundColor(HSSFColor::GREY_25_PERCENT.index)
168
+
169
+ @header_style
170
+ end
171
+
158
172
  # Populate a row of cells with data in an array
159
173
  # where the co-ordinates relate to row/column start position
160
174
  #
@@ -171,7 +185,8 @@ if(DataShift::Guards::jruby?)
171
185
  end
172
186
  end
173
187
 
174
- # Return a mapping from Ruby type to type for HSSFCell
188
+ # Return the suitable type for a HSSFCell from a Ruby data type
189
+
175
190
  def excel_cell_type(data)
176
191
 
177
192
  if(data.kind_of?(Numeric))
@@ -186,6 +201,17 @@ if(DataShift::Guards::jruby?)
186
201
  # HSSFCell::CELL_TYPE_FORMULA
187
202
  end
188
203
 
204
+
205
+ # Auto size either the given column index or all columns
206
+ def autosize(column = nil)
207
+ return if @sheet.nil?
208
+ if (column.kind_of? Integer)
209
+ @sheet.autoSizeColumn(column)
210
+ else
211
+ @sheet.getRow(0).cellIterator.each{|c| @sheet.autoSizeColumn(c.getColumnIndex)}
212
+ end
213
+ end
214
+
189
215
  # TODO - Move into an ActiveRecord helper module of it's own
190
216
  def ar_to_headers( records )
191
217
  return if( !records.first.is_a?(ActiveRecord::Base) || records.empty?)
@@ -193,23 +219,24 @@ if(DataShift::Guards::jruby?)
193
219
  headers = records.first.class.columns.collect( &:name )
194
220
  set_headers( headers )
195
221
  end
196
-
222
+
223
+
197
224
  # Pass a set of AR records
198
225
  def ar_to_xls(records, options = {})
199
226
  return if( ! records.first.is_a?(ActiveRecord::Base) || records.empty?)
200
227
 
201
228
  row_index =
202
229
  if(options[:no_headers])
203
- 1
230
+ 0
204
231
  else
205
232
  ar_to_headers( records )
206
- 2
233
+ 1
207
234
  end
208
235
 
209
236
  records.each do |record|
210
237
  create_row(row_index)
211
238
 
212
- ar_to_xls_row(1, record)
239
+ ar_to_xls_row(0, record)
213
240
 
214
241
  row_index += 1
215
242
  end
@@ -233,15 +260,15 @@ if(DataShift::Guards::jruby?)
233
260
  datum = record.send(connection_column.name)
234
261
 
235
262
  if(connection_column.sql_type =~ /date/)
236
- @row.createCell(column - 1, HSSFCell::CELL_TYPE_STRING).setCellValue(datum.to_s)
263
+ @row.createCell(column, HSSFCell::CELL_TYPE_STRING).setCellValue(datum.to_s)
237
264
 
238
265
  elsif(connection_column.type == :boolean || connection_column.sql_type =~ /tinyint/)
239
- @row.createCell(column - 1, HSSFCell::CELL_TYPE_BOOLEAN).setCellValue(datum)
266
+ @row.createCell(column, HSSFCell::CELL_TYPE_BOOLEAN).setCellValue(datum)
240
267
 
241
268
  elsif(connection_column.sql_type =~ /int/)
242
- @row.createCell(column - 1, HSSFCell::CELL_TYPE_NUMERIC).setCellValue(datum.to_i)
269
+ @row.createCell(column, HSSFCell::CELL_TYPE_NUMERIC).setCellValue(datum.to_i)
243
270
  else
244
- @row.createCell(column - 1, HSSFCell::CELL_TYPE_STRING).setCellValue( datum.to_s )
271
+ @row.createCell(column, HSSFCell::CELL_TYPE_STRING).setCellValue( datum.to_s )
245
272
  end
246
273
 
247
274
  rescue => e
@@ -263,7 +290,6 @@ if(DataShift::Guards::jruby?)
263
290
  # Return the raw data of an HSSFCell
264
291
  def cell_value(cell)
265
292
  return unless cell
266
- #puts "DEBUG CELL TYPE : #{cell} => #{cell.getCellType().inspect}"
267
293
  case (cell.getCellType())
268
294
  when HSSFCell::CELL_TYPE_FORMULA then return cell.getCellFormula()
269
295
  when HSSFCell::CELL_TYPE_NUMERIC then return cell.getNumericCellValue()
@@ -278,7 +304,7 @@ if(DataShift::Guards::jruby?)
278
304
  filename.nil? ? file = @filepath : file = filename
279
305
  begin
280
306
  out = FileOutputStream.new(file)
281
- @book.write(out) unless @book.nil?
307
+ @workbook.write(out) unless @workbook.nil?
282
308
 
283
309
  out.close
284
310
  rescue => e
@@ -312,28 +338,19 @@ if(DataShift::Guards::jruby?)
312
338
 
313
339
  # Get a percentage style
314
340
  def getPercentStyle()
315
- if (@percentCellStyle.nil? && @book)
316
- @percentCellStyle = @book.createCellStyle();
341
+ if (@percentCellStyle.nil? && @workbook)
342
+ @percentCellStyle = @workbook.createCellStyle();
317
343
  @percentCellStyle.setDataFormat(HSSFDataFormat.getBuiltinFormat("0.00%"));
318
344
  end
319
345
  return @percentCellStyle
320
346
  end
321
347
 
322
- # Auto size either the given column index or all columns
323
- def autosize(column = nil)
324
- return if @sheet.nil?
325
- if (column.kind_of? Integer)
326
- @sheet.autoSizeColumn(column)
327
- else
328
- @sheet.getRow(0).cellIterator.each{|c| @sheet.autoSizeColumn(c.getColumnIndex)}
329
- end
330
- end
331
348
 
332
349
  def to_s
333
- return "" unless @book
350
+ return "" unless @workbook
334
351
 
335
352
  outs = ByteArrayOutputStream.new
336
- @book.write(outs);
353
+ @workbook.write(outs);
337
354
  outs.close();
338
355
  String.from_java_bytes(outs.toByteArray)
339
356
  end