brick 1.0.92 → 1.0.94
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 +89 -161
- data/lib/brick/frameworks/rails/engine.rb +80 -52
- data/lib/brick/version_number.rb +1 -1
- data/lib/brick.rb +38 -85
- metadata +2 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4b4de74edca4a5dd2c7d57d23dda2aa4cbc966402db4f2d0490dfeb741224dd5
|
4
|
+
data.tar.gz: fe905ed82bf8ae8f249e5ce3fe1646cb6366caf3343086ea8ba597e51525f521
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 45d2dda76345ab043c4e62d92b5e12a466ffec892e1ce63b540bd6427fcecfa777739732754d554f78b5803599fca1390fa2d38cb8f0acd9b0ffd8e5c6dd34aa
|
7
|
+
data.tar.gz: 65b73535a5874fe5b3c46fec455fe62857259c7d56b68241a729f2c928a47e0fdcc71988a3b7ead506de717fae71932007855efaf18dd8866b920da2c6d5c779
|
data/lib/brick/extensions.rb
CHANGED
@@ -42,21 +42,6 @@
|
|
42
42
|
# Dynamically create model or controller classes when needed
|
43
43
|
# ==========================================================
|
44
44
|
|
45
|
-
# By default all models indicate that they are not views
|
46
|
-
module Arel
|
47
|
-
class Table
|
48
|
-
def _arel_table_type
|
49
|
-
# AR < 4.2 doesn't have type_caster at all, so rely on an instance variable getting set
|
50
|
-
# AR 4.2 - 5.1 have buggy type_caster entries for the root node
|
51
|
-
instance_variable_get(:@_arel_table_type) ||
|
52
|
-
# 5.2-7.0 does type_caster just fine, no bugs there, but the property with the type differs:
|
53
|
-
# 5.2 has "types" as public, 6.0 "types" as private, and >= 6.1 "klass" as private.
|
54
|
-
((tc = send(:type_caster)) && tc.instance_variable_get(:@types)) ||
|
55
|
-
tc.send(:klass)
|
56
|
-
end
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
45
|
module ActiveRecord
|
61
46
|
class Base
|
62
47
|
def self.is_brick?
|
@@ -260,17 +245,20 @@ module ActiveRecord
|
|
260
245
|
end
|
261
246
|
|
262
247
|
def self.bt_link(assoc_name)
|
263
|
-
|
248
|
+
assoc_html_name = unless (assoc_name = assoc_name.to_s).camelize == name
|
249
|
+
CGI.escapeHTML(assoc_name)
|
250
|
+
end
|
264
251
|
model_path = ::Rails.application.routes.url_helpers.send("#{_brick_index}_path".to_sym)
|
252
|
+
model_path << "?#{self.inheritance_column}=#{self.name}" if self != base_class
|
265
253
|
av_class = Class.new.extend(ActionView::Helpers::UrlHelper)
|
266
254
|
av_class.extend(ActionView::Helpers::TagHelper) if ActionView.version < ::Gem::Version.new('7')
|
267
|
-
link = av_class.link_to(name, model_path)
|
268
|
-
|
255
|
+
link = av_class.link_to(assoc_html_name ? name : assoc_name, model_path)
|
256
|
+
assoc_html_name ? "#{assoc_name}-#{link}".html_safe : link
|
269
257
|
end
|
270
258
|
|
271
259
|
def self._brick_index(mode = nil)
|
272
260
|
tbl_parts = ((mode == :singular) ? table_name.singularize : table_name).split('.')
|
273
|
-
tbl_parts.shift if ::Brick.apartment_multitenant && tbl_parts.length > 1 && tbl_parts.first ==
|
261
|
+
tbl_parts.shift if ::Brick.apartment_multitenant && tbl_parts.length > 1 && tbl_parts.first == ::Brick.apartment_default_tenant
|
274
262
|
tbl_parts.unshift(::Brick.config.path_prefix) if ::Brick.config.path_prefix
|
275
263
|
index = tbl_parts.map(&:underscore).join('_')
|
276
264
|
# Rails applies an _index suffix to that route when the resource name is singular
|
@@ -407,86 +395,12 @@ module ActiveRecord
|
|
407
395
|
end
|
408
396
|
|
409
397
|
class Relation
|
410
|
-
|
411
|
-
|
412
|
-
# Links from ActiveRecord association pathing names over to real
|
413
|
-
# table correlation names built from AREL aliasing
|
398
|
+
# Links from ActiveRecord association pathing names over to real table correlation names
|
399
|
+
# that get chosen when the AREL AST tree is walked.
|
414
400
|
def brick_links
|
415
401
|
@brick_links ||= {}
|
416
402
|
end
|
417
403
|
|
418
|
-
# CLASS STUFF
|
419
|
-
def _recurse_arel(piece, prefix = '')
|
420
|
-
names = []
|
421
|
-
# Our JOINs mashup of nested arrays and hashes
|
422
|
-
# binding.pry if defined?(@arel)
|
423
|
-
case piece
|
424
|
-
when Array
|
425
|
-
names += piece.inject([]) { |s, v| s + _recurse_arel(v, prefix) }
|
426
|
-
when Hash
|
427
|
-
names += piece.inject([]) do |s, v|
|
428
|
-
new_prefix = "#{prefix}#{v.first}_"
|
429
|
-
s << [v.last.shift, new_prefix]
|
430
|
-
s + _recurse_arel(v.last, new_prefix)
|
431
|
-
end
|
432
|
-
|
433
|
-
# ActiveRecord AREL objects
|
434
|
-
when Arel::Nodes::Join # INNER or OUTER JOIN
|
435
|
-
# rubocop:disable Style/IdenticalConditionalBranches
|
436
|
-
if piece.right.is_a?(Arel::Table) # Came in from AR < 3.2?
|
437
|
-
# Arel 2.x and older is a little curious because these JOINs work "back to front".
|
438
|
-
# The left side here is either another earlier JOIN, or at the end of the whole tree, it is
|
439
|
-
# the first table.
|
440
|
-
names += _recurse_arel(piece.left)
|
441
|
-
# The right side here at the top is the very last table, and anywhere else down the tree it is
|
442
|
-
# the later "JOIN" table of this pair. (The table that comes after all the rest of the JOINs
|
443
|
-
# from the left side.)
|
444
|
-
names << [piece.right._arel_table_type, (piece.right.table_alias || piece.right.name)]
|
445
|
-
else # "Normal" setup, fed from a JoinSource which has an array of JOINs
|
446
|
-
# The left side is the "JOIN" table
|
447
|
-
names += _recurse_arel(table = piece.left)
|
448
|
-
# The expression on the right side is the "ON" clause
|
449
|
-
# on = piece.right.expr
|
450
|
-
# # Find the table which is not ourselves, and thus must be the "path" that led us here
|
451
|
-
# parent = piece.left == on.left.relation ? on.right.relation : on.left.relation
|
452
|
-
# binding.pry if piece.left.is_a?(Arel::Nodes::TableAlias)
|
453
|
-
if table.is_a?(Arel::Nodes::TableAlias)
|
454
|
-
@_arel_applied_aliases << (alias_name = table.right)
|
455
|
-
table = table.left
|
456
|
-
end
|
457
|
-
end
|
458
|
-
# rubocop:enable Style/IdenticalConditionalBranches
|
459
|
-
when Arel::Table # Table
|
460
|
-
names << [piece._arel_table_type, (piece.table_alias || piece.name)]
|
461
|
-
when Arel::Nodes::TableAlias # Alias
|
462
|
-
# Can get the real table name from: self._recurse_arel(piece.left)
|
463
|
-
names << [piece.left._arel_table_type, piece.right.to_s] # This is simply a string; the alias name itself
|
464
|
-
when Arel::Nodes::JoinSource # Leaving this until the end because AR < 3.2 doesn't know at all about JoinSource!
|
465
|
-
# The left side is the "FROM" table
|
466
|
-
names << (this_name = [piece.left._arel_table_type, (piece.left.table_alias || piece.left.name)])
|
467
|
-
# The right side is an array of all JOINs
|
468
|
-
piece.right.each { |join| names << _recurse_arel(join) }
|
469
|
-
end
|
470
|
-
names
|
471
|
-
end
|
472
|
-
|
473
|
-
# INSTANCE STUFF
|
474
|
-
def _arel_alias_names
|
475
|
-
@_arel_applied_aliases = []
|
476
|
-
# %%% If with Rails 3.1 and older you get "NoMethodError: undefined method `eq' for nil:NilClass"
|
477
|
-
# when trying to call relation.arel, then somewhere along the line while navigating a has_many
|
478
|
-
# relationship it can't find the proper foreign key.
|
479
|
-
core = arel.ast.cores.first
|
480
|
-
# Accommodate AR < 3.2
|
481
|
-
if core.froms.is_a?(Arel::Table)
|
482
|
-
# All recent versions of AR have #source which brings up an Arel::Nodes::JoinSource
|
483
|
-
_recurse_arel(core.source)
|
484
|
-
else
|
485
|
-
# With AR < 3.2, "froms" brings up the top node, an Arel::Nodes::InnerJoin
|
486
|
-
_recurse_arel(core.froms)
|
487
|
-
end
|
488
|
-
end
|
489
|
-
|
490
404
|
def brick_select(params, selects = [], order_by = nil, translations = {}, join_array = ::Brick::JoinArray.new)
|
491
405
|
is_add_bts = is_add_hms = true
|
492
406
|
|
@@ -502,13 +416,16 @@ module ActiveRecord
|
|
502
416
|
params.each do |k, v|
|
503
417
|
next if ['_brick_schema', '_brick_order', 'controller', 'action'].include?(k)
|
504
418
|
|
505
|
-
|
419
|
+
if (where_col = (ks = k.split('.')).last)[-1] == '!'
|
420
|
+
where_col = where_col[0..-2]
|
421
|
+
end
|
422
|
+
case ks.length
|
506
423
|
when 1
|
507
|
-
next unless klass.column_names.any?(
|
424
|
+
next unless klass.column_names.any?(where_col) || klass._brick_get_fks.include?(where_col)
|
508
425
|
when 2
|
509
426
|
assoc_name = ks.first.to_sym
|
510
427
|
# Make sure it's a good association name and that the model has that column name
|
511
|
-
next unless klass.reflect_on_association(assoc_name)&.klass&.column_names&.any?(
|
428
|
+
next unless klass.reflect_on_association(assoc_name)&.klass&.column_names&.any?(where_col)
|
512
429
|
|
513
430
|
join_array[assoc_name] = nil # Store this relation name in our special collection for .joins()
|
514
431
|
is_distinct = true
|
@@ -546,8 +463,19 @@ module ActiveRecord
|
|
546
463
|
|
547
464
|
if join_array.present?
|
548
465
|
left_outer_joins!(join_array)
|
549
|
-
#
|
550
|
-
|
466
|
+
# Touching AREL AST walks the JoinDependency tree, and in that process uses our
|
467
|
+
# "brick_links" patch to find how every AR chain of association names relates to exact
|
468
|
+
# table correlation names chosen by AREL. We use a duplicate relation object for this
|
469
|
+
# because an important side-effect of referencing the AST is that the @arel instance
|
470
|
+
# variable gets set, and this is a signal to ActiveRecord that a relation has now
|
471
|
+
# become immutable. (We aren't quite ready for our "real deal" relation object to be
|
472
|
+
# set in stone ... still need to add .select(), and possibly .where() and .order()
|
473
|
+
# things ... also if there are any HM counts then an OUTER JOIN for each of them out
|
474
|
+
# to a derived table to do that counting. All of these things need to know proper
|
475
|
+
# table correlation names, which will now become available in brick_links on the
|
476
|
+
# rel_dupe object.)
|
477
|
+
(rel_dupe = dup).arel.ast
|
478
|
+
|
551
479
|
core_selects = selects.dup
|
552
480
|
id_for_tables = Hash.new { |h, k| h[k] = [] }
|
553
481
|
field_tbl_names = Hash.new { |h, k| h[k] = {} }
|
@@ -609,15 +537,15 @@ module ActiveRecord
|
|
609
537
|
next unless (tbl_name = rel_dupe.brick_links[v.first.to_s]&.split('.')&.last)
|
610
538
|
|
611
539
|
# If it's Oracle, quote any AREL aliases that had been applied
|
612
|
-
tbl_name = "\"#{tbl_name}\"" if ::Brick.is_oracle && rel_dupe.
|
540
|
+
tbl_name = "\"#{tbl_name}\"" if ::Brick.is_oracle && rel_dupe.brick_links.values.include?(tbl_name)
|
613
541
|
field_tbl_name = nil
|
614
542
|
v1.map { |x| [x[0..-2].map(&:to_s).join('.'), x.last] }.each_with_index do |sel_col, idx|
|
615
543
|
field_tbl_name = rel_dupe.brick_links[sel_col.first].split('.').last
|
616
544
|
# If it's Oracle, quote any AREL aliases that had been applied
|
617
|
-
field_tbl_name = "\"#{field_tbl_name}\"" if ::Brick.is_oracle && rel_dupe.
|
545
|
+
field_tbl_name = "\"#{field_tbl_name}\"" if ::Brick.is_oracle && rel_dupe.brick_links.values.include?(field_tbl_name)
|
618
546
|
|
619
547
|
# Postgres can not use DISTINCT with any columns that are XML, so for any of those just convert to text
|
620
|
-
is_xml = is_distinct && Brick.relations[
|
548
|
+
is_xml = is_distinct && Brick.relations[k1.table_name]&.[](:cols)&.[](sel_col.last)&.first&.start_with?('xml')
|
621
549
|
# If it's not unique then also include the belongs_to association name before the column name
|
622
550
|
if used_col_aliases.key?(col_alias = "br_fk_#{v.first}__#{sel_col.last}")
|
623
551
|
col_alias = "br_fk_#{v.first}__#{v1[idx][-2..-1].map(&:to_s).join('__')}"
|
@@ -786,16 +714,23 @@ JOIN (SELECT #{hm_selects.map { |s| "#{'br_t0.' if from_clause}#{s}" }.join(', '
|
|
786
714
|
|
787
715
|
unless wheres.empty?
|
788
716
|
# Rewrite the wheres to reference table and correlation names built out by AREL
|
717
|
+
where_nots = {}
|
789
718
|
wheres2 = wheres.each_with_object({}) do |v, s|
|
719
|
+
is_not = if v.first[-1] == '!'
|
720
|
+
v[0] = v[0][0..-2] # Take off ending ! from column name
|
721
|
+
end
|
790
722
|
if (v_parts = v.first.split('.')).length == 1
|
791
|
-
s[v.first] = v.last
|
723
|
+
(is_not ? where_nots : s)[v.first] = v.last
|
792
724
|
else
|
793
725
|
tbl_name = rel_dupe.brick_links[v_parts.first].split('.').last
|
794
|
-
s["#{tbl_name}.#{v_parts.last}"] = v.last
|
726
|
+
(is_not ? where_nots : s)["#{tbl_name}.#{v_parts.last}"] = v.last
|
795
727
|
end
|
796
728
|
end
|
797
729
|
if respond_to?(:where!)
|
798
|
-
where!(wheres2)
|
730
|
+
where!(wheres2) if wheres2.present?
|
731
|
+
if where_nots.present?
|
732
|
+
self.where_clause += WhereClause.new(predicate_builder.build_from_hash(where_nots)).invert
|
733
|
+
end
|
799
734
|
else # AR < 4.0
|
800
735
|
self.where_values << build_where(wheres2)
|
801
736
|
end
|
@@ -1091,7 +1026,8 @@ Module.class_exec do
|
|
1091
1026
|
(table_name = singular_table_name.pluralize),
|
1092
1027
|
::Brick.is_oracle ? class_name.upcase : class_name,
|
1093
1028
|
(plural_class_name = class_name.pluralize)].find { |s| Brick.db_schemas&.include?(s) }&.camelize ||
|
1094
|
-
(::Brick.config.sti_namespace_prefixes&.key?("::#{class_name}::") && class_name)
|
1029
|
+
(::Brick.config.sti_namespace_prefixes&.key?("::#{class_name}::") && class_name) ||
|
1030
|
+
(::Brick.config.table_name_prefixes&.values.include?(class_name) && class_name))
|
1095
1031
|
return self.const_get(schema_name) if self.const_defined?(schema_name)
|
1096
1032
|
|
1097
1033
|
# Build out a module for the schema if it's namespaced
|
@@ -1142,6 +1078,7 @@ class Object
|
|
1142
1078
|
private
|
1143
1079
|
|
1144
1080
|
def build_model(relations, base_module, base_name, class_name, inheritable_name = nil)
|
1081
|
+
tnp = ::Brick.config.table_name_prefixes&.find { |p| p.last == base_module.name }&.first
|
1145
1082
|
if (base_model = ::Brick.config.sti_namespace_prefixes&.fetch("::#{base_module.name}::", nil)&.constantize) || # Are we part of an auto-STI namespace? ...
|
1146
1083
|
base_module != Object # ... or otherwise already in some namespace?
|
1147
1084
|
schema_name = [(singular_schema_name = base_name.underscore),
|
@@ -1163,11 +1100,11 @@ class Object
|
|
1163
1100
|
table_name = if (base_model = ::Brick.sti_models[model_name]&.fetch(:base, nil) || ::Brick.existing_stis[model_name]&.constantize)
|
1164
1101
|
base_model.table_name
|
1165
1102
|
else
|
1166
|
-
ActiveSupport::Inflector.pluralize(singular_table_name)
|
1103
|
+
"#{tnp}#{ActiveSupport::Inflector.pluralize(singular_table_name)}"
|
1167
1104
|
end
|
1168
1105
|
if ::Brick.apartment_multitenant &&
|
1169
1106
|
Apartment.excluded_models.include?(table_name.singularize.camelize)
|
1170
|
-
schema_name =
|
1107
|
+
schema_name = ::Brick.apartment_default_tenant
|
1171
1108
|
end
|
1172
1109
|
# Maybe, just maybe there's a database table that will satisfy this need
|
1173
1110
|
if (matching = [table_name, singular_table_name, plural_class_name, model_name, table_name.titleize].find { |m| relations.key?(schema_name ? "#{schema_name}.#{m}" : m) })
|
@@ -1178,7 +1115,7 @@ class Object
|
|
1178
1115
|
|
1179
1116
|
def build_model_worker(schema_name, inheritable_name, model_name, singular_table_name, table_name, relations, matching)
|
1180
1117
|
if ::Brick.apartment_multitenant &&
|
1181
|
-
schema_name ==
|
1118
|
+
schema_name == ::Brick.apartment_default_tenant
|
1182
1119
|
relation = relations["#{schema_name}.#{matching}"]
|
1183
1120
|
end
|
1184
1121
|
full_name = if relation || schema_name.blank?
|
@@ -1380,7 +1317,7 @@ class Object
|
|
1380
1317
|
# If it's multitenant with something like: public.____ ...
|
1381
1318
|
if (it_parts = inverse_table.split('.')).length > 1 &&
|
1382
1319
|
::Brick.apartment_multitenant &&
|
1383
|
-
it_parts.first ==
|
1320
|
+
it_parts.first == ::Brick.apartment_default_tenant
|
1384
1321
|
it_parts.shift # ... then ditch the generic schema name
|
1385
1322
|
end
|
1386
1323
|
inverse_assoc_name, _x = _brick_get_hm_assoc_name(relations[inverse_table], inverse, it_parts.join('_').singularize)
|
@@ -1477,7 +1414,7 @@ class Object
|
|
1477
1414
|
instance_variable_set(:@resources, ::Brick.get_status_of_resources)
|
1478
1415
|
end
|
1479
1416
|
self.define_method :orphans do
|
1480
|
-
instance_variable_set(:@orphans, ::Brick.find_orphans(::Brick.set_db_schema(params)))
|
1417
|
+
instance_variable_set(:@orphans, ::Brick.find_orphans(::Brick.set_db_schema(params).first))
|
1481
1418
|
end
|
1482
1419
|
return [new_controller_class, code + "end # BrickGem controller\n"]
|
1483
1420
|
when 'BrickOpenapi'
|
@@ -1495,7 +1432,7 @@ class Object
|
|
1495
1432
|
api_params = referrer_params&.to_h
|
1496
1433
|
end
|
1497
1434
|
end
|
1498
|
-
::Brick.set_db_schema(params || api_params)
|
1435
|
+
_schema, @_is_show_schema_list = ::Brick.set_db_schema(params || api_params)
|
1499
1436
|
|
1500
1437
|
if is_openapi
|
1501
1438
|
json = { 'openapi': '3.0.1', 'info': { 'title': Rswag::Ui.config.config_object[:urls].last&.fetch(:name, 'API documentation'), 'version': ::Brick.config.api_version },
|
@@ -1597,7 +1534,6 @@ class Object
|
|
1597
1534
|
end
|
1598
1535
|
|
1599
1536
|
unless is_openapi
|
1600
|
-
::Brick.set_db_schema
|
1601
1537
|
_, order_by_txt = model._brick_calculate_ordering(default_ordering(table_name, pk)) if pk
|
1602
1538
|
code << " def index\n"
|
1603
1539
|
code << " @#{table_name.pluralize} = #{model.name}#{pk&.present? ? ".order(#{order_by_txt.join(', ')})" : '.all'}\n"
|
@@ -1610,7 +1546,7 @@ class Object
|
|
1610
1546
|
code << " #{find_by_name = "find_#{singular_table_name}"}\n"
|
1611
1547
|
code << " end\n"
|
1612
1548
|
self.define_method :show do
|
1613
|
-
::Brick.set_db_schema(params)
|
1549
|
+
_schema, @_is_show_schema_list = ::Brick.set_db_schema(params)
|
1614
1550
|
instance_variable_set("@#{singular_table_name}".to_sym, find_obj)
|
1615
1551
|
end
|
1616
1552
|
end
|
@@ -1621,7 +1557,7 @@ class Object
|
|
1621
1557
|
code << " @#{singular_table_name} = #{model.name}.new\n"
|
1622
1558
|
code << " end\n"
|
1623
1559
|
self.define_method :new do
|
1624
|
-
::Brick.set_db_schema(params)
|
1560
|
+
_schema, @_is_show_schema_list = ::Brick.set_db_schema(params)
|
1625
1561
|
instance_variable_set("@#{singular_table_name}".to_sym, model.new)
|
1626
1562
|
end
|
1627
1563
|
|
@@ -1660,7 +1596,7 @@ class Object
|
|
1660
1596
|
code << " #{find_by_name}\n"
|
1661
1597
|
code << " end\n"
|
1662
1598
|
self.define_method :edit do
|
1663
|
-
::Brick.set_db_schema(params)
|
1599
|
+
_schema, @_is_show_schema_list = ::Brick.set_db_schema(params)
|
1664
1600
|
instance_variable_set("@#{singular_table_name}".to_sym, find_obj)
|
1665
1601
|
end
|
1666
1602
|
|
@@ -1889,13 +1825,22 @@ end.class_exec do
|
|
1889
1825
|
s[row.first] = { dt: row.last } unless ['information_schema', 'pg_catalog', 'pg_toast', 'heroku_ext',
|
1890
1826
|
'INFORMATION_SCHEMA', 'sys'].include?(row.first)
|
1891
1827
|
end
|
1892
|
-
if (
|
1893
|
-
|
1894
|
-
|
1895
|
-
|
1896
|
-
|
1897
|
-
|
1898
|
-
|
1828
|
+
if (possible_schemas = (multitenancy = ::Brick.config.schema_behavior&.[](:multitenant)) &&
|
1829
|
+
multitenancy&.[](:schema_to_analyse))
|
1830
|
+
possible_schemas = [possible_schemas] unless possible_schemas.is_a?(Array)
|
1831
|
+
if (possible_schema = possible_schemas.find { |ps| ::Brick.db_schemas.key?(ps) })
|
1832
|
+
::Brick.default_schema = ::Brick.apartment_default_tenant
|
1833
|
+
schema = possible_schema
|
1834
|
+
orig_schema = ActiveRecord::Base.execute_sql('SELECT current_schemas(true)').first['current_schemas'][1..-2].split(',')
|
1835
|
+
ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?", schema)
|
1836
|
+
elsif Rails.env == 'test' # When testing, just find the most recently-created schema
|
1837
|
+
::Brick.default_schema = schema = ::Brick.db_schemas.to_a.sort { |a, b| b.last[:dt] <=> a.last[:dt] }.first.first
|
1838
|
+
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}."
|
1839
|
+
orig_schema = ActiveRecord::Base.execute_sql('SELECT current_schemas(true)').first['current_schemas'][1..-2].split(',')
|
1840
|
+
ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?", schema)
|
1841
|
+
else
|
1842
|
+
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. ***"
|
1843
|
+
end
|
1899
1844
|
end
|
1900
1845
|
when 'Mysql2'
|
1901
1846
|
::Brick.default_schema = schema = ActiveRecord::Base.connection.current_database
|
@@ -1918,24 +1863,6 @@ end.class_exec do
|
|
1918
1863
|
|
1919
1864
|
::Brick.db_schemas ||= {}
|
1920
1865
|
|
1921
|
-
if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
|
1922
|
-
if (possible_schemas = ::Brick.config.schema_behavior&.[](:multitenant)&.[](:schema_to_analyse))
|
1923
|
-
possible_schemas = [possible_schemas] unless possible_schemas.is_a?(Array)
|
1924
|
-
if (possible_schema = possible_schemas.find { |ps| ::Brick.db_schemas.key?(ps) })
|
1925
|
-
::Brick.default_schema = schema = possible_schema
|
1926
|
-
orig_schema = ActiveRecord::Base.execute_sql('SELECT current_schemas(true)').first['current_schemas'][1..-2].split(',')
|
1927
|
-
ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?", schema)
|
1928
|
-
elsif Rails.env == 'test' # When testing, just find the most recently-created schema
|
1929
|
-
::Brick.default_schema = schema = ::Brick.db_schemas.to_a.sort { |a, b| b.last[:dt] <=> a.last[:dt] }.first.first
|
1930
|
-
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}."
|
1931
|
-
orig_schema = ActiveRecord::Base.execute_sql('SELECT current_schemas(true)').first['current_schemas'][1..-2].split(',')
|
1932
|
-
ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?", schema)
|
1933
|
-
else
|
1934
|
-
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. ***"
|
1935
|
-
end
|
1936
|
-
end
|
1937
|
-
end
|
1938
|
-
|
1939
1866
|
# %%% Retrieve internal ActiveRecord table names like this:
|
1940
1867
|
# ActiveRecord::Base.internal_metadata_table_name, ActiveRecord::Base.schema_migrations_table_name
|
1941
1868
|
# For if it's not SQLite -- so this is the Postgres and MySQL version
|
@@ -1948,7 +1875,7 @@ end.class_exec do
|
|
1948
1875
|
# If Apartment gem lists the table as being associated with a non-tenanted model then use whatever it thinks
|
1949
1876
|
# is the default schema, usually 'public'.
|
1950
1877
|
schema_name = if ::Brick.config.schema_behavior[:multitenant]
|
1951
|
-
|
1878
|
+
::Brick.apartment_default_tenant if apartment_excluded&.include?(r['relation_name'].singularize.camelize)
|
1952
1879
|
elsif ![schema, 'public'].include?(r['schema'])
|
1953
1880
|
r['schema']
|
1954
1881
|
end
|
@@ -2099,16 +2026,16 @@ ORDER BY 1, 2, c.internal_column_id, acc.position"
|
|
2099
2026
|
fk = fk.values unless fk.is_a?(Array)
|
2100
2027
|
# Multitenancy makes things a little more general overall, except for non-tenanted tables
|
2101
2028
|
if apartment_excluded&.include?(::Brick.namify(fk[1]).singularize.camelize)
|
2102
|
-
fk[0] =
|
2103
|
-
elsif (is_postgres && (fk[0] == 'public' || (
|
2029
|
+
fk[0] = ::Brick.apartment_default_tenant
|
2030
|
+
elsif (is_postgres && (fk[0] == 'public' || (multitenancy && fk[0] == schema))) ||
|
2104
2031
|
(::Brick.is_oracle && fk[0] == schema) ||
|
2105
2032
|
(is_mssql && fk[0] == 'dbo') ||
|
2106
2033
|
(!is_postgres && !::Brick.is_oracle && !is_mssql && ['mysql', 'performance_schema', 'sys'].exclude?(fk[0]))
|
2107
2034
|
fk[0] = nil
|
2108
2035
|
end
|
2109
2036
|
if apartment_excluded&.include?(fk[4].singularize.camelize)
|
2110
|
-
fk[3] =
|
2111
|
-
elsif (is_postgres && (fk[3] == 'public' || (
|
2037
|
+
fk[3] = ::Brick.apartment_default_tenant
|
2038
|
+
elsif (is_postgres && (fk[3] == 'public' || (multitenancy && fk[3] == schema))) ||
|
2112
2039
|
(::Brick.is_oracle && fk[3] == schema) ||
|
2113
2040
|
(is_mssql && fk[3] == 'dbo') ||
|
2114
2041
|
(!is_postgres && !::Brick.is_oracle && !is_mssql && ['mysql', 'performance_schema', 'sys'].exclude?(fk[3]))
|
@@ -2126,7 +2053,7 @@ ORDER BY 1, 2, c.internal_column_id, acc.position"
|
|
2126
2053
|
relations.each do |k, v|
|
2127
2054
|
rel_name = k.split('.').map { |rel_part| ::Brick.namify(rel_part, :underscore) }
|
2128
2055
|
schema_names = rel_name[0..-2]
|
2129
|
-
schema_names.shift if ::Brick.apartment_multitenant && schema_names.first ==
|
2056
|
+
schema_names.shift if ::Brick.apartment_multitenant && schema_names.first == ::Brick.apartment_default_tenant
|
2130
2057
|
v[:schema] = schema_names.join('.') unless schema_names.empty?
|
2131
2058
|
# %%% If more than one schema has the same table name, will need to add a schema name prefix to have uniqueness
|
2132
2059
|
v[:resource] = rel_name.last
|
@@ -2137,7 +2064,7 @@ ORDER BY 1, 2, c.internal_column_id, acc.position"
|
|
2137
2064
|
end
|
2138
2065
|
::Brick.load_additional_references if initializer_loaded
|
2139
2066
|
|
2140
|
-
if orig_schema && (orig_schema = (orig_schema - ['pg_catalog', 'heroku_ext']).first)
|
2067
|
+
if orig_schema && (orig_schema = (orig_schema - ['pg_catalog', 'pg_toast', 'heroku_ext']).first)
|
2141
2068
|
puts "Now switching back to \"#{orig_schema}\" schema."
|
2142
2069
|
ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?", orig_schema)
|
2143
2070
|
end
|
@@ -2175,7 +2102,7 @@ ORDER BY 1, 2, c.internal_column_id, acc.position"
|
|
2175
2102
|
AND kcu.column_name = c.column_name#{"
|
2176
2103
|
-- AND kcu.position_in_unique_constraint IS NULL" unless is_mssql}
|
2177
2104
|
WHERE t.table_schema #{is_postgres || is_mssql ?
|
2178
|
-
"NOT IN ('information_schema', 'pg_catalog',
|
2105
|
+
"NOT IN ('information_schema', 'pg_catalog', 'pg_toast', 'heroku_ext',
|
2179
2106
|
'INFORMATION_SCHEMA', 'sys')"
|
2180
2107
|
:
|
2181
2108
|
"= '#{ActiveRecord::Base.connection.current_database.tr("'", "''")}'"}#{"
|
@@ -2274,7 +2201,7 @@ module Brick
|
|
2274
2201
|
for_tbl = fk[1]
|
2275
2202
|
fk_namified = ::Brick.namify(fk[1])
|
2276
2203
|
apartment = Object.const_defined?('Apartment') && Apartment
|
2277
|
-
fk[0] =
|
2204
|
+
fk[0] = ::Brick.apartment_default_tenant if apartment && apartment.excluded_models.include?(fk_namified.singularize.camelize)
|
2278
2205
|
fk[1] = "#{fk[0]}.#{fk[1]}" if fk[0] # && fk[0] != ::Brick.default_schema
|
2279
2206
|
bts = (relation = relations.fetch(fk[1], nil))&.fetch(:fks) { relation[:fks] = {} }
|
2280
2207
|
|
@@ -2290,7 +2217,7 @@ module Brick
|
|
2290
2217
|
# If Apartment gem lists the primary table as being associated with a non-tenanted model
|
2291
2218
|
# then use 'public' schema for the primary table
|
2292
2219
|
if apartment && apartment&.excluded_models.include?(fk[4].singularize.camelize)
|
2293
|
-
fk[3] =
|
2220
|
+
fk[3] = ::Brick.apartment_default_tenant
|
2294
2221
|
true
|
2295
2222
|
end
|
2296
2223
|
else
|
@@ -2365,7 +2292,7 @@ module Brick
|
|
2365
2292
|
end
|
2366
2293
|
assoc_hm[:alternate_name] = "#{assoc_hm[:alternate_name]}_#{bt_assoc_name}" unless assoc_hm[:alternate_name] == bt_assoc_name
|
2367
2294
|
else
|
2368
|
-
inv_tbl = if ::Brick.config.schema_behavior[:multitenant] && apartment && fk[0] ==
|
2295
|
+
inv_tbl = if ::Brick.config.schema_behavior[:multitenant] && apartment && fk[0] == ::Brick.apartment_default_tenant
|
2369
2296
|
for_tbl
|
2370
2297
|
else
|
2371
2298
|
fk[1]
|
@@ -2386,7 +2313,7 @@ module Brick
|
|
2386
2313
|
rails_root = ::Rails.root.to_s
|
2387
2314
|
migrations = if Dir.exist?(mig_path = ActiveRecord::Migrator.migrations_paths.first || "#{rails_root}/db/migrate")
|
2388
2315
|
Dir["#{mig_path}/**/*.rb"].each_with_object(Hash.new { |h, k| h[k] = [] }) do |v, s|
|
2389
|
-
File.read(v).split("\n").
|
2316
|
+
File.read(v).split("\n").each_with_index do |line, line_idx|
|
2390
2317
|
# For all non-commented lines, look for any that have "create_table", "alter_table", or "drop_table"
|
2391
2318
|
if !line.lstrip.start_with?('#') &&
|
2392
2319
|
(idx = (line.index('create_table ') || line.index('create_table('))&.+(13)) ||
|
@@ -2394,8 +2321,9 @@ module Brick
|
|
2394
2321
|
(idx = (line.index('drop_table ') || line.index('drop_table('))&.+(11))
|
2395
2322
|
tbl = line[idx..-1].match(/([:'"\w\.]+)/)&.captures&.first
|
2396
2323
|
if tbl
|
2397
|
-
|
2398
|
-
|
2324
|
+
v = v[(rails_root.length)..-1] if v.start_with?(rails_root)
|
2325
|
+
v = v[1..-1] if v.start_with?('/')
|
2326
|
+
s[tbl.tr(':\'"', '').pluralize] << [v, line_idx + 1]
|
2399
2327
|
end
|
2400
2328
|
end
|
2401
2329
|
end
|
@@ -2424,7 +2352,7 @@ module Brick
|
|
2424
2352
|
end
|
2425
2353
|
::Brick.relations.keys.map do |v|
|
2426
2354
|
tbl_parts = v.split('.')
|
2427
|
-
tbl_parts.shift if ::Brick.apartment_multitenant && tbl_parts.length > 1 && tbl_parts.first ==
|
2355
|
+
tbl_parts.shift if ::Brick.apartment_multitenant && tbl_parts.length > 1 && tbl_parts.first == ::Brick.apartment_default_tenant
|
2428
2356
|
res = tbl_parts.join('.')
|
2429
2357
|
[v, (model = models[res])&.last&.table_name, migrations&.fetch(res, nil), model&.first]
|
2430
2358
|
end
|
@@ -2454,14 +2382,14 @@ module Brick
|
|
2454
2382
|
|
2455
2383
|
# Locate orphaned records
|
2456
2384
|
def find_orphans(multi_schema)
|
2457
|
-
is_default_schema = multi_schema&.==(
|
2385
|
+
is_default_schema = multi_schema&.==(::Brick.apartment_default_tenant)
|
2458
2386
|
relations.each_with_object([]) do |v, s|
|
2459
2387
|
frn_tbl = v.first
|
2460
2388
|
next if (relation = v.last).key?(:isView) || config.exclude_tables.include?(frn_tbl) ||
|
2461
2389
|
!(for_pk = (relation[:pkey].values.first&.first))
|
2462
2390
|
|
2463
2391
|
is_default_frn_schema = !is_default_schema && multi_schema &&
|
2464
|
-
((frn_parts = frn_tbl.split('.')).length > 1 && frn_parts.first)&.==(
|
2392
|
+
((frn_parts = frn_tbl.split('.')).length > 1 && frn_parts.first)&.==(::Brick.apartment_default_tenant)
|
2465
2393
|
relation[:fks].select { |_k, assoc| assoc[:is_bt] }.each do |_k, bt|
|
2466
2394
|
begin
|
2467
2395
|
if bt.key?(:polymorphic)
|
@@ -2477,7 +2405,7 @@ module Brick
|
|
2477
2405
|
# Skip if database is multitenant, we're not focused on "public", and the foreign and primary tables
|
2478
2406
|
# are both in the "public" schema
|
2479
2407
|
next if is_default_frn_schema &&
|
2480
|
-
((pri_parts = pri_tbl&.split('.'))&.length > 1 && pri_parts.first)&.==(
|
2408
|
+
((pri_parts = pri_tbl&.split('.'))&.length > 1 && pri_parts.first)&.==(::Brick.apartment_default_tenant)
|
2481
2409
|
|
2482
2410
|
selects << "SELECT '#{pri_tbl}' AS pri_tbl, frn.#{fk_type_col} AS pri_type, frn.#{fk_id_col} AS pri_id, frn.#{for_pk} AS frn_id
|
2483
2411
|
FROM #{frn_tbl} AS frn
|
@@ -2496,7 +2424,7 @@ module Brick
|
|
2496
2424
|
# are both in the "public" schema
|
2497
2425
|
pri_tbl = bt.key?(:inverse_table) && bt[:inverse_table]
|
2498
2426
|
next if is_default_frn_schema &&
|
2499
|
-
((pri_parts = pri_tbl&.split('.'))&.length > 1 && pri_parts.first)&.==(
|
2427
|
+
((pri_parts = pri_tbl&.split('.'))&.length > 1 && pri_parts.first)&.==(::Brick.apartment_default_tenant)
|
2500
2428
|
|
2501
2429
|
pri_pk = relations[pri_tbl].fetch(:pkey, nil)&.values&.first&.first ||
|
2502
2430
|
_class_pk(pri_tbl, multi_schema)
|
@@ -141,10 +141,10 @@ module Brick
|
|
141
141
|
next unless @_brick_model.instance_methods.include?(through) &&
|
142
142
|
(associative = @_brick_model._br_associatives.fetch(hm.first, nil))
|
143
143
|
|
144
|
-
tbl_nm = if (source = hm_assoc.source_reflection).macro == :
|
145
|
-
hm_assoc.through_reflection&.name # for standard HMT, which is HM -> BT
|
146
|
-
else
|
144
|
+
tbl_nm = if (source = hm_assoc.source_reflection).macro == :has_many
|
147
145
|
source.inverse_of&.name # For HM -> HM style HMT
|
146
|
+
else # belongs_to or has_one
|
147
|
+
hm_assoc.through_reflection&.name # for standard HMT, which is HM -> BT
|
148
148
|
end
|
149
149
|
# If there is no inverse available for the source belongs_to association, make one based on the class name
|
150
150
|
unless tbl_nm
|
@@ -192,18 +192,17 @@ module Brick
|
|
192
192
|
end
|
193
193
|
end
|
194
194
|
|
195
|
-
apartment_default_schema = ::Brick.apartment_multitenant &&
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
195
|
+
apartment_default_schema = ::Brick.apartment_multitenant && ::Brick.apartment_default_tenant
|
196
|
+
if ::Brick.apartment_multitenant && ::Brick.db_schemas.length > 1
|
197
|
+
schema_options = +'<select id="schema"><% if @_is_show_schema_list %>'
|
198
|
+
::Brick.db_schemas.keys.each { |v| schema_options << "\n <option value=\"#{v}\">#{v}</option>" }
|
199
|
+
schema_options << "\n<% else %><option selected value=\"#{Apartment::Tenant.current}\">#{Apartment::Tenant.current}</option>\n"
|
200
|
+
schema_options << '<% end %></select>'
|
201
|
+
end
|
202
202
|
# %%% If we are not auto-creating controllers (or routes) then omit by default, and if enabled anyway, such as in a development
|
203
203
|
# environment or whatever, then get either the controllers or routes list instead
|
204
204
|
prefix = "#{::Brick.config.path_prefix}/" if ::Brick.config.path_prefix
|
205
205
|
table_options = (::Brick.relations.keys - ::Brick.config.exclude_tables).each_with_object({}) do |tbl, s|
|
206
|
-
binding.pry if tbl.is_a?(Symbol)
|
207
206
|
if (tbl_parts = tbl.split('.')).first == apartment_default_schema
|
208
207
|
tbl = tbl_parts.last
|
209
208
|
end
|
@@ -466,6 +465,22 @@ var #{table_name}HtColumns;
|
|
466
465
|
// This PageTransitionEvent fires when the page first loads, as well as after any other history
|
467
466
|
// transition such as when using the browser's Back and Forward buttons.
|
468
467
|
window.addEventListener(\"pageshow\", function() {
|
468
|
+
if (tblSelect) { // Always present
|
469
|
+
var i = #{::Brick.config.path_prefix ? '0' : 'schemaSelect ? 1 : 0'},
|
470
|
+
changeoutList = changeout(location.href);
|
471
|
+
for (; i < changeoutList.length; ++i) {
|
472
|
+
tblSelect.value = changeoutList[i];
|
473
|
+
if (tblSelect.value !== \"\") break;
|
474
|
+
}
|
475
|
+
|
476
|
+
tblSelect.addEventListener(\"change\", function () {
|
477
|
+
var lhr = changeout(location.href, null, this.value);
|
478
|
+
if (brickSchema)
|
479
|
+
lhr = changeout(lhr, \"_brick_schema\", schemaSelect.value);
|
480
|
+
location.href = lhr;
|
481
|
+
});
|
482
|
+
}
|
483
|
+
|
469
484
|
if (schemaSelect && schemaSelect.options.length > 1) { // First drop-down is only present if multitenant
|
470
485
|
brickSchema = changeout(location.href, \"_brick_schema\");
|
471
486
|
if (brickSchema) {
|
@@ -478,6 +493,7 @@ window.addEventListener(\"pageshow\", function() {
|
|
478
493
|
location.href = changeout(location.href, \"_brick_schema\", this.value, tblSelect.value);
|
479
494
|
});
|
480
495
|
}
|
496
|
+
|
481
497
|
[... document.getElementsByTagName(\"FORM\")].forEach(function (form) {
|
482
498
|
if (brickSchema)
|
483
499
|
form.action = changeout(form.action, \"_brick_schema\", brickSchema);
|
@@ -489,22 +505,6 @@ window.addEventListener(\"pageshow\", function() {
|
|
489
505
|
return true;
|
490
506
|
});
|
491
507
|
});
|
492
|
-
|
493
|
-
if (tblSelect) { // Always present
|
494
|
-
var i = #{::Brick.config.path_prefix ? '0' : 'schemaSelect ? 1 : 0'},
|
495
|
-
changeoutList = changeout(location.href);
|
496
|
-
for (; i < changeoutList.length; ++i) {
|
497
|
-
tblSelect.value = changeoutList[i];
|
498
|
-
if (tblSelect.value !== \"\") break;
|
499
|
-
}
|
500
|
-
|
501
|
-
tblSelect.addEventListener(\"change\", function () {
|
502
|
-
var lhr = changeout(location.href, null, this.value);
|
503
|
-
if (brickSchema)
|
504
|
-
lhr = changeout(lhr, \"_brick_schema\", schemaSelect.value);
|
505
|
-
location.href = lhr;
|
506
|
-
});
|
507
|
-
}
|
508
508
|
});
|
509
509
|
|
510
510
|
// Add \"Are you sure?\" behaviour to any data-confirm buttons out there
|
@@ -834,7 +834,7 @@ erDiagram
|
|
834
834
|
</head>
|
835
835
|
<body>
|
836
836
|
<p style=\"color: green\"><%= notice %></p>#{"
|
837
|
-
|
837
|
+
#{schema_options}" if schema_options}
|
838
838
|
<select id=\"tbl\">#{table_options}</select>
|
839
839
|
<table id=\"resourceName\"><tr>
|
840
840
|
<td><h1>#{model_name}</h1></td>
|
@@ -910,7 +910,9 @@ erDiagram
|
|
910
910
|
end
|
911
911
|
unless @_brick_sequence # If no sequence is defined, start with all inclusions
|
912
912
|
cust_cols = #{model_name}._br_cust_cols
|
913
|
-
|
913
|
+
# HOT columns, kept as symbols
|
914
|
+
hots = #{model_name}._br_bt_descrip.keys.select { |k| bts.key?(k) }
|
915
|
+
@_brick_sequence = col_keys + cust_cols.keys + hots + #{(hms_keys).inspect}.reject { |assoc_name| @_brick_incl&.exclude?(assoc_name) }
|
914
916
|
end
|
915
917
|
@_brick_sequence.reject! { |nm| @_brick_excl.include?(nm) } if @_brick_excl # Reject exclusions
|
916
918
|
@_brick_sequence.each_with_object(+'') do |col_name, s|
|
@@ -927,8 +929,12 @@ erDiagram
|
|
927
929
|
elsif col # HM column
|
928
930
|
s << \"<th#\{' x-order=\"' + col_name + '\"' if true}>#\{col[2]} \"
|
929
931
|
s << (col.first ? \"#\{col[3]}\" : \"#\{link_to(col[3], send(\"#\{col[1]._brick_index}_path\"))}\")
|
930
|
-
elsif
|
932
|
+
elsif cust_cols.key?(col_name) # Custom column
|
931
933
|
s << \"<th x-order=\\\"#\{col_name}\\\">#\{col_name}\"
|
934
|
+
elsif col_name.is_a?(Symbol) && (hot = bts[col_name]) # has_one :through
|
935
|
+
s << \"<th x-order=\\\"#\{hot.first.to_s}\\\">HOT \" +
|
936
|
+
hot[1].map { |hot_pair| hot_pair.first.bt_link(col_name) }.join(' ')
|
937
|
+
hot[1].first
|
932
938
|
else # Bad column name!
|
933
939
|
s << \"<th title=\\\"<< Unknown column >>\\\">#\{col_name}\"
|
934
940
|
end
|
@@ -946,18 +952,22 @@ erDiagram
|
|
946
952
|
<td><%= link_to '⇛', #{path_obj_name}_path(slashify(#{obj_pk})), { class: 'big-arrow' } %></td>" if obj_pk}
|
947
953
|
<% @_brick_sequence.each do |col_name|
|
948
954
|
val = #{obj_name}.attributes[col_name] %>
|
949
|
-
<td<%= ' class=\"dimmed\"'.html_safe unless cols.key?(col_name) || (cust_col = cust_cols[col_name])
|
955
|
+
<td<%= ' class=\"dimmed\"'.html_safe unless cols.key?(col_name) || (cust_col = cust_cols[col_name]) ||
|
956
|
+
(col_name.is_a?(Symbol) && bts.key?(col_name)) # HOT
|
957
|
+
%>><%
|
950
958
|
if (bt = bts[col_name])
|
951
959
|
if bt[2] # Polymorphic?
|
952
960
|
bt_class = #{obj_name}.send(\"#\{bt.first}_type\")
|
953
961
|
base_class_underscored = (::Brick.existing_stis[bt_class] || bt_class).constantize.base_class._brick_index(:singular)
|
954
962
|
poly_id = #{obj_name}.send(\"#\{bt.first}_id\")
|
955
963
|
%><%= link_to(\"#\{bt_class} ##\{poly_id}\", send(\"#\{base_class_underscored}_path\".to_sym, poly_id)) if poly_id %><%
|
956
|
-
else
|
957
|
-
# binding.pry if @_brick_bt_descrip[bt.first][bt[1].first.first].nil?
|
964
|
+
else # BT or HOT
|
958
965
|
bt_class = bt[1].first.first
|
959
966
|
descrips = @_brick_bt_descrip[bt.first][bt_class]
|
960
|
-
bt_id_col = if descrips.
|
967
|
+
bt_id_col = if descrips.nil?
|
968
|
+
puts \"Caught it in the act for #{obj_name} / #\{col_name}!\"
|
969
|
+
# binding.pry
|
970
|
+
elsif descrips.length == 1
|
961
971
|
[#{obj_name}.class.reflect_on_association(bt.first)&.foreign_key]
|
962
972
|
else
|
963
973
|
descrips.last
|
@@ -974,16 +984,16 @@ erDiagram
|
|
974
984
|
if hms_col.length == 1 %>
|
975
985
|
<%= hms_col.first %>
|
976
986
|
<% else
|
977
|
-
|
978
|
-
|
979
|
-
|
980
|
-
|
981
|
-
|
982
|
-
|
983
|
-
|
984
|
-
|
985
|
-
|
986
|
-
|
987
|
+
%><%= klass = (col = cols[col_name])[1]
|
988
|
+
if col[2] == 'HO'
|
989
|
+
descrips = @_brick_bt_descrip[col_name.to_sym][klass]
|
990
|
+
if (ho_id = (ho_id_col = descrips.last).map { |id_col| #{obj_name}.send(id_col.to_sym) })&.first
|
991
|
+
ho_txt = klass.brick_descrip(#{obj_name}, descrips[0..-2].map { |id| #{obj_name}.send(id.last[0..62]) }, ho_id_col)
|
992
|
+
link_to(ho_txt, send(\"#\{klass.base_class._brick_index(:singular)}_path\".to_sym, ho_id))
|
993
|
+
end
|
994
|
+
elsif hms_col[1]&.positive?
|
995
|
+
link_to \"#\{hms_col[1] || 'View'} #\{hms_col.first}\", send(\"#\{klass._brick_index}_path\".to_sym, hms_col[2])
|
996
|
+
end %>
|
987
997
|
<% end
|
988
998
|
elsif (col = cols[col_name])
|
989
999
|
col_type = col&.sql_type == 'geography' ? col.sql_type : col&.type
|
@@ -1019,7 +1029,7 @@ erDiagram
|
|
1019
1029
|
# Easily could be multiple files involved (STI for instance)
|
1020
1030
|
+"#{css}
|
1021
1031
|
<p style=\"color: green\"><%= notice %></p>#{"
|
1022
|
-
|
1032
|
+
#{schema_options}" if schema_options}
|
1023
1033
|
<select id=\"tbl\">#{table_options}</select>
|
1024
1034
|
<h1>Status</h1>
|
1025
1035
|
<table id=\"status\" class=\"shadow\"><thead><tr>
|
@@ -1038,7 +1048,7 @@ erDiagram
|
|
1038
1048
|
%>
|
1039
1049
|
<tr>
|
1040
1050
|
<td><%= begin
|
1041
|
-
kls = Object.const_get(::Brick.relations
|
1051
|
+
kls = Object.const_get(::Brick.relations.fetch(r[0], nil)&.fetch(:class_name, nil))
|
1042
1052
|
rescue
|
1043
1053
|
end
|
1044
1054
|
kls ? link_to(r[0], send(\"#\{kls._brick_index}_path\".to_sym)) : r[0] %></td>
|
@@ -1048,8 +1058,9 @@ erDiagram
|
|
1048
1058
|
' class=\"dimmed\"'
|
1049
1059
|
end&.html_safe %>><%= # Table
|
1050
1060
|
r[1] %></td>
|
1051
|
-
<td<%=
|
1052
|
-
|
1061
|
+
<td<%= lines = r[2]&.map { |line| \"#\{line.first}:#\{line.last}\" }
|
1062
|
+
' class=\"dimmed\"'.html_safe unless r[2] %>><%= # Migration
|
1063
|
+
lines&.join('<br>')&.html_safe %></td>
|
1053
1064
|
<td<%= ' class=\"dimmed\"'.html_safe unless r[3] %>><%= # Model
|
1054
1065
|
r[3] %></td>
|
1055
1066
|
<td<%= ' class=\"dimmed\"'.html_safe unless r[4] %>><%= # Route
|
@@ -1068,7 +1079,7 @@ erDiagram
|
|
1068
1079
|
if is_orphans
|
1069
1080
|
+"#{css}
|
1070
1081
|
<p style=\"color: green\"><%= notice %></p>#{"
|
1071
|
-
|
1082
|
+
#{schema_options}" if schema_options}
|
1072
1083
|
<select id=\"tbl\">#{table_options}</select>
|
1073
1084
|
<h1>Orphans<%= \" for #\{}\" if false %></h1>
|
1074
1085
|
<% @orphans.each do |o|
|
@@ -1099,7 +1110,7 @@ erDiagram
|
|
1099
1110
|
</svg>
|
1100
1111
|
|
1101
1112
|
<p style=\"color: green\"><%= notice %></p>#{"
|
1102
|
-
|
1113
|
+
#{schema_options}" if schema_options}
|
1103
1114
|
<select id=\"tbl\">#{table_options}</select>
|
1104
1115
|
<h1><%= page_title %></h1><%
|
1105
1116
|
if (description = (relation = Brick.relations[#{model_name}.table_name])&.fetch(:description, nil)) %><%=
|
@@ -1411,9 +1422,26 @@ document.querySelectorAll(\"input, select\").forEach(function (inp) {
|
|
1411
1422
|
# As if it were an inline template (see #determine_template in actionview-5.2.6.2/lib/action_view/renderer/template_renderer.rb)
|
1412
1423
|
keys = options.has_key?(:locals) ? options[:locals].keys : []
|
1413
1424
|
handler = ActionView::Template.handler_for_extension(options[:type] || 'erb')
|
1414
|
-
ActionView::Template.new(inline, "auto-generated #{args.first} template", handler, locals: keys)
|
1425
|
+
ActionView::Template.new(inline, "auto-generated #{args.first} template", handler, locals: keys).tap do |t|
|
1426
|
+
t.instance_variable_set(:@is_brick, true)
|
1427
|
+
end
|
1415
1428
|
end
|
1416
|
-
end
|
1429
|
+
end # LookupContext
|
1430
|
+
|
1431
|
+
# For any auto-generated template, if multitenancy is active via some flavour of an Apartment gem, switch back to the default tenant.
|
1432
|
+
# (Underlying reason -- ros-apartment can hold on to a selected tenant between requests when there is no elevator middleware.)
|
1433
|
+
ActionView::TemplateRenderer.class_exec do
|
1434
|
+
private
|
1435
|
+
|
1436
|
+
alias _brick_render_template render_template
|
1437
|
+
def render_template(view, template, *args)
|
1438
|
+
result = _brick_render_template(view, template, *args)
|
1439
|
+
if template.instance_variable_get(:@is_brick)
|
1440
|
+
Apartment::Tenant.switch!(::Brick.apartment_default_tenant) if ::Brick.apartment_multitenant
|
1441
|
+
end
|
1442
|
+
result
|
1443
|
+
end
|
1444
|
+
end # TemplateRenderer
|
1417
1445
|
end
|
1418
1446
|
|
1419
1447
|
if ::Brick.enable_routes?
|
data/lib/brick/version_number.rb
CHANGED
data/lib/brick.rb
CHANGED
@@ -136,17 +136,19 @@ module Brick
|
|
136
136
|
attr_accessor :default_schema, :db_schemas, :routes_done, :is_oracle, :is_eager_loading, :auto_models
|
137
137
|
|
138
138
|
def set_db_schema(params = nil)
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
139
|
+
# If Apartment::Tenant.current is not still the default (usually 'public') then an elevator has brought us into
|
140
|
+
# a different tenant. If so then don't allow schema navigation.
|
141
|
+
chosen = if (is_show_schema_list = (apartment_multitenant && Apartment::Tenant.current == ::Brick.default_schema)) &&
|
142
|
+
(schema = (params ? params['_brick_schema'] : ::Brick.default_schema)) &&
|
143
|
+
::Brick.db_schemas&.key?(schema)
|
144
|
+
Apartment::Tenant.switch!(schema)
|
145
|
+
schema
|
146
|
+
elsif ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
|
147
|
+
# Just return the current schema
|
148
|
+
current_schema = ActiveRecord::Base.execute_sql('SELECT current_schemas(true)').first['current_schemas'][1..-2].split(',')
|
149
|
+
(current_schema - ['pg_catalog', 'pg_toast', 'heroku_ext']).first
|
150
|
+
end
|
151
|
+
[chosen == ::Brick.default_schema ? nil : chosen, is_show_schema_list]
|
150
152
|
end
|
151
153
|
|
152
154
|
# All tables and views (what Postgres calls "relations" including column and foreign key info)
|
@@ -162,6 +164,10 @@ module Brick
|
|
162
164
|
@apartment_multitenant
|
163
165
|
end
|
164
166
|
|
167
|
+
def apartment_default_tenant
|
168
|
+
Apartment.default_tenant || 'public'
|
169
|
+
end
|
170
|
+
|
165
171
|
# If multitenancy is enabled, a list of non-tenanted "global" models
|
166
172
|
def non_tenanted_models
|
167
173
|
@pending_models ||= {}
|
@@ -205,12 +211,13 @@ module Brick
|
|
205
211
|
else
|
206
212
|
s.first[a.foreign_key.to_s] = [a.name, a.klass]
|
207
213
|
end
|
208
|
-
else # This gets
|
209
|
-
if through
|
214
|
+
else # This gets all forms of has_many and has_one
|
215
|
+
if through # has_many :through or has_one :through
|
210
216
|
is_invalid_source = nil
|
211
217
|
begin
|
212
|
-
if a.through_reflection
|
213
|
-
|
218
|
+
if a.through_reflection.macro != :has_many # This HM goes through either a belongs_to or a has_one, so essentially a HOT?
|
219
|
+
# Treat it like a belongs_to - just keyed on the association name instead of a foreign_key
|
220
|
+
s.first[a.name] = [a.name, a.klass]
|
214
221
|
next
|
215
222
|
elsif !a.source_reflection # Had considered: a.active_record.reflect_on_association(a.source_reflection_name).nil?
|
216
223
|
is_invalid_source = true
|
@@ -1086,21 +1093,9 @@ ActiveSupport.on_load(:active_record) do
|
|
1086
1093
|
end
|
1087
1094
|
end
|
1088
1095
|
|
1089
|
-
# First part of arel_table_type stuff:
|
1090
|
-
# ------------------------------------
|
1091
|
-
# (more found below)
|
1092
1096
|
# was: ActiveRecord.version >= ::Gem::Version.new('3.2') &&
|
1093
1097
|
if ActiveRecord.version < ::Gem::Version.new('5.0')
|
1094
|
-
# Used by Util#_arel_table_type
|
1095
1098
|
module ActiveRecord
|
1096
|
-
class Base
|
1097
|
-
def self.arel_table
|
1098
|
-
@arel_table ||= Arel::Table.new(table_name, arel_engine).tap do |x|
|
1099
|
-
x.instance_variable_set(:@_arel_table_type, self)
|
1100
|
-
end
|
1101
|
-
end
|
1102
|
-
end
|
1103
|
-
|
1104
1099
|
# Final pieces for left_outer_joins support, which was derived from this commit:
|
1105
1100
|
# https://github.com/rails/rails/commit/3f46ef1ddab87482b730a3f53987e04308783d8b
|
1106
1101
|
module Associations
|
@@ -1185,13 +1180,9 @@ if is_postgres && ActiveRecord.version < ::Gem::Version.new('5.0') # Was: && Ob
|
|
1185
1180
|
PGError = PG::Error
|
1186
1181
|
end
|
1187
1182
|
|
1188
|
-
# More arel_table_type stuff:
|
1189
|
-
# ---------------------------
|
1190
1183
|
if ActiveRecord.version < ::Gem::Version.new('5.2')
|
1191
1184
|
# Specifically for AR 3.1 and 3.2 to avoid: "undefined method `delegate' for ActiveRecord::Reflection::ThroughReflection:Class"
|
1192
1185
|
require 'active_support/core_ext/module/delegation' if ActiveRecord.version < ::Gem::Version.new('4.0')
|
1193
|
-
# Used by Util#_arel_table_type
|
1194
|
-
# rubocop:disable Style/CommentedKeyword
|
1195
1186
|
module ActiveRecord
|
1196
1187
|
module Reflection
|
1197
1188
|
# AR < 4.0 doesn't know about join_table and derive_join_table
|
@@ -1209,61 +1200,23 @@ if ActiveRecord.version < ::Gem::Version.new('5.2')
|
|
1209
1200
|
end
|
1210
1201
|
end
|
1211
1202
|
end
|
1212
|
-
|
1213
|
-
module Associations
|
1214
|
-
# Specific to AR 4.2 - 5.1:
|
1215
|
-
if self.const_defined?('JoinDependency') && JoinDependency.private_instance_methods.include?(:table_aliases_for)
|
1216
|
-
class JoinDependency
|
1217
|
-
private
|
1218
|
-
|
1219
|
-
if ActiveRecord.version < ::Gem::Version.new('5.1') # 4.2 or 5.0
|
1220
|
-
def table_aliases_for(parent, node)
|
1221
|
-
node.reflection.chain.map do |reflection|
|
1222
|
-
alias_tracker.aliased_table_for(
|
1223
|
-
reflection.table_name,
|
1224
|
-
table_alias_for(reflection, parent, reflection != node.reflection)
|
1225
|
-
).tap do |x|
|
1226
|
-
# %%% Specific only to Rails 4.2 (and maybe 4.1?)
|
1227
|
-
x = x.left if x.is_a?(Arel::Nodes::TableAlias)
|
1228
|
-
y = reflection.chain.find { |c| c.table_name == x.name }
|
1229
|
-
x.instance_variable_set(:@_arel_table_type, y.klass)
|
1230
|
-
end
|
1231
|
-
end
|
1232
|
-
end
|
1233
|
-
end
|
1234
|
-
end
|
1235
|
-
elsif Associations.const_defined?('JoinHelper') && JoinHelper.private_instance_methods.include?(:construct_tables)
|
1236
|
-
module JoinHelper
|
1237
|
-
private
|
1238
|
-
|
1239
|
-
# AR > 3.0 and < 4.2 (%%% maybe only < 4.1?) uses construct_tables like this:
|
1240
|
-
def construct_tables
|
1241
|
-
tables = []
|
1242
|
-
chain.each do |reflection|
|
1243
|
-
tables << alias_tracker.aliased_table_for(
|
1244
|
-
table_name_for(reflection),
|
1245
|
-
table_alias_for(reflection, reflection != self.reflection)
|
1246
|
-
).tap do |x|
|
1247
|
-
x = x.left if x.is_a?(Arel::Nodes::TableAlias)
|
1248
|
-
x.instance_variable_set(:@_arel_table_type, reflection.chain.find { |c| c.table_name == x.name }.klass)
|
1249
|
-
end
|
1250
|
-
|
1251
|
-
next unless reflection.source_macro == :has_and_belongs_to_many
|
1252
|
-
|
1253
|
-
tables << alias_tracker.aliased_table_for(
|
1254
|
-
(reflection.source_reflection || reflection).join_table,
|
1255
|
-
table_alias_for(reflection, true)
|
1256
|
-
)
|
1257
|
-
end
|
1258
|
-
tables
|
1259
|
-
end
|
1260
|
-
end
|
1261
|
-
end
|
1262
|
-
end
|
1263
|
-
end # module ActiveRecord
|
1264
|
-
# rubocop:enable Style/CommentedKeyword
|
1203
|
+
end
|
1265
1204
|
end
|
1266
1205
|
|
1206
|
+
# The "brick_links" patch -- this finds how every AR chain of association names
|
1207
|
+
# relates back to an exact table correlation name chosen by AREL when the AST tree is
|
1208
|
+
# walked. For instance, from a Customer model there could be a join_tree such as
|
1209
|
+
# { orders: { line_items: :product} }, which would end up recording three entries, the
|
1210
|
+
# last of which for products would have a key of "orders.line_items.product" after
|
1211
|
+
# having gone through two HMs and one BT. AREL would have chosen a correlation name of
|
1212
|
+
# "products", being able to use the same name as the table name because it's the first
|
1213
|
+
# time that table is used in this query. But let's see what happens if each customer
|
1214
|
+
# also had a BT to a favourite product, referenced earlier in the join_tree like this:
|
1215
|
+
# [:favourite_product, orders: { line_items: :product}] -- then the second reference to
|
1216
|
+
# "products" would end up being called "products_line_items" in order to differentiate
|
1217
|
+
# it from the first reference, which would have already snagged the simpler name
|
1218
|
+
# "products". It's essential that The Brick can find accurate correlation names when
|
1219
|
+
# there are multiple JOINs to the same table.
|
1267
1220
|
module ActiveRecord
|
1268
1221
|
module QueryMethods
|
1269
1222
|
private
|
@@ -1290,7 +1243,7 @@ module ActiveRecord
|
|
1290
1243
|
end
|
1291
1244
|
end
|
1292
1245
|
|
1293
|
-
# require '
|
1246
|
+
# require 'active_record/associations/join_dependency'
|
1294
1247
|
module Associations
|
1295
1248
|
# For AR >= 4.2
|
1296
1249
|
if self.const_defined?('JoinDependency')
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: brick
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.94
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Lorin Thwaits
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-11-
|
11
|
+
date: 2022-11-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -164,20 +164,6 @@ dependencies:
|
|
164
164
|
- - "~>"
|
165
165
|
- !ruby/object:Gem::Version
|
166
166
|
version: 1.42.0
|
167
|
-
- !ruby/object:Gem::Dependency
|
168
|
-
name: mysql2
|
169
|
-
requirement: !ruby/object:Gem::Requirement
|
170
|
-
requirements:
|
171
|
-
- - "~>"
|
172
|
-
- !ruby/object:Gem::Version
|
173
|
-
version: '0.5'
|
174
|
-
type: :development
|
175
|
-
prerelease: false
|
176
|
-
version_requirements: !ruby/object:Gem::Requirement
|
177
|
-
requirements:
|
178
|
-
- - "~>"
|
179
|
-
- !ruby/object:Gem::Version
|
180
|
-
version: '0.5'
|
181
167
|
- !ruby/object:Gem::Dependency
|
182
168
|
name: pg
|
183
169
|
requirement: !ruby/object:Gem::Requirement
|