duty_free 1.0.3 → 1.0.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []