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