duty_free 1.0.7 → 1.0.9
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/column.rb +2 -2
- data/lib/duty_free/extensions.rb +603 -446
- data/lib/duty_free/suggest_template.rb +381 -170
- data/lib/duty_free/util.rb +129 -11
- data/lib/duty_free/version_number.rb +1 -1
- data/lib/duty_free.rb +227 -52
- data/lib/generators/duty_free/model_generator.rb +349 -0
- data/lib/generators/duty_free/templates/create_versions.rb.erb +2 -2
- metadata +10 -29
data/lib/duty_free/util.rb
CHANGED
@@ -25,17 +25,18 @@ module DutyFree
|
|
25
25
|
def self._recurse_arel(piece, prefix = '')
|
26
26
|
names = []
|
27
27
|
# Our JOINs mashup of nested arrays and hashes
|
28
|
-
|
28
|
+
case piece
|
29
|
+
when Array
|
29
30
|
names += piece.inject([]) { |s, v| s + _recurse_arel(v, prefix) }
|
30
|
-
|
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
|
-
|
39
|
+
when Arel::Nodes::Join # INNER or OUTER JOIN
|
39
40
|
# rubocop:disable Style/IdenticalConditionalBranches
|
40
41
|
if piece.right.is_a?(Arel::Table) # Came in from AR < 3.2?
|
41
42
|
# Arel 2.x and older is a little curious because these JOINs work "back to front".
|
@@ -45,28 +46,38 @@ module DutyFree
|
|
45
46
|
# The right side here at the top is the very last table, and anywhere else down the tree it is
|
46
47
|
# the later "JOIN" table of this pair. (The table that comes after all the rest of the JOINs
|
47
48
|
# from the left side.)
|
48
|
-
names << (piece.right.table_alias || piece.right.name)
|
49
|
+
names << [_arel_table_type(piece.right), (piece.right.table_alias || piece.right.name)]
|
49
50
|
else # "Normal" setup, fed from a JoinSource which has an array of JOINs
|
50
51
|
# The left side is the "JOIN" table
|
51
52
|
names += _recurse_arel(piece.left)
|
52
53
|
# (The right side of these is the "ON" clause)
|
53
54
|
end
|
54
55
|
# rubocop:enable Style/IdenticalConditionalBranches
|
55
|
-
|
56
|
-
names << (piece.table_alias || piece.name)
|
57
|
-
|
56
|
+
when Arel::Table # Table
|
57
|
+
names << [_arel_table_type(piece), (piece.table_alias || piece.name)]
|
58
|
+
when Arel::Nodes::TableAlias # Alias
|
58
59
|
# Can get the real table name from: self._recurse_arel(piece.left)
|
59
|
-
names << piece.right.to_s # This is simply a string; the alias name itself
|
60
|
-
|
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!
|
61
62
|
# The left side is the "FROM" table
|
62
63
|
# names += _recurse_arel(piece.left)
|
63
|
-
names << (piece.left.table_alias || piece.left.name)
|
64
|
+
names << [_arel_table_type(piece.left), (piece.left.table_alias || piece.left.name)]
|
64
65
|
# The right side is an array of all JOINs
|
65
66
|
names += piece.right.inject([]) { |s, v| s + _recurse_arel(v) }
|
66
67
|
end
|
67
68
|
names
|
68
69
|
end
|
69
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
|
+
|
70
81
|
def self._prefix_join(prefixes, separator = nil)
|
71
82
|
prefixes.reject(&:blank?).join(separator || '.')
|
72
83
|
end
|
@@ -83,5 +94,112 @@ module DutyFree
|
|
83
94
|
end
|
84
95
|
name
|
85
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
|
86
204
|
end
|
87
205
|
end
|
data/lib/duty_free.rb
CHANGED
@@ -21,8 +21,8 @@ if ActiveRecord.version < ::Gem::Version.new('5.0') &&
|
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
24
|
-
# Allow
|
25
|
-
# when ActiveSupport tries to smarten up Numeric by messing with Fixnum and Bignum at the end of:
|
24
|
+
# Allow ActiveRecord 4.0 and 4.1 to work with newer Ruby (>= 2.4) by avoiding a "stack level too deep"
|
25
|
+
# error when ActiveSupport tries to smarten up Numeric by messing with Fixnum and Bignum at the end of:
|
26
26
|
# activesupport-4.0.13/lib/active_support/core_ext/numeric/conversions.rb
|
27
27
|
if ActiveRecord.version < ::Gem::Version.new('4.2') &&
|
28
28
|
ActiveRecord.version > ::Gem::Version.new('3.2') &&
|
@@ -33,7 +33,7 @@ if ActiveRecord.version < ::Gem::Version.new('4.2') &&
|
|
33
33
|
Numeric.const_set('Bignum', OurBignum)
|
34
34
|
end
|
35
35
|
|
36
|
-
# Allow
|
36
|
+
# Allow ActiveRecord < 3.2 to run with newer versions of Psych gem
|
37
37
|
if BigDecimal.respond_to?(:yaml_tag) && !BigDecimal.respond_to?(:yaml_as)
|
38
38
|
class BigDecimal
|
39
39
|
class <<self
|
@@ -42,6 +42,28 @@ if BigDecimal.respond_to?(:yaml_tag) && !BigDecimal.respond_to?(:yaml_as)
|
|
42
42
|
end
|
43
43
|
end
|
44
44
|
|
45
|
+
require 'duty_free/util'
|
46
|
+
|
47
|
+
# Allow ActiveRecord < 3.2 to work with Ruby 2.7 and later
|
48
|
+
if ActiveRecord.version < ::Gem::Version.new('3.2') &&
|
49
|
+
::Gem::Version.new(RUBY_VERSION) >= ::Gem::Version.new('2.7')
|
50
|
+
# Remove circular reference for "now"
|
51
|
+
::DutyFree::Util._patch_require(
|
52
|
+
'active_support/values/time_zone.rb', '/activesupport',
|
53
|
+
' def parse(str, now=now)',
|
54
|
+
' def parse(str, now=now())'
|
55
|
+
)
|
56
|
+
# Remove circular reference for "reflection" for ActiveRecord 3.1
|
57
|
+
if ActiveRecord.version >= ::Gem::Version.new('3.1')
|
58
|
+
::DutyFree::Util._patch_require(
|
59
|
+
'active_record/associations/has_many_association.rb', '/activerecord',
|
60
|
+
'reflection = reflection)',
|
61
|
+
'reflection = reflection())',
|
62
|
+
:HasManyAssociation # Make sure the path for this guy is available to be autoloaded
|
63
|
+
)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
45
67
|
require 'active_record'
|
46
68
|
|
47
69
|
require 'duty_free/config'
|
@@ -107,14 +129,31 @@ end
|
|
107
129
|
# Major compatibility fixes for ActiveRecord < 4.2
|
108
130
|
# ================================================
|
109
131
|
ActiveSupport.on_load(:active_record) do
|
110
|
-
#
|
111
|
-
|
112
|
-
|
113
|
-
|
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')
|
147
|
+
# Normally find_by is in FinderMethods, which older AR doesn't have
|
148
|
+
module Calculations
|
114
149
|
def find_by(*args)
|
115
150
|
where(*args).limit(1).to_a.first
|
116
151
|
end
|
117
152
|
|
153
|
+
def find_or_create_by(attributes, &block)
|
154
|
+
find_by(attributes) || create(attributes, &block)
|
155
|
+
end
|
156
|
+
|
118
157
|
def pluck(*column_names)
|
119
158
|
column_names.map! do |column_name|
|
120
159
|
if column_name.is_a?(Symbol) && self.column_names.include?(column_name.to_s)
|
@@ -174,7 +213,7 @@ ActiveSupport.on_load(:active_record) do
|
|
174
213
|
unless Base.is_a?(Calculations)
|
175
214
|
class Base
|
176
215
|
class << self
|
177
|
-
delegate :pluck, :find_by, to: :scoped
|
216
|
+
delegate :pluck, :find_by, :find_or_create_by, to: :scoped
|
178
217
|
end
|
179
218
|
end
|
180
219
|
end
|
@@ -205,72 +244,208 @@ ActiveSupport.on_load(:active_record) do
|
|
205
244
|
end
|
206
245
|
end
|
207
246
|
end
|
208
|
-
end
|
209
|
-
end
|
210
247
|
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
248
|
+
# ActiveRecord 3.1 and 3.2 didn't try to bring in &block for the .extending() convenience thing
|
249
|
+
# that smartens up scopes, and Ruby 2.7 complained loudly about just doing the magical "Proc.new"
|
250
|
+
# that historically would just capture the incoming block.
|
251
|
+
module QueryMethods
|
252
|
+
unless instance_method(:extending).parameters.include?([:block, :block])
|
253
|
+
# These first two lines used to be:
|
254
|
+
# def extending(*modules)
|
255
|
+
# modules << Module.new(&Proc.new) if block_given?
|
256
|
+
|
257
|
+
def extending(*modules, &block)
|
258
|
+
modules << Module.new(&block) if block_given?
|
259
|
+
|
260
|
+
return self if modules.empty?
|
220
261
|
|
221
|
-
|
222
|
-
|
262
|
+
relation = clone
|
263
|
+
relation.send(:apply_modules, modules.flatten)
|
264
|
+
relation
|
223
265
|
end
|
266
|
+
end
|
267
|
+
end
|
224
268
|
|
225
|
-
|
226
|
-
|
269
|
+
# Same kind of thing for ActiveRecord::Scoping::Default#default_scope
|
270
|
+
module Scoping
|
271
|
+
module Default
|
272
|
+
module ClassMethods
|
273
|
+
if instance_methods.include?(:default_scope) &&
|
274
|
+
!instance_method(:default_scope).parameters.include?([:block, :block])
|
275
|
+
# Fix for AR 3.2-5.1
|
276
|
+
def default_scope(scope = nil, &block)
|
277
|
+
scope = block if block_given?
|
278
|
+
|
279
|
+
if scope.is_a?(Relation) || !scope.respond_to?(:call)
|
280
|
+
raise ArgumentError,
|
281
|
+
'Support for calling #default_scope without a block is removed. For example instead ' \
|
282
|
+
"of `default_scope where(color: 'red')`, please use " \
|
283
|
+
"`default_scope { where(color: 'red') }`. (Alternatively you can just redefine " \
|
284
|
+
'self.default_scope.)'
|
285
|
+
end
|
227
286
|
|
228
|
-
|
229
|
-
unless private_instance_methods.include?(:literal)
|
230
|
-
def literal(obj)
|
231
|
-
obj
|
287
|
+
self.default_scopes += [scope]
|
232
288
|
end
|
233
289
|
end
|
234
|
-
alias visit_Integer literal
|
235
290
|
end
|
236
291
|
end
|
237
292
|
end
|
238
293
|
end
|
239
294
|
end
|
295
|
+
# rubocop:enable Lint/ConstantDefinitionInBlock
|
240
296
|
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
297
|
+
# Rails < 4.2 is not innately compatible with Ruby 2.4 and later, and comes up with:
|
298
|
+
# "TypeError: Cannot visit Integer" unless we patch like this:
|
299
|
+
if ::Gem::Version.new(RUBY_VERSION) >= ::Gem::Version.new('2.4') &&
|
300
|
+
Arel::Visitors.const_defined?('DepthFirst') &&
|
301
|
+
!Arel::Visitors::DepthFirst.private_instance_methods.include?(:visit_Integer)
|
302
|
+
module Arel
|
303
|
+
module Visitors
|
304
|
+
class DepthFirst < Visitor
|
305
|
+
alias visit_Integer terminal
|
306
|
+
end
|
307
|
+
|
308
|
+
class Dot < Visitor
|
309
|
+
alias visit_Integer visit_String
|
310
|
+
end
|
311
|
+
|
312
|
+
class ToSql < Visitor
|
313
|
+
private
|
314
|
+
|
315
|
+
# ActiveRecord before v3.2 uses Arel < 3.x, which does not have Arel#literal.
|
316
|
+
unless private_instance_methods.include?(:literal)
|
317
|
+
def literal(obj)
|
318
|
+
obj
|
319
|
+
end
|
320
|
+
end
|
321
|
+
alias visit_Integer literal
|
322
|
+
end
|
323
|
+
end
|
324
|
+
end
|
246
325
|
end
|
247
326
|
|
248
327
|
unless DateTime.instance_methods.include?(:nsec)
|
249
328
|
class DateTime < Date
|
250
329
|
def nsec
|
251
|
-
(sec_fraction *
|
330
|
+
(sec_fraction * 1_000_000_000).to_i
|
331
|
+
end
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
# First part of arel_table_type stuff:
|
336
|
+
# ------------------------------------
|
337
|
+
# (more found below)
|
338
|
+
# was: ActiveRecord.version >= ::Gem::Version.new('3.2') &&
|
339
|
+
if ActiveRecord.version < ::Gem::Version.new('5.0')
|
340
|
+
# Used by Util#_arel_table_type
|
341
|
+
module ActiveRecord
|
342
|
+
class Base
|
343
|
+
def self.arel_table
|
344
|
+
@arel_table ||= Arel::Table.new(table_name, arel_engine).tap do |x|
|
345
|
+
x.instance_variable_set(:@_arel_table_type, self)
|
346
|
+
end
|
347
|
+
end
|
252
348
|
end
|
253
349
|
end
|
254
350
|
end
|
255
351
|
|
256
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
|
257
364
|
end
|
258
365
|
|
259
|
-
#
|
260
|
-
|
261
|
-
#
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
#
|
268
|
-
#
|
269
|
-
|
270
|
-
#
|
271
|
-
|
272
|
-
#
|
273
|
-
#
|
274
|
-
|
275
|
-
|
276
|
-
#
|
366
|
+
# Do this earlier because stuff here gets mixed into JoinDependency::JoinAssociation and AssociationScope
|
367
|
+
if ActiveRecord.version < ::Gem::Version.new('5.0') && Object.const_defined?('PG::Connection')
|
368
|
+
# Avoid pg gem deprecation warning: "You should use PG::Connection, PG::Result, and PG::Error instead"
|
369
|
+
PGconn = PG::Connection
|
370
|
+
PGresult = PG::Result
|
371
|
+
PGError = PG::Error
|
372
|
+
end
|
373
|
+
|
374
|
+
# More arel_table_type stuff:
|
375
|
+
# ---------------------------
|
376
|
+
if ActiveRecord.version < ::Gem::Version.new('5.2')
|
377
|
+
# Specifically for AR 3.1 and 3.2 to avoid: "undefined method `delegate' for ActiveRecord::Reflection::ThroughReflection:Class"
|
378
|
+
require 'active_support/core_ext/module/delegation' if ActiveRecord.version < ::Gem::Version.new('4.0')
|
379
|
+
# Used by Util#_arel_table_type
|
380
|
+
# rubocop:disable Style/CommentedKeyword
|
381
|
+
module ActiveRecord
|
382
|
+
module Reflection
|
383
|
+
# AR < 4.0 doesn't know about join_table and derive_join_table
|
384
|
+
unless AssociationReflection.instance_methods.include?(:join_table)
|
385
|
+
class AssociationReflection < MacroReflection
|
386
|
+
def join_table
|
387
|
+
@join_table ||= options[:join_table] || derive_join_table
|
388
|
+
end
|
389
|
+
|
390
|
+
private
|
391
|
+
|
392
|
+
def derive_join_table
|
393
|
+
[active_record.table_name, klass.table_name].sort.join("\0").gsub(/^(.*[._])(.+)\0\1(.+)/, '\1\2_\3').gsub("\0", '_')
|
394
|
+
end
|
395
|
+
end
|
396
|
+
end
|
397
|
+
end
|
398
|
+
|
399
|
+
module Associations
|
400
|
+
# Specific to AR 4.2 - 5.1:
|
401
|
+
if Associations.const_defined?('JoinDependency') && JoinDependency.private_instance_methods.include?(:table_aliases_for)
|
402
|
+
class JoinDependency
|
403
|
+
private
|
404
|
+
|
405
|
+
if ActiveRecord.version < ::Gem::Version.new('5.1') # 4.2 or 5.0
|
406
|
+
def table_aliases_for(parent, node)
|
407
|
+
node.reflection.chain.map do |reflection|
|
408
|
+
alias_tracker.aliased_table_for(
|
409
|
+
reflection.table_name,
|
410
|
+
table_alias_for(reflection, parent, reflection != node.reflection)
|
411
|
+
).tap do |x|
|
412
|
+
# %%% Specific only to Rails 4.2 (and maybe 4.1?)
|
413
|
+
x = x.left if x.is_a?(Arel::Nodes::TableAlias)
|
414
|
+
y = reflection.chain.find { |c| c.table_name == x.name }
|
415
|
+
x.instance_variable_set(:@_arel_table_type, y.klass)
|
416
|
+
end
|
417
|
+
end
|
418
|
+
end
|
419
|
+
end
|
420
|
+
end
|
421
|
+
elsif Associations.const_defined?('JoinHelper') && JoinHelper.private_instance_methods.include?(:construct_tables)
|
422
|
+
module JoinHelper
|
423
|
+
private
|
424
|
+
|
425
|
+
# AR > 3.0 and < 4.2 (%%% maybe only < 4.1?) uses construct_tables like this:
|
426
|
+
def construct_tables
|
427
|
+
tables = []
|
428
|
+
chain.each do |reflection|
|
429
|
+
tables << alias_tracker.aliased_table_for(
|
430
|
+
table_name_for(reflection),
|
431
|
+
table_alias_for(reflection, reflection != self.reflection)
|
432
|
+
).tap do |x|
|
433
|
+
x = x.left if x.is_a?(Arel::Nodes::TableAlias)
|
434
|
+
x.instance_variable_set(:@_arel_table_type, reflection.chain.find { |c| c.table_name == x.name }.klass)
|
435
|
+
end
|
436
|
+
|
437
|
+
next unless reflection.source_macro == :has_and_belongs_to_many
|
438
|
+
|
439
|
+
tables << alias_tracker.aliased_table_for(
|
440
|
+
(reflection.source_reflection || reflection).join_table,
|
441
|
+
table_alias_for(reflection, true)
|
442
|
+
)
|
443
|
+
end
|
444
|
+
tables
|
445
|
+
end
|
446
|
+
end
|
447
|
+
end
|
448
|
+
end
|
449
|
+
end # module ActiveRecord
|
450
|
+
# rubocop:enable Style/CommentedKeyword
|
451
|
+
end
|