brick 1.0.199 → 1.0.201
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/brick/extensions.rb +196 -648
- data/lib/brick/frameworks/rails/engine.rb +55 -61
- data/lib/brick/frameworks/rails/form_tags.rb +3 -3
- data/lib/brick/reflect_tables.rb +582 -0
- data/lib/brick/route_mapper.rb +20 -4
- data/lib/brick/version_number.rb +1 -1
- data/lib/brick.rb +1 -0
- metadata +4 -3
data/lib/brick/extensions.rb
CHANGED
@@ -334,10 +334,10 @@ module ActiveRecord
|
|
334
334
|
end
|
335
335
|
|
336
336
|
# Providing a relation object allows auto-modules built from table name prefixes to work
|
337
|
-
def self._brick_index(mode = nil, separator = nil, relation = nil)
|
337
|
+
def self._brick_index(mode = nil, separator = nil, relation = nil, not_path = nil)
|
338
338
|
return if abstract_class?
|
339
339
|
|
340
|
-
::Brick._brick_index(table_name, mode, separator, relation)
|
340
|
+
::Brick._brick_index(table_name, mode, separator, relation, not_path)
|
341
341
|
end
|
342
342
|
|
343
343
|
def self.brick_import_template
|
@@ -436,7 +436,11 @@ module ActiveRecord
|
|
436
436
|
order_by = ordering&.each_with_object([]) do |ord_part, s| # %%% If a term is also used as an eqi-condition in the WHERE clause, it can be omitted from ORDER BY
|
437
437
|
case ord_part
|
438
438
|
when String
|
439
|
-
ord_expr =
|
439
|
+
ord_expr = if ord_part.index('(') # Any kind of SQL function at play here?
|
440
|
+
ord_part.gsub('^^^', _br_quoted_name(table_name))
|
441
|
+
else
|
442
|
+
_br_quoted_name(ord_part.gsub('^^^', table_name))
|
443
|
+
end
|
440
444
|
s << Arel.sql(ord_expr)
|
441
445
|
order_by_txt&.<<(ord_expr.index('.') ? "Arel.sql(#{ord_expr.inspect})" : ord_part.inspect)
|
442
446
|
else # Expecting only Symbol
|
@@ -887,12 +891,16 @@ module ActiveRecord
|
|
887
891
|
|
888
892
|
tbl_alias = unique63("b_r_#{hm.name}", previous)
|
889
893
|
on_clause = []
|
890
|
-
hm_selects = if
|
891
|
-
fk_col.
|
892
|
-
|
893
|
-
|
894
|
+
hm_selects = if !pri_key.is_a?(Array) # Probable standard key?
|
895
|
+
if fk_col.is_a?(Array) # Foreign is composite but not Primary? OK, or choose the first part of the foreign key if nothing else
|
896
|
+
fk_col = fk_col.find { |col_name| col_name == pri_key } || # Try to associate with the same-named part of the foreign key ...
|
897
|
+
fk_col.first # ... and if no good match, just choose the first part
|
898
|
+
end
|
894
899
|
on_clause << "#{_br_quoted_name("#{tbl_alias}.#{fk_col}")} = #{_br_quoted_name("#{pri_tbl.table_name}.#{pri_key}")}"
|
895
900
|
[fk_col]
|
901
|
+
else # Composite key
|
902
|
+
fk_col.each_with_index { |fk_col_part, idx| on_clause << "#{tbl_alias}.#{fk_col_part} = #{pri_tbl.table_name}.#{pri_key[idx]}" }
|
903
|
+
fk_col.dup
|
896
904
|
end
|
897
905
|
if poly_type
|
898
906
|
hm_selects << poly_type
|
@@ -1166,6 +1174,34 @@ Might want to add this in your brick.rb:
|
|
1166
1174
|
end
|
1167
1175
|
end
|
1168
1176
|
|
1177
|
+
if ::Brick.enable_routes? && Object.const_defined?('ActionDispatch')
|
1178
|
+
require 'brick/route_mapper'
|
1179
|
+
ActionDispatch::Routing::RouteSet.class_exec do
|
1180
|
+
# In order to defer auto-creation of any routes that already exist, calculate Brick routes only after having loaded all others
|
1181
|
+
prepend ::Brick::RouteSet
|
1182
|
+
end
|
1183
|
+
ActionDispatch::Routing::Mapper.class_exec do
|
1184
|
+
include ::Brick::RouteMapper
|
1185
|
+
end
|
1186
|
+
|
1187
|
+
# Do the root route before the Rails Welcome one would otherwise take precedence
|
1188
|
+
if (route = ::Brick.config.default_route_fallback).present?
|
1189
|
+
action = "#{route}#{'#index' unless route.index('#')}"
|
1190
|
+
if ::Brick.config.path_prefix
|
1191
|
+
::Rails.application.routes.append do
|
1192
|
+
send(:namespace, ::Brick.config.path_prefix) do
|
1193
|
+
send(:root, action)
|
1194
|
+
end
|
1195
|
+
end
|
1196
|
+
elsif ::Rails.application.routes.named_routes.send(:routes)[:root].nil?
|
1197
|
+
::Rails.application.routes.append do
|
1198
|
+
send(:root, action)
|
1199
|
+
end
|
1200
|
+
end
|
1201
|
+
::Brick.established_drf = "/#{::Brick.config.path_prefix}#{action[action.index('#')..-1]}"
|
1202
|
+
end
|
1203
|
+
end
|
1204
|
+
|
1169
1205
|
if Object.const_defined?('ActionView')
|
1170
1206
|
require 'brick/frameworks/rails/form_tags'
|
1171
1207
|
require 'brick/frameworks/rails/form_builder'
|
@@ -1298,19 +1334,23 @@ end
|
|
1298
1334
|
unless (is_tnp_module = (is_brick_prefix && !is_controller && ::Brick.config.table_name_prefixes.values.include?(requested)))
|
1299
1335
|
# ... first look around for an existing module or class.
|
1300
1336
|
desired_classname = (self == Object || !name) ? requested : "#{name}::#{requested}"
|
1301
|
-
if (
|
1337
|
+
if (self.const_defined?(args.first) && (possible = self.const_get(args.first)) &&
|
1302
1338
|
# Reset `possible` if it's a controller request that's not a perfect match
|
1303
1339
|
# Was: (possible = nil) but changed to #local_variable_set in order to suppress the "= should be ==" warning
|
1304
1340
|
(possible&.name == desired_classname || (is_controller && binding.local_variable_set(:possible, nil)))) ||
|
1341
|
+
|
1305
1342
|
# Try to require the respective Ruby file
|
1343
|
+
# ((filename = ActiveSupport::Dependencies.search_for_file(desired_classname.underscore)) &&
|
1344
|
+
# (require_dependency(filename) || true) &&
|
1306
1345
|
((filename = ActiveSupport::Dependencies.search_for_file(desired_classname.underscore) ||
|
1307
1346
|
(self != Object && ActiveSupport::Dependencies.search_for_file((desired_classname = requested).underscore))
|
1308
1347
|
) && (require_dependency(filename) || true) &&
|
1309
|
-
((possible = self.const_get(args.first)) && possible.name == desired_classname)
|
1348
|
+
(!anonymous? && (possible = self.const_get(args.first)) && possible.name == desired_classname)
|
1310
1349
|
) ||
|
1350
|
+
|
1311
1351
|
# If any class has turned up so far (and we're not in the middle of eager loading)
|
1312
1352
|
# then return what we've found.
|
1313
|
-
(
|
1353
|
+
(possible&.module_parent == base_module && !::Brick.is_eager_loading) # Used to also have: && possible != self
|
1314
1354
|
if ((!brick_root && (filename || possible.instance_of?(Class))) ||
|
1315
1355
|
(possible.instance_of?(Module) && possible&.module_parent == self) ||
|
1316
1356
|
(possible.instance_of?(Class) && possible == self)) && # Are we simply searching for ourselves?
|
@@ -1326,13 +1366,14 @@ end
|
|
1326
1366
|
end
|
1327
1367
|
end
|
1328
1368
|
class_name = ::Brick.namify(requested)
|
1369
|
+
is_avo_present = Object.const_defined?('Avo') && ::Avo.respond_to?(:railtie_namespace)
|
1329
1370
|
# CONTROLLER
|
1330
1371
|
result = if ::Brick.enable_controllers? &&
|
1331
1372
|
is_controller && (plural_class_name = class_name[0..-11]).length.positive?
|
1332
1373
|
# Otherwise now it's up to us to fill in the gaps
|
1333
1374
|
controller_class_name = +''
|
1334
1375
|
full_class_name = +''
|
1335
|
-
unless self == Object
|
1376
|
+
unless self == Object || (is_avo_present && self.name == 'Avo')
|
1336
1377
|
controller_class_name << ((split_self_name&.first && split_self_name.join('::')) || self.name)
|
1337
1378
|
full_class_name << "::#{controller_class_name}"
|
1338
1379
|
controller_class_name << '::'
|
@@ -1342,13 +1383,14 @@ end
|
|
1342
1383
|
singular_class_name = ::Brick.namify(plural_class_name, :underscore).singularize.camelize
|
1343
1384
|
full_class_name << "::#{singular_class_name}"
|
1344
1385
|
skip_controller = nil
|
1345
|
-
|
1346
|
-
|
1347
|
-
(
|
1348
|
-
|
1349
|
-
|
1350
|
-
|
1351
|
-
|
1386
|
+
begin
|
1387
|
+
if plural_class_name == 'BrickOpenapi' ||
|
1388
|
+
(
|
1389
|
+
(::Brick.config.add_status || ::Brick.config.add_orphans) &&
|
1390
|
+
plural_class_name == 'BrickGem'
|
1391
|
+
# Was: ) || (model = self.const_get(full_class_name))
|
1392
|
+
) || (model = Object.const_get(full_class_name))
|
1393
|
+
# puts "#{self.name} - #{full_class_name}"
|
1352
1394
|
|
1353
1395
|
# In the very rare case that we've picked up a MODULE which has the same name as what would be the
|
1354
1396
|
# resource's MODEL name, just build out an appropriate auto-model on-the-fly. (RailsDevs code has this in PayCustomer.)
|
@@ -1356,10 +1398,10 @@ end
|
|
1356
1398
|
if model && !model.is_a?(Class)
|
1357
1399
|
model, _code = Object.send(:build_model, relations, model.module_parent, model.module_parent.name, singular_class_name)
|
1358
1400
|
end
|
1359
|
-
rescue NameError # If the const_get for the model has failed...
|
1360
|
-
skip_controller = true
|
1361
|
-
# ... then just fall through and allow it to fail when trying to load the ____Controller class normally.
|
1362
1401
|
end
|
1402
|
+
rescue NameError # If the const_get for the model has failed...
|
1403
|
+
skip_controller = true
|
1404
|
+
# ... then just fall through and allow it to fail when trying to load the ____Controller class normally.
|
1363
1405
|
end
|
1364
1406
|
unless skip_controller
|
1365
1407
|
Object.send(:build_controller, self, class_name, plural_class_name, model, relations)
|
@@ -1391,58 +1433,43 @@ end
|
|
1391
1433
|
base_module.const_set(class_name.to_sym, (built_module = Module.new))
|
1392
1434
|
[built_module, "module #{possible_module}; end\n"]
|
1393
1435
|
|
1394
|
-
# AVO Resource
|
1395
|
-
elsif base_module == Object &&
|
1436
|
+
# AVO 2.x Resource
|
1437
|
+
elsif base_module == Object && is_avo_present && requested.end_with?('Resource') &&
|
1396
1438
|
# Expect that anything called MotorResource or SpinaResource could be from those administrative gems
|
1397
|
-
requested.length > 8 && ['MotorResource', 'SpinaResource'].exclude?(requested)
|
1398
|
-
|
1399
|
-
|
1400
|
-
|
1401
|
-
|
1402
|
-
|
1403
|
-
|
1404
|
-
if (f = f.strip).start_with?('field ')
|
1405
|
-
f = f[6..-1].split(',')
|
1406
|
-
s << [f.first[1..-1].to_sym, [f[1][1..-1].split(': :').map(&:to_sym)].to_h]
|
1407
|
-
end
|
1408
|
-
end || []
|
1409
|
-
built_resource = Class.new(Avo::BaseResource) do |new_resource_class|
|
1410
|
-
self.model_class = model
|
1411
|
-
self.title = :brick_descrip
|
1412
|
-
self.includes = []
|
1413
|
-
if (!model.is_view? && mod_pk = model.primary_key)
|
1414
|
-
field((mod_pk.is_a?(Array) ? mod_pk.first : mod_pk).to_sym, { as: :id })
|
1415
|
-
end
|
1416
|
-
# Create a call such as: field :name, as: :text
|
1417
|
-
fields.each do |f|
|
1418
|
-
# Add proper types if this is a polymorphic belongs_to
|
1419
|
-
if f.last == { as: :belongs_to } &&
|
1420
|
-
(fk = ::Brick.relations[model.table_name][:fks].find { |k, v| v[:assoc_name] == f.first.to_s }) &&
|
1421
|
-
fk.last.fetch(:polymorphic, nil)
|
1422
|
-
poly_types = fk.last.fetch(:inverse_table, nil)&.each_with_object([]) do |poly_table, s|
|
1423
|
-
s << Object.const_get(::Brick.relations[poly_table][:class_name])
|
1424
|
-
end
|
1425
|
-
if poly_types.present?
|
1426
|
-
f.last[:polymorphic_as] = f.first
|
1427
|
-
f.last[:types] = poly_types
|
1428
|
-
end
|
1429
|
-
end
|
1430
|
-
self.send(:field, *f)
|
1431
|
-
end
|
1432
|
-
end
|
1433
|
-
Object.const_set(requested.to_sym, built_resource)
|
1434
|
-
[built_resource, nil]
|
1439
|
+
requested.length > 8 && ['MotorResource', 'SpinaResource'].exclude?(requested) &&
|
1440
|
+
(model = Object.const_get(requested[0..-9])) && model < ActiveRecord::Base
|
1441
|
+
built_resource = Class.new(Avo::BaseResource) do
|
1442
|
+
self.model_class = model
|
1443
|
+
self.title = :brick_descrip
|
1444
|
+
self.includes = []
|
1445
|
+
::Brick::ADD_AVO_FIELDS.call(self, model)
|
1435
1446
|
end
|
1447
|
+
base_module.const_set(requested.to_sym, built_resource)
|
1448
|
+
[built_resource, nil]
|
1449
|
+
|
1450
|
+
# AVO 3.x Resource
|
1451
|
+
elsif is_avo_present && self.name == 'Avo::Resources' &&
|
1452
|
+
(model = begin
|
1453
|
+
(model = Object.const_get(requested)) && model < ActiveRecord::Base
|
1454
|
+
model
|
1455
|
+
rescue
|
1456
|
+
end)
|
1457
|
+
[::Brick.avo_3x_resource(model, requested), nil]
|
1436
1458
|
|
1437
1459
|
# MODEL
|
1438
1460
|
elsif ::Brick.enable_models?
|
1461
|
+
# Avo sometimes tries to find a model class inside of the Avo namespace
|
1462
|
+
if is_avo_present && self.name == 'Avo'
|
1463
|
+
name = (base_module = Object).name
|
1464
|
+
end
|
1465
|
+
name ||= base_module.name
|
1439
1466
|
# Custom inheritable Brick base model?
|
1440
1467
|
class_name = (inheritable_name = class_name)[5..-1] if class_name.start_with?('Brick')
|
1441
1468
|
Object.send(:build_model, relations, base_module, name, class_name, inheritable_name)
|
1442
1469
|
end
|
1443
1470
|
if result
|
1444
1471
|
built_class, code = result
|
1445
|
-
puts "\n#{code}\n"
|
1472
|
+
puts "\n#{code}\n" if code
|
1446
1473
|
built_class
|
1447
1474
|
elsif !schema_name && ::Brick.config.sti_namespace_prefixes&.key?("::#{class_name}")
|
1448
1475
|
# module_prefixes = type_name.split('::')
|
@@ -1452,7 +1479,7 @@ end
|
|
1452
1479
|
base_module._brick_const_missing(*args)
|
1453
1480
|
# elsif base_module != Object
|
1454
1481
|
# module_parent.const_missing(*args)
|
1455
|
-
elsif Rails.respond_to?(:autoloaders) && # After finding nothing else, if Zeitwerk is enabled ...
|
1482
|
+
elsif Object.const_defined?('Rails') && ::Rails.respond_to?(:autoloaders) && # After finding nothing else, if Zeitwerk is enabled ...
|
1456
1483
|
(Rails::Autoloaders.respond_to?(:zeitwerk_enabled?) ? Rails::Autoloaders.zeitwerk_enabled? : true)
|
1457
1484
|
self._brick_const_missing(*args) # ... rely solely on Zeitwerk.
|
1458
1485
|
else # Classic mode
|
@@ -1471,13 +1498,63 @@ end
|
|
1471
1498
|
end
|
1472
1499
|
end
|
1473
1500
|
|
1501
|
+
module Brick
|
1502
|
+
def self.avo_3x_resource(model, requested)
|
1503
|
+
built_resource = Class.new(Avo::BaseResource) do
|
1504
|
+
self.model_class = model
|
1505
|
+
self.title = :brick_descrip
|
1506
|
+
self.includes = []
|
1507
|
+
define_method :fields do # Have to be inside of a fields method
|
1508
|
+
::Brick::ADD_AVO_FIELDS.call(self, model)
|
1509
|
+
end
|
1510
|
+
end
|
1511
|
+
::Avo::BaseResource.const_set(requested.to_sym, built_resource)
|
1512
|
+
built_resource
|
1513
|
+
end
|
1514
|
+
end
|
1515
|
+
|
1516
|
+
::Brick::ADD_AVO_FIELDS = lambda do |obj, model|
|
1517
|
+
require 'generators/avo/resource_generator'
|
1518
|
+
field_generator = Generators::Avo::ResourceGenerator.new([''])
|
1519
|
+
field_generator.instance_variable_set(:@model, model)
|
1520
|
+
flds = field_generator.send(:generate_fields)&.split("\n")
|
1521
|
+
&.each_with_object([]) do |f, s|
|
1522
|
+
if (f = f.strip).start_with?('field ')
|
1523
|
+
f = f[6..-1].split(',')
|
1524
|
+
s << [f.first[1..-1].to_sym, [f[1][1..-1].split(': :').map(&:to_sym)].to_h]
|
1525
|
+
end
|
1526
|
+
end || []
|
1527
|
+
if (!model.is_view? && mod_pk = model.primary_key)
|
1528
|
+
obj.field((mod_pk.is_a?(Array) ? mod_pk.first : mod_pk).to_sym, **{ as: :id })
|
1529
|
+
end
|
1530
|
+
# Create a call such as: field :name, as: :text
|
1531
|
+
flds.each do |f|
|
1532
|
+
# Add proper types if this is a polymorphic belongs_to
|
1533
|
+
if f.last == { as: :belongs_to } &&
|
1534
|
+
(fk = ::Brick.relations[model.table_name][:fks].find { |k, v| v[:assoc_name] == f.first.to_s }) &&
|
1535
|
+
fk.last.fetch(:polymorphic, nil)
|
1536
|
+
poly_types = fk.last.fetch(:inverse_table, nil)&.each_with_object([]) do |poly_table, s|
|
1537
|
+
s << Object.const_get(::Brick.relations[poly_table][:class_name])
|
1538
|
+
end
|
1539
|
+
if poly_types.present?
|
1540
|
+
f.last[:polymorphic_as] = f.first
|
1541
|
+
f.last[:types] = poly_types
|
1542
|
+
end
|
1543
|
+
end
|
1544
|
+
kwargs = f.last.is_a?(Hash) ? f.pop : {}
|
1545
|
+
obj.send(:field, *f, **kwargs)
|
1546
|
+
end
|
1547
|
+
end
|
1548
|
+
|
1474
1549
|
class Object
|
1475
1550
|
class << self
|
1476
1551
|
|
1477
1552
|
private
|
1478
1553
|
|
1479
1554
|
def build_model(relations, base_module, base_name, class_name, inheritable_name = nil)
|
1480
|
-
tnp = ::Brick.config.table_name_prefixes&.find { |p| p.last == base_module.name }
|
1555
|
+
tnp = ::Brick.config.table_name_prefixes&.find { |p| p.last == base_module.name }
|
1556
|
+
# return [base_module, ''] if !base_module.is_a?(Class) && base_name == tnp&.last
|
1557
|
+
|
1481
1558
|
if (base_model = (::Brick.config.sti_namespace_prefixes&.fetch("::#{base_module.name}::#{class_name}", nil) || # Are we part of an auto-STI namespace? ...
|
1482
1559
|
::Brick.config.sti_namespace_prefixes&.fetch("::#{base_module.name}::", nil))&.constantize) ||
|
1483
1560
|
base_module != Object # ... or otherwise already in some namespace?
|
@@ -1499,7 +1576,7 @@ class Object
|
|
1499
1576
|
table_name = if (base_model = ::Brick.sti_models[model_name]&.fetch(:base, nil) || ::Brick.existing_stis[model_name]&.constantize)
|
1500
1577
|
base_model.table_name
|
1501
1578
|
else
|
1502
|
-
"#{tnp}#{ActiveSupport::Inflector.pluralize(singular_table_name)}"
|
1579
|
+
"#{tnp&.first}#{ActiveSupport::Inflector.pluralize(singular_table_name)}"
|
1503
1580
|
end
|
1504
1581
|
if ::Brick.apartment_multitenant &&
|
1505
1582
|
Apartment.excluded_models.include?(table_name.singularize.camelize)
|
@@ -1553,7 +1630,7 @@ class Object
|
|
1553
1630
|
else
|
1554
1631
|
# Class for auto-generated models to inherit from
|
1555
1632
|
base_model = (::Brick.config.models_inherit_from ||= (begin
|
1556
|
-
::ApplicationRecord
|
1633
|
+
Object.const_defined?('ApplicationRecord') ? ::ApplicationRecord : ::ActiveRecord::Base
|
1557
1634
|
rescue StandardError => ex
|
1558
1635
|
::ActiveRecord::Base
|
1559
1636
|
end))
|
@@ -1728,7 +1805,8 @@ class Object
|
|
1728
1805
|
end
|
1729
1806
|
|
1730
1807
|
def build_bt_or_hm(full_name, relations, relation, hmts, assoc, inverse_assoc_name, inverse_table, code)
|
1731
|
-
singular_table_name = inverse_table&.singularize
|
1808
|
+
return unless (singular_table_name = inverse_table&.singularize)
|
1809
|
+
|
1732
1810
|
options = {}
|
1733
1811
|
macro = if assoc[:is_bt]
|
1734
1812
|
# Try to take care of screwy names if this is a belongs_to going to an STI subclass
|
@@ -2558,8 +2636,9 @@ class Object
|
|
2558
2636
|
assoc_parts[0].downcase! if assoc_parts[0] =~ /^[A-Z0-9_]+$/
|
2559
2637
|
assoc_name = assoc_parts.join('.')
|
2560
2638
|
else
|
2561
|
-
|
2562
|
-
|
2639
|
+
last_class_name_part = ::Brick.relations[hm_assoc[:inverse_table]].fetch(:class_name, nil)&.split('::')&.last ||
|
2640
|
+
::Brick.namify(hm_assoc[:inverse_table], :underscore).split('.').last.singularize.camelize
|
2641
|
+
needs_class = assoc_name.singularize.camelize != last_class_name_part
|
2563
2642
|
end
|
2564
2643
|
[assoc_name, needs_class]
|
2565
2644
|
end
|
@@ -2592,586 +2671,49 @@ end
|
|
2592
2671
|
# ==========================================================
|
2593
2672
|
# Get info on all relations during first database connection
|
2594
2673
|
# ==========================================================
|
2674
|
+
if Object.const_defined?('OTR') && OTR.const_defined?('ActiveRecord')
|
2675
|
+
OTR::ActiveRecord.class_exec do
|
2676
|
+
class << self
|
2677
|
+
alias _brick_establish_connection! establish_connection!
|
2678
|
+
def establish_connection!(*args)
|
2679
|
+
conn = _brick_establish_connection!(*args)
|
2680
|
+
return conn unless ::Brick.config.mode == :on
|
2595
2681
|
|
2596
|
-
|
2597
|
-
|
2598
|
-
|
2599
|
-
|
2600
|
-
|
2601
|
-
alias _brick_establish_connection establish_connection
|
2602
|
-
def establish_connection(*args)
|
2603
|
-
conn = _brick_establish_connection(*args)
|
2604
|
-
return conn unless ::Brick.config.mode == :on
|
2605
|
-
|
2606
|
-
begin
|
2607
|
-
# Overwrite SQLite's #begin_db_transaction so it opens in IMMEDIATE mode instead of
|
2608
|
-
# the default DEFERRED mode.
|
2609
|
-
# https://discuss.rubyonrails.org/t/failed-write-transaction-upgrades-in-sqlite3/81480/2
|
2610
|
-
if ActiveRecord::Base.connection.adapter_name == 'SQLite'
|
2611
|
-
arca = ::ActiveRecord::ConnectionAdapters
|
2612
|
-
db_statements = arca::SQLite3.const_defined?('DatabaseStatements') ? arca::SQLite3::DatabaseStatements : arca::SQLite3::SchemaStatements
|
2613
|
-
# Rails 7.1 and later
|
2614
|
-
if arca::AbstractAdapter.private_instance_methods.include?(:with_raw_connection)
|
2615
|
-
db_statements.define_method(:begin_db_transaction) do
|
2616
|
-
log("begin immediate transaction", "TRANSACTION") do
|
2617
|
-
with_raw_connection(allow_retry: true, materialize_transactions: false) do |conn|
|
2618
|
-
conn.transaction(:immediate)
|
2619
|
-
end
|
2620
|
-
end
|
2621
|
-
end
|
2622
|
-
else # Rails < 7.1
|
2623
|
-
db_statements.define_method(:begin_db_transaction) do
|
2624
|
-
log('begin immediate transaction', 'TRANSACTION') { @connection.transaction(:immediate) }
|
2625
|
-
end
|
2682
|
+
begin
|
2683
|
+
# ::Brick.is_db_present = true
|
2684
|
+
::Brick.reflect_tables
|
2685
|
+
rescue ActiveRecord::NoDatabaseError
|
2686
|
+
# ::Brick.is_db_present = false
|
2626
2687
|
end
|
2688
|
+
Module.class_exec &::Brick::ADD_CONST_MISSING
|
2689
|
+
conn
|
2627
2690
|
end
|
2628
|
-
# ::Brick.is_db_present = true
|
2629
|
-
_brick_reflect_tables
|
2630
|
-
rescue ActiveRecord::NoDatabaseError
|
2631
|
-
# ::Brick.is_db_present = false
|
2632
2691
|
end
|
2633
|
-
conn
|
2634
2692
|
end
|
2693
|
+
else
|
2694
|
+
if ActiveRecord.const_defined?('ConnectionHandling')
|
2695
|
+
ActiveRecord::ConnectionHandling
|
2696
|
+
else
|
2697
|
+
ActiveRecord::ConnectionAdapters::ConnectionHandler
|
2698
|
+
end.class_exec do
|
2699
|
+
alias _brick_establish_connection establish_connection
|
2700
|
+
def establish_connection(*args)
|
2701
|
+
conn = _brick_establish_connection(*args)
|
2702
|
+
return conn unless ::Brick.config.mode == :on
|
2635
2703
|
|
2636
|
-
|
2637
|
-
|
2638
|
-
|
2639
|
-
|
2640
|
-
|
2641
|
-
# return if ActiveRecord::Base.connection.current_database == 'postgres'
|
2642
|
-
|
2643
|
-
orig_schema = nil
|
2644
|
-
if (relations = ::Brick.relations).keys == [:db_name]
|
2645
|
-
::Brick.remove_instance_variable(:@_additional_references_loaded) if ::Brick.instance_variable_defined?(:@_additional_references_loaded)
|
2646
|
-
|
2647
|
-
# --------------------------------------------
|
2648
|
-
# 1. Load three initializers early
|
2649
|
-
# (inflectsions.rb, brick.rb, apartment.rb)
|
2650
|
-
# Very first thing, load inflections since we'll be using .pluralize and .singularize on table and model names
|
2651
|
-
if File.exist?(inflections = ::Rails.root&.join('config/initializers/inflections.rb') || '')
|
2652
|
-
load inflections
|
2653
|
-
end
|
2654
|
-
# Now the Brick initializer since there may be important schema things configured
|
2655
|
-
if !::Brick.initializer_loaded && File.exist?(brick_initializer = ::Rails.root&.join('config/initializers/brick.rb') || '')
|
2656
|
-
::Brick.initializer_loaded = load brick_initializer
|
2657
|
-
|
2658
|
-
# After loading the initializer, add compatibility for ActiveStorage and ActionText if those haven't already been
|
2659
|
-
# defined. (Further JSON configuration for ActiveStorage metadata happens later in the after_initialize hook.)
|
2660
|
-
['ActiveStorage', 'ActionText'].each do |ar_extension|
|
2661
|
-
if Object.const_defined?(ar_extension) &&
|
2662
|
-
(extension = Object.const_get(ar_extension)).respond_to?(:table_name_prefix) &&
|
2663
|
-
!::Brick.config.table_name_prefixes.key?(as_tnp = extension.table_name_prefix)
|
2664
|
-
::Brick.config.table_name_prefixes[as_tnp] = ar_extension
|
2665
|
-
end
|
2666
|
-
end
|
2667
|
-
|
2668
|
-
# Support the followability gem: https://github.com/nejdetkadir/followability
|
2669
|
-
if Object.const_defined?('Followability') && !::Brick.config.table_name_prefixes.key?('followability_')
|
2670
|
-
::Brick.config.table_name_prefixes['followability_'] = 'Followability'
|
2671
|
-
end
|
2672
|
-
end
|
2673
|
-
# Load the initializer for the Apartment gem a little early so that if .excluded_models and
|
2674
|
-
# .default_schema are specified then we can work with non-tenanted models more appropriately
|
2675
|
-
if (apartment = Object.const_defined?('Apartment')) &&
|
2676
|
-
File.exist?(apartment_initializer = ::Rails.root.join('config/initializers/apartment.rb'))
|
2677
|
-
require 'apartment/adapters/abstract_adapter'
|
2678
|
-
Apartment::Adapters::AbstractAdapter.class_exec do
|
2679
|
-
if instance_methods.include?(:process_excluded_models)
|
2680
|
-
def process_excluded_models
|
2681
|
-
# All other models will share a connection (at Apartment.connection_class) and we can modify at will
|
2682
|
-
Apartment.excluded_models.each do |excluded_model|
|
2683
|
-
begin
|
2684
|
-
process_excluded_model(excluded_model)
|
2685
|
-
rescue NameError => e
|
2686
|
-
(@bad_models ||= []) << excluded_model
|
2687
|
-
end
|
2688
|
-
end
|
2689
|
-
end
|
2690
|
-
end
|
2691
|
-
end
|
2692
|
-
unless @_apartment_loaded
|
2693
|
-
load apartment_initializer
|
2694
|
-
@_apartment_loaded = true
|
2695
|
-
end
|
2696
|
-
end
|
2697
|
-
# Only for Postgres (Doesn't work in sqlite3 or MySQL)
|
2698
|
-
# puts ActiveRecord::Base.execute_sql("SELECT current_setting('SEARCH_PATH')").to_a.inspect
|
2699
|
-
|
2700
|
-
# ---------------------------
|
2701
|
-
# 2. Figure out schema things
|
2702
|
-
is_postgres = nil
|
2703
|
-
is_mssql = ActiveRecord::Base.connection.adapter_name == 'SQLServer'
|
2704
|
-
case ActiveRecord::Base.connection.adapter_name
|
2705
|
-
when 'PostgreSQL', 'SQLServer'
|
2706
|
-
is_postgres = !is_mssql
|
2707
|
-
db_schemas = if is_postgres
|
2708
|
-
ActiveRecord::Base.execute_sql('SELECT nspname AS table_schema, MAX(oid) AS dt FROM pg_namespace GROUP BY 1 ORDER BY 1;')
|
2709
|
-
else
|
2710
|
-
ActiveRecord::Base.execute_sql('SELECT DISTINCT table_schema, NULL AS dt FROM INFORMATION_SCHEMA.tables;')
|
2711
|
-
end
|
2712
|
-
::Brick.db_schemas = db_schemas.each_with_object({}) do |row, s|
|
2713
|
-
row = case row
|
2714
|
-
when Array
|
2715
|
-
row
|
2716
|
-
else
|
2717
|
-
[row['table_schema'], row['dt']]
|
2718
|
-
end
|
2719
|
-
# Remove any system schemas
|
2720
|
-
s[row.first] = { dt: row.last } unless ['information_schema', 'pg_catalog', 'pg_toast', 'heroku_ext',
|
2721
|
-
'INFORMATION_SCHEMA', 'sys'].include?(row.first)
|
2722
|
-
end
|
2723
|
-
possible_schema, possible_schemas, multitenancy = ::Brick.get_possible_schemas
|
2724
|
-
if possible_schemas
|
2725
|
-
if possible_schema
|
2726
|
-
::Brick.default_schema = ::Brick.apartment_default_tenant
|
2727
|
-
schema = possible_schema
|
2728
|
-
orig_schema = ActiveRecord::Base.execute_sql('SELECT current_schemas(true)').first['current_schemas'][1..-2].split(',')
|
2729
|
-
ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?", schema)
|
2730
|
-
# When testing, just find the most recently-created schema
|
2731
|
-
elsif begin
|
2732
|
-
Rails.env == 'test' ||
|
2733
|
-
ActiveRecord::Base.execute_sql("SELECT value FROM ar_internal_metadata WHERE key='environment';").first&.fetch('value', nil) == 'test'
|
2734
|
-
rescue
|
2735
|
-
end
|
2736
|
-
::Brick.default_schema = ::Brick.apartment_default_tenant
|
2737
|
-
::Brick.test_schema = schema = ::Brick.db_schemas.to_a.sort { |a, b| b.last[:dt] <=> a.last[:dt] }.first.first
|
2738
|
-
if possible_schema.blank?
|
2739
|
-
puts "While running tests, using the most recently-created schema, #{schema}."
|
2740
|
-
else
|
2741
|
-
puts "While running tests, had noticed in the brick.rb initializer that the line \"::Brick.schema_behavior = ...\" refers to a schema called \"#{possible_schema}\" which does not exist. Reading table structure from the most recently-created schema, #{schema}."
|
2742
|
-
end
|
2743
|
-
orig_schema = ActiveRecord::Base.execute_sql('SELECT current_schemas(true)').first['current_schemas'][1..-2].split(',')
|
2744
|
-
::Brick.config.schema_behavior = { multitenant: {} } # schema_to_analyse: [schema]
|
2745
|
-
ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?", schema)
|
2746
|
-
else
|
2747
|
-
puts "*** In the brick.rb initializer the line \"::Brick.schema_behavior = ...\" refers to schema(s) called #{possible_schemas.map { |s| "\"#{s}\"" }.join(', ')}. No mentioned schema exists. ***"
|
2748
|
-
if ::Brick.db_schemas.key?(::Brick.apartment_default_tenant)
|
2749
|
-
::Brick.default_schema = schema = ::Brick.apartment_default_tenant
|
2750
|
-
end
|
2751
|
-
end
|
2752
|
-
end
|
2753
|
-
when 'Mysql2', 'Trilogy'
|
2754
|
-
::Brick.default_schema = schema = ActiveRecord::Base.connection.current_database
|
2755
|
-
when 'OracleEnhanced'
|
2756
|
-
# ActiveRecord::Base.connection.current_database will be something like "XEPDB1"
|
2757
|
-
::Brick.default_schema = schema = ActiveRecord::Base.connection.raw_connection.username
|
2758
|
-
::Brick.db_schemas = {}
|
2759
|
-
ActiveRecord::Base.execute_sql("SELECT username FROM sys.all_users WHERE ORACLE_MAINTAINED != 'Y'").each { |s| ::Brick.db_schemas[s.first] = {} }
|
2760
|
-
when 'SQLite'
|
2761
|
-
sql = "SELECT m.name AS relation_name, UPPER(m.type) AS table_type,
|
2762
|
-
p.name AS column_name, p.type AS data_type,
|
2763
|
-
CASE p.pk WHEN 1 THEN 'PRIMARY KEY' END AS const
|
2764
|
-
FROM sqlite_master AS m
|
2765
|
-
INNER JOIN pragma_table_info(m.name) AS p
|
2766
|
-
WHERE m.name NOT IN ('sqlite_sequence', ?, ?)
|
2767
|
-
ORDER BY m.name, p.cid"
|
2768
|
-
else
|
2769
|
-
puts "Unfamiliar with connection adapter #{ActiveRecord::Base.connection.adapter_name}"
|
2770
|
-
end
|
2771
|
-
|
2772
|
-
::Brick.db_schemas ||= {}
|
2773
|
-
|
2774
|
-
# ---------------------
|
2775
|
-
# 3. Tables and columns
|
2776
|
-
# %%% Retrieve internal ActiveRecord table names like this:
|
2777
|
-
# ActiveRecord::Base.internal_metadata_table_name, ActiveRecord::Base.schema_migrations_table_name
|
2778
|
-
# For if it's not SQLite -- so this is the Postgres and MySQL version
|
2779
|
-
measures = []
|
2780
|
-
::Brick.is_oracle = true if ActiveRecord::Base.connection.adapter_name == 'OracleEnhanced'
|
2781
|
-
case ActiveRecord::Base.connection.adapter_name
|
2782
|
-
when 'PostgreSQL', 'SQLite' # These bring back a hash for each row because the query uses column aliases
|
2783
|
-
# schema ||= 'public' if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
|
2784
|
-
retrieve_schema_and_tables(sql, is_postgres, is_mssql, schema).each do |r|
|
2785
|
-
# If Apartment gem lists the table as being associated with a non-tenanted model then use whatever it thinks
|
2786
|
-
# is the default schema, usually 'public'.
|
2787
|
-
schema_name = if ::Brick.config.schema_behavior[:multitenant]
|
2788
|
-
::Brick.apartment_default_tenant if ::Brick.is_apartment_excluded_table(r['relation_name'])
|
2789
|
-
elsif ![schema, 'public'].include?(r['schema'])
|
2790
|
-
r['schema']
|
2791
|
-
end
|
2792
|
-
relation_name = schema_name ? "#{schema_name}.#{r['relation_name']}" : r['relation_name']
|
2793
|
-
# Both uppers and lowers as well as underscores?
|
2794
|
-
::Brick.apply_double_underscore_patch if relation_name =~ /[A-Z]/ && relation_name =~ /[a-z]/ && relation_name.index('_')
|
2795
|
-
relation = relations[relation_name]
|
2796
|
-
relation[:isView] = true if r['table_type'] == 'VIEW'
|
2797
|
-
relation[:description] = r['table_description'] if r['table_description']
|
2798
|
-
col_name = r['column_name']
|
2799
|
-
key = case r['const']
|
2800
|
-
when 'PRIMARY KEY'
|
2801
|
-
relation[:pkey][r['key'] || relation_name] ||= []
|
2802
|
-
when 'UNIQUE'
|
2803
|
-
relation[:ukeys][r['key'] || "#{relation_name}.#{col_name}"] ||= []
|
2804
|
-
# key = (relation[:ukeys] = Hash.new { |h, k| h[k] = [] }) if key.is_a?(Array)
|
2805
|
-
# key[r['key']]
|
2806
|
-
else
|
2807
|
-
if r['data_type'] == 'uuid'
|
2808
|
-
# && r['column_name'] == ::Brick.ar_base.primary_key
|
2809
|
-
# binding.pry
|
2810
|
-
relation[:pkey][r['key'] || relation_name] ||= []
|
2811
|
-
end
|
2812
|
-
end
|
2813
|
-
# binding.pry if key && r['data_type'] == 'uuid'
|
2814
|
-
key << col_name if key
|
2815
|
-
cols = relation[:cols] # relation.fetch(:cols) { relation[:cols] = [] }
|
2816
|
-
cols[col_name] = [r['data_type'], r['max_length'], measures&.include?(col_name), r['is_nullable'] == 'NO']
|
2817
|
-
# puts "KEY! #{r['relation_name']}.#{col_name} #{r['key']} #{r['const']}" if r['key']
|
2818
|
-
relation[:col_descrips][col_name] = r['column_description'] if r['column_description']
|
2819
|
-
end
|
2820
|
-
else # MySQL2, OracleEnhanced, and MSSQL act a little differently, bringing back an array for each row
|
2821
|
-
schema_and_tables = case ActiveRecord::Base.connection.adapter_name
|
2822
|
-
when 'OracleEnhanced'
|
2823
|
-
sql =
|
2824
|
-
"SELECT c.owner AS schema, c.table_name AS relation_name,
|
2825
|
-
CASE WHEN v.owner IS NULL THEN 'BASE_TABLE' ELSE 'VIEW' END AS table_type,
|
2826
|
-
c.column_name, c.data_type,
|
2827
|
-
COALESCE(c.data_length, c.data_precision) AS max_length,
|
2828
|
-
CASE ac.constraint_type WHEN 'P' THEN 'PRIMARY KEY' END AS const,
|
2829
|
-
ac.constraint_name AS \"key\",
|
2830
|
-
CASE c.nullable WHEN 'Y' THEN 'YES' ELSE 'NO' END AS is_nullable
|
2831
|
-
FROM all_tab_cols c
|
2832
|
-
LEFT OUTER JOIN all_cons_columns acc ON acc.owner = c.owner AND acc.table_name = c.table_name AND acc.column_name = c.column_name
|
2833
|
-
LEFT OUTER JOIN all_constraints ac ON ac.owner = acc.owner AND ac.table_name = acc.table_name AND ac.constraint_name = acc.constraint_name AND constraint_type = 'P'
|
2834
|
-
LEFT OUTER JOIN all_views v ON c.owner = v.owner AND c.table_name = v.view_name
|
2835
|
-
WHERE c.owner IN (#{::Brick.db_schemas.keys.map { |s| "'#{s}'" }.join(', ')})
|
2836
|
-
AND c.table_name NOT IN (?, ?)
|
2837
|
-
ORDER BY 1, 2, c.internal_column_id, acc.position"
|
2838
|
-
ActiveRecord::Base.execute_sql(sql, *ar_tables)
|
2839
|
-
else
|
2840
|
-
retrieve_schema_and_tables(sql)
|
2841
|
-
end
|
2842
|
-
|
2843
|
-
schema_and_tables.each do |r|
|
2844
|
-
next if r[1].index('$') # Oracle can have goofy table names with $
|
2845
|
-
|
2846
|
-
if (relation_name = r[1]) =~ /^[A-Z0-9_]+$/
|
2847
|
-
relation_name.downcase!
|
2848
|
-
# Both uppers and lowers as well as underscores?
|
2849
|
-
elsif relation_name =~ /[A-Z]/ && relation_name =~ /[a-z]/ && relation_name.index('_')
|
2850
|
-
::Brick.apply_double_underscore_patch
|
2851
|
-
end
|
2852
|
-
# Expect the default schema for SQL Server to be 'dbo'.
|
2853
|
-
if (::Brick.is_oracle && r[0] != schema) || (is_mssql && r[0] != 'dbo')
|
2854
|
-
relation_name = "#{r[0]}.#{relation_name}"
|
2855
|
-
end
|
2856
|
-
|
2857
|
-
relation = relations[relation_name] # here relation represents a table or view from the database
|
2858
|
-
relation[:isView] = true if r[2] == 'VIEW' # table_type
|
2859
|
-
col_name = ::Brick.is_oracle ? connection.send(:oracle_downcase, r[3]) : r[3]
|
2860
|
-
key = case r[6] # constraint type
|
2861
|
-
when 'PRIMARY KEY'
|
2862
|
-
# key
|
2863
|
-
relation[:pkey][r[7] || relation_name] ||= []
|
2864
|
-
when 'UNIQUE'
|
2865
|
-
relation[:ukeys][r[7] || "#{relation_name}.#{col_name}"] ||= []
|
2866
|
-
# key = (relation[:ukeys] = Hash.new { |h, k| h[k] = [] }) if key.is_a?(Array)
|
2867
|
-
# key[r['key']]
|
2868
|
-
end
|
2869
|
-
key << col_name if key
|
2870
|
-
cols = relation[:cols] # relation.fetch(:cols) { relation[:cols] = [] }
|
2871
|
-
# 'data_type', 'max_length', measure, 'is_nullable'
|
2872
|
-
cols[col_name] = [r[4], r[5], measures&.include?(col_name), r[8] == 'NO']
|
2873
|
-
# puts "KEY! #{r['relation_name']}.#{col_name} #{r['key']} #{r['const']}" if r['key']
|
2874
|
-
end
|
2875
|
-
end
|
2876
|
-
|
2877
|
-
# PostGIS adds three views which would confuse Rails if models were to be built for them.
|
2878
|
-
if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
|
2879
|
-
if relations.key?('geography_columns') && relations.key?('geometry_columns') && relations.key?('spatial_ref_sys')
|
2880
|
-
(::Brick.config.exclude_tables ||= []) << 'geography_columns'
|
2881
|
-
::Brick.config.exclude_tables << 'geometry_columns'
|
2882
|
-
::Brick.config.exclude_tables << 'spatial_ref_sys'
|
2883
|
-
end
|
2884
|
-
end
|
2885
|
-
|
2886
|
-
# # Add unique OIDs
|
2887
|
-
# if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
|
2888
|
-
# ActiveRecord::Base.execute_sql(
|
2889
|
-
# "SELECT c.oid, n.nspname, c.relname
|
2890
|
-
# FROM pg_catalog.pg_namespace AS n
|
2891
|
-
# INNER JOIN pg_catalog.pg_class AS c ON n.oid = c.relnamespace
|
2892
|
-
# WHERE c.relkind IN ('r', 'v')"
|
2893
|
-
# ).each do |r|
|
2894
|
-
# next if ['pg_catalog', 'information_schema', ''].include?(r['nspname']) ||
|
2895
|
-
# ['ar_internal_metadata', 'schema_migrations'].include?(r['relname'])
|
2896
|
-
# relation = relations.fetch(r['relname'], nil)
|
2897
|
-
# if relation
|
2898
|
-
# (relation[:oid] ||= {})[r['nspname']] = r['oid']
|
2899
|
-
# else
|
2900
|
-
# puts "Where is #{r['nspname']} #{r['relname']} ?"
|
2901
|
-
# end
|
2902
|
-
# end
|
2903
|
-
# end
|
2904
|
-
# schema = ::Brick.default_schema # Reset back for this next round of fun
|
2905
|
-
|
2906
|
-
# ---------------------------------------------
|
2907
|
-
# 4. Foreign key info
|
2908
|
-
# (done in two parts which get JOINed together in Ruby code)
|
2909
|
-
kcus = nil
|
2910
|
-
entry_type = nil
|
2911
|
-
case ActiveRecord::Base.connection.adapter_name
|
2912
|
-
when 'PostgreSQL', 'Mysql2', 'Trilogy', 'SQLServer'
|
2913
|
-
# Part 1 -- all KCUs
|
2914
|
-
sql = "SELECT CONSTRAINT_CATALOG, CONSTRAINT_SCHEMA, CONSTRAINT_NAME, ORDINAL_POSITION,
|
2915
|
-
TABLE_NAME, COLUMN_NAME
|
2916
|
-
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE#{"
|
2917
|
-
WHERE CONSTRAINT_SCHEMA = COALESCE(current_setting('SEARCH_PATH'), 'public')" if is_postgres && schema }#{"
|
2918
|
-
WHERE CONSTRAINT_SCHEMA = '#{ActiveRecord::Base.connection.current_database&.tr("'", "''")}'" if is_mysql
|
2919
|
-
}"
|
2920
|
-
kcus = ActiveRecord::Base.execute_sql(sql).each_with_object({}) do |v, s|
|
2921
|
-
if (entry_type ||= v.is_a?(Array) ? :array : :hash) == :hash
|
2922
|
-
key = "#{v['constraint_name']}.#{v['constraint_schema']}.#{v['constraint_catalog']}.#{v['ordinal_position']}"
|
2923
|
-
key << ".#{v['table_name']}.#{v['column_name']}" unless is_postgres || is_mssql
|
2924
|
-
s[key] = [v['constraint_schema'], v['table_name']]
|
2925
|
-
else # Array
|
2926
|
-
key = "#{v[2]}.#{v[1]}.#{v[0]}.#{v[3]}"
|
2927
|
-
key << ".#{v[4]}.#{v[5]}" unless is_postgres || is_mssql
|
2928
|
-
s[key] = [v[1], v[4]]
|
2929
|
-
end
|
2930
|
-
end
|
2931
|
-
|
2932
|
-
# Part 2 -- fk_references
|
2933
|
-
sql = "SELECT kcu.CONSTRAINT_SCHEMA, kcu.TABLE_NAME, kcu.COLUMN_NAME,
|
2934
|
-
#{# These will get filled in with real values (effectively doing the JOIN in Ruby)
|
2935
|
-
is_postgres || is_mssql ? 'NULL as primary_schema, NULL as primary_table' :
|
2936
|
-
'kcu.REFERENCED_TABLE_NAME, kcu.REFERENCED_COLUMN_NAME'},
|
2937
|
-
kcu.CONSTRAINT_NAME AS CONSTRAINT_SCHEMA_FK,
|
2938
|
-
rc.UNIQUE_CONSTRAINT_NAME, rc.UNIQUE_CONSTRAINT_SCHEMA, rc.UNIQUE_CONSTRAINT_CATALOG, kcu.ORDINAL_POSITION
|
2939
|
-
FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS AS rc
|
2940
|
-
INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS kcu
|
2941
|
-
ON kcu.CONSTRAINT_CATALOG = rc.CONSTRAINT_CATALOG
|
2942
|
-
AND kcu.CONSTRAINT_SCHEMA = rc.CONSTRAINT_SCHEMA
|
2943
|
-
AND kcu.CONSTRAINT_NAME = rc.CONSTRAINT_NAME#{"
|
2944
|
-
WHERE kcu.CONSTRAINT_SCHEMA = COALESCE(current_setting('SEARCH_PATH'), 'public')" if is_postgres && schema}#{"
|
2945
|
-
WHERE kcu.CONSTRAINT_SCHEMA = '#{ActiveRecord::Base.connection.current_database&.tr("'", "''")}'" if is_mysql}"
|
2946
|
-
fk_references = ActiveRecord::Base.execute_sql(sql)
|
2947
|
-
when 'SQLite'
|
2948
|
-
sql = "SELECT NULL AS constraint_schema, m.name, fkl.\"from\", NULL AS primary_schema, fkl.\"table\", m.name || '_' || fkl.\"from\" AS constraint_name
|
2949
|
-
FROM sqlite_master m
|
2950
|
-
INNER JOIN pragma_foreign_key_list(m.name) fkl ON m.type = 'table'
|
2951
|
-
ORDER BY m.name, fkl.seq"
|
2952
|
-
fk_references = ActiveRecord::Base.execute_sql(sql)
|
2953
|
-
when 'OracleEnhanced'
|
2954
|
-
schemas = ::Brick.db_schemas.keys.map { |s| "'#{s}'" }.join(', ')
|
2955
|
-
sql =
|
2956
|
-
"SELECT -- fk
|
2957
|
-
ac.owner AS constraint_schema, acc_fk.table_name, acc_fk.column_name,
|
2958
|
-
-- referenced pk
|
2959
|
-
ac.r_owner AS primary_schema, acc_pk.table_name AS primary_table, acc_fk.constraint_name AS constraint_schema_fk
|
2960
|
-
-- , acc_pk.column_name
|
2961
|
-
FROM all_cons_columns acc_fk
|
2962
|
-
INNER JOIN all_constraints ac ON acc_fk.owner = ac.owner
|
2963
|
-
AND acc_fk.constraint_name = ac.constraint_name
|
2964
|
-
INNER JOIN all_cons_columns acc_pk ON ac.r_owner = acc_pk.owner
|
2965
|
-
AND ac.r_constraint_name = acc_pk.constraint_name
|
2966
|
-
WHERE ac.constraint_type = 'R'
|
2967
|
-
AND ac.owner IN (#{schemas})
|
2968
|
-
AND ac.r_owner IN (#{schemas})"
|
2969
|
-
fk_references = ActiveRecord::Base.execute_sql(sql)
|
2970
|
-
end
|
2971
|
-
::Brick.is_oracle = true if ActiveRecord::Base.connection.adapter_name == 'OracleEnhanced'
|
2972
|
-
# ::Brick.default_schema ||= schema ||= 'public' if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
|
2973
|
-
::Brick.default_schema ||= 'public' if is_postgres
|
2974
|
-
fk_references&.each do |fk|
|
2975
|
-
fk = fk.values unless fk.is_a?(Array)
|
2976
|
-
# Virtually JOIN KCUs to fk_references in order to fill in the primary schema and primary table
|
2977
|
-
kcu_key = "#{fk[6]}.#{fk[7]}.#{fk[8]}.#{fk[9]}"
|
2978
|
-
kcu_key << ".#{fk[3]}.#{fk[4]}" unless is_postgres || is_mssql
|
2979
|
-
if (kcu = kcus&.fetch(kcu_key, nil))
|
2980
|
-
fk[3] = kcu[0]
|
2981
|
-
fk[4] = kcu[1]
|
2982
|
-
end
|
2983
|
-
# Multitenancy makes things a little more general overall, except for non-tenanted tables
|
2984
|
-
if ::Brick.is_apartment_excluded_table(::Brick.namify(fk[1]))
|
2985
|
-
fk[0] = ::Brick.apartment_default_tenant
|
2986
|
-
elsif (is_postgres && (fk[0] == 'public' || (multitenancy && fk[0] == schema))) ||
|
2987
|
-
(::Brick.is_oracle && fk[0] == schema) ||
|
2988
|
-
(is_mssql && fk[0] == 'dbo') ||
|
2989
|
-
(!is_postgres && !::Brick.is_oracle && !is_mssql && ['mysql', 'performance_schema', 'sys'].exclude?(fk[0]))
|
2990
|
-
fk[0] = nil
|
2991
|
-
end
|
2992
|
-
if ::Brick.is_apartment_excluded_table(fk[4])
|
2993
|
-
fk[3] = ::Brick.apartment_default_tenant
|
2994
|
-
elsif (is_postgres && (fk[3] == 'public' || (multitenancy && fk[3] == schema))) ||
|
2995
|
-
(::Brick.is_oracle && fk[3] == schema) ||
|
2996
|
-
(is_mssql && fk[3] == 'dbo') ||
|
2997
|
-
(!is_postgres && !::Brick.is_oracle && !is_mssql && ['mysql', 'performance_schema', 'sys'].exclude?(fk[3]))
|
2998
|
-
fk[3] = nil
|
2999
|
-
end
|
3000
|
-
if ::Brick.is_oracle
|
3001
|
-
fk[1].downcase! if fk[1] =~ /^[A-Z0-9_]+$/
|
3002
|
-
fk[4].downcase! if fk[4] =~ /^[A-Z0-9_]+$/
|
3003
|
-
fk[2] = connection.send(:oracle_downcase, fk[2])
|
3004
|
-
end
|
3005
|
-
::Brick._add_bt_and_hm(fk, relations)
|
3006
|
-
end
|
3007
|
-
kcus = nil # Allow this large item to be garbage collected
|
3008
|
-
end
|
3009
|
-
|
3010
|
-
table_name_lookup = (::Brick.table_name_lookup ||= {})
|
3011
|
-
relations.each do |k, v|
|
3012
|
-
next if k.is_a?(Symbol)
|
3013
|
-
|
3014
|
-
rel_name = k.split('.').map { |rel_part| ::Brick.namify(rel_part, :underscore) }
|
3015
|
-
schema_names = rel_name[0..-2]
|
3016
|
-
schema_names.shift if ::Brick.apartment_multitenant && schema_names.first == ::Brick.apartment_default_tenant
|
3017
|
-
v[:schema] = schema_names.join('.') unless schema_names.empty?
|
3018
|
-
# %%% If more than one schema has the same table name, will need to add a schema name prefix to have uniqueness
|
3019
|
-
if (singular = rel_name.last.singularize).blank?
|
3020
|
-
singular = rel_name.last
|
3021
|
-
end
|
3022
|
-
name_parts = if (tnp = ::Brick.config.table_name_prefixes
|
3023
|
-
&.find { |k1, _v1| singular.start_with?(k1) && singular.length > k1.length }
|
3024
|
-
).present?
|
3025
|
-
v[:auto_prefixed_schema] = tnp.first
|
3026
|
-
# v[:resource] = rel_name.last[tnp.first.length..-1]
|
3027
|
-
[tnp.last, singular[tnp.first.length..-1]]
|
3028
|
-
else
|
3029
|
-
# v[:resource] = rel_name.last
|
3030
|
-
[singular]
|
3031
|
-
end
|
3032
|
-
proposed_name_parts = (schema_names + name_parts).map { |p| ::Brick.namify(p, :underscore).camelize }
|
3033
|
-
# Find out if the proposed name leads to a module or class that already exists and is not an AR class
|
3034
|
-
colliding_thing = nil
|
3035
|
-
loop do
|
3036
|
-
klass = Object
|
3037
|
-
proposed_name_parts.each do |part|
|
3038
|
-
if klass.const_defined?(part)
|
3039
|
-
klass = klass.const_get(part)
|
3040
|
-
else
|
3041
|
-
klass = nil
|
3042
|
-
break
|
3043
|
-
end
|
3044
|
-
end
|
3045
|
-
break if !klass || (klass < ActiveRecord::Base) # Break if all good -- no conflicts
|
3046
|
-
|
3047
|
-
# Find a unique name since there's already something that's non-AR with that same name
|
3048
|
-
last_idx = proposed_name_parts.length - 1
|
3049
|
-
proposed_name_parts[last_idx] = ::Brick.ensure_unique(proposed_name_parts[last_idx], 'X')
|
3050
|
-
colliding_thing ||= klass
|
3051
|
-
end
|
3052
|
-
v[:class_name] = proposed_name_parts.join('::')
|
3053
|
-
# Was: v[:resource] = v[:class_name].underscore.tr('/', '.')
|
3054
|
-
v[:resource] = proposed_name_parts.last.underscore
|
3055
|
-
if colliding_thing
|
3056
|
-
message_start = if colliding_thing.is_a?(Module) && Object.const_defined?(:Rails) &&
|
3057
|
-
colliding_thing.constants.find { |c| colliding_thing.const_get(c) < Rails::Application }
|
3058
|
-
"The module for the Rails application itself, \"#{colliding_thing.name}\","
|
3059
|
-
else
|
3060
|
-
"Non-AR #{colliding_thing.class.name.downcase} \"#{colliding_thing.name}\""
|
3061
|
-
end
|
3062
|
-
puts "WARNING: #{message_start} already exists.\n Will set up to auto-create model #{v[:class_name]} for table #{k}."
|
2704
|
+
begin
|
2705
|
+
# ::Brick.is_db_present = true
|
2706
|
+
::Brick.reflect_tables
|
2707
|
+
rescue ActiveRecord::NoDatabaseError
|
2708
|
+
# ::Brick.is_db_present = false
|
3063
2709
|
end
|
3064
|
-
|
3065
|
-
|
3066
|
-
end
|
3067
|
-
::Brick.load_additional_references if ::Brick.initializer_loaded
|
3068
|
-
|
3069
|
-
if is_postgres
|
3070
|
-
params = []
|
3071
|
-
ActiveRecord::Base.execute_sql("-- inherited and partitioned tables counts
|
3072
|
-
SELECT n.nspname, parent.relname,
|
3073
|
-
((SUM(child.reltuples::float) / greatest(SUM(child.relpages), 1))) *
|
3074
|
-
(SUM(pg_relation_size(child.oid))::float / (current_setting('block_size')::float))::integer AS rowcount
|
3075
|
-
FROM pg_inherits
|
3076
|
-
INNER JOIN pg_class parent ON pg_inherits.inhparent = parent.oid
|
3077
|
-
INNER JOIN pg_class child ON pg_inherits.inhrelid = child.oid
|
3078
|
-
INNER JOIN pg_catalog.pg_namespace n ON n.oid = parent.relnamespace#{
|
3079
|
-
if schema
|
3080
|
-
params << schema
|
3081
|
-
"
|
3082
|
-
WHERE n.nspname = COALESCE(?, 'public')"
|
3083
|
-
end}
|
3084
|
-
GROUP BY n.nspname, parent.relname, child.reltuples, child.relpages, child.oid
|
3085
|
-
|
3086
|
-
UNION ALL
|
3087
|
-
|
3088
|
-
-- table count
|
3089
|
-
SELECT n.nspname, pg_class.relname,
|
3090
|
-
(pg_class.reltuples::float / greatest(pg_class.relpages, 1)) *
|
3091
|
-
(pg_relation_size(pg_class.oid)::float / (current_setting('block_size')::float))::integer AS rowcount
|
3092
|
-
FROM pg_class
|
3093
|
-
INNER JOIN pg_catalog.pg_namespace n ON n.oid = pg_class.relnamespace#{
|
3094
|
-
if schema
|
3095
|
-
params << schema
|
3096
|
-
"
|
3097
|
-
WHERE n.nspname = COALESCE(?, 'public')"
|
3098
|
-
end}
|
3099
|
-
GROUP BY n.nspname, pg_class.relname, pg_class.reltuples, pg_class.relpages, pg_class.oid", params).each do |tblcount|
|
3100
|
-
# %%% What is the default schema here?
|
3101
|
-
prefix = "#{tblcount['nspname']}." unless tblcount['nspname'] == (schema || 'public')
|
3102
|
-
relations.fetch("#{prefix}#{tblcount['relname']}", nil)&.[]=(:rowcount, tblcount['rowcount'].to_i.round)
|
2710
|
+
if Object.const_defined?('Avo') && ::Avo.respond_to?(:railtie_namespace)
|
2711
|
+
Module.class_exec &::Brick::ADD_CONST_MISSING
|
3103
2712
|
end
|
2713
|
+
conn
|
3104
2714
|
end
|
3105
|
-
|
3106
|
-
if orig_schema && (orig_schema = (orig_schema - ['pg_catalog', 'pg_toast', 'heroku_ext']).first)
|
3107
|
-
puts "Now switching back to \"#{orig_schema}\" schema."
|
3108
|
-
ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?", orig_schema)
|
3109
|
-
end
|
3110
|
-
end
|
3111
|
-
|
3112
|
-
def retrieve_schema_and_tables(sql = nil, is_postgres = nil, is_mssql = nil, schema = nil)
|
3113
|
-
is_mssql = ActiveRecord::Base.connection.adapter_name == 'SQLServer' if is_mssql.nil?
|
3114
|
-
params = ar_tables
|
3115
|
-
sql ||= "SELECT t.table_schema AS \"schema\", t.table_name AS relation_name, t.table_type,#{"
|
3116
|
-
pg_catalog.obj_description(
|
3117
|
-
('\"' || t.table_schema || '\".\"' || t.table_name || '\"')::regclass::oid, 'pg_class'
|
3118
|
-
) AS table_description,
|
3119
|
-
pg_catalog.col_description(
|
3120
|
-
('\"' || t.table_schema || '\".\"' || t.table_name || '\"')::regclass::oid, c.ordinal_position
|
3121
|
-
) AS column_description," if is_postgres}
|
3122
|
-
c.column_name, #{is_postgres ? "CASE c.data_type WHEN 'USER-DEFINED' THEN pg_t.typname ELSE c.data_type END AS data_type" : 'c.data_type'},
|
3123
|
-
COALESCE(c.character_maximum_length, c.numeric_precision) AS max_length,
|
3124
|
-
kcu.constraint_type AS const, kcu.constraint_name AS \"key\",
|
3125
|
-
c.is_nullable
|
3126
|
-
FROM INFORMATION_SCHEMA.tables AS t
|
3127
|
-
LEFT OUTER JOIN INFORMATION_SCHEMA.columns AS c ON t.table_schema = c.table_schema
|
3128
|
-
AND t.table_name = c.table_name
|
3129
|
-
LEFT OUTER JOIN
|
3130
|
-
(SELECT kcu1.constraint_schema, kcu1.table_name, kcu1.column_name, kcu1.ordinal_position,
|
3131
|
-
tc.constraint_type, kcu1.constraint_name
|
3132
|
-
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS kcu1
|
3133
|
-
INNER JOIN INFORMATION_SCHEMA.table_constraints AS tc
|
3134
|
-
ON kcu1.CONSTRAINT_SCHEMA = tc.CONSTRAINT_SCHEMA
|
3135
|
-
AND kcu1.TABLE_NAME = tc.TABLE_NAME
|
3136
|
-
AND kcu1.CONSTRAINT_NAME = tc.constraint_name
|
3137
|
-
AND tc.constraint_type != 'FOREIGN KEY' -- For MSSQL
|
3138
|
-
) AS kcu ON
|
3139
|
-
-- kcu.CONSTRAINT_CATALOG = t.table_catalog AND
|
3140
|
-
kcu.CONSTRAINT_SCHEMA = c.table_schema
|
3141
|
-
AND kcu.TABLE_NAME = c.table_name
|
3142
|
-
AND kcu.column_name = c.column_name#{"
|
3143
|
-
-- AND kcu.position_in_unique_constraint IS NULL" unless is_mssql}#{"
|
3144
|
-
INNER JOIN pg_catalog.pg_namespace pg_n ON pg_n.nspname = t.table_schema
|
3145
|
-
INNER JOIN pg_catalog.pg_class pg_c ON pg_n.oid = pg_c.relnamespace AND pg_c.relname = c.table_name
|
3146
|
-
INNER JOIN pg_catalog.pg_attribute pg_a ON pg_c.oid = pg_a.attrelid AND pg_a.attname = c.column_name
|
3147
|
-
INNER JOIN pg_catalog.pg_type pg_t ON pg_t.oid = pg_a.atttypid" if is_postgres}
|
3148
|
-
WHERE t.table_schema #{is_postgres || is_mssql ?
|
3149
|
-
"NOT IN ('information_schema', 'pg_catalog', 'pg_toast', 'heroku_ext',
|
3150
|
-
'INFORMATION_SCHEMA', 'sys')"
|
3151
|
-
:
|
3152
|
-
"= '#{ActiveRecord::Base.connection.current_database&.tr("'", "''")}'"}#{
|
3153
|
-
if is_postgres && schema
|
3154
|
-
params = params.unshift(schema) # Used to use this SQL: current_setting('SEARCH_PATH')
|
3155
|
-
"
|
3156
|
-
AND t.table_schema = COALESCE(?, 'public')"
|
3157
|
-
end}
|
3158
|
-
-- AND t.table_type IN ('VIEW') -- 'BASE TABLE', 'FOREIGN TABLE'
|
3159
|
-
AND t.table_name NOT IN ('pg_stat_statements', ?, ?)
|
3160
|
-
ORDER BY 1, t.table_type DESC, 2, c.ordinal_position"
|
3161
|
-
ActiveRecord::Base.execute_sql(sql, *params)
|
3162
|
-
end
|
3163
|
-
|
3164
|
-
def ar_tables
|
3165
|
-
ar_smtn = if ActiveRecord::Base.respond_to?(:schema_migrations_table_name)
|
3166
|
-
ActiveRecord::Base.schema_migrations_table_name
|
3167
|
-
else
|
3168
|
-
'schema_migrations'
|
3169
|
-
end
|
3170
|
-
ar_imtn = ActiveRecord.version >= ::Gem::Version.new('5.0') ? ActiveRecord::Base.internal_metadata_table_name : 'ar_internal_metadata'
|
3171
|
-
[ar_smtn, ar_imtn]
|
3172
2715
|
end
|
3173
2716
|
end
|
3174
|
-
|
3175
2717
|
# ==========================================
|
3176
2718
|
|
3177
2719
|
# :nodoc:
|
@@ -3464,7 +3006,7 @@ module Brick
|
|
3464
3006
|
end
|
3465
3007
|
end
|
3466
3008
|
|
3467
|
-
def _brick_index(tbl_name, mode = nil, separator = nil, relation = nil)
|
3009
|
+
def _brick_index(tbl_name, mode = nil, separator = nil, relation = nil, not_path = nil)
|
3468
3010
|
separator ||= '_'
|
3469
3011
|
res_name = (tbl_name_parts = tbl_name.split('.'))[0..-2].first
|
3470
3012
|
res_name << '.' if res_name
|
@@ -3472,22 +3014,28 @@ module Brick
|
|
3472
3014
|
|
3473
3015
|
res_parts = ((mode == :singular) ? res_name.singularize : res_name).split('.')
|
3474
3016
|
res_parts.shift if ::Brick.apartment_multitenant && res_parts.length > 1 && res_parts.first == ::Brick.apartment_default_tenant
|
3017
|
+
index2 = []
|
3018
|
+
if ::Brick.config.path_prefix
|
3019
|
+
res_parts.unshift(::Brick.config.path_prefix)
|
3020
|
+
index2 << ::Brick.config.path_prefix
|
3021
|
+
end
|
3475
3022
|
if (aps = relation&.fetch(:auto_prefixed_schema, nil)) # && res_parts.last.start_with?(aps)
|
3476
3023
|
aps = aps[0..-2] if aps[-1] == '_'
|
3477
3024
|
last_part = res_parts.last # [aps.length..-1]
|
3478
3025
|
res_parts[-1] = aps
|
3479
3026
|
res_parts << last_part
|
3480
|
-
|
3481
|
-
path_prefix = []
|
3482
|
-
if ::Brick.config.path_prefix
|
3483
|
-
res_parts.unshift(::Brick.config.path_prefix)
|
3484
|
-
path_prefix << ::Brick.config.path_prefix
|
3027
|
+
index2 << aps
|
3485
3028
|
end
|
3486
3029
|
index = res_parts.map(&:underscore).join(separator)
|
3487
|
-
|
3488
|
-
|
3489
|
-
|
3490
|
-
|
3030
|
+
if separator == 'x'
|
3031
|
+
index = index.tr('_', 'x')
|
3032
|
+
else
|
3033
|
+
# Rails applies an _index suffix to that route when the resource name isn't something plural
|
3034
|
+
index << '_index' if mode != :singular && !not_path &&
|
3035
|
+
index == (
|
3036
|
+
index2 + [relation[:class_name][(relation&.fetch(:auto_prefixed_class, nil)&.length&.+ 2) || 0..-1]&.underscore&.tr('/', '_') || '_']
|
3037
|
+
).join(separator)
|
3038
|
+
end
|
3491
3039
|
index
|
3492
3040
|
end
|
3493
3041
|
|