brick 1.0.91 → 1.0.93
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/compatibility.rb +1 -0
- data/lib/brick/extensions.rb +118 -181
- data/lib/brick/frameworks/rails/engine.rb +61 -41
- data/lib/brick/version_number.rb +1 -1
- data/lib/brick.rb +141 -68
- 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: 8488acac0623c04f11e916c67303fd06d471154231c92613318cc7458fcfe23f
|
4
|
+
data.tar.gz: af566c853bb25e7497a6afbba539ad0d14ec23ad000c053e6dbdf757deefbaa0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d0d8428b6f80bf3a79abb16265fb8625404f92caf30034da94186a5c5982a78543f29e0a49cc0dc515eb8b6d6da24ccdd53e8d089c90dce46ce214ce5bee4339
|
7
|
+
data.tar.gz: 562bd7522f0f45843a89464645e1cc153e455e1e5b4e3dec5f1b38a8bfaefd8bd359a2538aa274b90b50353285d2175f315b7e304e54e60516a5ce2ed3b2fc7e
|
data/lib/brick/compatibility.rb
CHANGED
@@ -11,6 +11,7 @@ unless ActiveRecord.respond_to?(:version)
|
|
11
11
|
end
|
12
12
|
|
13
13
|
# ActiveSupport, ActionPack, and ActionView before 4.0 didn't have #version
|
14
|
+
require 'active_support' # Needed for Rails 4.x
|
14
15
|
unless ActiveSupport.respond_to?(:version)
|
15
16
|
module ActiveSupport
|
16
17
|
def self.version
|
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?
|
@@ -270,7 +255,7 @@ module ActiveRecord
|
|
270
255
|
|
271
256
|
def self._brick_index(mode = nil)
|
272
257
|
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 ==
|
258
|
+
tbl_parts.shift if ::Brick.apartment_multitenant && tbl_parts.length > 1 && tbl_parts.first == ::Brick.apartment_default_tenant
|
274
259
|
tbl_parts.unshift(::Brick.config.path_prefix) if ::Brick.config.path_prefix
|
275
260
|
index = tbl_parts.map(&:underscore).join('_')
|
276
261
|
# Rails applies an _index suffix to that route when the resource name is singular
|
@@ -407,83 +392,10 @@ module ActiveRecord
|
|
407
392
|
end
|
408
393
|
|
409
394
|
class Relation
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
names = []
|
415
|
-
# Our JOINs mashup of nested arrays and hashes
|
416
|
-
# binding.pry if defined?(@arel)
|
417
|
-
case piece
|
418
|
-
when Array
|
419
|
-
names += piece.inject([]) { |s, v| s + _recurse_arel(v, prefix) }
|
420
|
-
when Hash
|
421
|
-
names += piece.inject([]) do |s, v|
|
422
|
-
new_prefix = "#{prefix}#{v.first}_"
|
423
|
-
s << [v.last.shift, new_prefix]
|
424
|
-
s + _recurse_arel(v.last, new_prefix)
|
425
|
-
end
|
426
|
-
|
427
|
-
# ActiveRecord AREL objects
|
428
|
-
when Arel::Nodes::Join # INNER or OUTER JOIN
|
429
|
-
# rubocop:disable Style/IdenticalConditionalBranches
|
430
|
-
if piece.right.is_a?(Arel::Table) # Came in from AR < 3.2?
|
431
|
-
# Arel 2.x and older is a little curious because these JOINs work "back to front".
|
432
|
-
# The left side here is either another earlier JOIN, or at the end of the whole tree, it is
|
433
|
-
# the first table.
|
434
|
-
names += _recurse_arel(piece.left)
|
435
|
-
# The right side here at the top is the very last table, and anywhere else down the tree it is
|
436
|
-
# the later "JOIN" table of this pair. (The table that comes after all the rest of the JOINs
|
437
|
-
# from the left side.)
|
438
|
-
names << [piece.right._arel_table_type, (piece.right.table_alias || piece.right.name)]
|
439
|
-
else # "Normal" setup, fed from a JoinSource which has an array of JOINs
|
440
|
-
# The left side is the "JOIN" table
|
441
|
-
names += _recurse_arel(table = piece.left)
|
442
|
-
# The expression on the right side is the "ON" clause
|
443
|
-
# on = piece.right.expr
|
444
|
-
# # Find the table which is not ourselves, and thus must be the "path" that led us here
|
445
|
-
# parent = piece.left == on.left.relation ? on.right.relation : on.left.relation
|
446
|
-
# binding.pry if piece.left.is_a?(Arel::Nodes::TableAlias)
|
447
|
-
if table.is_a?(Arel::Nodes::TableAlias)
|
448
|
-
@_arel_applied_aliases << (alias_name = table.right)
|
449
|
-
table = table.left
|
450
|
-
end
|
451
|
-
(_brick_chains[table._arel_table_type] ||= []) << (alias_name || table.table_alias || table.name)
|
452
|
-
end
|
453
|
-
# rubocop:enable Style/IdenticalConditionalBranches
|
454
|
-
when Arel::Table # Table
|
455
|
-
names << [piece._arel_table_type, (piece.table_alias || piece.name)]
|
456
|
-
when Arel::Nodes::TableAlias # Alias
|
457
|
-
# Can get the real table name from: self._recurse_arel(piece.left)
|
458
|
-
names << [piece.left._arel_table_type, piece.right.to_s] # This is simply a string; the alias name itself
|
459
|
-
when Arel::Nodes::JoinSource # Leaving this until the end because AR < 3.2 doesn't know at all about JoinSource!
|
460
|
-
# Spin up an empty set of Brick alias name chains at the start
|
461
|
-
@_brick_chains = {}
|
462
|
-
# The left side is the "FROM" table
|
463
|
-
names << (this_name = [piece.left._arel_table_type, (piece.left.table_alias || piece.left.name)])
|
464
|
-
# # Do not currently need the root "FROM" table in our list of chains
|
465
|
-
# (_brick_chains[this_name.first] ||= []) << this_name.last
|
466
|
-
# The right side is an array of all JOINs
|
467
|
-
piece.right.each { |join| names << _recurse_arel(join) }
|
468
|
-
end
|
469
|
-
names
|
470
|
-
end
|
471
|
-
|
472
|
-
# INSTANCE STUFF
|
473
|
-
def _arel_alias_names
|
474
|
-
@_arel_applied_aliases = []
|
475
|
-
# %%% If with Rails 3.1 and older you get "NoMethodError: undefined method `eq' for nil:NilClass"
|
476
|
-
# when trying to call relation.arel, then somewhere along the line while navigating a has_many
|
477
|
-
# relationship it can't find the proper foreign key.
|
478
|
-
core = arel.ast.cores.first
|
479
|
-
# Accommodate AR < 3.2
|
480
|
-
if core.froms.is_a?(Arel::Table)
|
481
|
-
# All recent versions of AR have #source which brings up an Arel::Nodes::JoinSource
|
482
|
-
_recurse_arel(core.source)
|
483
|
-
else
|
484
|
-
# With AR < 3.2, "froms" brings up the top node, an Arel::Nodes::InnerJoin
|
485
|
-
_recurse_arel(core.froms)
|
486
|
-
end
|
395
|
+
# Links from ActiveRecord association pathing names over to real table correlation names
|
396
|
+
# that get chosen when the AREL AST tree is walked.
|
397
|
+
def brick_links
|
398
|
+
@brick_links ||= {}
|
487
399
|
end
|
488
400
|
|
489
401
|
def brick_select(params, selects = [], order_by = nil, translations = {}, join_array = ::Brick::JoinArray.new)
|
@@ -501,13 +413,16 @@ module ActiveRecord
|
|
501
413
|
params.each do |k, v|
|
502
414
|
next if ['_brick_schema', '_brick_order', 'controller', 'action'].include?(k)
|
503
415
|
|
504
|
-
|
416
|
+
if (where_col = (ks = k.split('.')).last)[-1] == '!'
|
417
|
+
where_col = where_col[0..-2]
|
418
|
+
end
|
419
|
+
case ks.length
|
505
420
|
when 1
|
506
|
-
next unless klass.column_names.any?(
|
421
|
+
next unless klass.column_names.any?(where_col) || klass._brick_get_fks.include?(where_col)
|
507
422
|
when 2
|
508
423
|
assoc_name = ks.first.to_sym
|
509
424
|
# Make sure it's a good association name and that the model has that column name
|
510
|
-
next unless klass.reflect_on_association(assoc_name)&.klass&.column_names&.any?(
|
425
|
+
next unless klass.reflect_on_association(assoc_name)&.klass&.column_names&.any?(where_col)
|
511
426
|
|
512
427
|
join_array[assoc_name] = nil # Store this relation name in our special collection for .joins()
|
513
428
|
is_distinct = true
|
@@ -545,10 +460,20 @@ module ActiveRecord
|
|
545
460
|
|
546
461
|
if join_array.present?
|
547
462
|
left_outer_joins!(join_array)
|
548
|
-
#
|
549
|
-
|
463
|
+
# Touching AREL AST walks the JoinDependency tree, and in that process uses our
|
464
|
+
# "brick_links" patch to find how every AR chain of association names relates to exact
|
465
|
+
# table correlation names chosen by AREL. We use a duplicate relation object for this
|
466
|
+
# because an important side-effect of referencing the AST is that the @arel instance
|
467
|
+
# variable gets set, and this is a signal to ActiveRecord that a relation has now
|
468
|
+
# become immutable. (We aren't quite ready for our "real deal" relation object to be
|
469
|
+
# set in stone ... still need to add .select(), and possibly .where() and .order()
|
470
|
+
# things ... also if there are any HM counts then an OUTER JOIN for each of them out
|
471
|
+
# to a derived table to do that counting. All of these things need to know proper
|
472
|
+
# table correlation names, which will now become available in brick_links on the
|
473
|
+
# rel_dupe object.)
|
474
|
+
(rel_dupe = dup).arel.ast
|
475
|
+
|
550
476
|
core_selects = selects.dup
|
551
|
-
chains = rel_dupe._brick_chains
|
552
477
|
id_for_tables = Hash.new { |h, k| h[k] = [] }
|
553
478
|
field_tbl_names = Hash.new { |h, k| h[k] = {} }
|
554
479
|
used_col_aliases = {} # Used to make sure there is not a name clash
|
@@ -569,7 +494,7 @@ module ActiveRecord
|
|
569
494
|
key_alias = nil
|
570
495
|
cc.first.each do |cc_part|
|
571
496
|
dest_klass = cc_part[0..-2].inject(klass) { |kl, cc_part_term| kl.reflect_on_association(cc_part_term).klass }
|
572
|
-
tbl_name =
|
497
|
+
tbl_name = rel_dupe.brick_links[cc_part[0..-2].map(&:to_s).join('.')]
|
573
498
|
# Deal with the conflict if there are two parts in the custom column named the same,
|
574
499
|
# "category.name" and "product.name" for instance will end up with aliases of "name"
|
575
500
|
# and "product__name".
|
@@ -606,23 +531,18 @@ module ActiveRecord
|
|
606
531
|
|
607
532
|
klass._br_bt_descrip.each do |v|
|
608
533
|
v.last.each do |k1, v1| # k1 is class, v1 is array of columns to snag
|
609
|
-
next
|
534
|
+
next unless (tbl_name = rel_dupe.brick_links[v.first.to_s]&.split('.')&.last)
|
610
535
|
|
611
|
-
tbl_name = (field_tbl_names[v.first][k1] ||= shift_or_first(chains[k1])).split('.').last
|
612
536
|
# If it's Oracle, quote any AREL aliases that had been applied
|
613
|
-
tbl_name = "\"#{tbl_name}\"" if ::Brick.is_oracle && rel_dupe.
|
537
|
+
tbl_name = "\"#{tbl_name}\"" if ::Brick.is_oracle && rel_dupe.brick_links.values.include?(tbl_name)
|
614
538
|
field_tbl_name = nil
|
615
|
-
v1.map { |x| [
|
616
|
-
|
617
|
-
# puts 'You might have some bogus DSL in your brick.rb file'
|
618
|
-
# next
|
619
|
-
# end
|
620
|
-
field_tbl_name = (field_tbl_names[v.first][sel_col.first] ||= shift_or_first(chains[sel_col.first])).split('.').last
|
539
|
+
v1.map { |x| [x[0..-2].map(&:to_s).join('.'), x.last] }.each_with_index do |sel_col, idx|
|
540
|
+
field_tbl_name = rel_dupe.brick_links[sel_col.first].split('.').last
|
621
541
|
# If it's Oracle, quote any AREL aliases that had been applied
|
622
|
-
field_tbl_name = "\"#{field_tbl_name}\"" if ::Brick.is_oracle && rel_dupe.
|
542
|
+
field_tbl_name = "\"#{field_tbl_name}\"" if ::Brick.is_oracle && rel_dupe.brick_links.values.include?(field_tbl_name)
|
623
543
|
|
624
544
|
# Postgres can not use DISTINCT with any columns that are XML, so for any of those just convert to text
|
625
|
-
is_xml = is_distinct && Brick.relations[
|
545
|
+
is_xml = is_distinct && Brick.relations[field_tbl_name]&.[](:cols)&.[](sel_col.last)&.first&.start_with?('xml')
|
626
546
|
# If it's not unique then also include the belongs_to association name before the column name
|
627
547
|
if used_col_aliases.key?(col_alias = "br_fk_#{v.first}__#{sel_col.last}")
|
628
548
|
col_alias = "br_fk_#{v.first}__#{v1[idx][-2..-1].map(&:to_s).join('__')}"
|
@@ -659,14 +579,9 @@ module ActiveRecord
|
|
659
579
|
end
|
660
580
|
end
|
661
581
|
join_array.each do |assoc_name|
|
662
|
-
# %%% Need to support {user: :profile}
|
663
582
|
next unless assoc_name.is_a?(Symbol)
|
664
583
|
|
665
|
-
table_alias =
|
666
|
-
shift_or_first(chain)
|
667
|
-
else
|
668
|
-
klass.table_name # ActiveRecord < 4.2 can't (yet) use the cool chains thing
|
669
|
-
end
|
584
|
+
table_alias = rel_dupe.brick_links[assoc_name.to_s]
|
670
585
|
_assoc_names[assoc_name] = [table_alias, klass]
|
671
586
|
end
|
672
587
|
end
|
@@ -677,26 +592,47 @@ module ActiveRecord
|
|
677
592
|
# Build the chain of JOINs going to the final destination HMT table
|
678
593
|
# (Usually just one JOIN, but could be many.)
|
679
594
|
hmt_assoc = hm
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
|
595
|
+
through_sources = []
|
596
|
+
# %%% Inverse path back to the original object -- not yet used, but soon
|
597
|
+
# will be leveraged in order to build links with multi-table-hop filters.
|
598
|
+
link_back = []
|
599
|
+
# Track polymorphic type field if necessary
|
600
|
+
if hm.source_reflection.options[:as]
|
601
|
+
poly_ft = [hm.source_reflection.inverse_of.foreign_type, hmt_assoc.source_reflection.class_name]
|
602
|
+
end
|
603
|
+
# link_back << hm.source_reflection.inverse_of.name
|
604
|
+
while hmt_assoc.options[:through] && (hmt_assoc = klass.reflect_on_association(hmt_assoc.options[:through]))
|
605
|
+
through_sources.unshift(hmt_assoc)
|
606
|
+
end
|
607
|
+
# Turn the last member of link_back into a foreign key
|
608
|
+
link_back << hmt_assoc.source_reflection.foreign_key
|
609
|
+
# If it's a HMT based on a HM -> HM, must JOIN the last table into the mix at the end
|
610
|
+
through_sources.push(hm.source_reflection) unless hm.source_reflection.belongs_to?
|
611
|
+
from_clause = +"#{through_sources.first.table_name} br_t0"
|
612
|
+
fk_col = through_sources.shift.foreign_key
|
613
|
+
|
685
614
|
idx = 0
|
686
615
|
bail_out = nil
|
687
|
-
|
616
|
+
through_sources.map do |a|
|
688
617
|
from_clause << "\n LEFT OUTER JOIN #{a.table_name} br_t#{idx += 1} "
|
689
618
|
from_clause << if (src_ref = a.source_reflection).macro == :belongs_to
|
619
|
+
(nm = hmt_assoc.source_reflection.inverse_of&.name)
|
620
|
+
# binding.pry unless nm
|
621
|
+
link_back << nm
|
690
622
|
"ON br_t#{idx}.id = br_t#{idx - 1}.#{a.foreign_key}"
|
691
623
|
elsif src_ref.options[:as]
|
692
624
|
"ON br_t#{idx}.#{src_ref.type} = '#{src_ref.active_record.name}'" + # "polymorphable_type"
|
693
625
|
" AND br_t#{idx}.#{src_ref.foreign_key} = br_t#{idx - 1}.id"
|
694
626
|
elsif src_ref.options[:source_type]
|
695
|
-
print "Skipping #{hm.name} --HMT-> #{hm.source_reflection.name} as it uses source_type which is not supported"
|
627
|
+
print "Skipping #{hm.name} --HMT-> #{hm.source_reflection.name} as it uses source_type which is not yet supported"
|
696
628
|
nix << k
|
697
629
|
bail_out = true
|
698
630
|
break
|
699
631
|
else # Standard has_many
|
632
|
+
# binding.pry unless (
|
633
|
+
nm = hmt_assoc.source_reflection.inverse_of&.name
|
634
|
+
# )
|
635
|
+
link_back << nm # if nm
|
700
636
|
"ON br_t#{idx}.#{a.foreign_key} = br_t#{idx - 1}.id"
|
701
637
|
end
|
702
638
|
link_back.unshift(a.source_reflection.name)
|
@@ -704,6 +640,7 @@ module ActiveRecord
|
|
704
640
|
end
|
705
641
|
next if bail_out
|
706
642
|
|
643
|
+
# puts "LINK BACK! #{k} : #{hm.table_name} #{link_back.map(&:to_s).join('.')}"
|
707
644
|
# count_column is determined from the originating HMT member
|
708
645
|
if (src_ref = hm.source_reflection).nil?
|
709
646
|
puts "*** Warning: Could not determine destination model for this HMT association in model #{klass.name}:\n has_many :#{hm.name}, through: :#{hm.options[:through]}"
|
@@ -711,8 +648,10 @@ module ActiveRecord
|
|
711
648
|
nix << k
|
712
649
|
next
|
713
650
|
elsif src_ref.macro == :belongs_to # Traditional HMT using an associative table
|
651
|
+
# binding.pry if link_back.length > 2
|
714
652
|
"br_t#{idx}.#{hm.foreign_key}"
|
715
653
|
else # A HMT that goes HM -> HM, something like Categories -> Products -> LineItems
|
654
|
+
# binding.pry if link_back.length > 2
|
716
655
|
"br_t#{idx}.#{src_ref.active_record.primary_key}"
|
717
656
|
end
|
718
657
|
else
|
@@ -772,17 +711,23 @@ JOIN (SELECT #{hm_selects.map { |s| "#{'br_t0.' if from_clause}#{s}" }.join(', '
|
|
772
711
|
|
773
712
|
unless wheres.empty?
|
774
713
|
# Rewrite the wheres to reference table and correlation names built out by AREL
|
714
|
+
where_nots = {}
|
775
715
|
wheres2 = wheres.each_with_object({}) do |v, s|
|
716
|
+
is_not = if v.first[-1] == '!'
|
717
|
+
v[0] = v[0][0..-2] # Take off ending ! from column name
|
718
|
+
end
|
776
719
|
if (v_parts = v.first.split('.')).length == 1
|
777
|
-
s[v.first] = v.last
|
720
|
+
(is_not ? where_nots : s)[v.first] = v.last
|
778
721
|
else
|
779
|
-
|
780
|
-
|
781
|
-
s["#{tbl_name}.#{v_parts.last}"] = v.last
|
722
|
+
tbl_name = rel_dupe.brick_links[v_parts.first].split('.').last
|
723
|
+
(is_not ? where_nots : s)["#{tbl_name}.#{v_parts.last}"] = v.last
|
782
724
|
end
|
783
725
|
end
|
784
726
|
if respond_to?(:where!)
|
785
|
-
where!(wheres2)
|
727
|
+
where!(wheres2) if wheres2.present?
|
728
|
+
if where_nots.present?
|
729
|
+
self.where_clause += WhereClause.new(predicate_builder.build_from_hash(where_nots)).invert
|
730
|
+
end
|
786
731
|
else # AR < 4.0
|
787
732
|
self.where_values << build_where(wheres2)
|
788
733
|
end
|
@@ -1078,7 +1023,8 @@ Module.class_exec do
|
|
1078
1023
|
(table_name = singular_table_name.pluralize),
|
1079
1024
|
::Brick.is_oracle ? class_name.upcase : class_name,
|
1080
1025
|
(plural_class_name = class_name.pluralize)].find { |s| Brick.db_schemas&.include?(s) }&.camelize ||
|
1081
|
-
(::Brick.config.sti_namespace_prefixes&.key?("::#{class_name}::") && class_name)
|
1026
|
+
(::Brick.config.sti_namespace_prefixes&.key?("::#{class_name}::") && class_name) ||
|
1027
|
+
(::Brick.config.table_name_prefixes&.values.include?(class_name) && class_name))
|
1082
1028
|
return self.const_get(schema_name) if self.const_defined?(schema_name)
|
1083
1029
|
|
1084
1030
|
# Build out a module for the schema if it's namespaced
|
@@ -1129,6 +1075,7 @@ class Object
|
|
1129
1075
|
private
|
1130
1076
|
|
1131
1077
|
def build_model(relations, base_module, base_name, class_name, inheritable_name = nil)
|
1078
|
+
tnp = ::Brick.config.table_name_prefixes&.find { |p| p.last == base_module.name }&.first
|
1132
1079
|
if (base_model = ::Brick.config.sti_namespace_prefixes&.fetch("::#{base_module.name}::", nil)&.constantize) || # Are we part of an auto-STI namespace? ...
|
1133
1080
|
base_module != Object # ... or otherwise already in some namespace?
|
1134
1081
|
schema_name = [(singular_schema_name = base_name.underscore),
|
@@ -1150,11 +1097,11 @@ class Object
|
|
1150
1097
|
table_name = if (base_model = ::Brick.sti_models[model_name]&.fetch(:base, nil) || ::Brick.existing_stis[model_name]&.constantize)
|
1151
1098
|
base_model.table_name
|
1152
1099
|
else
|
1153
|
-
ActiveSupport::Inflector.pluralize(singular_table_name)
|
1100
|
+
"#{tnp}#{ActiveSupport::Inflector.pluralize(singular_table_name)}"
|
1154
1101
|
end
|
1155
1102
|
if ::Brick.apartment_multitenant &&
|
1156
1103
|
Apartment.excluded_models.include?(table_name.singularize.camelize)
|
1157
|
-
schema_name =
|
1104
|
+
schema_name = ::Brick.apartment_default_tenant
|
1158
1105
|
end
|
1159
1106
|
# Maybe, just maybe there's a database table that will satisfy this need
|
1160
1107
|
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) })
|
@@ -1165,7 +1112,7 @@ class Object
|
|
1165
1112
|
|
1166
1113
|
def build_model_worker(schema_name, inheritable_name, model_name, singular_table_name, table_name, relations, matching)
|
1167
1114
|
if ::Brick.apartment_multitenant &&
|
1168
|
-
schema_name ==
|
1115
|
+
schema_name == ::Brick.apartment_default_tenant
|
1169
1116
|
relation = relations["#{schema_name}.#{matching}"]
|
1170
1117
|
end
|
1171
1118
|
full_name = if relation || schema_name.blank?
|
@@ -1367,7 +1314,7 @@ class Object
|
|
1367
1314
|
# If it's multitenant with something like: public.____ ...
|
1368
1315
|
if (it_parts = inverse_table.split('.')).length > 1 &&
|
1369
1316
|
::Brick.apartment_multitenant &&
|
1370
|
-
it_parts.first ==
|
1317
|
+
it_parts.first == ::Brick.apartment_default_tenant
|
1371
1318
|
it_parts.shift # ... then ditch the generic schema name
|
1372
1319
|
end
|
1373
1320
|
inverse_assoc_name, _x = _brick_get_hm_assoc_name(relations[inverse_table], inverse, it_parts.join('_').singularize)
|
@@ -1464,7 +1411,7 @@ class Object
|
|
1464
1411
|
instance_variable_set(:@resources, ::Brick.get_status_of_resources)
|
1465
1412
|
end
|
1466
1413
|
self.define_method :orphans do
|
1467
|
-
instance_variable_set(:@orphans, ::Brick.find_orphans(::Brick.set_db_schema(params)))
|
1414
|
+
instance_variable_set(:@orphans, ::Brick.find_orphans(::Brick.set_db_schema(params).first))
|
1468
1415
|
end
|
1469
1416
|
return [new_controller_class, code + "end # BrickGem controller\n"]
|
1470
1417
|
when 'BrickOpenapi'
|
@@ -1482,7 +1429,7 @@ class Object
|
|
1482
1429
|
api_params = referrer_params&.to_h
|
1483
1430
|
end
|
1484
1431
|
end
|
1485
|
-
::Brick.set_db_schema(params || api_params)
|
1432
|
+
_schema, @_is_show_schema_list = ::Brick.set_db_schema(params || api_params)
|
1486
1433
|
|
1487
1434
|
if is_openapi
|
1488
1435
|
json = { 'openapi': '3.0.1', 'info': { 'title': Rswag::Ui.config.config_object[:urls].last&.fetch(:name, 'API documentation'), 'version': ::Brick.config.api_version },
|
@@ -1584,7 +1531,6 @@ class Object
|
|
1584
1531
|
end
|
1585
1532
|
|
1586
1533
|
unless is_openapi
|
1587
|
-
::Brick.set_db_schema
|
1588
1534
|
_, order_by_txt = model._brick_calculate_ordering(default_ordering(table_name, pk)) if pk
|
1589
1535
|
code << " def index\n"
|
1590
1536
|
code << " @#{table_name.pluralize} = #{model.name}#{pk&.present? ? ".order(#{order_by_txt.join(', ')})" : '.all'}\n"
|
@@ -1597,7 +1543,7 @@ class Object
|
|
1597
1543
|
code << " #{find_by_name = "find_#{singular_table_name}"}\n"
|
1598
1544
|
code << " end\n"
|
1599
1545
|
self.define_method :show do
|
1600
|
-
::Brick.set_db_schema(params)
|
1546
|
+
_schema, @_is_show_schema_list = ::Brick.set_db_schema(params)
|
1601
1547
|
instance_variable_set("@#{singular_table_name}".to_sym, find_obj)
|
1602
1548
|
end
|
1603
1549
|
end
|
@@ -1608,7 +1554,7 @@ class Object
|
|
1608
1554
|
code << " @#{singular_table_name} = #{model.name}.new\n"
|
1609
1555
|
code << " end\n"
|
1610
1556
|
self.define_method :new do
|
1611
|
-
::Brick.set_db_schema(params)
|
1557
|
+
_schema, @_is_show_schema_list = ::Brick.set_db_schema(params)
|
1612
1558
|
instance_variable_set("@#{singular_table_name}".to_sym, model.new)
|
1613
1559
|
end
|
1614
1560
|
|
@@ -1647,7 +1593,7 @@ class Object
|
|
1647
1593
|
code << " #{find_by_name}\n"
|
1648
1594
|
code << " end\n"
|
1649
1595
|
self.define_method :edit do
|
1650
|
-
::Brick.set_db_schema(params)
|
1596
|
+
_schema, @_is_show_schema_list = ::Brick.set_db_schema(params)
|
1651
1597
|
instance_variable_set("@#{singular_table_name}".to_sym, find_obj)
|
1652
1598
|
end
|
1653
1599
|
|
@@ -1876,13 +1822,22 @@ end.class_exec do
|
|
1876
1822
|
s[row.first] = { dt: row.last } unless ['information_schema', 'pg_catalog', 'pg_toast', 'heroku_ext',
|
1877
1823
|
'INFORMATION_SCHEMA', 'sys'].include?(row.first)
|
1878
1824
|
end
|
1879
|
-
if (
|
1880
|
-
|
1881
|
-
|
1882
|
-
|
1883
|
-
|
1884
|
-
|
1885
|
-
|
1825
|
+
if (possible_schemas = (multitenancy = ::Brick.config.schema_behavior&.[](:multitenant)) &&
|
1826
|
+
multitenancy&.[](:schema_to_analyse))
|
1827
|
+
possible_schemas = [possible_schemas] unless possible_schemas.is_a?(Array)
|
1828
|
+
if (possible_schema = possible_schemas.find { |ps| ::Brick.db_schemas.key?(ps) })
|
1829
|
+
::Brick.default_schema = ::Brick.apartment_default_tenant
|
1830
|
+
schema = possible_schema
|
1831
|
+
orig_schema = ActiveRecord::Base.execute_sql('SELECT current_schemas(true)').first['current_schemas'][1..-2].split(',')
|
1832
|
+
ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?", schema)
|
1833
|
+
elsif Rails.env == 'test' # When testing, just find the most recently-created schema
|
1834
|
+
::Brick.default_schema = schema = ::Brick.db_schemas.to_a.sort { |a, b| b.last[:dt] <=> a.last[:dt] }.first.first
|
1835
|
+
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}."
|
1836
|
+
orig_schema = ActiveRecord::Base.execute_sql('SELECT current_schemas(true)').first['current_schemas'][1..-2].split(',')
|
1837
|
+
ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?", schema)
|
1838
|
+
else
|
1839
|
+
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. ***"
|
1840
|
+
end
|
1886
1841
|
end
|
1887
1842
|
when 'Mysql2'
|
1888
1843
|
::Brick.default_schema = schema = ActiveRecord::Base.connection.current_database
|
@@ -1905,24 +1860,6 @@ end.class_exec do
|
|
1905
1860
|
|
1906
1861
|
::Brick.db_schemas ||= {}
|
1907
1862
|
|
1908
|
-
if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
|
1909
|
-
if (possible_schemas = ::Brick.config.schema_behavior&.[](:multitenant)&.[](:schema_to_analyse))
|
1910
|
-
possible_schemas = [possible_schemas] unless possible_schemas.is_a?(Array)
|
1911
|
-
if (possible_schema = possible_schemas.find { |ps| ::Brick.db_schemas.key?(ps) })
|
1912
|
-
::Brick.default_schema = schema = possible_schema
|
1913
|
-
orig_schema = ActiveRecord::Base.execute_sql('SELECT current_schemas(true)').first['current_schemas'][1..-2].split(',')
|
1914
|
-
ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?", schema)
|
1915
|
-
elsif Rails.env == 'test' # When testing, just find the most recently-created schema
|
1916
|
-
::Brick.default_schema = schema = ::Brick.db_schemas.to_a.sort { |a, b| b.last[:dt] <=> a.last[:dt] }.first.first
|
1917
|
-
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}."
|
1918
|
-
orig_schema = ActiveRecord::Base.execute_sql('SELECT current_schemas(true)').first['current_schemas'][1..-2].split(',')
|
1919
|
-
ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?", schema)
|
1920
|
-
else
|
1921
|
-
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. ***"
|
1922
|
-
end
|
1923
|
-
end
|
1924
|
-
end
|
1925
|
-
|
1926
1863
|
# %%% Retrieve internal ActiveRecord table names like this:
|
1927
1864
|
# ActiveRecord::Base.internal_metadata_table_name, ActiveRecord::Base.schema_migrations_table_name
|
1928
1865
|
# For if it's not SQLite -- so this is the Postgres and MySQL version
|
@@ -1935,7 +1872,7 @@ end.class_exec do
|
|
1935
1872
|
# If Apartment gem lists the table as being associated with a non-tenanted model then use whatever it thinks
|
1936
1873
|
# is the default schema, usually 'public'.
|
1937
1874
|
schema_name = if ::Brick.config.schema_behavior[:multitenant]
|
1938
|
-
|
1875
|
+
::Brick.apartment_default_tenant if apartment_excluded&.include?(r['relation_name'].singularize.camelize)
|
1939
1876
|
elsif ![schema, 'public'].include?(r['schema'])
|
1940
1877
|
r['schema']
|
1941
1878
|
end
|
@@ -2086,16 +2023,16 @@ ORDER BY 1, 2, c.internal_column_id, acc.position"
|
|
2086
2023
|
fk = fk.values unless fk.is_a?(Array)
|
2087
2024
|
# Multitenancy makes things a little more general overall, except for non-tenanted tables
|
2088
2025
|
if apartment_excluded&.include?(::Brick.namify(fk[1]).singularize.camelize)
|
2089
|
-
fk[0] =
|
2090
|
-
elsif (is_postgres && (fk[0] == 'public' || (
|
2026
|
+
fk[0] = ::Brick.apartment_default_tenant
|
2027
|
+
elsif (is_postgres && (fk[0] == 'public' || (multitenancy && fk[0] == schema))) ||
|
2091
2028
|
(::Brick.is_oracle && fk[0] == schema) ||
|
2092
2029
|
(is_mssql && fk[0] == 'dbo') ||
|
2093
2030
|
(!is_postgres && !::Brick.is_oracle && !is_mssql && ['mysql', 'performance_schema', 'sys'].exclude?(fk[0]))
|
2094
2031
|
fk[0] = nil
|
2095
2032
|
end
|
2096
2033
|
if apartment_excluded&.include?(fk[4].singularize.camelize)
|
2097
|
-
fk[3] =
|
2098
|
-
elsif (is_postgres && (fk[3] == 'public' || (
|
2034
|
+
fk[3] = ::Brick.apartment_default_tenant
|
2035
|
+
elsif (is_postgres && (fk[3] == 'public' || (multitenancy && fk[3] == schema))) ||
|
2099
2036
|
(::Brick.is_oracle && fk[3] == schema) ||
|
2100
2037
|
(is_mssql && fk[3] == 'dbo') ||
|
2101
2038
|
(!is_postgres && !::Brick.is_oracle && !is_mssql && ['mysql', 'performance_schema', 'sys'].exclude?(fk[3]))
|
@@ -2113,7 +2050,7 @@ ORDER BY 1, 2, c.internal_column_id, acc.position"
|
|
2113
2050
|
relations.each do |k, v|
|
2114
2051
|
rel_name = k.split('.').map { |rel_part| ::Brick.namify(rel_part, :underscore) }
|
2115
2052
|
schema_names = rel_name[0..-2]
|
2116
|
-
schema_names.shift if ::Brick.apartment_multitenant && schema_names.first ==
|
2053
|
+
schema_names.shift if ::Brick.apartment_multitenant && schema_names.first == ::Brick.apartment_default_tenant
|
2117
2054
|
v[:schema] = schema_names.join('.') unless schema_names.empty?
|
2118
2055
|
# %%% If more than one schema has the same table name, will need to add a schema name prefix to have uniqueness
|
2119
2056
|
v[:resource] = rel_name.last
|
@@ -2124,7 +2061,7 @@ ORDER BY 1, 2, c.internal_column_id, acc.position"
|
|
2124
2061
|
end
|
2125
2062
|
::Brick.load_additional_references if initializer_loaded
|
2126
2063
|
|
2127
|
-
if orig_schema && (orig_schema = (orig_schema - ['pg_catalog', 'heroku_ext']).first)
|
2064
|
+
if orig_schema && (orig_schema = (orig_schema - ['pg_catalog', 'pg_toast', 'heroku_ext']).first)
|
2128
2065
|
puts "Now switching back to \"#{orig_schema}\" schema."
|
2129
2066
|
ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?", orig_schema)
|
2130
2067
|
end
|
@@ -2162,7 +2099,7 @@ ORDER BY 1, 2, c.internal_column_id, acc.position"
|
|
2162
2099
|
AND kcu.column_name = c.column_name#{"
|
2163
2100
|
-- AND kcu.position_in_unique_constraint IS NULL" unless is_mssql}
|
2164
2101
|
WHERE t.table_schema #{is_postgres || is_mssql ?
|
2165
|
-
"NOT IN ('information_schema', 'pg_catalog',
|
2102
|
+
"NOT IN ('information_schema', 'pg_catalog', 'pg_toast', 'heroku_ext',
|
2166
2103
|
'INFORMATION_SCHEMA', 'sys')"
|
2167
2104
|
:
|
2168
2105
|
"= '#{ActiveRecord::Base.connection.current_database.tr("'", "''")}'"}#{"
|
@@ -2261,7 +2198,7 @@ module Brick
|
|
2261
2198
|
for_tbl = fk[1]
|
2262
2199
|
fk_namified = ::Brick.namify(fk[1])
|
2263
2200
|
apartment = Object.const_defined?('Apartment') && Apartment
|
2264
|
-
fk[0] =
|
2201
|
+
fk[0] = ::Brick.apartment_default_tenant if apartment && apartment.excluded_models.include?(fk_namified.singularize.camelize)
|
2265
2202
|
fk[1] = "#{fk[0]}.#{fk[1]}" if fk[0] # && fk[0] != ::Brick.default_schema
|
2266
2203
|
bts = (relation = relations.fetch(fk[1], nil))&.fetch(:fks) { relation[:fks] = {} }
|
2267
2204
|
|
@@ -2277,7 +2214,7 @@ module Brick
|
|
2277
2214
|
# If Apartment gem lists the primary table as being associated with a non-tenanted model
|
2278
2215
|
# then use 'public' schema for the primary table
|
2279
2216
|
if apartment && apartment&.excluded_models.include?(fk[4].singularize.camelize)
|
2280
|
-
fk[3] =
|
2217
|
+
fk[3] = ::Brick.apartment_default_tenant
|
2281
2218
|
true
|
2282
2219
|
end
|
2283
2220
|
else
|
@@ -2352,7 +2289,7 @@ module Brick
|
|
2352
2289
|
end
|
2353
2290
|
assoc_hm[:alternate_name] = "#{assoc_hm[:alternate_name]}_#{bt_assoc_name}" unless assoc_hm[:alternate_name] == bt_assoc_name
|
2354
2291
|
else
|
2355
|
-
inv_tbl = if ::Brick.config.schema_behavior[:multitenant] && apartment && fk[0] ==
|
2292
|
+
inv_tbl = if ::Brick.config.schema_behavior[:multitenant] && apartment && fk[0] == ::Brick.apartment_default_tenant
|
2356
2293
|
for_tbl
|
2357
2294
|
else
|
2358
2295
|
fk[1]
|
@@ -2411,7 +2348,7 @@ module Brick
|
|
2411
2348
|
end
|
2412
2349
|
::Brick.relations.keys.map do |v|
|
2413
2350
|
tbl_parts = v.split('.')
|
2414
|
-
tbl_parts.shift if ::Brick.apartment_multitenant && tbl_parts.length > 1 && tbl_parts.first ==
|
2351
|
+
tbl_parts.shift if ::Brick.apartment_multitenant && tbl_parts.length > 1 && tbl_parts.first == ::Brick.apartment_default_tenant
|
2415
2352
|
res = tbl_parts.join('.')
|
2416
2353
|
[v, (model = models[res])&.last&.table_name, migrations&.fetch(res, nil), model&.first]
|
2417
2354
|
end
|
@@ -2441,14 +2378,14 @@ module Brick
|
|
2441
2378
|
|
2442
2379
|
# Locate orphaned records
|
2443
2380
|
def find_orphans(multi_schema)
|
2444
|
-
is_default_schema = multi_schema&.==(
|
2381
|
+
is_default_schema = multi_schema&.==(::Brick.apartment_default_tenant)
|
2445
2382
|
relations.each_with_object([]) do |v, s|
|
2446
2383
|
frn_tbl = v.first
|
2447
2384
|
next if (relation = v.last).key?(:isView) || config.exclude_tables.include?(frn_tbl) ||
|
2448
2385
|
!(for_pk = (relation[:pkey].values.first&.first))
|
2449
2386
|
|
2450
2387
|
is_default_frn_schema = !is_default_schema && multi_schema &&
|
2451
|
-
((frn_parts = frn_tbl.split('.')).length > 1 && frn_parts.first)&.==(
|
2388
|
+
((frn_parts = frn_tbl.split('.')).length > 1 && frn_parts.first)&.==(::Brick.apartment_default_tenant)
|
2452
2389
|
relation[:fks].select { |_k, assoc| assoc[:is_bt] }.each do |_k, bt|
|
2453
2390
|
begin
|
2454
2391
|
if bt.key?(:polymorphic)
|
@@ -2464,7 +2401,7 @@ module Brick
|
|
2464
2401
|
# Skip if database is multitenant, we're not focused on "public", and the foreign and primary tables
|
2465
2402
|
# are both in the "public" schema
|
2466
2403
|
next if is_default_frn_schema &&
|
2467
|
-
((pri_parts = pri_tbl&.split('.'))&.length > 1 && pri_parts.first)&.==(
|
2404
|
+
((pri_parts = pri_tbl&.split('.'))&.length > 1 && pri_parts.first)&.==(::Brick.apartment_default_tenant)
|
2468
2405
|
|
2469
2406
|
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
|
2470
2407
|
FROM #{frn_tbl} AS frn
|
@@ -2483,7 +2420,7 @@ module Brick
|
|
2483
2420
|
# are both in the "public" schema
|
2484
2421
|
pri_tbl = bt.key?(:inverse_table) && bt[:inverse_table]
|
2485
2422
|
next if is_default_frn_schema &&
|
2486
|
-
((pri_parts = pri_tbl&.split('.'))&.length > 1 && pri_parts.first)&.==(
|
2423
|
+
((pri_parts = pri_tbl&.split('.'))&.length > 1 && pri_parts.first)&.==(::Brick.apartment_default_tenant)
|
2487
2424
|
|
2488
2425
|
pri_pk = relations[pri_tbl].fetch(:pkey, nil)&.values&.first&.first ||
|
2489
2426
|
_class_pk(pri_tbl, multi_schema)
|
@@ -192,13 +192,13 @@ 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
|
@@ -466,6 +466,22 @@ var #{table_name}HtColumns;
|
|
466
466
|
// This PageTransitionEvent fires when the page first loads, as well as after any other history
|
467
467
|
// transition such as when using the browser's Back and Forward buttons.
|
468
468
|
window.addEventListener(\"pageshow\", function() {
|
469
|
+
if (tblSelect) { // Always present
|
470
|
+
var i = #{::Brick.config.path_prefix ? '0' : 'schemaSelect ? 1 : 0'},
|
471
|
+
changeoutList = changeout(location.href);
|
472
|
+
for (; i < changeoutList.length; ++i) {
|
473
|
+
tblSelect.value = changeoutList[i];
|
474
|
+
if (tblSelect.value !== \"\") break;
|
475
|
+
}
|
476
|
+
|
477
|
+
tblSelect.addEventListener(\"change\", function () {
|
478
|
+
var lhr = changeout(location.href, null, this.value);
|
479
|
+
if (brickSchema)
|
480
|
+
lhr = changeout(lhr, \"_brick_schema\", schemaSelect.value);
|
481
|
+
location.href = lhr;
|
482
|
+
});
|
483
|
+
}
|
484
|
+
|
469
485
|
if (schemaSelect && schemaSelect.options.length > 1) { // First drop-down is only present if multitenant
|
470
486
|
brickSchema = changeout(location.href, \"_brick_schema\");
|
471
487
|
if (brickSchema) {
|
@@ -478,6 +494,7 @@ window.addEventListener(\"pageshow\", function() {
|
|
478
494
|
location.href = changeout(location.href, \"_brick_schema\", this.value, tblSelect.value);
|
479
495
|
});
|
480
496
|
}
|
497
|
+
|
481
498
|
[... document.getElementsByTagName(\"FORM\")].forEach(function (form) {
|
482
499
|
if (brickSchema)
|
483
500
|
form.action = changeout(form.action, \"_brick_schema\", brickSchema);
|
@@ -489,22 +506,6 @@ window.addEventListener(\"pageshow\", function() {
|
|
489
506
|
return true;
|
490
507
|
});
|
491
508
|
});
|
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
509
|
});
|
509
510
|
|
510
511
|
// Add \"Are you sure?\" behaviour to any data-confirm buttons out there
|
@@ -834,7 +835,7 @@ erDiagram
|
|
834
835
|
</head>
|
835
836
|
<body>
|
836
837
|
<p style=\"color: green\"><%= notice %></p>#{"
|
837
|
-
|
838
|
+
#{schema_options}" if schema_options}
|
838
839
|
<select id=\"tbl\">#{table_options}</select>
|
839
840
|
<table id=\"resourceName\"><tr>
|
840
841
|
<td><h1>#{model_name}</h1></td>
|
@@ -954,10 +955,12 @@ erDiagram
|
|
954
955
|
poly_id = #{obj_name}.send(\"#\{bt.first}_id\")
|
955
956
|
%><%= link_to(\"#\{bt_class} ##\{poly_id}\", send(\"#\{base_class_underscored}_path\".to_sym, poly_id)) if poly_id %><%
|
956
957
|
else
|
957
|
-
# binding.pry if @_brick_bt_descrip[bt.first][bt[1].first.first].nil?
|
958
958
|
bt_class = bt[1].first.first
|
959
959
|
descrips = @_brick_bt_descrip[bt.first][bt_class]
|
960
|
-
bt_id_col = if descrips.
|
960
|
+
bt_id_col = if descrips.nil?
|
961
|
+
puts \"Caught it in the act for #{obj_name} / #\{col_name}!\"
|
962
|
+
# binding.pry
|
963
|
+
elsif descrips.length == 1
|
961
964
|
[#{obj_name}.class.reflect_on_association(bt.first)&.foreign_key]
|
962
965
|
else
|
963
966
|
descrips.last
|
@@ -974,16 +977,16 @@ erDiagram
|
|
974
977
|
if hms_col.length == 1 %>
|
975
978
|
<%= hms_col.first %>
|
976
979
|
<% else
|
977
|
-
|
978
|
-
|
979
|
-
|
980
|
-
|
981
|
-
|
982
|
-
|
983
|
-
|
984
|
-
|
985
|
-
|
986
|
-
|
980
|
+
%><%= klass = (col = cols[col_name])[1]
|
981
|
+
if col[2] == 'HO'
|
982
|
+
descrips = @_brick_bt_descrip[col_name.to_sym][klass]
|
983
|
+
if (ho_id = (ho_id_col = descrips.last).map { |id_col| #{obj_name}.send(id_col.to_sym) })&.first
|
984
|
+
ho_txt = klass.brick_descrip(#{obj_name}, descrips[0..-2].map { |id| #{obj_name}.send(id.last[0..62]) }, ho_id_col)
|
985
|
+
link_to(ho_txt, send(\"#\{klass.base_class._brick_index(:singular)}_path\".to_sym, ho_id))
|
986
|
+
end
|
987
|
+
elsif hms_col[1]&.positive?
|
988
|
+
link_to \"#\{hms_col[1] || 'View'} #\{hms_col.first}\", send(\"#\{klass._brick_index}_path\".to_sym, hms_col[2])
|
989
|
+
end %>
|
987
990
|
<% end
|
988
991
|
elsif (col = cols[col_name])
|
989
992
|
col_type = col&.sql_type == 'geography' ? col.sql_type : col&.type
|
@@ -1019,7 +1022,7 @@ erDiagram
|
|
1019
1022
|
# Easily could be multiple files involved (STI for instance)
|
1020
1023
|
+"#{css}
|
1021
1024
|
<p style=\"color: green\"><%= notice %></p>#{"
|
1022
|
-
|
1025
|
+
#{schema_options}" if schema_options}
|
1023
1026
|
<select id=\"tbl\">#{table_options}</select>
|
1024
1027
|
<h1>Status</h1>
|
1025
1028
|
<table id=\"status\" class=\"shadow\"><thead><tr>
|
@@ -1068,7 +1071,7 @@ erDiagram
|
|
1068
1071
|
if is_orphans
|
1069
1072
|
+"#{css}
|
1070
1073
|
<p style=\"color: green\"><%= notice %></p>#{"
|
1071
|
-
|
1074
|
+
#{schema_options}" if schema_options}
|
1072
1075
|
<select id=\"tbl\">#{table_options}</select>
|
1073
1076
|
<h1>Orphans<%= \" for #\{}\" if false %></h1>
|
1074
1077
|
<% @orphans.each do |o|
|
@@ -1099,7 +1102,7 @@ erDiagram
|
|
1099
1102
|
</svg>
|
1100
1103
|
|
1101
1104
|
<p style=\"color: green\"><%= notice %></p>#{"
|
1102
|
-
|
1105
|
+
#{schema_options}" if schema_options}
|
1103
1106
|
<select id=\"tbl\">#{table_options}</select>
|
1104
1107
|
<h1><%= page_title %></h1><%
|
1105
1108
|
if (description = (relation = Brick.relations[#{model_name}.table_name])&.fetch(:description, nil)) %><%=
|
@@ -1411,9 +1414,26 @@ document.querySelectorAll(\"input, select\").forEach(function (inp) {
|
|
1411
1414
|
# As if it were an inline template (see #determine_template in actionview-5.2.6.2/lib/action_view/renderer/template_renderer.rb)
|
1412
1415
|
keys = options.has_key?(:locals) ? options[:locals].keys : []
|
1413
1416
|
handler = ActionView::Template.handler_for_extension(options[:type] || 'erb')
|
1414
|
-
ActionView::Template.new(inline, "auto-generated #{args.first} template", handler, locals: keys)
|
1417
|
+
ActionView::Template.new(inline, "auto-generated #{args.first} template", handler, locals: keys).tap do |t|
|
1418
|
+
t.instance_variable_set(:@is_brick, true)
|
1419
|
+
end
|
1415
1420
|
end
|
1416
|
-
end
|
1421
|
+
end # LookupContext
|
1422
|
+
|
1423
|
+
# For any auto-generated template, if multitenancy is active via some flavour of an Apartment gem, switch back to the default tenant.
|
1424
|
+
# (Underlying reason -- ros-apartment can hold on to a selected tenant between requests when there is no elevator middleware.)
|
1425
|
+
ActionView::TemplateRenderer.class_exec do
|
1426
|
+
private
|
1427
|
+
|
1428
|
+
alias _brick_render_template render_template
|
1429
|
+
def render_template(view, template, *args)
|
1430
|
+
result = _brick_render_template(view, template, *args)
|
1431
|
+
if template.instance_variable_get(:@is_brick)
|
1432
|
+
Apartment::Tenant.switch!(::Brick.apartment_default_tenant) if ::Brick.apartment_multitenant
|
1433
|
+
end
|
1434
|
+
result
|
1435
|
+
end
|
1436
|
+
end # TemplateRenderer
|
1417
1437
|
end
|
1418
1438
|
|
1419
1439
|
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 ||= {}
|
@@ -1052,21 +1058,43 @@ ActiveSupport.on_load(:active_record) do
|
|
1052
1058
|
end
|
1053
1059
|
end
|
1054
1060
|
|
1055
|
-
|
1056
|
-
|
1057
|
-
|
1058
|
-
|
1059
|
-
|
1060
|
-
|
1061
|
-
|
1062
|
-
|
1063
|
-
|
1064
|
-
|
1065
|
-
|
1066
|
-
|
1061
|
+
class ActiveRecord::Associations::JoinDependency
|
1062
|
+
if JoinBase.instance_method(:initialize).arity == 2 # Older ActiveRecord 4.x?
|
1063
|
+
def initialize(base, associations, joins)
|
1064
|
+
@alias_tracker = ::ActiveRecord::Associations::AliasTracker.create(base.connection, joins)
|
1065
|
+
@alias_tracker.aliased_table_for(base.table_name, base.table_name) # Updates the count for base.table_name to 1
|
1066
|
+
tree = self.class.make_tree associations
|
1067
|
+
|
1068
|
+
# Provide a way to find the original relation that this tree is being used for
|
1069
|
+
# (so that we can maintain a list of links for all tables used in JOINs)
|
1070
|
+
if (relation = associations.instance_variable_get(:@relation))
|
1071
|
+
tree.instance_variable_set(:@relation, relation)
|
1072
|
+
end
|
1073
|
+
|
1074
|
+
@join_root = JoinBase.new base, build(tree, base)
|
1075
|
+
@join_root.children.each { |child| construct_tables! @join_root, child }
|
1076
|
+
end
|
1077
|
+
|
1078
|
+
else # For ActiveRecord 5.0 - 7.1
|
1079
|
+
|
1080
|
+
def initialize(base, table, associations, join_type = nil)
|
1081
|
+
tree = self.class.make_tree associations
|
1082
|
+
|
1083
|
+
# Provide a way to find the original relation that this tree is being used for
|
1084
|
+
# (so that we can maintain a list of links for all tables used in JOINs)
|
1085
|
+
if (relation = associations.instance_variable_get(:@relation))
|
1086
|
+
tree.instance_variable_set(:@relation, relation)
|
1067
1087
|
end
|
1088
|
+
|
1089
|
+
@join_root = JoinBase.new(base, table, build(tree, base))
|
1090
|
+
@join_type = join_type if join_type
|
1068
1091
|
end
|
1092
|
+
end
|
1093
|
+
end
|
1069
1094
|
|
1095
|
+
# was: ActiveRecord.version >= ::Gem::Version.new('3.2') &&
|
1096
|
+
if ActiveRecord.version < ::Gem::Version.new('5.0')
|
1097
|
+
module ActiveRecord
|
1070
1098
|
# Final pieces for left_outer_joins support, which was derived from this commit:
|
1071
1099
|
# https://github.com/rails/rails/commit/3f46ef1ddab87482b730a3f53987e04308783d8b
|
1072
1100
|
module Associations
|
@@ -1151,13 +1179,9 @@ if is_postgres && ActiveRecord.version < ::Gem::Version.new('5.0') # Was: && Ob
|
|
1151
1179
|
PGError = PG::Error
|
1152
1180
|
end
|
1153
1181
|
|
1154
|
-
# More arel_table_type stuff:
|
1155
|
-
# ---------------------------
|
1156
1182
|
if ActiveRecord.version < ::Gem::Version.new('5.2')
|
1157
1183
|
# Specifically for AR 3.1 and 3.2 to avoid: "undefined method `delegate' for ActiveRecord::Reflection::ThroughReflection:Class"
|
1158
1184
|
require 'active_support/core_ext/module/delegation' if ActiveRecord.version < ::Gem::Version.new('4.0')
|
1159
|
-
# Used by Util#_arel_table_type
|
1160
|
-
# rubocop:disable Style/CommentedKeyword
|
1161
1185
|
module ActiveRecord
|
1162
1186
|
module Reflection
|
1163
1187
|
# AR < 4.0 doesn't know about join_table and derive_join_table
|
@@ -1175,59 +1199,108 @@ if ActiveRecord.version < ::Gem::Version.new('5.2')
|
|
1175
1199
|
end
|
1176
1200
|
end
|
1177
1201
|
end
|
1202
|
+
end
|
1203
|
+
end
|
1178
1204
|
|
1179
|
-
|
1180
|
-
|
1181
|
-
|
1182
|
-
|
1183
|
-
|
1205
|
+
# The "brick_links" patch -- this finds how every AR chain of association names
|
1206
|
+
# relates back to an exact table correlation name chosen by AREL when the AST tree is
|
1207
|
+
# walked. For instance, from a Customer model there could be a join_tree such as
|
1208
|
+
# { orders: { line_items: :product} }, which would end up recording three entries, the
|
1209
|
+
# last of which for products would have a key of "orders.line_items.product" after
|
1210
|
+
# having gone through two HMs and one BT. AREL would have chosen a correlation name of
|
1211
|
+
# "products", being able to use the same name as the table name because it's the first
|
1212
|
+
# time that table is used in this query. But let's see what happens if each customer
|
1213
|
+
# also had a BT to a favourite product, referenced earlier in the join_tree like this:
|
1214
|
+
# [:favourite_product, orders: { line_items: :product}] -- then the second reference to
|
1215
|
+
# "products" would end up being called "products_line_items" in order to differentiate
|
1216
|
+
# it from the first reference, which would have already snagged the simpler name
|
1217
|
+
# "products". It's essential that The Brick can find accurate correlation names when
|
1218
|
+
# there are multiple JOINs to the same table.
|
1219
|
+
module ActiveRecord
|
1220
|
+
module QueryMethods
|
1221
|
+
private
|
1222
|
+
|
1223
|
+
if private_instance_methods.include?(:build_join_query)
|
1224
|
+
alias _brick_build_join_query build_join_query
|
1225
|
+
def build_join_query(manager, buckets, *args)
|
1226
|
+
# %%% Better way to bring relation into the mix
|
1227
|
+
if (aj = buckets.fetch(:association_join, nil))
|
1228
|
+
aj.instance_variable_set(:@relation, self)
|
1229
|
+
end
|
1184
1230
|
|
1185
|
-
|
1186
|
-
|
1187
|
-
|
1188
|
-
|
1189
|
-
|
1190
|
-
|
1191
|
-
|
1192
|
-
|
1193
|
-
|
1194
|
-
|
1195
|
-
|
1196
|
-
|
1197
|
-
|
1231
|
+
_brick_build_join_query(manager, buckets, *args)
|
1232
|
+
end
|
1233
|
+
|
1234
|
+
else
|
1235
|
+
|
1236
|
+
alias _brick_select_association_list select_association_list
|
1237
|
+
def select_association_list(associations, stashed_joins = nil)
|
1238
|
+
result = _brick_select_association_list(associations, stashed_joins)
|
1239
|
+
result.instance_variable_set(:@relation, self)
|
1240
|
+
result
|
1241
|
+
end
|
1242
|
+
end
|
1243
|
+
end
|
1244
|
+
|
1245
|
+
# require 'active_record/associations/join_dependency'
|
1246
|
+
module Associations
|
1247
|
+
# For AR >= 4.2
|
1248
|
+
if self.const_defined?('JoinDependency')
|
1249
|
+
class JoinDependency
|
1250
|
+
private
|
1251
|
+
|
1252
|
+
# %%% Pretty much have to flat-out replace this guy (I think anyway)
|
1253
|
+
# Good with Rails 5.24 and 7 on this
|
1254
|
+
def build(associations, base_klass, root = nil, path = '')
|
1255
|
+
root ||= associations
|
1256
|
+
associations.map do |name, right|
|
1257
|
+
reflection = find_reflection base_klass, name
|
1258
|
+
reflection.check_validity!
|
1259
|
+
reflection.check_eager_loadable!
|
1260
|
+
|
1261
|
+
if reflection.polymorphic?
|
1262
|
+
raise EagerLoadPolymorphicError.new(reflection)
|
1198
1263
|
end
|
1264
|
+
|
1265
|
+
# %%% The path
|
1266
|
+
link_path = path.blank? ? name.to_s : path + ".#{name}"
|
1267
|
+
ja = JoinAssociation.new(reflection, build(right, reflection.klass, root, link_path))
|
1268
|
+
ja.instance_variable_set(:@link_path, link_path) # Make note on the JoinAssociation of its AR path
|
1269
|
+
ja.instance_variable_set(:@assocs, root)
|
1270
|
+
ja
|
1199
1271
|
end
|
1200
1272
|
end
|
1201
|
-
elsif Associations.const_defined?('JoinHelper') && JoinHelper.private_instance_methods.include?(:construct_tables)
|
1202
|
-
module JoinHelper
|
1203
|
-
private
|
1204
1273
|
|
1205
|
-
|
1206
|
-
|
1207
|
-
|
1208
|
-
|
1209
|
-
|
1210
|
-
table_name_for(reflection),
|
1211
|
-
table_alias_for(reflection, reflection != self.reflection)
|
1212
|
-
).tap do |x|
|
1213
|
-
x = x.left if x.is_a?(Arel::Nodes::TableAlias)
|
1214
|
-
x.instance_variable_set(:@_arel_table_type, reflection.chain.find { |c| c.table_name == x.name }.klass)
|
1215
|
-
end
|
1216
|
-
|
1217
|
-
next unless reflection.source_macro == :has_and_belongs_to_many
|
1274
|
+
if JoinDependency.private_instance_methods.include?(:table_aliases_for)
|
1275
|
+
# No matter if it's older or newer Rails, now extend so that we can associate AR links to table_alias names
|
1276
|
+
alias _brick_table_aliases_for table_aliases_for
|
1277
|
+
def table_aliases_for(parent, node)
|
1278
|
+
result = _brick_table_aliases_for(parent, node)
|
1218
1279
|
|
1219
|
-
|
1220
|
-
|
1221
|
-
|
1222
|
-
|
1280
|
+
# Capture the table alias name that was chosen
|
1281
|
+
link_path = node.instance_variable_get(:@link_path)
|
1282
|
+
if (relation = node.instance_variable_get(:@assocs)&.instance_variable_get(:@relation))
|
1283
|
+
relation.brick_links[link_path] = result.first.table_alias || result.first.table_name
|
1223
1284
|
end
|
1224
|
-
|
1285
|
+
|
1286
|
+
result
|
1287
|
+
end
|
1288
|
+
else # Same idea but for Rails 7
|
1289
|
+
alias _brick_make_constraints make_constraints
|
1290
|
+
def make_constraints(parent, child, join_type)
|
1291
|
+
result = _brick_make_constraints(parent, child, join_type)
|
1292
|
+
|
1293
|
+
# Capture the table alias name that was chosen
|
1294
|
+
link_path = child.instance_variable_get(:@link_path)
|
1295
|
+
relation = child.instance_variable_get(:@assocs)&.instance_variable_get(:@relation)
|
1296
|
+
# binding.pry if relation
|
1297
|
+
relation.brick_links[link_path] = result.first.left.table_alias || result.first.left.table_name
|
1298
|
+
result
|
1225
1299
|
end
|
1226
1300
|
end
|
1227
1301
|
end
|
1228
1302
|
end
|
1229
|
-
end
|
1230
|
-
# rubocop:enable Style/CommentedKeyword
|
1303
|
+
end
|
1231
1304
|
end
|
1232
1305
|
|
1233
1306
|
require 'brick/extensions'
|
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.93
|
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-21 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
|