duty_free 1.0.5 → 1.0.6
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.
- checksums.yaml +4 -4
- data/lib/duty_free.rb +61 -10
- data/lib/duty_free/extensions.rb +186 -95
- data/lib/duty_free/suggest_template.rb +4 -4
- data/lib/duty_free/util.rb +3 -3
- data/lib/duty_free/version_number.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f9ac922dd807fc18539ff130c1ccf63412e837acbe4eba0127423235c11d0f1e
|
4
|
+
data.tar.gz: e5c27fe4a58718dcbbd4c5ebc1aed2dbb5e7e4b386c977e0ec31269cfad08d48
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 54811a68bd34fd5bb881fa19006a7297a8697a3a1575b7060ba7d82e6fcd6d36f545e1d5cfcfc51c756a5b87880e445c415924d2f54aa39471f0b884f0b4c6c4
|
7
|
+
data.tar.gz: 32ccb88e429e64bccb59c7741955b99537c661d6416d6f8c352cb384df728081d5b54079cb2dbd904203fbcf1c60abb1a4a7c70bee9c2764fa1622eaaeb6843f
|
data/lib/duty_free.rb
CHANGED
@@ -1,5 +1,47 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'active_record/version'
|
4
|
+
|
5
|
+
# ActiveRecord before 4.0 didn't have #version
|
6
|
+
unless ActiveRecord.respond_to?(:version)
|
7
|
+
module ActiveRecord
|
8
|
+
def self.version
|
9
|
+
::Gem::Version.new(ActiveRecord::VERSION::STRING)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# In ActiveSupport older than 5.0, the duplicable? test tries to new up a BigDecimal,
|
15
|
+
# and Ruby 2.6 and later deprecates #new. This removes the warning from BigDecimal.
|
16
|
+
require 'bigdecimal'
|
17
|
+
if ActiveRecord.version < ::Gem::Version.new('5.0') &&
|
18
|
+
::Gem::Version.new(RUBY_VERSION) >= ::Gem::Version.new('2.6')
|
19
|
+
def BigDecimal.new(*args, **kwargs)
|
20
|
+
BigDecimal(*args, **kwargs)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Allow Rails 4.0 and 4.1 to work with newer Ruby (>= 2.4) by avoiding a "stack level too deep" error
|
25
|
+
# when ActiveSupport tries to smarten up Numeric by messing with Fixnum and Bignum at the end of:
|
26
|
+
# activesupport-4.0.13/lib/active_support/core_ext/numeric/conversions.rb
|
27
|
+
if ActiveRecord.version < ::Gem::Version.new('4.2') &&
|
28
|
+
ActiveRecord.version > ::Gem::Version.new('3.2') &&
|
29
|
+
Object.const_defined?('Integer') && Integer.superclass.name == 'Numeric'
|
30
|
+
class OurFixnum < Integer; end
|
31
|
+
Numeric.const_set('Fixnum', OurFixnum)
|
32
|
+
class OurBignum < Integer; end
|
33
|
+
Numeric.const_set('Bignum', OurBignum)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Allow Rails < 3.2 to run with newer versions of Psych gem
|
37
|
+
if BigDecimal.respond_to?(:yaml_tag) && !BigDecimal.respond_to?(:yaml_as)
|
38
|
+
class BigDecimal
|
39
|
+
class <<self
|
40
|
+
alias yaml_as yaml_tag
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
3
45
|
require 'active_record'
|
4
46
|
|
5
47
|
require 'duty_free/config'
|
@@ -65,15 +107,6 @@ end
|
|
65
107
|
# Major compatibility fixes for ActiveRecord < 4.2
|
66
108
|
# ================================================
|
67
109
|
ActiveSupport.on_load(:active_record) do
|
68
|
-
# ActiveRecord before 4.0 didn't have #version
|
69
|
-
unless ActiveRecord.respond_to?(:version)
|
70
|
-
module ActiveRecord
|
71
|
-
def self.version
|
72
|
-
::Gem::Version.new(ActiveRecord::VERSION::STRING)
|
73
|
-
end
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
110
|
# Rails < 4.0 cannot do #find_by, or do #pluck on multiple columns, so here are the patches:
|
78
111
|
if ActiveRecord.version < ::Gem::Version.new('4.0')
|
79
112
|
module ActiveRecord
|
@@ -97,7 +130,18 @@ ActiveSupport.on_load(:active_record) do
|
|
97
130
|
else
|
98
131
|
relation = clone # spawn
|
99
132
|
relation.select_values = column_names
|
100
|
-
result = if
|
133
|
+
result = if klass.connection.class.name.end_with?('::PostgreSQLAdapter')
|
134
|
+
rslt = klass.connection.execute(relation.arel.to_sql)
|
135
|
+
rslt.type_map =
|
136
|
+
@type_map ||= proc do
|
137
|
+
# This aliasing avoids the warning:
|
138
|
+
# "no type cast defined for type "numeric" with oid 1700. Please cast this type
|
139
|
+
# explicitly to TEXT to be safe for future changes."
|
140
|
+
PG::BasicTypeRegistry.alias_type(0, 'numeric', 'text')
|
141
|
+
PG::BasicTypeMapForResults.new(klass.connection.raw_connection)
|
142
|
+
end.call
|
143
|
+
rslt.to_a
|
144
|
+
elsif respond_to?(:bind_values)
|
101
145
|
klass.connection.select_all(relation.arel, nil, bind_values)
|
102
146
|
else
|
103
147
|
klass.connection.select_all(relation.arel.to_sql, nil)
|
@@ -193,6 +237,13 @@ ActiveSupport.on_load(:active_record) do
|
|
193
237
|
end
|
194
238
|
end
|
195
239
|
|
240
|
+
if ActiveRecord.version < ::Gem::Version.new('5.0')
|
241
|
+
# Avoid pg gem deprecation warning: "You should use PG::Connection, PG::Result, and PG::Error instead"
|
242
|
+
PGconn = PG::Connection
|
243
|
+
PGresult = PG::Result
|
244
|
+
PGError = PG::Error
|
245
|
+
end
|
246
|
+
|
196
247
|
include ::DutyFree::Extensions
|
197
248
|
end
|
198
249
|
|
data/lib/duty_free/extensions.rb
CHANGED
@@ -51,8 +51,10 @@ module DutyFree
|
|
51
51
|
rows = [rows]
|
52
52
|
|
53
53
|
if is_with_data
|
54
|
+
order_by = []
|
55
|
+
order_by << ['_', primary_key] if primary_key
|
54
56
|
# Automatically create a JOINs strategy and select list to get back all related rows
|
55
|
-
template_cols, template_joins = ::DutyFree::Extensions._recurse_def(self, import_template[:all], import_template)
|
57
|
+
template_cols, template_joins = ::DutyFree::Extensions._recurse_def(self, import_template[:all], import_template, order_by)
|
56
58
|
relation = use_inner_joins ? joins(template_joins) : left_joins(template_joins)
|
57
59
|
|
58
60
|
# So we can properly create the SELECT list, create a mapping between our
|
@@ -68,7 +70,9 @@ module DutyFree
|
|
68
70
|
end
|
69
71
|
our_names = ['_'] + ::DutyFree::Util._recurse_arel(template_joins)
|
70
72
|
mapping = our_names.zip(arel_alias_names).to_h
|
71
|
-
|
73
|
+
relation = (order_by.empty? ? relation : relation.order(order_by.map { |o| "#{mapping[o.first]}.#{o.last}" }))
|
74
|
+
# puts mapping.inspect
|
75
|
+
# puts relation.dup.select(template_cols.map { |x| x.to_s(mapping) }).to_sql
|
72
76
|
relation.select(template_cols.map { |x| x.to_s(mapping) }).each do |result|
|
73
77
|
rows << ::DutyFree::Extensions._template_columns(self, import_template).map do |col|
|
74
78
|
value = result.send(col)
|
@@ -217,20 +221,20 @@ module DutyFree
|
|
217
221
|
is_insert = false
|
218
222
|
existing_unique = valid_unique.inject([]) do |s, v|
|
219
223
|
s << if v.last.is_a?(Array)
|
220
|
-
# binding.pry
|
221
224
|
v.last[0].where(v.last[1] => row[v.last[2]]).limit(1).pluck(MAX_ID).first.to_s
|
222
225
|
else
|
223
226
|
row[v.last].to_s
|
224
227
|
end
|
225
228
|
end
|
229
|
+
to_be_saved = []
|
226
230
|
# Check to see if they want to preprocess anything
|
227
231
|
existing_unique = @before_process.call(valid_unique, existing_unique) if @before_process ||= import_template[:before_process]
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
232
|
+
if (criteria = existing[existing_unique])
|
233
|
+
obj = find(criteria)
|
234
|
+
else
|
235
|
+
is_insert = true
|
236
|
+
to_be_saved << [obj = new]
|
237
|
+
end
|
234
238
|
sub_obj = nil
|
235
239
|
is_has_one = false
|
236
240
|
has_ones = []
|
@@ -240,28 +244,13 @@ module DutyFree
|
|
240
244
|
keepers.each do |key, v|
|
241
245
|
next if v.nil?
|
242
246
|
|
243
|
-
# Not the same as the last path?
|
244
|
-
if this_path && v.path != this_path.split(',').map(&:to_sym) && !is_has_one
|
245
|
-
# puts sub_obj.class.name
|
246
|
-
if respond_to?(:around_import_save)
|
247
|
-
# Send them the sub_obj even if it might be invalid so they can choose
|
248
|
-
# to make it valid if they wish.
|
249
|
-
# binding.pry
|
250
|
-
around_import_save(sub_obj) do |modded_obj = nil|
|
251
|
-
modded_obj = (modded_obj || sub_obj)
|
252
|
-
modded_obj.save if sub_obj&.valid?
|
253
|
-
end
|
254
|
-
elsif sub_obj&.valid?
|
255
|
-
sub_obj.save
|
256
|
-
end
|
257
|
-
end
|
258
247
|
sub_obj = obj
|
259
248
|
this_path = +''
|
260
249
|
# puts "p: #{v.path}"
|
261
250
|
v.path.each_with_index do |path_part, idx|
|
262
251
|
this_path << (this_path.blank? ? path_part.to_s : ",#{path_part}")
|
263
252
|
unless (sub_next = sub_objects[this_path])
|
264
|
-
# Check if we're hitting
|
253
|
+
# Check if we're hitting reference data / a lookup thing
|
265
254
|
assoc = v.prefix_assocs[idx]
|
266
255
|
# belongs_to some lookup (reference) data
|
267
256
|
if assoc && reference_models.include?(assoc.class_name)
|
@@ -273,44 +262,33 @@ module DutyFree
|
|
273
262
|
lookup_match = (lookup_match.length == 1 ? lookup_match.first : nil)
|
274
263
|
end
|
275
264
|
sub_obj.send("#{path_part}=", lookup_match) unless lookup_match.nil?
|
276
|
-
# Reference data from the
|
265
|
+
# Reference data from the public level means we stop here
|
277
266
|
sub_obj = nil
|
278
267
|
break
|
279
268
|
end
|
280
|
-
# This works for belongs_to or has_one. has_many gets sorted below.
|
281
269
|
# Get existing related object, or create a new one
|
282
|
-
|
270
|
+
# This first part works for belongs_to. has_many and has_one get sorted below.
|
271
|
+
if (sub_next = sub_obj.send(path_part)).nil? && assoc.belongs_to?
|
283
272
|
klass = Object.const_get(assoc&.class_name)
|
284
|
-
#
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
sub_obj.send("#{path_part}=", sub_bt)
|
301
|
-
sub_bt
|
302
|
-
end
|
303
|
-
end
|
304
|
-
# Look for possible missing polymorphic detail
|
305
|
-
# Maybe can test for this via assoc.through_reflection
|
306
|
-
if assoc.is_a?(ActiveRecord::Reflection::ThroughReflection) &&
|
307
|
-
(delegate = assoc.send(:delegate_reflection)&.active_record&.reflect_on_association(assoc.source_reflection_name)) &&
|
308
|
-
delegate.options[:polymorphic]
|
309
|
-
polymorphics << { parent: sub_next, child: sub_obj, type_col: delegate.foreign_type, id_col: delegate.foreign_key.to_s }
|
310
|
-
end
|
311
|
-
# From a has_many?
|
273
|
+
# Try to find a unique item if one is referenced
|
274
|
+
sub_next = nil
|
275
|
+
begin
|
276
|
+
trim_prefix = v.titleize[0..-(v.name.length + 2)]
|
277
|
+
trim_prefix << ' ' unless trim_prefix.blank?
|
278
|
+
sub_next, criteria = klass.find_existing(uniques, cols, starred, import_template, keepers, nil, row, klass, all, trim_prefix)
|
279
|
+
rescue ::DutyFree::NoUniqueColumnError
|
280
|
+
end
|
281
|
+
# puts "#{v.path} #{criteria.inspect}"
|
282
|
+
bt_name = "#{path_part}="
|
283
|
+
unless sub_next || (klass == sub_obj.class && criteria.empty?)
|
284
|
+
sub_next = klass.new(criteria || {})
|
285
|
+
to_be_saved << [sub_next, sub_obj, bt_name, sub_next]
|
286
|
+
end
|
287
|
+
sub_obj.send(bt_name, sub_next)
|
288
|
+
# From a has_many or has_one?
|
312
289
|
# Rails 4.0 and later can do: sub_next.is_a?(ActiveRecord::Associations::CollectionProxy)
|
313
|
-
|
290
|
+
elsif [:has_many, :has_one].include?(assoc.macro) && !assoc.options[:through]
|
291
|
+
::DutyFree::Extensions._save_pending(to_be_saved)
|
314
292
|
# Try to find a unique item if one is referenced
|
315
293
|
# %%% There is possibility that when bringing in related classes using a nil
|
316
294
|
# in IMPORT_TEMPLATE[:all] that this will break. Need to test deeply nested things.
|
@@ -319,10 +297,33 @@ module DutyFree
|
|
319
297
|
trim_prefix << ' ' unless trim_prefix.blank?
|
320
298
|
# assoc.inverse_of is the belongs_to side of the has_many train we came in here on.
|
321
299
|
sub_hm, criteria = assoc.klass.find_existing(uniques, cols, starred, import_template, keepers, assoc.inverse_of, row, sub_next, all, trim_prefix)
|
322
|
-
|
323
300
|
# If still not found then create a new related object using this has_many collection
|
324
301
|
# (criteria.empty? ? nil : sub_next.new(criteria))
|
325
|
-
|
302
|
+
if sub_hm
|
303
|
+
sub_next = sub_hm
|
304
|
+
elsif assoc.macro == :has_one
|
305
|
+
bt_name = "#{assoc.inverse.name}="
|
306
|
+
sub_next = assoc.klass.new(criteria)
|
307
|
+
to_be_saved << [sub_next, sub_obj, bt_name, sub_next]
|
308
|
+
else
|
309
|
+
# Two other methods that are possible to check for here are :conditions and
|
310
|
+
# :sanitized_conditions, which do not exist in Rails 4.0 and later.
|
311
|
+
sub_next = if assoc.respond_to?(:require_association)
|
312
|
+
# With Rails < 4.0, sub_next could come in as an Array or a broken CollectionProxy
|
313
|
+
assoc.klass.new({ fk_from(assoc) => sub_obj.send(sub_obj.class.primary_key) }.merge(criteria))
|
314
|
+
else
|
315
|
+
sub_next.proxy_association.reflection.instance_variable_set(:@foreign_key, fk_from(assoc))
|
316
|
+
sub_next.new(criteria)
|
317
|
+
end
|
318
|
+
to_be_saved << [sub_next]
|
319
|
+
end
|
320
|
+
end
|
321
|
+
# Look for possible missing polymorphic detail
|
322
|
+
# Maybe can test for this via assoc.through_reflection
|
323
|
+
if assoc.is_a?(ActiveRecord::Reflection::ThroughReflection) &&
|
324
|
+
(delegate = assoc.send(:delegate_reflection)&.active_record&.reflect_on_association(assoc.source_reflection_name)) &&
|
325
|
+
delegate.options[:polymorphic]
|
326
|
+
polymorphics << { parent: sub_next, child: sub_obj, type_col: delegate.foreign_type, id_col: delegate.foreign_key.to_s }
|
326
327
|
end
|
327
328
|
unless sub_next.nil?
|
328
329
|
# if sub_next.class.name == devise_class && # only for Devise users
|
@@ -336,7 +337,7 @@ module DutyFree
|
|
336
337
|
sub_objects[this_path] = sub_next if this_path.present?
|
337
338
|
end
|
338
339
|
end
|
339
|
-
sub_obj = sub_next
|
340
|
+
sub_obj = sub_next
|
340
341
|
end
|
341
342
|
next if sub_obj.nil?
|
342
343
|
|
@@ -363,15 +364,16 @@ module DutyFree
|
|
363
364
|
# else
|
364
365
|
# puts " #{sub_obj.class.name} doesn't respond to #{sym}"
|
365
366
|
end
|
367
|
+
::DutyFree::Extensions._save_pending(to_be_saved)
|
366
368
|
# Try to save a final sub-object if one exists
|
367
|
-
sub_obj.save if sub_obj && this_path && !is_has_one && sub_obj.valid?
|
369
|
+
# sub_obj.save if sub_obj && this_path && !is_has_one && sub_obj.valid?
|
368
370
|
|
369
|
-
# Wire up has_one associations
|
370
|
-
has_ones.each do |hasone|
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
end
|
371
|
+
# # Wire up has_one associations
|
372
|
+
# has_ones.each do |hasone|
|
373
|
+
# parent = sub_objects[hasone[0..-2].map(&:to_s).join(',')] || obj
|
374
|
+
# hasone_object = sub_objects[hasone.map(&:to_s).join(',')]
|
375
|
+
# parent.send("#{hasone[-1]}=", hasone_object) if parent.new_record? || hasone_object.valid?
|
376
|
+
# end
|
375
377
|
|
376
378
|
# Reinstate any missing polymorphic _type and _id values
|
377
379
|
polymorphics.each do |poly|
|
@@ -425,11 +427,46 @@ module DutyFree
|
|
425
427
|
ret
|
426
428
|
end
|
427
429
|
|
430
|
+
def fk_from(assoc)
|
431
|
+
# Try first to trust whatever they've marked as being the foreign_key, and then look
|
432
|
+
# at the inverse's foreign key setting if available. In all cases don't accept
|
433
|
+
# anything that's not backed with a real column in the table.
|
434
|
+
col_names = assoc.klass.column_names
|
435
|
+
if (fk_name = assoc.options[:foreign_key]) && col_names.include?(fk_name.to_s)
|
436
|
+
return fk_name
|
437
|
+
end
|
438
|
+
if (fk_name = assoc.inverse_of&.options&.fetch(:foreign_key) { nil }) &&
|
439
|
+
col_names.include?(fk_name.to_s)
|
440
|
+
return fk_name
|
441
|
+
end
|
442
|
+
if assoc.respond_to?(:foreign_key) && (fk_name = assoc.foreign_key) &&
|
443
|
+
col_names.include?(fk_name.to_s)
|
444
|
+
return fk_name
|
445
|
+
end
|
446
|
+
if (fk_name = assoc.inverse_of&.foreign_key) &&
|
447
|
+
col_names.include?(fk_name.to_s)
|
448
|
+
return fk_name
|
449
|
+
end
|
450
|
+
if (fk_name = assoc.inverse_of&.association_foreign_key) &&
|
451
|
+
col_names.include?(fk_name.to_s)
|
452
|
+
return fk_name
|
453
|
+
end
|
454
|
+
# Don't let this fool you -- we really are in search of the foreign key name here,
|
455
|
+
# and Rails 3.0 and older used some fairly interesting conventions, calling it instead
|
456
|
+
# the "primary_key_name"!
|
457
|
+
if assoc.respond_to?(:primary_key_name) && (fk_name = assoc.primary_key_name) &&
|
458
|
+
col_names.include?(fk_name.to_s)
|
459
|
+
return fk_name
|
460
|
+
end
|
461
|
+
|
462
|
+
puts "* Wow, had no luck at all finding a foreign key for #{assoc.inspect}"
|
463
|
+
end
|
464
|
+
|
428
465
|
# For use with importing, based on the provided column list calculate all valid combinations
|
429
466
|
# of unique columns. If there is no valid combination, throws an error.
|
430
467
|
# Returns an object found by this means.
|
431
468
|
def find_existing(uniques, cols, starred, import_template, keepers, train_we_came_in_here_on,
|
432
|
-
row = nil, klass_or_collection = nil,
|
469
|
+
row = nil, klass_or_collection = nil, template_all = nil, trim_prefix = '')
|
433
470
|
unless trim_prefix.blank?
|
434
471
|
cols = cols.map { |c| c.start_with?(trim_prefix) ? c[trim_prefix.length..-1] : nil }
|
435
472
|
starred = starred.each_with_object([]) do |v, s|
|
@@ -451,11 +488,16 @@ module DutyFree
|
|
451
488
|
|
452
489
|
vus = ((@valid_uniques ||= {})[col_list] ||= {}) # Fancy memoisation
|
453
490
|
|
491
|
+
# First, get an overall list of AVAILABLE COLUMNS before considering tricky foreign key stuff.
|
492
|
+
# ============================================================================================
|
493
|
+
# Generate a list of column names matched up with their zero-ordinal column number mapping for
|
494
|
+
# all columns from the incoming import data.
|
454
495
|
if (is_new_vus = vus.empty?)
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
496
|
+
template_column_objects = ::DutyFree::Extensions._recurse_def(
|
497
|
+
self,
|
498
|
+
template_all || import_template[:all],
|
499
|
+
import_template
|
500
|
+
).first
|
459
501
|
available = if trim_prefix.blank?
|
460
502
|
template_column_objects.select { |col| col.pre_prefix.blank? && col.prefix.blank? }
|
461
503
|
else
|
@@ -465,15 +507,18 @@ module DutyFree
|
|
465
507
|
trim_prefix_snake == "#{this_prefix}_"
|
466
508
|
end
|
467
509
|
end.map { |avail| avail.name.to_s.titleize }
|
468
|
-
all_vus = defined_uniques(uniques, cols, nil, starred, trim_prefix)
|
469
|
-
# k, v = all_vus.first
|
470
|
-
# k.each_with_index do |col, idx|
|
471
|
-
# if available.include?(col) # || available_bts.include?(col)
|
472
|
-
# vus[col] ||= v[idx]
|
473
|
-
# end
|
474
|
-
# # if available_bts.include?(k)
|
475
510
|
end
|
476
511
|
|
512
|
+
# Process FOREIGN KEY stuff by going through each belongs_to in this model.
|
513
|
+
# =========================================================================
|
514
|
+
# This list of all valid uniques will help to filter which foreign keys are kept, and also
|
515
|
+
# get further filtered later to arrive upon a final set of valid uniques. (Often but not
|
516
|
+
# necessarily a specific valid unique as perhaps with a list of users you want to update some
|
517
|
+
# folks based on having their email as a unique identifier, and other folks by having a
|
518
|
+
# combination of their name and street address as unique, and use both of those possible
|
519
|
+
# unique variations to update phone numbers, and do that all as a part of one import.)
|
520
|
+
all_vus = defined_uniques(uniques, cols, col_list, starred, trim_prefix)
|
521
|
+
|
477
522
|
# %%% Ultimately may consider making this recursive
|
478
523
|
reflect_on_all_associations.each do |sn_bt|
|
479
524
|
next unless sn_bt.belongs_to? && (!train_we_came_in_here_on || sn_bt != train_we_came_in_here_on)
|
@@ -488,7 +533,7 @@ module DutyFree
|
|
488
533
|
|
489
534
|
available_bts << bt_col
|
490
535
|
fk_id = if row
|
491
|
-
# Max ID so if there are multiple, only the most recent one is picked.
|
536
|
+
# Max ID so if there are multiple matches, only the most recent one is picked.
|
492
537
|
# %%% Need to stack these up in case there are multiple
|
493
538
|
# (like first_name, last_name on a referenced employee)
|
494
539
|
sn_bt.klass.where(keepers[idx].name => row[idx]).limit(1).pluck(MAX_ID).first
|
@@ -519,15 +564,25 @@ module DutyFree
|
|
519
564
|
bt_col_indexes << idx
|
520
565
|
bt_criteria_all_nil = false
|
521
566
|
end
|
567
|
+
# If we're processing a row then this list of foreign key column name entries, named such as
|
568
|
+
# "order_id" or "product_id" instead of column-specific stuff like "Order Date" and "Product Name",
|
569
|
+
# is kept until the last and then gets merged on top of the other criteria before being returned.
|
522
570
|
bt_criteria[(fk_name = sn_bt.foreign_key)] = fk_id
|
523
571
|
|
524
|
-
#
|
572
|
+
# Check to see if belongs_tos are generally required on this specific table
|
525
573
|
bt_req_by_default = sn_bt.klass.respond_to?(:belongs_to_required_by_default) &&
|
526
574
|
sn_bt.klass.belongs_to_required_by_default
|
527
575
|
|
528
|
-
#
|
576
|
+
# Add to our CRITERIA just the belongs_to things that check out.
|
577
|
+
# ==============================================================
|
578
|
+
# The first check, "all_vus.keys.first.none? { |k| k.start_with?(bt_prefix) }"
|
529
579
|
# is to see if one of the columns we're working with from the unique that we've chosen
|
530
580
|
# comes from the table referenced by this belongs_to (sn_bt).
|
581
|
+
#
|
582
|
+
# The second check on the :optional option and bt_req_by_default comes directly from
|
583
|
+
# how Rails 5 and later checks to see if a specific belongs_to is marked optional
|
584
|
+
# (or required), and without having that indication will fall back on checking the model
|
585
|
+
# itself to see if it requires belongs_tos by default.
|
531
586
|
next if all_vus.keys.first.none? { |k| k.start_with?(bt_prefix) } &&
|
532
587
|
(sn_bt.options[:optional] || !bt_req_by_default)
|
533
588
|
|
@@ -536,6 +591,8 @@ module DutyFree
|
|
536
591
|
end
|
537
592
|
end
|
538
593
|
|
594
|
+
# Now circle back find a final list of VALID UNIQUES by re-assessing the list of all valid uniques
|
595
|
+
# in relation to the available belongs_tos found in the last foreign key step.
|
539
596
|
if is_new_vus
|
540
597
|
available += available_bts
|
541
598
|
all_vus.each do |k, v|
|
@@ -585,21 +642,21 @@ module DutyFree
|
|
585
642
|
end
|
586
643
|
end
|
587
644
|
|
588
|
-
# Short-circuiting this to only get back the valid_uniques?
|
589
|
-
# unless uniq_lookups == criteria
|
590
|
-
# puts "Compare #{uniq_lookups.inspect}"
|
591
|
-
# puts "Compare #{criteria.inspect}"
|
592
|
-
# end
|
593
645
|
return uniq_lookups.merge(criteria) if only_valid_uniques
|
594
646
|
|
595
647
|
# If there's nothing to match upon then we're out
|
596
648
|
return [nil, {}] if new_criteria_all_nil
|
597
649
|
|
598
|
-
# With this criteria, find any matching has_many row we can so we can update it
|
599
|
-
# First try looking it up through ActiveRecord
|
650
|
+
# With this criteria, find any matching has_many row we can so we can update it.
|
651
|
+
# First try directly looking it up through ActiveRecord.
|
600
652
|
found_object = klass_or_collection.find_by(criteria)
|
601
653
|
# If not successful, such as when fields are exposed via helper methods instead of being
|
602
|
-
# real columns in the database tables, try this more intensive
|
654
|
+
# real columns in the database tables, try this more intensive approach. This is useful
|
655
|
+
# if you had full name kind of data coming in on a spreadsheeet, but in the destination
|
656
|
+
# table it's broken out to first_name, middle_name, surname. By writing both full_name
|
657
|
+
# and full_name= methods, the importer can check to see if this entry is already there,
|
658
|
+
# and put a new row in if not, having one incoming name break out to populate three
|
659
|
+
# destination columns.
|
603
660
|
unless found_object || klass_or_collection.is_a?(Array)
|
604
661
|
found_object = klass_or_collection.find do |obj|
|
605
662
|
is_good = true
|
@@ -612,6 +669,9 @@ module DutyFree
|
|
612
669
|
is_good
|
613
670
|
end
|
614
671
|
end
|
672
|
+
# Standard criteria as well as foreign key column name detail with exact foreign keys
|
673
|
+
# that match up to a primary key so that if needed a new related object can be built,
|
674
|
+
# complete with all its association detail.
|
615
675
|
[found_object, criteria.merge(bt_criteria)]
|
616
676
|
end
|
617
677
|
|
@@ -657,6 +717,32 @@ module DutyFree
|
|
657
717
|
end
|
658
718
|
end # module ClassMethods
|
659
719
|
|
720
|
+
# Called before building any object linked through a has_one or has_many so that foreign key IDs
|
721
|
+
# can be added properly to those new objects. Finally at the end also called to save everything.
|
722
|
+
def self._save_pending(to_be_saved)
|
723
|
+
while (tbs = to_be_saved.pop)
|
724
|
+
ais = (tbs.first.class.respond_to?(:around_import_save) && tbs.first.class.method(:around_import_save)) ||
|
725
|
+
(respond_to?(:around_import_save) && method(:around_import_save))
|
726
|
+
if ais
|
727
|
+
# Send them the sub_obj even if it might be invalid so they can choose
|
728
|
+
# to make it valid if they wish.
|
729
|
+
ais.call(tbs.first) do |modded_obj = nil|
|
730
|
+
modded_obj = (modded_obj || tbs.first)
|
731
|
+
modded_obj.save if modded_obj&.valid?
|
732
|
+
end
|
733
|
+
elsif tbs.first.valid?
|
734
|
+
tbs.first.save
|
735
|
+
else
|
736
|
+
puts "* Unable to save #{tbs.first.inspect}"
|
737
|
+
end
|
738
|
+
# puts "Save #{tbs.first.class.name} #{tbs.first&.id} #{!tbs.first.new_record?}"
|
739
|
+
unless tbs[1].nil? || tbs.first.new_record?
|
740
|
+
# puts "Calling #{tbs[1].class.name} #{tbs[1]&.id} .#{tbs[2]} #{tbs[3].class.name} #{tbs[3]&.id}"
|
741
|
+
tbs[1].send(tbs[2], tbs[3])
|
742
|
+
end
|
743
|
+
end
|
744
|
+
end
|
745
|
+
|
660
746
|
# The snake-cased column alias names used in the query to export data
|
661
747
|
def self._template_columns(klass, import_template = nil)
|
662
748
|
template_detail_columns = klass.instance_variable_get(:@template_detail_columns)
|
@@ -674,16 +760,21 @@ module DutyFree
|
|
674
760
|
|
675
761
|
# Recurse and return two arrays -- one with all columns in sequence, and one a hierarchy of
|
676
762
|
# nested hashes to be used with ActiveRecord's .joins() to facilitate export.
|
677
|
-
def self._recurse_def(klass, array, import_template, assocs = [], joins = [], pre_prefix = '', prefix = '')
|
763
|
+
def self._recurse_def(klass, array, import_template, order_by = [], assocs = [], joins = [], pre_prefix = '', prefix = '')
|
764
|
+
prefixes = ::DutyFree::Util._prefix_join([pre_prefix, prefix])
|
678
765
|
# Confirm we can actually navigate through this association
|
679
766
|
prefix_assoc = (assocs.last&.klass || klass).reflect_on_association(prefix) if prefix.present?
|
680
|
-
|
681
|
-
|
767
|
+
if prefix_assoc
|
768
|
+
assocs = assocs.dup << prefix_assoc
|
769
|
+
if prefix_assoc.macro == :has_many && (pk = prefix_assoc.active_record.primary_key)
|
770
|
+
order_by << ["#{prefixes.tr('.', '_')}_", pk]
|
771
|
+
end
|
772
|
+
end
|
682
773
|
array = array.inject([]) do |s, col|
|
683
774
|
s + if col.is_a?(Hash)
|
684
775
|
col.inject([]) do |s2, v|
|
685
776
|
joins << { v.first.to_sym => (joins_array = []) }
|
686
|
-
s2 + _recurse_def(klass, (v.last.is_a?(Array) ? v.last : [v.last]), import_template, assocs, joins_array, prefixes, v.first.to_sym).first
|
777
|
+
s2 + _recurse_def(klass, (v.last.is_a?(Array) ? v.last : [v.last]), import_template, order_by, assocs, joins_array, prefixes, v.first.to_sym).first
|
687
778
|
end
|
688
779
|
elsif col.nil?
|
689
780
|
if assocs.empty?
|
@@ -692,7 +783,7 @@ module DutyFree
|
|
692
783
|
# Bring in from another class
|
693
784
|
joins << { prefix => (joins_array = []) }
|
694
785
|
# %%% Also bring in uniques and requireds
|
695
|
-
_recurse_def(klass, assocs.last.klass::IMPORT_TEMPLATE[:all], import_template, assocs, joins_array, prefixes).first
|
786
|
+
_recurse_def(klass, assocs.last.klass::IMPORT_TEMPLATE[:all], import_template, order_by, assocs, joins_array, prefixes).first
|
696
787
|
end
|
697
788
|
else
|
698
789
|
[::DutyFree::Column.new(col, pre_prefix, prefix, assocs, klass, import_template[:as])]
|
@@ -65,7 +65,7 @@ module DutyFree
|
|
65
65
|
end
|
66
66
|
next if is_through || is_habtm || (!is_belongs_to && !do_has_many) || errored_assocs.include?(assoc)
|
67
67
|
|
68
|
-
if is_belongs_to && assoc.polymorphic
|
68
|
+
if is_belongs_to && assoc.options[:polymorphic] # Polymorphic belongs_to?
|
69
69
|
# Load all models
|
70
70
|
# %%% Note that this works in Rails 5.x, but may not work in Rails 6.0 and later, which uses the Zeitwerk loader by default:
|
71
71
|
Rails.configuration.eager_load_namespaces.select { |ns| ns < Rails::Application }.each(&:eager_load!)
|
@@ -89,7 +89,7 @@ module DutyFree
|
|
89
89
|
end
|
90
90
|
else
|
91
91
|
# Is it a polymorphic has_many, which is defined using as: :somethingable ?
|
92
|
-
is_polymorphic_hm = assoc.inverse_of&.polymorphic
|
92
|
+
is_polymorphic_hm = assoc.inverse_of&.options&.fetch(:polymorphic) { nil }
|
93
93
|
begin
|
94
94
|
# Standard has_one, or has_many, and belongs_to uses assoc.klass.
|
95
95
|
# Also polymorphic belongs_to uses assoc.klass.
|
@@ -105,9 +105,9 @@ module DutyFree
|
|
105
105
|
[[[fk], assoc.active_record], assoc_klass]
|
106
106
|
else # has_many or has_one
|
107
107
|
inverse_foreign_keys = is_polymorphic_hm ? [assoc.type, assoc.foreign_key] : [assoc.inverse_of&.foreign_key&.to_s]
|
108
|
-
puts "* Missing inverse foreign key for #{assoc.inspect}" if inverse_foreign_keys.first.nil?
|
109
108
|
missing_key_columns = inverse_foreign_keys - assoc_klass.columns.map(&:name)
|
110
109
|
if missing_key_columns.empty?
|
110
|
+
puts "* Missing inverse foreign key for #{this_klass.name} #{belongs_to_or_has_many} :#{assoc.name}" if inverse_foreign_keys.first.nil?
|
111
111
|
# puts "Has columns #{inverse_foreign_keys.inspect}"
|
112
112
|
[[inverse_foreign_keys, assoc_klass], assoc_klass]
|
113
113
|
else
|
@@ -185,7 +185,7 @@ module DutyFree
|
|
185
185
|
next unless bt_assoc.belongs_to? && !errored_assocs.include?(bt_assoc)
|
186
186
|
|
187
187
|
begin
|
188
|
-
s << bt_assoc if !bt_assoc.polymorphic
|
188
|
+
s << bt_assoc if !bt_assoc.options[:polymorphic] && bt_assoc.klass == to_klass
|
189
189
|
rescue NameError
|
190
190
|
errored_assocs << bt_assoc
|
191
191
|
puts "* In the #{bt_assoc.active_record.name} model \"belongs_to :#{bt_assoc.name}\" could not find a model named #{bt_assoc.class_name}."
|
data/lib/duty_free/util.rb
CHANGED
@@ -45,7 +45,7 @@ module DutyFree
|
|
45
45
|
# The right side here at the top is the very last table, and anywhere else down the tree it is
|
46
46
|
# the later "JOIN" table of this pair. (The table that comes after all the rest of the JOINs
|
47
47
|
# from the left side.)
|
48
|
-
names << piece.right.name
|
48
|
+
names << (piece.right.table_alias || piece.right.name)
|
49
49
|
else # "Normal" setup, fed from a JoinSource which has an array of JOINs
|
50
50
|
# The left side is the "JOIN" table
|
51
51
|
names += _recurse_arel(piece.left)
|
@@ -53,14 +53,14 @@ module DutyFree
|
|
53
53
|
end
|
54
54
|
# rubocop:enable Style/IdenticalConditionalBranches
|
55
55
|
elsif piece.is_a?(Arel::Table) # Table
|
56
|
-
names << piece.name
|
56
|
+
names << (piece.table_alias || piece.name)
|
57
57
|
elsif piece.is_a?(Arel::Nodes::TableAlias) # Alias
|
58
58
|
# Can get the real table name from: self._recurse_arel(piece.left)
|
59
59
|
names << piece.right.to_s # This is simply a string; the alias name itself
|
60
60
|
elsif piece.is_a?(Arel::Nodes::JoinSource) # Leaving this until the end because AR < 3.2 doesn't know at all about JoinSource!
|
61
61
|
# The left side is the "FROM" table
|
62
62
|
# names += _recurse_arel(piece.left)
|
63
|
-
names << piece.left.name
|
63
|
+
names << (piece.left.table_alias || piece.left.name)
|
64
64
|
# The right side is an array of all JOINs
|
65
65
|
names += piece.right.inject([]) { |s, v| s + _recurse_arel(v) }
|
66
66
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: duty_free
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Lorin Thwaits
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-11-
|
11
|
+
date: 2020-11-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|