active_importer 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -48,6 +48,27 @@ columns declared. Any extra columns are ignored. Any errors while processing
48
48
  the data file does not interrupt the whole process. Instead, errors are
49
49
  notified via some callbacks defined in the importer (see below).
50
50
 
51
+ ### Supported formats
52
+
53
+ This library currently supports reading from most spreadsheet formats, thanks
54
+ to the wonderfull [roo](https://github.com/Empact/roo) gem. Specifically, the
55
+ following formats are supported:
56
+
57
+ * OpenOffice
58
+ * Excel
59
+ * Google spreadsheets
60
+ * Excelx
61
+ * LibreOffice
62
+ * CSV
63
+
64
+ The spreadsheet contents are scanned, row by row, until a row is found that
65
+ matches the expect header column, which should contain header cells for all the
66
+ columns declared in the importer. If no such row is found, the spreadsheet
67
+ processing fails without importing any data.
68
+
69
+ If the header row is found, data is scanned from the next row on, until the end
70
+ of the spreadsheet.
71
+
51
72
  ### Callbacks
52
73
 
53
74
  TODO: Document callbacks
@@ -26,7 +26,7 @@ module ActiveImporter
26
26
  self.class.model_class
27
27
  end
28
28
 
29
- def self.column(title, field, &block)
29
+ def self.column(title, field = nil, &block)
30
30
  title = title.strip
31
31
  if columns[title]
32
32
  raise "Duplicate importer column '#{title}'"
@@ -52,10 +52,9 @@ module ActiveImporter
52
52
  @context = options.delete(:context)
53
53
 
54
54
  @book = Roo::Spreadsheet.open(file, options)
55
- @header = @book.row(1).map(&:strip)
56
- check_header
55
+ load_header
57
56
 
58
- @data_row_indices = (2..@book.last_row)
57
+ @data_row_indices = ((@header_index+1)..@book.last_row)
59
58
  @row_count = @data_row_indices.count
60
59
  rescue => e
61
60
  @book = @header = nil
@@ -111,30 +110,44 @@ module ActiveImporter
111
110
  self.class.columns
112
111
  end
113
112
 
114
- def check_header
115
- # Header should contain all columns declared for this importer
116
- unless columns.keys.all? { |item| @header.include?(item) }
113
+ def find_header_index
114
+ (1..@book.last_row).each do |index|
115
+ row = @book.row(index).map(&:strip)
116
+ return index if columns.keys.all? { |item| row.include?(item) }
117
+ end
118
+ return nil
119
+ end
120
+
121
+ def load_header
122
+ @header_index = find_header_index
123
+ if @header_index
124
+ @header = @book.row(@header_index).map(&:strip)
125
+ else
117
126
  raise 'Spreadsheet does not contain all the expected columns'
118
127
  end
119
128
  end
120
129
 
121
130
  def import_row
122
- @model = fetch_model
123
- build_model
124
- model.save!
131
+ begin
132
+ @model = fetch_model
133
+ build_model
134
+ model.save!
135
+ rescue => e
136
+ @row_errors << { row_index: row_index, error_message: e.message }
137
+ row_error(e.message)
138
+ return false
139
+ end
125
140
  row_success
126
- rescue => e
127
- @row_errors << { row_index: row_index, error_message: e.message }
128
- row_error(e.message)
141
+ true
129
142
  end
130
143
 
131
144
  def build_model
132
145
  row.each_pair do |key, value|
133
146
  column_def = columns[key]
134
- next if column_def.nil?
147
+ next if column_def.nil? || column_def[:field_name].nil?
135
148
  field_name = column_def[:field_name]
136
149
  transform = column_def[:transform]
137
- value = transform.call(value) if transform
150
+ value = self.instance_exec(value, &transform) if transform
138
151
  model[field_name] = value
139
152
  end
140
153
  hook
@@ -1,3 +1,3 @@
1
1
  module ActiveImporter
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.3"
3
3
  end
@@ -4,7 +4,7 @@ require 'stubs/employee'
4
4
  describe ActiveImporter::Base do
5
5
  let(:spreadsheet_data) do
6
6
  [
7
- [' Name ', 'Birth Date', 'Department'],
7
+ [' Name ', 'Birth Date', 'Department', 'Manager'],
8
8
  ['John Doe', '2013-10-25', 'IT'],
9
9
  ['Jane Doe', '2013-10-26', 'Sales'],
10
10
  ]
@@ -36,7 +36,7 @@ describe ActiveImporter::Base do
36
36
  context 'when there are rows with errors' do
37
37
  let(:spreadsheet_data) do
38
38
  [
39
- ['Name', 'Birth Date', 'Department'],
39
+ ['Name', 'Birth Date', 'Department', 'Manager'],
40
40
  ['John Doe', '2013-10-25', 'IT'],
41
41
  ['Invalid', '2013-10-24', 'Management'],
42
42
  ['Invalid', '2013-10-24', 'Accounting'],
@@ -64,7 +64,7 @@ describe ActiveImporter::Base do
64
64
  context 'when the import fails' do
65
65
  let(:spreadsheet_data) do
66
66
  [
67
- ['Name', 'Birth Date'],
67
+ ['Name', 'Birth Date', 'Manager'],
68
68
  ['John Doe', '2013-10-25'],
69
69
  ['Jane Doe', '2013-10-26'],
70
70
  ]
@@ -76,6 +76,23 @@ describe ActiveImporter::Base do
76
76
  end
77
77
  end
78
78
 
79
+ context 'when header row is not the first one' do
80
+ let(:spreadsheet_data) do
81
+ [
82
+ [],
83
+ ['List of employees', '', 'Company Name'],
84
+ ['Ordered by', 'Birth Date'],
85
+ ['Name', 'Department', 'Birth Date', 'Manager'],
86
+ ['John Doe', 'IT', '2013-10-25'],
87
+ ['Jane Doe', 'Sales', '2013-10-26'],
88
+ ]
89
+ end
90
+
91
+ it 'smartly skips any rows before the header' do
92
+ expect { EmployeeImporter.import('/dummy/file') }.to change(Employee, :count).by(2)
93
+ end
94
+ end
95
+
79
96
  describe '.fetch_model' do
80
97
  let(:model) { Employee.new }
81
98
 
@@ -14,7 +14,12 @@ class EmployeeImporter < ActiveImporter::Base
14
14
 
15
15
  column 'Name', :name
16
16
  column 'Birth Date', :birth_date
17
+ column 'Manager'
17
18
  column ' Department ', :department_id do |value|
18
- value.length # Quick dummy way to get an integer out of a string
19
+ find_department(value)
20
+ end
21
+
22
+ def find_department(name)
23
+ name.length # Quick dummy way to get an integer out of a string
19
24
  end
20
25
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_importer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-11-06 00:00:00.000000000 Z
12
+ date: 2013-11-08 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: roo
@@ -112,7 +112,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
112
112
  version: '0'
113
113
  segments:
114
114
  - 0
115
- hash: -3244463273596288739
115
+ hash: 3421867451760096133
116
116
  required_rubygems_version: !ruby/object:Gem::Requirement
117
117
  none: false
118
118
  requirements:
@@ -121,7 +121,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
121
121
  version: '0'
122
122
  segments:
123
123
  - 0
124
- hash: -3244463273596288739
124
+ hash: 3421867451760096133
125
125
  requirements: []
126
126
  rubyforge_project:
127
127
  rubygems_version: 1.8.23