active_importer 0.0.3 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -71,7 +71,66 @@ of the spreadsheet.
71
71
 
72
72
  ### Callbacks
73
73
 
74
- TODO: Document callbacks
74
+ An importer class can define blocks of code acting as callbacks, to be notified
75
+ of certain events that occur while importing the data.
76
+
77
+ ```ruby
78
+ class EmployeeImporter < ActiveImporter::Base
79
+ imports Employee
80
+
81
+ attr_reader :row_count
82
+
83
+ column 'First name', :first_name
84
+ column 'Last name', :last_name
85
+ column 'Department', :department do |department_name|
86
+ Department.find_by(name: department_name)
87
+ end
88
+
89
+ on :import_started do
90
+ @row_count = 0
91
+ end
92
+
93
+ on :row_processed do
94
+ @row_count += 1
95
+ end
96
+
97
+ on :import_finished do
98
+ send_notification("Data imported successfully!")
99
+ end
100
+
101
+ on :import_failed do |exception|
102
+ send_notification("Fatal error while importing data: #{exception.message}")
103
+ end
104
+
105
+ private
106
+
107
+ def send_notification(message)
108
+ # ...
109
+ end
110
+ end
111
+ ```
112
+
113
+ The supported events are:
114
+
115
+ - **import_failed:** Fired once **before** the beginning of the data
116
+ processing, if the input data cannot be processed for some reason. If this
117
+ event is fired by an importer, none of its other events are ever fired.
118
+ - **import_started:** Fired once at the beginning of the data processing,
119
+ before the first row is processed.
120
+ - **row_processed:** Fired once for each row that has been processed,
121
+ regardless of whether it resulted in success or error.
122
+ - **row_success:** Fired once for each row that was imported successfully into
123
+ the data model.
124
+ - **row_error:** Fired once for each row that was **not** imported successfully
125
+ into the data model.
126
+ - **import_finished:** Fired once **after** all rows have been processed.
127
+
128
+ More than one block of code can be provided for each of these events, and they
129
+ will all be invoked in the same order in which they were declared. All blocks
130
+ are executed in the context of the importer instance, so they have access to
131
+ all the importer attributes and instance variables. Error-related events
132
+ (`:import_failed` and `:row_error`) pass to the blocks the instance of the
133
+ exception that provoked the error condition.
75
134
 
76
135
  ## Contributing
77
136
 
@@ -38,6 +38,36 @@ module ActiveImporter
38
38
  new(file, options).import
39
39
  end
40
40
 
41
+ #
42
+ # Callbacks
43
+ #
44
+
45
+ EVENTS = [
46
+ :row_success,
47
+ :row_error,
48
+ :row_processed,
49
+ :import_started,
50
+ :import_finished,
51
+ :import_failed,
52
+ ]
53
+
54
+ def self.event_handlers
55
+ @event_handlers ||= EVENTS.inject({}) { |hash, event| hash.merge({event => []}) }
56
+ end
57
+
58
+ def self.on(event, &block)
59
+ raise "Unknown ActiveImporter event '#{event}'" unless EVENTS.include?(event)
60
+ event_handlers[event] << block
61
+ end
62
+
63
+ def fire_event(event, param = nil)
64
+ self.class.event_handlers[event].each do |block|
65
+ self.instance_exec(param, &block)
66
+ end
67
+ end
68
+
69
+ private :fire_event
70
+
41
71
  #
42
72
  # Implementation
43
73
  #
@@ -60,7 +90,7 @@ module ActiveImporter
60
90
  @book = @header = nil
61
91
  @row_count = 0
62
92
  @row_index = 1
63
- import_failed(e.message)
93
+ fire_event :import_failed, e
64
94
  end
65
95
 
66
96
  def fetch_model
@@ -69,12 +99,13 @@ module ActiveImporter
69
99
 
70
100
  def import
71
101
  return if @book.nil?
102
+ fire_event :import_started
72
103
  @data_row_indices.each do |index|
73
104
  @row_index = index
74
105
  @row = row_to_hash @book.row(index)
75
106
  import_row
76
107
  end
77
- import_finished
108
+ fire_event :import_finished
78
109
  end
79
110
 
80
111
  def row_processed_count
@@ -92,18 +123,6 @@ module ActiveImporter
92
123
  def hook
93
124
  end
94
125
 
95
- def row_success
96
- end
97
-
98
- def row_error(error_message)
99
- end
100
-
101
- def import_failed(error_message)
102
- end
103
-
104
- def import_finished
105
- end
106
-
107
126
  private
108
127
 
109
128
  def columns
@@ -134,11 +153,13 @@ module ActiveImporter
134
153
  model.save!
135
154
  rescue => e
136
155
  @row_errors << { row_index: row_index, error_message: e.message }
137
- row_error(e.message)
156
+ fire_event :row_error, e
138
157
  return false
139
158
  end
140
- row_success
159
+ fire_event :row_success
141
160
  true
161
+ ensure
162
+ fire_event :row_processed
142
163
  end
143
164
 
144
165
  def build_model
@@ -1,3 +1,3 @@
1
1
  module ActiveImporter
2
- VERSION = "0.0.3"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -27,8 +27,9 @@ describe ActiveImporter::Base do
27
27
  EmployeeImporter.import('/dummy/file')
28
28
  end
29
29
 
30
- it 'notifies when the import process has finished' do
30
+ it 'notifies when the import process starts and finishes' do
31
31
  expect(EmployeeImporter).to receive(:new).once.and_return(importer)
32
+ expect(importer).to receive(:import_started).once
32
33
  expect(importer).to receive(:import_finished).once
33
34
  EmployeeImporter.import('/dummy/file')
34
35
  end
@@ -59,6 +60,12 @@ describe ActiveImporter::Base do
59
60
  expect(EmployeeImporter).to receive(:new).once.and_return(importer)
60
61
  expect { EmployeeImporter.import('/dummy/file') }.to change(importer.row_errors, :count).by(2)
61
62
  end
63
+
64
+ it 'still notifies all rows as processed' do
65
+ expect(EmployeeImporter).to receive(:new).once.and_return(importer)
66
+ expect(importer).to receive(:row_processed).exactly(4).times
67
+ EmployeeImporter.import('/dummy/file')
68
+ end
62
69
  end
63
70
 
64
71
  context 'when the import fails' do
@@ -22,4 +22,9 @@ class EmployeeImporter < ActiveImporter::Base
22
22
  def find_department(name)
23
23
  name.length # Quick dummy way to get an integer out of a string
24
24
  end
25
+
26
+ ActiveImporter::Base::EVENTS.each do |event_name|
27
+ define_method(event_name) {}
28
+ on(event_name) { send event_name }
29
+ end
25
30
  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.3
4
+ version: 0.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -112,7 +112,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
112
112
  version: '0'
113
113
  segments:
114
114
  - 0
115
- hash: 3421867451760096133
115
+ hash: -645039623466056614
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: 3421867451760096133
124
+ hash: -645039623466056614
125
125
  requirements: []
126
126
  rubyforge_project:
127
127
  rubygems_version: 1.8.23