duty_free 1.0.3 → 1.0.8

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.
@@ -40,33 +40,31 @@ module DutyFree
40
40
  assocs = {}
41
41
  this_klass.reflect_on_all_associations.each do |assoc|
42
42
  # PolymorphicReflection AggregateReflection RuntimeReflection
43
- is_belongs_to = assoc.is_a?(ActiveRecord::Reflection::BelongsToReflection)
43
+ is_belongs_to = assoc.belongs_to?
44
44
  # Figure out if it's belongs_to, has_many, or has_one
45
45
  belongs_to_or_has_many =
46
46
  if is_belongs_to
47
47
  'belongs_to'
48
- elsif (is_habtm = assoc.is_a?(ActiveRecord::Reflection::HasAndBelongsToManyReflection))
48
+ elsif (is_habtm = assoc.macro == :has_and_belongs_to_many)
49
49
  'has_and_belongs_to_many'
50
+ elsif assoc.macro == :has_many
51
+ 'has_many'
50
52
  else
51
- (assoc.is_a?(ActiveRecord::Reflection::HasManyReflection) ? 'has_many' : 'has_one')
53
+ 'has_one'
52
54
  end
53
55
  # Always process belongs_to, and also process has_one and has_many if do_has_many is chosen.
54
56
  # Skip any HMT or HABTM. (Maybe break out HABTM into a combo HM and BT in the future.)
55
57
  if is_habtm
56
- unless ActiveRecord::Base.connection.table_exists?(assoc.join_table)
57
- puts "* In the #{this_klass.name} model there's a problem with: \"has_and_belongs_to_many :#{assoc.name}\" because join table \"#{assoc.join_table}\" does not exist. You can create it with a create_join_table migration."
58
- end
58
+ puts "* In the #{this_klass.name} model there's a problem with: \"has_and_belongs_to_many :#{assoc.name}\" because join table \"#{assoc.join_table}\" does not exist. You can create it with a create_join_table migration." unless ActiveRecord::Base.connection.table_exists?(assoc.join_table)
59
59
  # %%% Search for other associative candidates to use instead of this HABTM contraption
60
- if assoc.options.include?(:through)
61
- puts "* In the #{this_klass.name} model there's a problem with: \"has_and_belongs_to_many :#{assoc.name}\" because it includes \"through: #{assoc.options[:through].inspect}\" which is pointless and should be removed."
62
- end
60
+ puts "* In the #{this_klass.name} model there's a problem with: \"has_and_belongs_to_many :#{assoc.name}\" because it includes \"through: #{assoc.options[:through].inspect}\" which is pointless and should be removed." if assoc.options.include?(:through)
63
61
  end
64
62
  if (is_through = assoc.is_a?(ActiveRecord::Reflection::ThroughReflection)) && assoc.options.include?(:as)
65
63
  puts "* In the #{this_klass.name} model there's a problem with: \"has_many :#{assoc.name} through: #{assoc.options[:through].inspect}\" because it also includes \"as: #{assoc.options[:as].inspect}\", so please choose either for this line to be a \"has_many :#{assoc.name} through:\" or to be a polymorphic \"has_many :#{assoc.name} as:\". It can't be both."
66
64
  end
67
65
  next if is_through || is_habtm || (!is_belongs_to && !do_has_many) || errored_assocs.include?(assoc)
68
66
 
69
- if is_belongs_to && assoc.polymorphic? # Polymorphic belongs_to?
67
+ if is_belongs_to && assoc.options[:polymorphic] # Polymorphic belongs_to?
70
68
  # Load all models
71
69
  # %%% 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:
72
70
  Rails.configuration.eager_load_namespaces.select { |ns| ns < Rails::Application }.each(&:eager_load!)
@@ -81,8 +79,7 @@ module DutyFree
81
79
 
82
80
  # Find applicable polymorphic has_many associations from each real model
83
81
  model.reflect_on_all_associations.each do |poly_assoc|
84
- next unless poly_assoc.is_a?(ActiveRecord::Reflection::HasManyReflection) &&
85
- poly_assoc.inverse_of == assoc
82
+ next unless poly_assoc.macro == :has_many && poly_assoc.inverse_of == assoc
86
83
 
87
84
  this_belongs_tos += (fkeys = [poly_assoc.type, poly_assoc.foreign_key])
88
85
  assocs["#{assoc.name}_#{poly_assoc.active_record.name.underscore}".to_sym] = [[fkeys, assoc.active_record], poly_assoc.active_record]
@@ -90,7 +87,7 @@ module DutyFree
90
87
  end
91
88
  else
92
89
  # Is it a polymorphic has_many, which is defined using as: :somethingable ?
93
- is_polymorphic_hm = assoc.inverse_of&.polymorphic?
90
+ is_polymorphic_hm = assoc.inverse_of&.options&.fetch(:polymorphic) { nil }
94
91
  begin
95
92
  # Standard has_one, or has_many, and belongs_to uses assoc.klass.
96
93
  # Also polymorphic belongs_to uses assoc.klass.
@@ -106,9 +103,9 @@ module DutyFree
106
103
  [[[fk], assoc.active_record], assoc_klass]
107
104
  else # has_many or has_one
108
105
  inverse_foreign_keys = is_polymorphic_hm ? [assoc.type, assoc.foreign_key] : [assoc.inverse_of&.foreign_key&.to_s]
109
- puts "* Missing inverse foreign key for #{assoc.inspect}" if inverse_foreign_keys.first.nil?
110
106
  missing_key_columns = inverse_foreign_keys - assoc_klass.columns.map(&:name)
111
107
  if missing_key_columns.empty?
108
+ puts "* Missing inverse foreign key for #{this_klass.name} #{belongs_to_or_has_many} :#{assoc.name}" if inverse_foreign_keys.first.nil?
112
109
  # puts "Has columns #{inverse_foreign_keys.inspect}"
113
110
  [[inverse_foreign_keys, assoc_klass], assoc_klass]
114
111
  else
@@ -182,10 +179,11 @@ module DutyFree
182
179
  # Find belongs_tos for this model to one more more other klasses
183
180
  def self._find_belongs_tos(klass, to_klass, errored_assocs)
184
181
  klass.reflect_on_all_associations.each_with_object([]) do |bt_assoc, s|
185
- next unless bt_assoc.is_a?(ActiveRecord::Reflection::BelongsToReflection) && !errored_assocs.include?(bt_assoc)
182
+ # .is_a?(ActiveRecord::Reflection::BelongsToReflection)
183
+ next unless bt_assoc.belongs_to? && !errored_assocs.include?(bt_assoc)
186
184
 
187
185
  begin
188
- s << bt_assoc if !bt_assoc.polymorphic? && bt_assoc.klass == to_klass
186
+ s << bt_assoc if !bt_assoc.options[:polymorphic] && bt_assoc.klass == to_klass
189
187
  rescue NameError
190
188
  errored_assocs << bt_assoc
191
189
  puts "* In the #{bt_assoc.active_record.name} model \"belongs_to :#{bt_assoc.name}\" could not find a model named #{bt_assoc.class_name}."
@@ -203,9 +201,7 @@ module DutyFree
203
201
 
204
202
  # Requireds takes its cues from all attributes having a presence validator
205
203
  requireds = _find_requireds(klass)
206
- if priority_excluded_columns
207
- klass_columns = klass_columns.reject { |col| priority_excluded_columns.include?(col.name) }
208
- end
204
+ klass_columns = klass_columns.reject { |col| priority_excluded_columns.include?(col.name) } if priority_excluded_columns
209
205
  excluded_columns = %w[created_at updated_at deleted_at]
210
206
  unique = [(
211
207
  # Find the first text field of a required if one exists
@@ -240,8 +236,14 @@ module DutyFree
240
236
  if klass.columns.map(&:name).include?(attrib)
241
237
  s << attrib
242
238
  else
243
- puts "* In the #{klass.name} model \"validates_presence_of :#{attrib}\" should be removed as it does not refer to any existing column."
244
- errored_columns << klass_col
239
+ hm_and_bt_names = klass.reflect_on_all_associations.each_with_object([]) do |assoc, names|
240
+ names << assoc.name.to_s if [:belongs_to, :has_many, :has_one].include?(assoc.macro)
241
+ names
242
+ end
243
+ unless hm_and_bt_names.include?(attrib)
244
+ puts "* In the #{klass.name} model \"validates_presence_of :#{attrib}\" should be removed as it does not refer to any existing column."
245
+ errored_columns << klass_col
246
+ end
245
247
  end
246
248
  end
247
249
  end
@@ -283,10 +285,11 @@ module DutyFree
283
285
  v.each_with_index do |item, idx|
284
286
  # This is where most of the commas get printed, so you can do "#{child_count}," to diagnose things
285
287
  print ',' if idx.positive? && indent >= 0
286
- if item.is_a?(Hash)
288
+ case item
289
+ when Hash
287
290
  # puts '^' unless child_count < 5 || indent.negative?
288
291
  child_count = _template_pretty_print(item, indent + 2, child_count)
289
- elsif item.is_a?(Symbol)
292
+ when Symbol
290
293
  if indent.negative?
291
294
  child_count += 1
292
295
  else
@@ -25,34 +25,59 @@ module DutyFree
25
25
  def self._recurse_arel(piece, prefix = '')
26
26
  names = []
27
27
  # Our JOINs mashup of nested arrays and hashes
28
- if piece.is_a?(Array)
28
+ case piece
29
+ when Array
29
30
  names += piece.inject([]) { |s, v| s + _recurse_arel(v, prefix) }
30
- elsif piece.is_a?(Hash)
31
+ when Hash
31
32
  names += piece.inject([]) do |s, v|
32
33
  new_prefix = "#{prefix}#{v.first}_"
33
- s << new_prefix
34
+ s << [v.last.shift, new_prefix]
34
35
  s + _recurse_arel(v.last, new_prefix)
35
36
  end
36
37
 
37
38
  # ActiveRecord AREL objects
38
- elsif piece.is_a?(Arel::Nodes::JoinSource)
39
+ when Arel::Nodes::Join # INNER or OUTER JOIN
40
+ # rubocop:disable Style/IdenticalConditionalBranches
41
+ if piece.right.is_a?(Arel::Table) # Came in from AR < 3.2?
42
+ # Arel 2.x and older is a little curious because these JOINs work "back to front".
43
+ # The left side here is either another earlier JOIN, or at the end of the whole tree, it is
44
+ # the first table.
45
+ names += _recurse_arel(piece.left)
46
+ # The right side here at the top is the very last table, and anywhere else down the tree it is
47
+ # the later "JOIN" table of this pair. (The table that comes after all the rest of the JOINs
48
+ # from the left side.)
49
+ names << [_arel_table_type(piece.right), (piece.right.table_alias || piece.right.name)]
50
+ else # "Normal" setup, fed from a JoinSource which has an array of JOINs
51
+ # The left side is the "JOIN" table
52
+ names += _recurse_arel(piece.left)
53
+ # (The right side of these is the "ON" clause)
54
+ end
55
+ # rubocop:enable Style/IdenticalConditionalBranches
56
+ when Arel::Table # Table
57
+ names << [_arel_table_type(piece), (piece.table_alias || piece.name)]
58
+ when Arel::Nodes::TableAlias # Alias
59
+ # Can get the real table name from: self._recurse_arel(piece.left)
60
+ names << [_arel_table_type(piece.left), piece.right.to_s] # This is simply a string; the alias name itself
61
+ when Arel::Nodes::JoinSource # Leaving this until the end because AR < 3.2 doesn't know at all about JoinSource!
39
62
  # The left side is the "FROM" table
40
63
  # names += _recurse_arel(piece.left)
64
+ names << [_arel_table_type(piece.left), (piece.left.table_alias || piece.left.name)]
41
65
  # The right side is an array of all JOINs
42
66
  names += piece.right.inject([]) { |s, v| s + _recurse_arel(v) }
43
- elsif piece.is_a?(Arel::Nodes::Join) # INNER or OUTER JOIN
44
- # The left side is the "JOIN" table
45
- names += _recurse_arel(piece.left)
46
- # (The right side of these is the "ON" clause)
47
- elsif piece.is_a?(Arel::Table) # Table
48
- names << piece.name
49
- elsif piece.is_a?(Arel::Nodes::TableAlias) # Alias
50
- # Can get the real table name from: self._recurse_arel(piece.left)
51
- names << piece.right.to_s # This is simply a string; the alias name itself
52
67
  end
53
68
  names
54
69
  end
55
70
 
71
+ def self._arel_table_type(tbl)
72
+ # AR < 4.2 doesn't have type_caster at all, so rely on an instance variable getting set
73
+ # AR 4.2 - 5.1 have buggy type_caster entries for the root node
74
+ tbl.instance_variable_get(:@_arel_table_type) ||
75
+ # 5.2-6.1 does type_caster just fine, no bugs there, but the property with the type differs:
76
+ # 5.2 has "types" as public, 6.0 "types" as private, and 6.1 "klass" as private.
77
+ ((tc = tbl.send(:type_caster)) && tc.instance_variable_get(:@types)) ||
78
+ tc.send(:klass)
79
+ end
80
+
56
81
  def self._prefix_join(prefixes, separator = nil)
57
82
  prefixes.reject(&:blank?).join(separator || '.')
58
83
  end
@@ -69,5 +94,112 @@ module DutyFree
69
94
  end
70
95
  name
71
96
  end
97
+
98
+ # ===================================
99
+ # Epic require patch
100
+ def self._patch_require(module_filename, folder_matcher, search_text, replacement_text, autoload_symbol = nil)
101
+ mod_name_parts = module_filename.split('.')
102
+ extension = case mod_name_parts.last
103
+ when 'rb', 'so', 'o'
104
+ module_filename = mod_name_parts[0..-2].join('.')
105
+ ".#{mod_name_parts.last}"
106
+ else
107
+ '.rb'
108
+ end
109
+
110
+ if autoload_symbol
111
+ unless Object.const_defined?('ActiveSupport::Dependencies')
112
+ require 'active_support'
113
+ require 'active_support/dependencies'
114
+ end
115
+ alp = ActiveSupport::Dependencies.autoload_paths
116
+ custom_require_dir = ::DutyFree::Util._custom_require_dir
117
+ # Create any missing folder structure leading up to this file
118
+ module_filename.split('/')[0..-2].inject(custom_require_dir) do |s, part|
119
+ new_part = File.join(s, part)
120
+ Dir.mkdir(new_part) unless Dir.exist?(new_part)
121
+ new_part
122
+ end
123
+ if ::DutyFree::Util._write_patched(folder_matcher, module_filename, extension, custom_require_dir, nil, search_text, replacement_text) &&
124
+ !alp.include?(custom_require_dir)
125
+ alp.unshift(custom_require_dir)
126
+ end
127
+ else
128
+ unless (require_overrides = ::DutyFree::Util.instance_variable_get(:@_require_overrides))
129
+ ::DutyFree::Util.instance_variable_set(:@_require_overrides, (require_overrides = {}))
130
+
131
+ # Patch "require" itself so that when it specifically sees "active_support/values/time_zone" then
132
+ # a copy is taken of the original, an attempt is made to find the line with a circular error, that
133
+ # single line is patched, and then an updated version is written to a temporary folder which is
134
+ # then required in place of the original.
135
+
136
+ Kernel.module_exec do
137
+ # class << self
138
+ alias_method :orig_require, :require
139
+ # end
140
+ # To be most faithful to Ruby's normal behaviour, this should look like a public singleton
141
+ define_method(:require) do |name|
142
+ if (require_override = ::DutyFree::Util.instance_variable_get(:@_require_overrides)[name])
143
+ extension, folder_matcher, search_text, replacement_text, autoload_symbol = require_override
144
+ patched_filename = "/patched_#{name.tr('/', '_')}#{extension}"
145
+ if $LOADED_FEATURES.find { |f| f.end_with?(patched_filename) }
146
+ false
147
+ else
148
+ is_replaced = false
149
+ if (replacement_path = ::DutyFree::Util._write_patched(folder_matcher, name, extension, ::DutyFree::Util._custom_require_dir, patched_filename, search_text, replacement_text))
150
+ is_replaced = Kernel.send(:orig_require, replacement_path)
151
+ elsif replacement_path.nil?
152
+ puts "Couldn't find #{name} to require it!"
153
+ end
154
+ is_replaced
155
+ end
156
+ else
157
+ Kernel.send(:orig_require, name)
158
+ end
159
+ end
160
+ end
161
+ end
162
+ require_overrides[module_filename] = [extension, folder_matcher, search_text, replacement_text, autoload_symbol]
163
+ end
164
+ end
165
+
166
+ def self._custom_require_dir
167
+ unless (custom_require_dir = ::DutyFree::Util.instance_variable_get(:@_custom_require_dir))
168
+ ::DutyFree::Util.instance_variable_set(:@_custom_require_dir, (custom_require_dir = Dir.mktmpdir))
169
+ # So normal Ruby require will now pick this one up
170
+ $LOAD_PATH.unshift(custom_require_dir)
171
+ # When Ruby is exiting, remove this temporary directory
172
+ at_exit do
173
+ FileUtils.rm_rf(::DutyFree::Util.instance_variable_get(:@_custom_require_dir))
174
+ end
175
+ end
176
+ custom_require_dir
177
+ end
178
+
179
+ # Returns the full path to the replaced filename, or
180
+ # false if the file already exists, and nil if it was unable to write anything.
181
+ def self._write_patched(folder_matcher, name, extension, dir, patched_filename, search_text, replacement_text)
182
+ # See if our replacement file might already exist for some reason
183
+ name = +"/#{name}" unless name.start_with?('/')
184
+ name << extension unless name.end_with?(extension)
185
+ return false if File.exist?(replacement_path = "#{dir}#{patched_filename || name}")
186
+
187
+ # Dredge up the original .rb file, doctor it, and then require it instead
188
+ num_written = nil
189
+ orig_path = nil
190
+ orig_as = nil
191
+ # Using Ruby's approach to find files to require
192
+ $LOAD_PATH.each do |path|
193
+ orig_path = "#{path}#{name}"
194
+ break if path.include?(folder_matcher) && (orig_as = File.open(orig_path))
195
+ end
196
+ if (orig_text = orig_as&.read)
197
+ File.open(replacement_path, 'w') do |replacement|
198
+ num_written = replacement.write(orig_text.gsub(search_text, replacement_text))
199
+ end
200
+ orig_as.close
201
+ end
202
+ (num_written&.> 0) ? replacement_path : nil
203
+ end
72
204
  end
73
205
  end
@@ -5,7 +5,7 @@ module DutyFree
5
5
  module VERSION
6
6
  MAJOR = 1
7
7
  MINOR = 0
8
- TINY = 3
8
+ TINY = 8
9
9
 
10
10
  # PRE is nil unless it's a pre-release (beta, RC, etc.)
11
11
  PRE = nil
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.3
4
+ version: 1.0.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lorin Thwaits
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-11-06 00:00:00.000000000 Z
11
+ date: 2021-01-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -16,20 +16,20 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '4.2'
19
+ version: '3.0'
20
20
  - - "<"
21
21
  - !ruby/object:Gem::Version
22
- version: '6.0'
22
+ version: '6.2'
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
26
26
  requirements:
27
27
  - - ">="
28
28
  - !ruby/object:Gem::Version
29
- version: '4.2'
29
+ version: '3.0'
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
- version: '6.0'
32
+ version: '6.2'
33
33
  - !ruby/object:Gem::Dependency
34
34
  name: appraisal
35
35
  requirement: !ruby/object:Gem::Requirement
@@ -134,14 +134,14 @@ dependencies:
134
134
  requirements:
135
135
  - - "~>"
136
136
  - !ruby/object:Gem::Version
137
- version: 0.89.1
137
+ version: '0.93'
138
138
  type: :development
139
139
  prerelease: false
140
140
  version_requirements: !ruby/object:Gem::Requirement
141
141
  requirements:
142
142
  - - "~>"
143
143
  - !ruby/object:Gem::Version
144
- version: 0.89.1
144
+ version: '0.93'
145
145
  - !ruby/object:Gem::Dependency
146
146
  name: rubocop-rspec
147
147
  requirement: !ruby/object:Gem::Requirement
@@ -156,20 +156,6 @@ dependencies:
156
156
  - - "~>"
157
157
  - !ruby/object:Gem::Version
158
158
  version: 1.42.0
159
- - !ruby/object:Gem::Dependency
160
- name: mysql2
161
- requirement: !ruby/object:Gem::Requirement
162
- requirements:
163
- - - "~>"
164
- - !ruby/object:Gem::Version
165
- version: '0.5'
166
- type: :development
167
- prerelease: false
168
- version_requirements: !ruby/object:Gem::Requirement
169
- requirements:
170
- - - "~>"
171
- - !ruby/object:Gem::Version
172
- version: '0.5'
173
159
  - !ruby/object:Gem::Dependency
174
160
  name: pg
175
161
  requirement: !ruby/object:Gem::Requirement
@@ -204,10 +190,9 @@ dependencies:
204
190
  - - "~>"
205
191
  - !ruby/object:Gem::Version
206
192
  version: '1.4'
207
- description: |
208
- An ActiveRecord extension that simplifies importing and exporting of data
209
- stored in one or more models. Source and destination can be CSV, XLS,
210
- XLSX, ODT, HTML tables, or simple Ruby arrays.
193
+ description: 'Simplify data imports and exports with this slick ActiveRecord extension
194
+
195
+ '
211
196
  email: lorint@gmail.com
212
197
  executables: []
213
198
  extensions: []
@@ -235,7 +220,7 @@ homepage: https://github.com/lorint/duty_free
235
220
  licenses:
236
221
  - MIT
237
222
  metadata: {}
238
- post_install_message:
223
+ post_install_message:
239
224
  rdoc_options: []
240
225
  require_paths:
241
226
  - lib
@@ -243,15 +228,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
243
228
  requirements:
244
229
  - - ">="
245
230
  - !ruby/object:Gem::Version
246
- version: 2.4.0
231
+ version: 2.3.5
247
232
  required_rubygems_version: !ruby/object:Gem::Requirement
248
233
  requirements:
249
234
  - - ">="
250
235
  - !ruby/object:Gem::Version
251
236
  version: 1.3.6
252
237
  requirements: []
253
- rubygems_version: 3.0.8
254
- signing_key:
238
+ rubygems_version: 3.2.3
239
+ signing_key:
255
240
  specification_version: 4
256
241
  summary: Import and Export Data
257
242
  test_files: []