active_importer 0.0.3 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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