duty_free 1.0.8 → 1.0.10

Sign up to get free protection for your applications and to get access to all the features.
@@ -97,7 +97,7 @@ module DutyFree
97
97
 
98
98
  # ===================================
99
99
  # Epic require patch
100
- def self._patch_require(module_filename, folder_matcher, search_text, replacement_text, autoload_symbol = nil)
100
+ def self._patch_require(module_filename, folder_matcher, replacements, autoload_symbol = nil, is_bundler = false)
101
101
  mod_name_parts = module_filename.split('.')
102
102
  extension = case mod_name_parts.last
103
103
  when 'rb', 'so', 'o'
@@ -120,10 +120,17 @@ module DutyFree
120
120
  Dir.mkdir(new_part) unless Dir.exist?(new_part)
121
121
  new_part
122
122
  end
123
- if ::DutyFree::Util._write_patched(folder_matcher, module_filename, extension, custom_require_dir, nil, search_text, replacement_text) &&
123
+ if ::DutyFree::Util._write_patched(folder_matcher, module_filename, extension, custom_require_dir, nil, replacements) &&
124
124
  !alp.include?(custom_require_dir)
125
125
  alp.unshift(custom_require_dir)
126
126
  end
127
+ elsif is_bundler
128
+ puts "Bundler hack"
129
+ require 'pry-byebug'
130
+ binding.pry
131
+ x = 5
132
+ # bin_path
133
+ # puts Bundler.require.inspect
127
134
  else
128
135
  unless (require_overrides = ::DutyFree::Util.instance_variable_get(:@_require_overrides))
129
136
  ::DutyFree::Util.instance_variable_set(:@_require_overrides, (require_overrides = {}))
@@ -134,19 +141,19 @@ module DutyFree
134
141
  # then required in place of the original.
135
142
 
136
143
  Kernel.module_exec do
137
- # class << self
138
144
  alias_method :orig_require, :require
139
- # end
140
145
  # To be most faithful to Ruby's normal behaviour, this should look like a public singleton
141
146
  define_method(:require) do |name|
147
+ # %%% Can get a message such as "ActionDispatch::Routing is not missing constant RouteSet! (NameError)"
148
+ # binding.pry if name.start_with?('action_dispatch/routing/route_') # || name == 'active_support/values/time_zone'
142
149
  if (require_override = ::DutyFree::Util.instance_variable_get(:@_require_overrides)[name])
143
- extension, folder_matcher, search_text, replacement_text, autoload_symbol = require_override
150
+ extension, folder_matcher, replacements, autoload_symbol = require_override
144
151
  patched_filename = "/patched_#{name.tr('/', '_')}#{extension}"
145
152
  if $LOADED_FEATURES.find { |f| f.end_with?(patched_filename) }
146
153
  false
147
154
  else
148
155
  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))
156
+ if (replacement_path = ::DutyFree::Util._write_patched(folder_matcher, name, extension, ::DutyFree::Util._custom_require_dir, patched_filename, replacements))
150
157
  is_replaced = Kernel.send(:orig_require, replacement_path)
151
158
  elsif replacement_path.nil?
152
159
  puts "Couldn't find #{name} to require it!"
@@ -159,12 +166,13 @@ module DutyFree
159
166
  end
160
167
  end
161
168
  end
162
- require_overrides[module_filename] = [extension, folder_matcher, search_text, replacement_text, autoload_symbol]
169
+ require_overrides[module_filename] = [extension, folder_matcher, replacements, autoload_symbol]
163
170
  end
164
171
  end
165
172
 
166
173
  def self._custom_require_dir
167
174
  unless (custom_require_dir = ::DutyFree::Util.instance_variable_get(:@_custom_require_dir))
175
+ require 'tmpdir'
168
176
  ::DutyFree::Util.instance_variable_set(:@_custom_require_dir, (custom_require_dir = Dir.mktmpdir))
169
177
  # So normal Ruby require will now pick this one up
170
178
  $LOAD_PATH.unshift(custom_require_dir)
@@ -178,11 +186,12 @@ module DutyFree
178
186
 
179
187
  # Returns the full path to the replaced filename, or
180
188
  # 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)
189
+ def self._write_patched(folder_matcher, name, extension, dir, patched_filename, replacements)
182
190
  # See if our replacement file might already exist for some reason
183
191
  name = +"/#{name}" unless name.start_with?('/')
184
192
  name << extension unless name.end_with?(extension)
185
- return false if File.exist?(replacement_path = "#{dir}#{patched_filename || name}")
193
+ puts (replacement_path = "#{dir}#{patched_filename || name}")
194
+ return false if File.exist?(replacement_path)
186
195
 
187
196
  # Dredge up the original .rb file, doctor it, and then require it instead
188
197
  num_written = nil
@@ -193,9 +202,14 @@ module DutyFree
193
202
  orig_path = "#{path}#{name}"
194
203
  break if path.include?(folder_matcher) && (orig_as = File.open(orig_path))
195
204
  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))
205
+ puts [folder_matcher, name].inspect
206
+ if (updated_text = orig_as&.read)
207
+ File.open(replacement_path, 'w') do |replaced_file|
208
+ replacements = [replacements] unless replacements.first.is_a?(Array)
209
+ replacements.each do |search_text, replacement_text|
210
+ updated_text.gsub!(search_text, replacement_text)
211
+ end
212
+ num_written = replaced_file.write(updated_text)
199
213
  end
200
214
  orig_as.close
201
215
  end
@@ -5,7 +5,7 @@ module DutyFree
5
5
  module VERSION
6
6
  MAJOR = 1
7
7
  MINOR = 0
8
- TINY = 8
8
+ TINY = 10
9
9
 
10
10
  # PRE is nil unless it's a pre-release (beta, RC, etc.)
11
11
  PRE = nil
data/lib/duty_free.rb CHANGED
@@ -50,15 +50,15 @@ if ActiveRecord.version < ::Gem::Version.new('3.2') &&
50
50
  # Remove circular reference for "now"
51
51
  ::DutyFree::Util._patch_require(
52
52
  'active_support/values/time_zone.rb', '/activesupport',
53
- ' def parse(str, now=now)',
54
- ' def parse(str, now=now())'
53
+ [' def parse(str, now=now)',
54
+ ' def parse(str, now=now())']
55
55
  )
56
56
  # Remove circular reference for "reflection" for ActiveRecord 3.1
57
57
  if ActiveRecord.version >= ::Gem::Version.new('3.1')
58
58
  ::DutyFree::Util._patch_require(
59
59
  'active_record/associations/has_many_association.rb', '/activerecord',
60
- 'reflection = reflection)',
61
- 'reflection = reflection())',
60
+ ['reflection = reflection)',
61
+ 'reflection = reflection())'],
62
62
  :HasManyAssociation # Make sure the path for this guy is available to be autoloaded
63
63
  )
64
64
  end
@@ -129,9 +129,21 @@ end
129
129
  # Major compatibility fixes for ActiveRecord < 4.2
130
130
  # ================================================
131
131
  ActiveSupport.on_load(:active_record) do
132
- # Rails < 4.0 cannot do #find_by, #find_or_create_by, or do #pluck on multiple columns, so here are the patches:
133
- if ActiveRecord.version < ::Gem::Version.new('4.0')
134
- module ActiveRecord
132
+ # rubocop:disable Lint/ConstantDefinitionInBlock
133
+ module ActiveRecord
134
+ class Base
135
+ unless respond_to?(:execute_sql)
136
+ class << self
137
+ def execute_sql(sql, *param_array)
138
+ param_array = param_array.first if param_array.length == 1 && param_array.first.is_a?(Array)
139
+ connection.execute(send(:sanitize_sql_array, [sql] + param_array))
140
+ end
141
+ end
142
+ end
143
+ end
144
+
145
+ # Rails < 4.0 cannot do #find_by, #find_or_create_by, or do #pluck on multiple columns, so here are the patches:
146
+ if version < ::Gem::Version.new('4.0')
135
147
  # Normally find_by is in FinderMethods, which older AR doesn't have
136
148
  module Calculations
137
149
  def find_by(*args)
@@ -280,6 +292,7 @@ ActiveSupport.on_load(:active_record) do
280
292
  end
281
293
  end
282
294
  end
295
+ # rubocop:enable Lint/ConstantDefinitionInBlock
283
296
 
284
297
  # Rails < 4.2 is not innately compatible with Ruby 2.4 and later, and comes up with:
285
298
  # "TypeError: Cannot visit Integer" unless we patch like this:
@@ -322,6 +335,7 @@ ActiveSupport.on_load(:active_record) do
322
335
  # First part of arel_table_type stuff:
323
336
  # ------------------------------------
324
337
  # (more found below)
338
+ # was: ActiveRecord.version >= ::Gem::Version.new('3.2') &&
325
339
  if ActiveRecord.version < ::Gem::Version.new('5.0')
326
340
  # Used by Util#_arel_table_type
327
341
  module ActiveRecord
@@ -336,6 +350,17 @@ ActiveSupport.on_load(:active_record) do
336
350
  end
337
351
 
338
352
  include ::DutyFree::Extensions
353
+
354
+ unless ::DutyFree::Extensions::IS_AMOEBA
355
+ # Add amoeba-compatible support
356
+ module ActiveRecord
357
+ class Base
358
+ def self.amoeba(*args)
359
+ puts "Amoeba called from #{name} with #{args.inspect}"
360
+ end
361
+ end
362
+ end
363
+ end
339
364
  end
340
365
 
341
366
  # Do this earlier because stuff here gets mixed into JoinDependency::JoinAssociation and AssociationScope
@@ -0,0 +1,349 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators'
4
+ require 'rails/generators/active_record'
5
+ require 'fancy_gets'
6
+
7
+ module DutyFree
8
+ # Auto-generates an IMPORT_TEMPLATE entry for a model
9
+ class ModelGenerator < ::Rails::Generators::Base
10
+ include FancyGets
11
+ # include ::Rails::Generators::Migration
12
+
13
+ # # source_root File.expand_path('templates', __dir__)
14
+ # class_option(
15
+ # :with_changes,
16
+ # type: :boolean,
17
+ # default: false,
18
+ # desc: 'Add IMPORT_TEMPLATE to model'
19
+ # )
20
+
21
+ desc 'Adds an appropriate IMPORT_TEMPLATE entry into a model of your choosing so that' \
22
+ ' DutyFree can perform exports, and with the Pro version, the same template also' \
23
+ ' does imports.'
24
+
25
+ def df_model_template
26
+ # %%% If Apartment is active, ask which schema they want
27
+
28
+ # Load all models
29
+ Rails.configuration.eager_load_namespaces.select { |ns| ns < Rails::Application }.each(&:eager_load!)
30
+
31
+ # Generate a list of viable models that can be chosen
32
+ longest_length = 0
33
+ model_info = Hash.new { |h, k| h[k] = {} }
34
+ tableless = Hash.new { |h, k| h[k] = [] }
35
+ models = ActiveRecord::Base.descendants.reject do |m|
36
+ trouble = if m.abstract_class?
37
+ true
38
+ elsif !m.table_exists?
39
+ tableless[m.table_name] << m.name
40
+ ' (No Table)'
41
+ else
42
+ this_f_keys = (model_info[m][:f_keys] = m.reflect_on_all_associations.select { |a| a.macro == :belongs_to }) || []
43
+ column_names = (model_info[m][:column_names] = m.columns.map(&:name) - [m.primary_key, 'created_at', 'updated_at', 'deleted_at'] - this_f_keys.map(&:foreign_key))
44
+ if column_names.empty? && this_f_keys && !this_f_keys.empty?
45
+ fk_message = ", although #{this_f_keys.length} foreign keys"
46
+ " (No columns#{fk_message})"
47
+ end
48
+ end
49
+ # puts "#{m.name}#{trouble}" if trouble&.is_a?(String)
50
+ trouble
51
+ end
52
+ models.sort! do |a, b| # Sort first to separate namespaced stuff from the rest, then alphabetically
53
+ is_a_namespaced = a.name.include?('::')
54
+ is_b_namespaced = b.name.include?('::')
55
+ if is_a_namespaced && !is_b_namespaced
56
+ 1
57
+ elsif !is_a_namespaced && is_b_namespaced
58
+ -1
59
+ else
60
+ a.name <=> b.name
61
+ end
62
+ end
63
+ models.each do |m| # Find longest name in the list for future use to show lists on the right side of the screen
64
+ # Strangely this can't be inlined since it assigns to "len"
65
+ if longest_length < (len = m.name.length)
66
+ longest_length = len
67
+ end
68
+ end
69
+
70
+ model_name = ARGV[0]&.camelize
71
+ unless (starting = models.find { |m| m.name == model_name })
72
+ puts "#{"Couldn't find #{model_name}. " if model_name}Pick a model to start from:"
73
+ starting = gets_list(
74
+ list: models,
75
+ on_select: proc do |item|
76
+ selected = item[:selected] || item[:focused]
77
+ this_model_info = model_info[selected]
78
+ selected.name + " (#{(this_model_info[:column_names] + this_model_info[:f_keys].map(&:name).map(&:upcase)).join(', ')})"
79
+ end
80
+ )
81
+ end
82
+ puts "\nThinking..."
83
+
84
+ # %%% Find out how many hops at most we can go from this model
85
+ max_hm_nav = starting.suggest_template(-1, true, false)
86
+ max_bt_nav = starting.suggest_template(-1, false, false)
87
+ hops_with_hm, num_hm_hops_tables = calc_num_hops([[starting, max_hm_nav[:all]]], models)
88
+ hops, num_hops_tables = calc_num_hops([[starting, max_bt_nav[:all]]], models)
89
+ # print "\b" * 11
90
+ unless hops_with_hm.length == hops.length
91
+ starting_name = starting.name
92
+ unless (is_hm = ARGV[1]&.downcase)
93
+ puts "Navigate from #{starting_name} using:\n#{'=' * (21 + starting_name.length)}"
94
+ is_hm = gets_list(
95
+ ["Only belongs_to (max of #{hops.length} hops and #{num_hops_tables} tables)",
96
+ "has_many as well as belongs_to (max of #{hops_with_hm.length} hops and #{num_hm_hops_tables} tables)"]
97
+ )
98
+ end
99
+ is_hm = is_hm.start_with?('has_many') || is_hm[0] == 'y'
100
+ hops = hops_with_hm if is_hm
101
+ end
102
+
103
+ unless (num_hops = ARGV[2]&.to_i)
104
+ puts "\nNow, how many hops total would you like to navigate?"
105
+ index = 0
106
+ cumulative = 0
107
+ hops_list = ['0'] + hops.map { |h| "#{index += 1} (#{cumulative += h.length} linkages)" }
108
+ num_hops = gets_list(
109
+ list: hops_list,
110
+ on_select: proc do |value|
111
+ associations = Hash.new { |h, k| h[k] = 0 }
112
+ index = (value[:selected] || value[:focused]).split(' ').first.to_i - 1
113
+ layer = hops[index] if index >= 0
114
+ layer ||= []
115
+ layer.each { |i| associations[i.last] += 1 }
116
+ associations.each { |k, v| associations.delete(k) if v == 1 }
117
+ layer.map do |l|
118
+ associations.keys.include?(l.last) ? "#{l.first.name.demodulize} #{l.last}" : l.last
119
+ end.join(', ')
120
+ # y = model_info[data[:focused].name]
121
+ # data[:focused].name + " (#{(y[:column_names] + y[:f_keys].map(&:name).map(&:upcase)).join(', ')})"
122
+ # layer.inspect
123
+ end
124
+ ).split(' ').first.to_i
125
+ end
126
+
127
+ print "Navigating from #{starting_name}" if model_name
128
+ puts "\nOkay, #{num_hops} hops#{', including has_many,' if is_hm} it is!"
129
+ # Grab the console output from this:
130
+ original_stdout = $stdout
131
+ $stdout = StringIO.new
132
+ starting.suggest_template(num_hops, is_hm)
133
+ output = $stdout
134
+ $stdout = original_stdout
135
+ filename = nil
136
+ output.rewind
137
+ lines = output.each_line.each_with_object([]) do |line, s|
138
+ if line == "\n"
139
+ # Do nothing
140
+ elsif filename
141
+ s << line
142
+ elsif line.start_with?('# Place the following into ')
143
+ filename = line[27..-1]&.split(':')&.first
144
+ end
145
+ s
146
+ end
147
+
148
+ model_file = File.open(filename, 'r+')
149
+ insert_at = nil
150
+ starting_name = starting.name.demodulize
151
+ loop do
152
+ break if model_file.eof?
153
+
154
+ line_parts = model_file.readline.strip.gsub(/ +/, ' ').split(' ')
155
+ if line_parts.first == 'class' && line_parts[1] && (line_parts[1] == starting_name || line_parts[1].end_with?("::#{starting_name}"))
156
+ insert_at = model_file.pos
157
+ break
158
+ end
159
+ end
160
+ line = nil
161
+ import_template_blocks = []
162
+ import_template_block = nil
163
+ indentation = nil
164
+ # See if there's already any IMPORT_TEMPLATE entries in the model file.
165
+ # If there already is just one, we will comment it out if needs be before adding a fresh one.
166
+ loop do
167
+ break if model_file.eof?
168
+
169
+ line_parts = (line = model_file.readline).strip.split(/[\s=]+/)
170
+ indentation ||= line[0...(/\S/ =~ line)]
171
+ case line_parts[-2..-1]
172
+ when ['IMPORT_TEMPLATE', '{']
173
+ import_template_blocks << import_template_block if import_template_block
174
+ import_template_block = [model_file.pos - line.length, nil, line.strip[0] == '#', []]
175
+ when ['#', '------------------------------------------']
176
+ import_template_block[1] = model_file.pos if import_template_block # && import_template_block[1].nil?
177
+ end
178
+ next unless import_template_block
179
+
180
+ # Collect all the lines of any existing block
181
+ import_template_block[3] << line
182
+ # Cap this one if it's done
183
+ if import_template_block[1]
184
+ import_template_blocks << import_template_block
185
+ import_template_block = nil
186
+ end
187
+ end
188
+ import_template_blocks << import_template_block if import_template_block
189
+ comments = nil
190
+ is_add_cr = nil
191
+ if import_template_blocks.length > 1
192
+ # %%% maybe in the future: remove any older commented ones
193
+ puts 'Found multiple existing import template blocks. Will not attempt to automatically add yet another.'
194
+ insert_at = nil
195
+ elsif import_template_blocks.length == 1
196
+ # Get set up to add the new block after the existing one
197
+ insert_at = (import_template_block = import_template_blocks.first)[1]
198
+ if insert_at.nil?
199
+ puts "Found what looked like the start of an existing IMPORT_TEMPLATE block, but couldn't determine where it ends. Will not attempt to automatically add anything."
200
+ elsif import_template_block[2] # Already commented
201
+ is_add_cr = true
202
+ else # Needs to be commented
203
+ # Find what kind and how much indentation is present from the first commented line
204
+ indentation = import_template_block[3].first[0...(/\S/ =~ import_template_block[3].first)]
205
+ comments = import_template_block[3].map { |l| "#{l[0...indentation.length]}# #{l[indentation.length..-1]}" }
206
+ end
207
+ # else # Must be no IMPORT_TEMPLATE block yet
208
+ # insert_at = model_file.pos
209
+ end
210
+ if insert_at.nil?
211
+ puts "Please edit #{filename} manually and add this code:\n\n#{lines.join}"
212
+ else
213
+ is_good = ARGV[3]&.downcase&.start_with?('y')
214
+ args = [starting_name,
215
+ is_hm ? 'has_many' : 'no',
216
+ num_hops]
217
+ args << 'yes' if is_good
218
+ args = args.each_with_object(+'') do |v, s|
219
+ s << " #{v}"
220
+ s
221
+ end
222
+ lines.unshift("# Added #{DateTime.now.strftime('%b %d, %Y %I:%M%P')} by running `bin/rails g duty_free:model#{args}`\n")
223
+ # add a new one afterwards
224
+ print is_good ? 'Will' : 'OK to'
225
+ print "#{" comment #{comments.length} existing lines and" if comments} add #{lines.length} new lines to #{filename}"
226
+ puts is_good ? '.' : '?'
227
+ if is_good || gets_list(%w[Yes No]) == 'Yes'
228
+ # Store rest of file
229
+ model_file.pos = insert_at
230
+ rest_of_file = model_file.read
231
+ if comments
232
+ model_file.pos = import_template_block[0]
233
+ model_file.write("#{comments.join}\n")
234
+ puts "Commented #{comments.length} existing lines"
235
+ else
236
+ model_file.pos = insert_at
237
+ end
238
+ model_file.write("\n") if is_add_cr
239
+ model_file.write(lines.map { |l| "#{indentation}#{l}" }.join)
240
+ model_file.write(rest_of_file)
241
+ end
242
+ end
243
+ model_file.close
244
+ end
245
+
246
+ private
247
+
248
+ # def calc_num_hops(all, num = 0)
249
+ # max_num = num
250
+ # all.each do |item|
251
+ # if item.is_a?(Hash)
252
+ # item.each do |k, v|
253
+ # # puts "#{k} - #{num}"
254
+ # this_num = calc_num_hops(item[k], num + 1)
255
+ # max_num = this_num if this_num > max_num
256
+ # end
257
+ # end
258
+ # end
259
+ # max_num
260
+ # end
261
+
262
+ # Breadth first approach
263
+ def calc_num_hops(this_layer, models = nil)
264
+ seen_it = {}
265
+ layers = []
266
+ loop do
267
+ this_keys = []
268
+ next_layer = []
269
+ this_layer.each do |grouping|
270
+ klass = grouping.first
271
+ # binding.pry #unless klass.is_a?(Class)
272
+ grouping.last.each do |item|
273
+ next unless item.is_a?(Hash) && !seen_it.include?([klass, (k, v = item.first).first])
274
+
275
+ seen_it[[klass, k]] = nil
276
+ this_keys << [klass, k]
277
+ this_klass = klass.reflect_on_association(k)&.klass
278
+ if this_klass.nil? # Perhaps it's polymorphic
279
+ polymorphics = klass.reflect_on_all_associations.each_with_object([]) do |r, s|
280
+ prefix = "#{r.name}_"
281
+ if r.polymorphic? && k.to_s.start_with?(prefix)
282
+ suffix = k.to_s[prefix.length..-1]
283
+ possible_klass = models.find { |m| m.name.underscore == suffix }
284
+ s << [suffix, possible_klass] if possible_klass
285
+ end
286
+ s
287
+ end
288
+ # binding.pry if polymorphics.length != 1
289
+ this_klass = polymorphics.first&.last
290
+ end
291
+ next_layer << [this_klass, v.select { |ip| ip.is_a?(Hash) }] if this_klass
292
+ end
293
+ end
294
+ layers << this_keys unless this_keys.empty?
295
+ break if next_layer.empty?
296
+
297
+ this_layer = next_layer
298
+ end
299
+ # puts "#{k} - #{num}"
300
+ [layers, seen_it.keys.map(&:first).uniq.length]
301
+ end
302
+
303
+ # # MySQL 5.6 utf8mb4 limit is 191 chars for keys used in indexes.
304
+ # def item_type_options
305
+ # opt = { null: false }
306
+ # opt[:limit] = 191 if mysql?
307
+ # ", #{opt}"
308
+ # end
309
+
310
+ # def migration_version
311
+ # return unless (major = ActiveRecord::VERSION::MAJOR) >= 5
312
+
313
+ # "[#{major}.#{ActiveRecord::VERSION::MINOR}]"
314
+ # end
315
+
316
+ # # Class names of MySQL adapters.
317
+ # # - `MysqlAdapter` - Used by gems: `mysql`, `activerecord-jdbcmysql-adapter`.
318
+ # # - `Mysql2Adapter` - Used by `mysql2` gem.
319
+ # def mysql?
320
+ # [
321
+ # 'ActiveRecord::ConnectionAdapters::MysqlAdapter',
322
+ # 'ActiveRecord::ConnectionAdapters::Mysql2Adapter'
323
+ # ].freeze.include?(ActiveRecord::Base.connection.class.name)
324
+ # end
325
+
326
+ # # Even modern versions of MySQL still use `latin1` as the default character
327
+ # # encoding. Many users are not aware of this, and run into trouble when they
328
+ # # try to use DutyFree in apps that otherwise tend to use UTF-8. Postgres, by
329
+ # # comparison, uses UTF-8 except in the unusual case where the OS is configured
330
+ # # with a custom locale.
331
+ # #
332
+ # # - https://dev.mysql.com/doc/refman/5.7/en/charset-applications.html
333
+ # # - http://www.postgresql.org/docs/9.4/static/multibyte.html
334
+ # #
335
+ # # Furthermore, MySQL's original implementation of UTF-8 was flawed, and had
336
+ # # to be fixed later by introducing a new charset, `utf8mb4`.
337
+ # #
338
+ # # - https://mathiasbynens.be/notes/mysql-utf8mb4
339
+ # # - https://dev.mysql.com/doc/refman/5.5/en/charset-unicode-utf8mb4.html
340
+ # #
341
+ # def versions_table_options
342
+ # if mysql?
343
+ # ', { options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci" }'
344
+ # else
345
+ # ''
346
+ # end
347
+ # end
348
+ end
349
+ end
@@ -1,5 +1,5 @@
1
- # This migration creates the `versions` table, the only schema PT requires.
2
- # All other migrations PT provides are optional.
1
+ # This migration creates the `versions` table, the only schema DF requires.
2
+ # All other migrations DF provides are optional.
3
3
  class CreateVersions < ActiveRecord::Migration<%= migration_version %>
4
4
 
5
5
  # The largest text column available in all supported RDBMS is
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.8
4
+ version: 1.0.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lorin Thwaits
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-01-06 00:00:00.000000000 Z
11
+ date: 2024-03-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -17,9 +17,6 @@ dependencies:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: '3.0'
20
- - - "<"
21
- - !ruby/object:Gem::Version
22
- version: '6.2'
23
20
  type: :runtime
24
21
  prerelease: false
25
22
  version_requirements: !ruby/object:Gem::Requirement
@@ -27,9 +24,6 @@ dependencies:
27
24
  - - ">="
28
25
  - !ruby/object:Gem::Version
29
26
  version: '3.0'
30
- - - "<"
31
- - !ruby/object:Gem::Version
32
- version: '6.2'
33
27
  - !ruby/object:Gem::Dependency
34
28
  name: appraisal
35
29
  requirement: !ruby/object:Gem::Requirement
@@ -214,6 +208,7 @@ files:
214
208
  - lib/duty_free/version_number.rb
215
209
  - lib/generators/duty_free/USAGE
216
210
  - lib/generators/duty_free/install_generator.rb
211
+ - lib/generators/duty_free/model_generator.rb
217
212
  - lib/generators/duty_free/templates/add_object_changes_to_versions.rb.erb
218
213
  - lib/generators/duty_free/templates/create_versions.rb.erb
219
214
  homepage: https://github.com/lorint/duty_free
@@ -235,7 +230,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
235
230
  - !ruby/object:Gem::Version
236
231
  version: 1.3.6
237
232
  requirements: []
238
- rubygems_version: 3.2.3
233
+ rubygems_version: 3.1.6
239
234
  signing_key:
240
235
  specification_version: 4
241
236
  summary: Import and Export Data