abstract_importer 1.2.1 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6545777a4f5b26c7f43754894478c1ee2b38db44
4
- data.tar.gz: defa4ee62445d06bfdadaf9453b2c54f19829c90
3
+ metadata.gz: aef74ea6f4a839ec7a9fc349cf07cb44a53001e4
4
+ data.tar.gz: 8c2a8da38743766e795050d5fe67b1102ec44f60
5
5
  SHA512:
6
- metadata.gz: 607c5f129b8ac2742ad76f8c4a964b3ee24f5304b7d1701409bc5e3b0a5bf3eca0dda5d00d5f8556383d7ec32ba3786259946d55d3eee9f88709b83028c595fc
7
- data.tar.gz: edaecfc589c1844af50cf53239940ebfc17052be632fa2bf563fc8bcd74d8e33521b157b9caa73c3285c3685d4e724bda514749cefa9f3fc51ba85d787b4810e
6
+ metadata.gz: 5215f833089ae23c768c3b9763438e72d43030cb87c8e5d99eb85c61e1736d837c4f4d356a7b7b8708ac3de8136b18f4fb3b9bc2653c7e6c981bebc015b35bfa
7
+ data.tar.gz: 62ab5e5d9c9b8d984a0bfa098bea336238491ec6546783ae41757dc9922d6f5c7c491a1a3c537fce0bd46d95fb29fc7a8eaa9900ad647f8c66307c8fcbd9e856
@@ -0,0 +1 @@
1
+ 2.1.2
@@ -1,8 +1,10 @@
1
1
  # .travis.yml
2
2
  language: ruby
3
3
  rvm:
4
- - 2.0.0
5
- env:
6
- - DB=sqlite
4
+ - 2.1.2
5
+ before_install:
6
+ - sudo apt-add-repository -y ppa:travis-ci/sqlite3
7
+ - sudo apt-get -y update
8
+ - sudo apt-get install sqlite3=3.7.15.1-1~travis1
7
9
  script:
8
10
  - bundle exec rake test
@@ -17,7 +17,8 @@ Gem::Specification.new do |spec|
17
17
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
18
  spec.require_paths = ["lib"]
19
19
 
20
- spec.add_dependency "activerecord"
20
+ spec.add_dependency "activerecord", ">= 4.0"
21
+ spec.add_dependency "activerecord-insert_many", "~> 0.1.1"
21
22
 
22
23
  spec.add_development_dependency "bundler", "~> 1.3"
23
24
  spec.add_development_dependency "minitest", "~> 4.7"
@@ -9,29 +9,29 @@ require 'abstract_importer/summary'
9
9
 
10
10
  module AbstractImporter
11
11
  class Base
12
-
12
+
13
13
  class << self
14
14
  def import
15
15
  yield @import_plan = ImportPlan.new
16
16
  end
17
-
17
+
18
18
  def depends_on(*dependencies)
19
19
  @dependencies = dependencies
20
20
  end
21
-
21
+
22
22
  attr_reader :import_plan, :dependencies
23
23
  end
24
-
25
-
26
-
24
+
25
+
26
+
27
27
  def initialize(parent, source, options={})
28
28
  @source = source
29
29
  @parent = parent
30
-
30
+
31
31
  io = options.fetch(:io, $stderr)
32
32
  @reporter = default_reporter(io)
33
33
  @dry_run = options.fetch(:dry_run, false)
34
-
34
+
35
35
  @id_map = IdMap.new
36
36
  @results = {}
37
37
  @import_plan = self.class.import_plan.to_h
@@ -41,171 +41,159 @@ module AbstractImporter
41
41
  @only = Array(options[:only]) if options.key?(:only)
42
42
  @collections = []
43
43
  end
44
-
44
+
45
45
  attr_reader :source, :parent, :reporter, :id_map, :results
46
-
46
+
47
47
  def atomic?
48
48
  @atomic
49
49
  end
50
-
50
+
51
51
  def dry_run?
52
52
  @dry_run
53
53
  end
54
-
55
-
56
-
57
-
58
-
54
+
55
+
56
+
57
+
58
+
59
59
  def perform!
60
60
  reporter.start_all(self)
61
-
61
+
62
62
  ms = Benchmark.ms do
63
63
  setup
64
64
  end
65
65
  reporter.finish_setup(ms)
66
-
66
+
67
67
  ms = Benchmark.ms do
68
68
  with_transaction do
69
69
  collections.each &method(:import_collection)
70
70
  end
71
71
  end
72
-
72
+
73
73
  teardown
74
74
  reporter.finish_all(self, ms)
75
75
  results
76
76
  end
77
-
77
+
78
78
  def setup
79
79
  verify_source!
80
80
  verify_parent!
81
81
  instantiate_collections!
82
82
  prepopulate_id_map!
83
83
  end
84
-
84
+
85
85
  def import_collection(collection)
86
86
  return if skip? collection
87
87
  results[collection.name] = CollectionImporter.new(self, collection).perform!
88
88
  end
89
-
89
+
90
90
  def teardown
91
91
  end
92
-
92
+
93
93
  def skip?(collection)
94
94
  return true if skip.member?(collection.name)
95
95
  return true if only && !only.member?(collection.name)
96
96
  false
97
97
  end
98
-
98
+
99
99
  def strategy_for(collection)
100
100
  strategy_name = @strategies.fetch collection.name, :default
101
101
  AbstractImporter::Strategies.const_get :"#{strategy_name.capitalize}Strategy"
102
102
  end
103
-
104
-
105
-
106
-
107
-
103
+
104
+
105
+
106
+
107
+
108
108
  def describe_source
109
109
  source.to_s
110
110
  end
111
-
111
+
112
112
  def describe_destination
113
113
  parent.to_s
114
114
  end
115
-
116
-
117
-
118
-
119
-
115
+
116
+
117
+
118
+
119
+
120
120
  def remap_foreign_key?(plural, foreign_key)
121
121
  true
122
122
  end
123
-
123
+
124
124
  def map_foreign_key(legacy_id, plural, foreign_key, depends_on)
125
125
  id_map.apply!(legacy_id, depends_on)
126
126
  rescue IdMap::IdNotMappedError
127
127
  record_no_id_in_map_error(legacy_id, plural, foreign_key, depends_on)
128
128
  nil
129
129
  end
130
-
131
-
132
-
133
-
134
-
130
+
131
+
132
+
133
+
134
+
135
135
  private
136
-
136
+
137
137
  attr_reader :collections, :import_plan, :skip, :only
138
-
138
+
139
139
  def verify_source!
140
140
  import_plan.keys.each do |collection|
141
141
  next if source.respond_to?(collection)
142
-
142
+
143
143
  raise "#{source.class} does not respond to `#{collection}`; " <<
144
- "but #{self.class} plans to import records with that name"
144
+ "but #{self.class} plans to import records with that name"
145
145
  end
146
146
  end
147
-
147
+
148
148
  def verify_parent!
149
149
  import_plan.keys.each do |collection|
150
150
  next if parent.respond_to?(collection)
151
-
151
+
152
152
  raise "#{parent.class} does not have a collection named `#{collection}`; " <<
153
153
  "but #{self.class} plans to import records with that name"
154
154
  end
155
155
  end
156
-
156
+
157
157
  def instantiate_collections!
158
158
  @collections = import_plan.map do |name, block|
159
159
  reflection = parent.class.reflect_on_association(name)
160
160
  model = reflection.klass
161
161
  table_name = model.table_name
162
162
  scope = parent.public_send(name)
163
-
163
+
164
164
  options = ImportOptions.new
165
165
  instance_exec(options, &block) if block
166
-
166
+
167
167
  Collection.new(name, model, table_name, scope, options)
168
168
  end
169
169
  end
170
-
170
+
171
171
  def dependencies
172
172
  @dependencies ||= Array(self.class.dependencies).map do |name|
173
173
  reflection = parent.class.reflect_on_association(name)
174
174
  model = reflection.klass
175
175
  table_name = model.table_name
176
176
  scope = parent.public_send(name)
177
-
177
+
178
178
  Collection.new(name, model, table_name, scope, nil)
179
179
  end
180
180
  end
181
-
181
+
182
182
  def prepopulate_id_map!
183
183
  (collections + dependencies).each do |collection|
184
- query = collection.scope.where("#{collection.table_name}.legacy_id IS NOT NULL")
185
- map = values_of(query, :id, :legacy_id) \
186
- .each_with_object({}) { |(id, legacy_id), map| map[legacy_id] = id }
187
-
188
- id_map.init collection.table_name, map
189
- end
190
- end
191
-
192
- def values_of(query, *columns)
193
- if Rails.version < "4.0.0"
194
- query = query.select(columns.map { |column| "#{query.table_name}.#{column}" }.join(", "))
195
- ActiveRecord::Base.connection.select_rows(query.to_sql)
196
- else
197
- query.pluck(*columns)
184
+ id_map.init collection.table_name, collection.scope
185
+ .where("#{collection.table_name}.legacy_id IS NOT NULL")
198
186
  end
199
187
  end
200
-
201
-
202
-
188
+
189
+
190
+
203
191
  def record_no_id_in_map_error(legacy_id, plural, foreign_key, depends_on)
204
192
  reporter.count_notice "#{plural}.#{foreign_key} will be nil: a #{depends_on.to_s.singularize} with the legacy id #{legacy_id} was not mapped."
205
193
  end
206
-
207
-
208
-
194
+
195
+
196
+
209
197
  def with_transaction(&block)
210
198
  if atomic?
211
199
  ActiveRecord::Base.transaction(requires_new: true, &block)
@@ -213,7 +201,7 @@ module AbstractImporter
213
201
  block.call
214
202
  end
215
203
  end
216
-
204
+
217
205
  def default_reporter(io)
218
206
  case ENV["IMPORT_REPORTER"].to_s.downcase
219
207
  when "none" then Reporters::NullReporter.new(io)
@@ -221,6 +209,6 @@ module AbstractImporter
221
209
  else Reporters::DebugReporter.new(io)
222
210
  end
223
211
  end
224
-
212
+
225
213
  end
226
214
  end
@@ -2,15 +2,15 @@ require "abstract_importer/strategies"
2
2
 
3
3
  module AbstractImporter
4
4
  class CollectionImporter
5
-
5
+
6
6
  def initialize(importer, collection)
7
7
  @importer = importer
8
8
  @collection = collection
9
9
  @strategy = importer.strategy_for(collection).new(self)
10
10
  end
11
-
11
+
12
12
  attr_reader :importer, :collection, :summary, :strategy
13
-
13
+
14
14
  delegate :name,
15
15
  :table_name,
16
16
  :model,
@@ -18,7 +18,7 @@ module AbstractImporter
18
18
  :options,
19
19
  :association_attrs,
20
20
  :to => :collection
21
-
21
+
22
22
  delegate :dry_run?,
23
23
  :parent,
24
24
  :source,
@@ -27,46 +27,47 @@ module AbstractImporter
27
27
  :id_map,
28
28
  :map_foreign_key,
29
29
  :to => :importer
30
-
31
-
32
-
30
+
31
+
32
+
33
33
  def perform!
34
34
  reporter.start_collection(self)
35
35
  prepare!
36
-
36
+
37
37
  invoke_callback(:before_all)
38
38
  summary.ms = Benchmark.ms do
39
39
  each_new_record do |attributes|
40
40
  strategy.process_record(attributes)
41
41
  end
42
42
  end
43
+ strategy.flush
43
44
  invoke_callback(:after_all)
44
-
45
+
45
46
  reporter.finish_collection(self, summary)
46
47
  summary
47
48
  end
48
-
49
-
50
-
49
+
50
+
51
+
51
52
  def prepare!
52
53
  @summary = Summary.new
53
54
  @mappings = prepare_mappings!
54
55
  end
55
-
56
+
56
57
  def prepare_mappings!
57
58
  mappings = []
58
59
  model.reflect_on_all_associations.each do |association|
59
-
60
+
60
61
  # We only want the associations where this record
61
62
  # has foreign keys that refer to another
62
63
  next unless association.macro == :belongs_to
63
-
64
+
64
65
  # We support skipping some mappings entirely. I believe
65
66
  # this is largely to cut down on verbosity in the log
66
67
  # files and should be refactored to another place in time.
67
68
  foreign_key = association.foreign_key.to_sym
68
69
  next unless remap_foreign_key?(name, foreign_key)
69
-
70
+
70
71
  if association.options[:polymorphic]
71
72
  mappings << prepare_polymorphic_mapping!(association)
72
73
  else
@@ -75,11 +76,11 @@ module AbstractImporter
75
76
  end
76
77
  mappings
77
78
  end
78
-
79
+
79
80
  def prepare_mapping!(association)
80
81
  depends_on = association.table_name.to_sym
81
82
  foreign_key = association.foreign_key.to_sym
82
-
83
+
83
84
  Proc.new do |attrs|
84
85
  if attrs.key?(foreign_key)
85
86
  attrs[foreign_key] = map_foreign_key(attrs[foreign_key], name, foreign_key, depends_on)
@@ -88,11 +89,11 @@ module AbstractImporter
88
89
  end
89
90
  end
90
91
  end
91
-
92
+
92
93
  def prepare_polymorphic_mapping!(association)
93
94
  foreign_key = association.foreign_key.to_sym
94
95
  foreign_type = association.foreign_key.gsub(/_id$/, "_type").to_sym
95
-
96
+
96
97
  Proc.new do |attrs|
97
98
  if attrs.key?(foreign_key) && attrs.key?(foreign_type)
98
99
  foreign_model = attrs[foreign_type]
@@ -103,23 +104,23 @@ module AbstractImporter
103
104
  end
104
105
  end
105
106
  end
106
-
107
-
108
-
107
+
108
+
109
+
109
110
  def each_new_record
110
111
  source.public_send(name).each do |attrs|
111
112
  yield attrs.dup
112
113
  end
113
114
  end
114
-
115
-
116
-
115
+
116
+
117
+
117
118
  def remap_foreign_keys!(hash)
118
119
  @mappings.each do |proc|
119
120
  proc.call(hash)
120
121
  end
121
122
  end
122
-
123
+
123
124
  def redundant_record?(hash)
124
125
  existing_record = invoke_callback(:finder, hash)
125
126
  if existing_record
@@ -129,9 +130,9 @@ module AbstractImporter
129
130
  false
130
131
  end
131
132
  end
132
-
133
-
134
-
133
+
134
+
135
+
135
136
  def invoke_callback(callback, *args)
136
137
  callback_name = :"#{callback}_callback"
137
138
  callback = options.public_send(callback_name)
@@ -139,9 +140,9 @@ module AbstractImporter
139
140
  callback = importer.method(callback) if callback.is_a?(Symbol)
140
141
  callback.call(*args)
141
142
  end
142
-
143
+
143
144
  end
144
-
145
+
145
146
  class Skip < StandardError; end
146
-
147
+
147
148
  end