duty_free 1.0.7 → 1.0.9
Sign up to get free protection for your applications and to get access to all the features.
- 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
|