brick 1.0.31 → 1.0.32
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/brick/extensions.rb +129 -168
- data/lib/brick/frameworks/rails/engine.rb +5 -2
- data/lib/brick/version_number.rb +1 -1
- data/lib/brick.rb +9 -6
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8483d9a50013e440cce28f648d09d5322985702f952520cb83c69ad0e5742ce4
|
4
|
+
data.tar.gz: 40072d0504f4d2b1cc9a59e62fef305b939dd977da0e794445adf003dbfb8b7e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9740778159acb464652322ef36eff763a55b38c6579fe0a7550a5797db44928ad6832a8bccc0d924a4d4c5a14dc02658e61bf4220cecff08fc348d540b67e41c
|
7
|
+
data.tar.gz: 1b09db07a284512821b604f1d82f64c4be0c11db7f8a8973f36ab3f78b21804a7fec1eb641bcf1ac5c2ec118cb477c8086c124017c66550753ec4f9beddbd413
|
data/lib/brick/extensions.rb
CHANGED
@@ -104,7 +104,8 @@ module ActiveRecord
|
|
104
104
|
if ch == ']' # Time to process a bracketed thing?
|
105
105
|
parts = bracket_name.split('.')
|
106
106
|
first_parts = parts[0..-2].map do |part|
|
107
|
-
klass = klass.reflect_on_association(part_sym = part.to_sym)
|
107
|
+
klass = (orig_class = klass).reflect_on_association(part_sym = part.to_sym)&.klass
|
108
|
+
puts "Couldn't reference #{orig_class.name}##{part} that's part of the DSL \"#{dsl}\"." if klass.nil?
|
108
109
|
part_sym
|
109
110
|
end
|
110
111
|
parts = prefix + first_parts + [parts[-1]]
|
@@ -348,6 +349,7 @@ module ActiveRecord
|
|
348
349
|
next unless klass.reflect_on_association(assoc_name)&.klass&.column_names&.any?(ks.last)
|
349
350
|
|
350
351
|
join_array[assoc_name] = nil # Store this relation name in our special collection for .joins()
|
352
|
+
distinct!
|
351
353
|
end
|
352
354
|
wheres[k] = v.split(',')
|
353
355
|
end
|
@@ -420,11 +422,13 @@ module ActiveRecord
|
|
420
422
|
on_clause << "#{tbl_alias}.#{poly_type} = '#{name}'"
|
421
423
|
end
|
422
424
|
join_clause = "LEFT OUTER
|
423
|
-
JOIN (SELECT #{selects.join(', ')}, COUNT(#{
|
425
|
+
JOIN (SELECT #{selects.join(', ')}, COUNT(#{'DISTINCT ' if hm.options[:through]}#{count_column
|
426
|
+
}) AS _ct_ FROM #{associative&.table_name || hm.klass.table_name
|
427
|
+
} GROUP BY #{(1..selects.length).to_a.join(', ')}) AS #{tbl_alias}"
|
424
428
|
joins!("#{join_clause} ON #{on_clause.join(' AND ')}")
|
425
429
|
end
|
426
430
|
where!(wheres) unless wheres.empty?
|
427
|
-
limit(1000) # Don't want to get too carried away just yet
|
431
|
+
limit!(1000) # Don't want to get too carried away just yet
|
428
432
|
wheres unless wheres.empty? # Return the specific parameters that we did use
|
429
433
|
end
|
430
434
|
|
@@ -485,7 +489,9 @@ if ActiveSupport::Dependencies.respond_to?(:autoload_module!) # %%% Only works w
|
|
485
489
|
alias _brick_autoload_module! autoload_module!
|
486
490
|
def autoload_module!(*args)
|
487
491
|
into, const_name, qualified_name, path_suffix = args
|
488
|
-
|
492
|
+
base_class_name = ::Brick.config.sti_namespace_prefixes&.fetch("::#{into.name}::", nil)
|
493
|
+
base_class_name = "::#{base_class_name}" unless base_class_name.start_with?('::')
|
494
|
+
if (base_class = base_class_name&.constantize)
|
489
495
|
::Brick.sti_models[qualified_name] = { base: base_class }
|
490
496
|
# Build subclass and place it into the specially STI-namespaced module
|
491
497
|
into.const_set(const_name.to_sym, klass = Class.new(base_class))
|
@@ -506,8 +512,13 @@ end
|
|
506
512
|
Module.class_exec do
|
507
513
|
alias _brick_const_missing const_missing
|
508
514
|
def const_missing(*args)
|
509
|
-
if (self.const_defined?(args.first) && (possible = self.const_get(args.first)) != self) ||
|
510
|
-
(self != Object && Object.const_defined?(args.first) &&
|
515
|
+
if (self.const_defined?(args.first) && (possible = self.const_get(args.first)) && possible != self) ||
|
516
|
+
(self != Object && Object.const_defined?(args.first) &&
|
517
|
+
(
|
518
|
+
(possible = Object.const_get(args.first)) &&
|
519
|
+
(possible != self || (possible == self && possible.is_a?(Class)))
|
520
|
+
)
|
521
|
+
)
|
511
522
|
return possible
|
512
523
|
end
|
513
524
|
class_name = args.first.to_s
|
@@ -533,7 +544,7 @@ Module.class_exec do
|
|
533
544
|
result = if ::Brick.enable_controllers? && class_name.end_with?('Controller') && (plural_class_name = class_name[0..-11]).length.positive?
|
534
545
|
# Otherwise now it's up to us to fill in the gaps
|
535
546
|
# (Go over to underscores for a moment so that if we have something come in like VABCsController then the model name ends up as
|
536
|
-
#
|
547
|
+
# Vabc instead of VABC)
|
537
548
|
full_class_name = +''
|
538
549
|
full_class_name << "::#{self.name}" unless self == Object
|
539
550
|
full_class_name << "::#{plural_class_name.underscore.singularize.camelize}"
|
@@ -543,12 +554,13 @@ Module.class_exec do
|
|
543
554
|
end
|
544
555
|
elsif (::Brick.enable_models? || ::Brick.enable_controllers?) && # Schema match?
|
545
556
|
self == Object && # %%% This works for Person::Person -- but also limits us to not being able to allow more than one level of namespacing
|
546
|
-
schema_name = [(singular_table_name = class_name.underscore),
|
547
|
-
|
548
|
-
|
549
|
-
|
557
|
+
(schema_name = [(singular_table_name = class_name.underscore),
|
558
|
+
(table_name = singular_table_name.pluralize),
|
559
|
+
class_name,
|
560
|
+
(plural_class_name = class_name.pluralize)].find { |s| Brick.db_schemas.include?(s) }&.camelize ||
|
561
|
+
(::Brick.config.sti_namespace_prefixes&.key?("::#{class_name}::") && class_name))
|
550
562
|
# Build out a module for the schema if it's namespaced
|
551
|
-
schema_name = schema_name.camelize
|
563
|
+
# schema_name = schema_name.camelize
|
552
564
|
self.const_set(schema_name.to_sym, (built_module = Module.new))
|
553
565
|
|
554
566
|
[built_module, "module #{schema_name}; end\n"]
|
@@ -557,27 +569,34 @@ Module.class_exec do
|
|
557
569
|
# See if a file is there in the same way that ActiveSupport::Dependencies#load_missing_constant
|
558
570
|
# checks for it in ~/.rvm/gems/ruby-2.7.5/gems/activesupport-5.2.6.2/lib/active_support/dependencies.rb
|
559
571
|
|
560
|
-
|
572
|
+
if self != Object || # Are we in some namespace? ...
|
573
|
+
(base_model = ::Brick.config.sti_namespace_prefixes&.fetch("::#{name}::", nil)&.constantize) # ... or part of an auto-STI namespace?
|
561
574
|
schema_name = [(singular_schema_name = name.underscore),
|
562
575
|
(schema_name = singular_schema_name.pluralize),
|
563
576
|
name,
|
564
577
|
name.pluralize].find { |s| Brick.db_schemas.include?(s) }
|
565
578
|
end
|
566
|
-
|
567
579
|
plural_class_name = ActiveSupport::Inflector.pluralize(model_name = class_name)
|
568
580
|
# If it's namespaced then we turn the first part into what would be a schema name
|
569
|
-
singular_table_name = ActiveSupport::Inflector.underscore(model_name).gsub('
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
+
singular_table_name = ActiveSupport::Inflector.underscore(model_name).gsub('/', '.')
|
582
|
+
|
583
|
+
if base_model
|
584
|
+
schema_name = name.underscore # For the auto-STI namespace models
|
585
|
+
table_name = base_model.table_name
|
586
|
+
Object.send(:build_model, self, model_name, singular_table_name, table_name, relations, table_name)
|
587
|
+
else
|
588
|
+
# Adjust for STI if we know of a base model for the requested model name
|
589
|
+
# %%% Does not yet work with namespaced model names. Perhaps prefix with plural_class_name when doing the lookups here.
|
590
|
+
table_name = if (base_model = ::Brick.sti_models[model_name]&.fetch(:base, nil) || ::Brick.existing_stis[model_name]&.constantize)
|
591
|
+
base_model.table_name
|
592
|
+
else
|
593
|
+
ActiveSupport::Inflector.pluralize(singular_table_name)
|
594
|
+
end
|
595
|
+
|
596
|
+
# Maybe, just maybe there's a database table that will satisfy this need
|
597
|
+
if (matching = [table_name, singular_table_name, plural_class_name, model_name].find { |m| relations.key?(schema_name ? "#{schema_name}.#{m}" : m) })
|
598
|
+
Object.send(:build_model, schema_name, model_name, singular_table_name, table_name, relations, matching)
|
599
|
+
end
|
581
600
|
end
|
582
601
|
end
|
583
602
|
if result
|
@@ -593,7 +612,7 @@ Module.class_exec do
|
|
593
612
|
elsif self != Object
|
594
613
|
module_parent.const_missing(*args)
|
595
614
|
else
|
596
|
-
puts "MISSING!
|
615
|
+
puts "MISSING! #{self.name} #{args.inspect} #{table_name}"
|
597
616
|
self._brick_const_missing(*args)
|
598
617
|
end
|
599
618
|
end
|
@@ -601,99 +620,6 @@ end
|
|
601
620
|
|
602
621
|
class Object
|
603
622
|
class << self
|
604
|
-
# alias _brick_const_missing const_missing
|
605
|
-
# def const_missing(*args)
|
606
|
-
# # return self.const_get(args.first) if self.const_defined?(args.first)
|
607
|
-
# # return Object.const_get(args.first) if Object.const_defined?(args.first) unless self == Object
|
608
|
-
# if self.const_defined?(args.first) && (possible = self.const_get(args.first)) != self
|
609
|
-
# return possible
|
610
|
-
# end
|
611
|
-
# if self != Object && Object.const_defined?(args.first) && (possible = Object.const_get(args.first)) != self
|
612
|
-
# return possible
|
613
|
-
# end
|
614
|
-
|
615
|
-
# class_name = args.first.to_s
|
616
|
-
# # See if a file is there in the same way that ActiveSupport::Dependencies#load_missing_constant
|
617
|
-
# # checks for it in ~/.rvm/gems/ruby-2.7.5/gems/activesupport-5.2.6.2/lib/active_support/dependencies.rb
|
618
|
-
# # that is, checking #qualified_name_for with: from_mod, const_name
|
619
|
-
# # If we want to support namespacing in the future, might have to utilise something like this:
|
620
|
-
# # path_suffix = ActiveSupport::Dependencies.qualified_name_for(Object, args.first).underscore
|
621
|
-
# # return self._brick_const_missing(*args) if ActiveSupport::Dependencies.search_for_file(path_suffix)
|
622
|
-
# # If the file really exists, go and snag it:
|
623
|
-
# if !(is_found = ActiveSupport::Dependencies.search_for_file(class_name.underscore)) && (filepath = (self.name || class_name)&.split('::'))
|
624
|
-
# filepath = (filepath[0..-2] + [class_name]).join('/').underscore + '.rb'
|
625
|
-
# end
|
626
|
-
# if is_found
|
627
|
-
# return self._brick_const_missing(*args)
|
628
|
-
# elsif ActiveSupport::Dependencies.search_for_file(filepath) # Last-ditch effort to pick this thing up before we fill in the gaps on our own
|
629
|
-
# my_const = parent.const_missing(class_name) # ends up having: MyModule::MyClass
|
630
|
-
# return my_const
|
631
|
-
# end
|
632
|
-
# relations = ::Brick.instance_variable_get(:@relations)[ActiveRecord::Base.connection_pool.object_id] || {}
|
633
|
-
# result = if ::Brick.enable_controllers? && class_name.end_with?('Controller') && (plural_class_name = class_name[0..-11]).length.positive?
|
634
|
-
# # Otherwise now it's up to us to fill in the gaps
|
635
|
-
# # (Go over to underscores for a moment so that if we have something come in like VABCsController then the model name ends up as
|
636
|
-
# # VAbc instead of VABC)
|
637
|
-
# if (model = Object.const_get(plural_class_name.underscore.singularize.camelize))
|
638
|
-
# # if it's a controller and no match or a model doesn't really use the same table name, eager load all models and try to find a model class of the right name.
|
639
|
-
# build_controller(nil, class_name, plural_class_name, model, relations)
|
640
|
-
# end
|
641
|
-
# elsif (::Brick.enable_models? || ::Brick.enable_controllers?) && # Schema match?
|
642
|
-
# db_schema_name = [(singular_table_name = class_name.underscore),
|
643
|
-
# (table_name = singular_table_name.pluralize),
|
644
|
-
# class_name,
|
645
|
-
# (plural_class_name = class_name.pluralize)].find { |s| Brick.db_schemas.include?(s) }
|
646
|
-
# # Build out a module for the schema if it's namespaced
|
647
|
-
# schema_name = db_schema_name.camelize
|
648
|
-
# unless Object.const_defined?(schema_name.to_sym)
|
649
|
-
# Object.const_set(schema_name.to_sym, (built_module = Module.new))
|
650
|
-
# Brick.db_schemas[db_schema_name] = built_module
|
651
|
-
# [built_module, "module #{schema_name}; end\n"]
|
652
|
-
# end
|
653
|
-
# # # %%% Perhaps an option to use the first module just as schema, and additional modules as namespace with a table name prefix applied
|
654
|
-
# # schema_name, model_name =
|
655
|
-
# # code = +''
|
656
|
-
# # mod_tree = +''
|
657
|
-
# # model_name.split('::')[0..-2].each do |mod_name|
|
658
|
-
# # mod_tree << "::#{mod_name}"
|
659
|
-
# # Module.new(mod_tree)
|
660
|
-
# # code << "module #{mod_tree}; end\n"
|
661
|
-
# # end
|
662
|
-
# elsif ::Brick.enable_models?
|
663
|
-
# # See if a file is there in the same way that ActiveSupport::Dependencies#load_missing_constant
|
664
|
-
# # checks for it in ~/.rvm/gems/ruby-2.7.5/gems/activesupport-5.2.6.2/lib/active_support/dependencies.rb
|
665
|
-
# plural_class_name = ActiveSupport::Inflector.pluralize(model_name = class_name)
|
666
|
-
# singular_table_name = ActiveSupport::Inflector.underscore(model_name)
|
667
|
-
|
668
|
-
# # Adjust for STI if we know of a base model for the requested model name
|
669
|
-
# table_name = if (base_model = ::Brick.sti_models[model_name]&.fetch(:base, nil) || ::Brick.existing_stis[model_name]&.constantize)
|
670
|
-
# base_model.table_name
|
671
|
-
# else
|
672
|
-
# ActiveSupport::Inflector.pluralize(singular_table_name)
|
673
|
-
# end
|
674
|
-
|
675
|
-
# # Maybe, just maybe there's a database table that will satisfy this need
|
676
|
-
# if (matching = [table_name, singular_table_name, plural_class_name, model_name].find { |m| relations.key?(m) })
|
677
|
-
# build_model(nil, model_name, singular_table_name, table_name, relations, matching)
|
678
|
-
# end
|
679
|
-
# end
|
680
|
-
# if result
|
681
|
-
# built_class, code = result
|
682
|
-
# puts "\n#{code}"
|
683
|
-
# built_class
|
684
|
-
# elsif ::Brick.config.sti_namespace_prefixes&.key?("::#{class_name}") && !schema_name
|
685
|
-
# # module_prefixes = type_name.split('::')
|
686
|
-
# # path = self.name.split('::')[0..-2] + []
|
687
|
-
# # module_prefixes.unshift('') unless module_prefixes.first.blank?
|
688
|
-
# # candidate_file = Rails.root.join('app/models' + module_prefixes.map(&:underscore).join('/') + '.rb')
|
689
|
-
# self._brick_const_missing(*args)
|
690
|
-
# elsif self != Object
|
691
|
-
# module_parent.const_missing(*args)
|
692
|
-
# else
|
693
|
-
# puts "MISSING! obj #{self.name}/#{schema_name} #{args.inspect} #{table_name}"
|
694
|
-
# self._brick_const_missing(*args)
|
695
|
-
# end
|
696
|
-
# end
|
697
623
|
|
698
624
|
private
|
699
625
|
|
@@ -701,8 +627,12 @@ class Object
|
|
701
627
|
full_name = if schema_name.blank?
|
702
628
|
model_name
|
703
629
|
else # Prefix the schema to the table name + prefix the schema namespace to the class name
|
704
|
-
schema_module =
|
705
|
-
|
630
|
+
schema_module = if schema_name.instance_of?(Module) # from an auto-STI namespace?
|
631
|
+
schema_name
|
632
|
+
else
|
633
|
+
matching = "#{schema_name}.#{matching}"
|
634
|
+
(Brick.db_schemas[schema_name] ||= self.const_get(schema_name.singularize.camelize))
|
635
|
+
end
|
706
636
|
"#{schema_module&.name}::#{model_name}"
|
707
637
|
end
|
708
638
|
|
@@ -711,7 +641,7 @@ class Object
|
|
711
641
|
|
712
642
|
# Are they trying to use a pluralised class name such as "Employees" instead of "Employee"?
|
713
643
|
if table_name == singular_table_name && !ActiveSupport::Inflector.inflections.uncountable.include?(table_name)
|
714
|
-
unless ::Brick.config.sti_namespace_prefixes&.key?("::#{singular_table_name.
|
644
|
+
unless ::Brick.config.sti_namespace_prefixes&.key?("::#{singular_table_name.camelize}::")
|
715
645
|
puts "Warning: Class name for a model that references table \"#{matching}\" should be \"#{ActiveSupport::Inflector.singularize(model_name)}\"."
|
716
646
|
end
|
717
647
|
return
|
@@ -792,7 +722,7 @@ class Object
|
|
792
722
|
end # class definition
|
793
723
|
# Having this separate -- will this now work out better?
|
794
724
|
built_model.class_exec do
|
795
|
-
hmts
|
725
|
+
hmts&.each do |hmt_fk, fks|
|
796
726
|
hmt_fk = hmt_fk.tr('.', '_')
|
797
727
|
fks.each do |fk|
|
798
728
|
through = fk.first[:assoc_name]
|
@@ -807,10 +737,14 @@ class Object
|
|
807
737
|
else
|
808
738
|
hmt_fk
|
809
739
|
end
|
810
|
-
|
811
|
-
|
812
|
-
|
813
|
-
|
740
|
+
options = { through: through.to_sym }
|
741
|
+
if relation[:fks].any? { |k, v| v[:assoc_name] == hmt_name }
|
742
|
+
hmt_name = "#{hmt_name.singularize}_#{fk.first[:assoc_name]}"
|
743
|
+
options[:class_name] = fk.first[:inverse_table].singularize.camelize
|
744
|
+
options[:foreign_key] = fk.first[:fk].to_sym
|
745
|
+
end
|
746
|
+
options[:source] = fk.last.to_sym unless hmt_name.singularize == fk.last
|
747
|
+
code << " has_many :#{hmt_name}#{options.map { |opt| ", #{opt.first}: #{opt.last.inspect}" }.join}\n"
|
814
748
|
self.send(:has_many, hmt_name.to_sym, **options)
|
815
749
|
end
|
816
750
|
end
|
@@ -832,6 +766,7 @@ class Object
|
|
832
766
|
else
|
833
767
|
assoc[:assoc_name]
|
834
768
|
end
|
769
|
+
options[:optional] = true if assoc.key?(:optional)
|
835
770
|
if assoc.key?(:polymorphic)
|
836
771
|
options[:polymorphic] = true
|
837
772
|
else
|
@@ -875,7 +810,11 @@ class Object
|
|
875
810
|
end
|
876
811
|
# Figure out if we need to specially call out the class_name and/or foreign key
|
877
812
|
# (and if either of those then definitely also a specific inverse_of)
|
878
|
-
|
813
|
+
if (singular_table_parts = singular_table_name.split('.')).length > 1 &&
|
814
|
+
::Brick.config.schema_behavior[:multitenant] && singular_table_parts.first == 'public'
|
815
|
+
singular_table_parts.shift
|
816
|
+
end
|
817
|
+
options[:class_name] = "::#{assoc[:primary_class]&.name || singular_table_parts.map(&:camelize).join('::')}" if need_class_name
|
879
818
|
# Work around a bug in CPK where self-referencing belongs_to associations double up their foreign keys
|
880
819
|
if need_fk # Funky foreign key?
|
881
820
|
options[:foreign_key] = if assoc[:fk].is_a?(Array)
|
@@ -889,7 +828,6 @@ class Object
|
|
889
828
|
|
890
829
|
# Prepare a list of entries for "has_many :through"
|
891
830
|
if macro == :has_many
|
892
|
-
puts [inverse_table, relations[inverse_table].length].inspect
|
893
831
|
relations[inverse_table][:hmt_fks].each do |k, hmt_fk|
|
894
832
|
next if k == assoc[:fk]
|
895
833
|
|
@@ -993,7 +931,9 @@ class Object
|
|
993
931
|
# return
|
994
932
|
end
|
995
933
|
|
996
|
-
|
934
|
+
id = params[:id]&.split(/[\/,_]/)
|
935
|
+
id = id.first if id.is_a?(Array) && id.length == 1
|
936
|
+
instance_variable_set("@#{singular_table_name}".to_sym, (obj = model.find(id)))
|
997
937
|
obj = obj.first if obj.is_a?(Array)
|
998
938
|
obj.send(:update, send(params_name = params_name.to_sym))
|
999
939
|
end
|
@@ -1040,11 +980,14 @@ module ActiveRecord::ConnectionHandling
|
|
1040
980
|
conn
|
1041
981
|
end
|
1042
982
|
|
983
|
+
# This is done separately so that during testing it can be called right after a migration
|
984
|
+
# in order to make sure everything is good.
|
1043
985
|
def _brick_reflect_tables
|
986
|
+
initializer_loaded = false
|
1044
987
|
if (relations = ::Brick.relations).empty?
|
1045
|
-
#
|
988
|
+
# If there's schema things configured then we only expect our initializer to be named exactly this
|
1046
989
|
if File.exist?(brick_initializer = Rails.root.join('config/initializers/brick.rb'))
|
1047
|
-
load brick_initializer
|
990
|
+
initializer_loaded = load brick_initializer
|
1048
991
|
end
|
1049
992
|
# Only for Postgres? (Doesn't work in sqlite3)
|
1050
993
|
# puts ActiveRecord::Base.execute_sql("SELECT current_setting('SEARCH_PATH')").to_a.inspect
|
@@ -1052,7 +995,9 @@ module ActiveRecord::ConnectionHandling
|
|
1052
995
|
schema_sql = 'SELECT NULL AS table_schema;'
|
1053
996
|
case ActiveRecord::Base.connection.adapter_name
|
1054
997
|
when 'PostgreSQL'
|
1055
|
-
if (
|
998
|
+
if (is_multitenant = (multitenancy = ::Brick.config.schema_behavior[:multitenant]) &&
|
999
|
+
(sta = multitenancy[:schema_to_analyse]) != 'public')
|
1000
|
+
::Brick.default_schema = schema = sta
|
1056
1001
|
ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?", schema)
|
1057
1002
|
end
|
1058
1003
|
schema_sql = 'SELECT DISTINCT table_schema FROM INFORMATION_SCHEMA.tables;'
|
@@ -1070,6 +1015,28 @@ module ActiveRecord::ConnectionHandling
|
|
1070
1015
|
puts "Unfamiliar with connection adapter #{ActiveRecord::Base.connection.adapter_name}"
|
1071
1016
|
end
|
1072
1017
|
|
1018
|
+
unless (db_schemas = ActiveRecord::Base.execute_sql(schema_sql)).is_a?(Array)
|
1019
|
+
db_schemas = db_schemas.to_a
|
1020
|
+
end
|
1021
|
+
unless db_schemas.empty?
|
1022
|
+
::Brick.db_schemas = db_schemas.each_with_object({}) do |row, s|
|
1023
|
+
row = row.is_a?(String) ? row : row['table_schema']
|
1024
|
+
# Remove any system schemas
|
1025
|
+
s[row] = nil unless ['information_schema', 'pg_catalog'].include?(row)
|
1026
|
+
end
|
1027
|
+
end
|
1028
|
+
|
1029
|
+
if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
|
1030
|
+
if (possible_schema = ::Brick.config.schema_behavior&.[](:multitenant)&.[](:schema_to_analyse))
|
1031
|
+
if ::Brick.db_schemas.key?(possible_schema)
|
1032
|
+
::Brick.default_schema = schema = possible_schema
|
1033
|
+
ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?", schema)
|
1034
|
+
else
|
1035
|
+
puts "*** In the brick.rb initializer the line \"::Brick.schema_behavior = ...\" refers to a schema called \"#{possible_schema}\". This schema does not exist. ***"
|
1036
|
+
end
|
1037
|
+
end
|
1038
|
+
end
|
1039
|
+
|
1073
1040
|
sql ||= "SELECT t.table_schema AS schema, t.table_name AS relation_name, t.table_type,
|
1074
1041
|
c.column_name, c.data_type,
|
1075
1042
|
COALESCE(c.character_maximum_length, c.numeric_precision) AS max_length,
|
@@ -1096,10 +1063,9 @@ module ActiveRecord::ConnectionHandling
|
|
1096
1063
|
measures = []
|
1097
1064
|
case ActiveRecord::Base.connection.adapter_name
|
1098
1065
|
when 'PostgreSQL', 'SQLite' # These bring back a hash for each row because the query uses column aliases
|
1099
|
-
schema ||= 'public' if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
|
1066
|
+
# schema ||= 'public' if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
|
1100
1067
|
ActiveRecord::Base.execute_sql(sql).each do |r|
|
1101
|
-
|
1102
|
-
relation_name = if r['schema'] != schema
|
1068
|
+
relation_name = if ![schema, 'public'].include?(r['schema']) && !::Brick.config.schema_behavior[:multitenant]
|
1103
1069
|
"#{schema_name = r['schema']}.#{r['relation_name']}"
|
1104
1070
|
else
|
1105
1071
|
r['relation_name']
|
@@ -1122,7 +1088,6 @@ module ActiveRecord::ConnectionHandling
|
|
1122
1088
|
end
|
1123
1089
|
else # MySQL2 acts a little differently, bringing back an array for each row
|
1124
1090
|
ActiveRecord::Base.execute_sql(sql).each do |r|
|
1125
|
-
# next if internal_views.include?(r['relation_name']) # Skip internal views such as v_all_assessments
|
1126
1091
|
relation = relations[(relation_name = r[0])] # here relation represents a table or view from the database
|
1127
1092
|
relation[:isView] = true if r[1] == 'VIEW' # table_type
|
1128
1093
|
col_name = r[2]
|
@@ -1161,7 +1126,7 @@ module ActiveRecord::ConnectionHandling
|
|
1161
1126
|
# end
|
1162
1127
|
# end
|
1163
1128
|
# end
|
1164
|
-
schema = ::Brick.default_schema # Reset back for this next round of fun
|
1129
|
+
# schema = ::Brick.default_schema # Reset back for this next round of fun
|
1165
1130
|
case ActiveRecord::Base.connection.adapter_name
|
1166
1131
|
when 'PostgreSQL', 'Mysql2'
|
1167
1132
|
sql = "SELECT kcu1.CONSTRAINT_SCHEMA, kcu1.TABLE_NAME, kcu1.COLUMN_NAME,
|
@@ -1176,7 +1141,7 @@ module ActiveRecord::ConnectionHandling
|
|
1176
1141
|
AND kcu2.CONSTRAINT_SCHEMA = rc.UNIQUE_CONSTRAINT_SCHEMA
|
1177
1142
|
AND kcu2.CONSTRAINT_NAME = rc.UNIQUE_CONSTRAINT_NAME
|
1178
1143
|
AND kcu2.ORDINAL_POSITION = kcu1.ORDINAL_POSITION#{"
|
1179
|
-
|
1144
|
+
WHERE kcu1.CONSTRAINT_SCHEMA = COALESCE(current_setting('SEARCH_PATH'), 'public')" if schema }"
|
1180
1145
|
# AND kcu2.TABLE_NAME = ?;", Apartment::Tenant.current, table_name
|
1181
1146
|
when 'SQLite'
|
1182
1147
|
sql = "SELECT m.name, fkl.\"from\", fkl.\"table\", m.name || '_' || fkl.\"from\" AS constraint_name
|
@@ -1186,19 +1151,12 @@ module ActiveRecord::ConnectionHandling
|
|
1186
1151
|
else
|
1187
1152
|
end
|
1188
1153
|
if sql
|
1189
|
-
::Brick.default_schema ||= schema ||= 'public' if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
|
1190
|
-
unless (db_schemas = ActiveRecord::Base.execute_sql(schema_sql)).is_a?(Array)
|
1191
|
-
db_schemas = db_schemas.to_a
|
1192
|
-
end
|
1193
|
-
unless db_schemas.empty?
|
1194
|
-
::Brick.db_schemas = db_schemas.each_with_object({}) do |row, s|
|
1195
|
-
row = row.is_a?(String) ? row : row['table_schema']
|
1196
|
-
# Remove whatever default schema we're using and other system schemas
|
1197
|
-
s[row] = nil unless ['information_schema', 'pg_catalog', schema].include?(row)
|
1198
|
-
end
|
1199
|
-
end
|
1154
|
+
# ::Brick.default_schema ||= schema ||= 'public' if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
|
1200
1155
|
ActiveRecord::Base.execute_sql(sql).each do |fk|
|
1201
1156
|
fk = fk.values unless fk.is_a?(Array)
|
1157
|
+
# Multitenancy makes things a little more general overall
|
1158
|
+
fk[0] = nil if fk[0] == 'public' || (is_multitenant && fk[0] == schema)
|
1159
|
+
fk[3] = nil if fk[3] == 'public' || (is_multitenant && fk[3] == schema)
|
1202
1160
|
::Brick._add_bt_and_hm(fk, relations)
|
1203
1161
|
end
|
1204
1162
|
end
|
@@ -1211,11 +1169,7 @@ module ActiveRecord::ConnectionHandling
|
|
1211
1169
|
views.keys.each { |k| puts ActiveSupport::Inflector.singularize(k).camelize }
|
1212
1170
|
end
|
1213
1171
|
|
1214
|
-
|
1215
|
-
if File.exist?(brick_initialiser = Rails.root.join('config/initializers/brick.rb'))
|
1216
|
-
load brick_initialiser
|
1217
|
-
::Brick.load_additional_references
|
1218
|
-
end
|
1172
|
+
::Brick.load_additional_references if initializer_loaded
|
1219
1173
|
end
|
1220
1174
|
end
|
1221
1175
|
|
@@ -1242,31 +1196,37 @@ module Brick
|
|
1242
1196
|
# rubocop:enable Style/CommentedKeyword
|
1243
1197
|
|
1244
1198
|
class << self
|
1245
|
-
def _add_bt_and_hm(fk, relations, is_polymorphic = false)
|
1246
|
-
|
1247
|
-
|
1248
|
-
|
1249
|
-
|
1250
|
-
|
1251
|
-
|
1199
|
+
def _add_bt_and_hm(fk, relations, is_polymorphic = false, is_optional = false)
|
1200
|
+
bt_assoc_name = fk[2]
|
1201
|
+
unless is_polymorphic
|
1202
|
+
bt_assoc_name = if bt_assoc_name.underscore.end_with?('_id')
|
1203
|
+
bt_assoc_name[0..-4]
|
1204
|
+
elsif bt_assoc_name.downcase.end_with?('id') && bt_assoc_name.exclude?('_')
|
1205
|
+
bt_assoc_name[0..-3] # Make the bold assumption that we can just peel off any final ID part
|
1206
|
+
else
|
1207
|
+
"#{bt_assoc_name}_bt"
|
1208
|
+
end
|
1252
1209
|
end
|
1253
1210
|
# %%% Temporary schema patch
|
1254
|
-
|
1255
|
-
|
1211
|
+
for_tbl = fk[1]
|
1212
|
+
fk[1] = "#{fk[0]}.#{fk[1]}" if fk[0] # && fk[0] != ::Brick.default_schema
|
1256
1213
|
bts = (relation = relations.fetch(fk[1], nil))&.fetch(:fks) { relation[:fks] = {} }
|
1257
1214
|
# %%% Do we miss out on has_many :through or even HM based on constantizing this model early?
|
1258
1215
|
# Maybe it's already gotten this info because we got as far as to say there was a unique class
|
1259
1216
|
primary_table = if (is_class = fk[4].is_a?(Hash) && fk[4].key?(:class))
|
1260
1217
|
pri_tbl = (primary_class = fk[4][:class].constantize).table_name
|
1261
1218
|
else
|
1219
|
+
is_schema = fk[3] != ::Brick.default_schema && (::Brick.config.schema_behavior[:multitenant] || fk[3] != 'public')
|
1262
1220
|
pri_tbl = fk[4]
|
1263
|
-
|
1221
|
+
fk[3] && is_schema ? "#{fk[3]}.#{pri_tbl}" : pri_tbl
|
1264
1222
|
end
|
1265
1223
|
hms = (relation = relations.fetch(primary_table, nil))&.fetch(:fks) { relation[:fks] = {} } unless is_class
|
1266
1224
|
|
1267
1225
|
unless (cnstr_name = fk[5])
|
1268
1226
|
# For any appended references (those that come from config), arrive upon a definitely unique constraint name
|
1269
|
-
|
1227
|
+
pri_tbl = is_class ? fk[4][:class].underscore : pri_tbl
|
1228
|
+
pri_tbl = "#{bt_assoc_name}_#{pri_tbl}" if pri_tbl.singularize != bt_assoc_name
|
1229
|
+
cnstr_base = cnstr_name = "(brick) #{for_tbl}_#{pri_tbl}"
|
1270
1230
|
cnstr_added_num = 1
|
1271
1231
|
cnstr_name = "#{cnstr_base}_#{cnstr_added_num += 1}" while bts&.key?(cnstr_name) || hms&.key?(cnstr_name)
|
1272
1232
|
missing = []
|
@@ -1306,6 +1266,7 @@ module Brick
|
|
1306
1266
|
else
|
1307
1267
|
inverse_table = [primary_table] if is_polymorphic
|
1308
1268
|
assoc_bt = bts[cnstr_name] = { is_bt: true, fk: fk[2], assoc_name: bt_assoc_name, inverse_table: inverse_table || primary_table }
|
1269
|
+
assoc_bt[:optional] = true if is_optional
|
1309
1270
|
assoc_bt[:polymorphic] = true if is_polymorphic
|
1310
1271
|
end
|
1311
1272
|
if is_class
|
@@ -1326,7 +1287,7 @@ module Brick
|
|
1326
1287
|
assoc_hm[:alternate_name] = "#{assoc_hm[:alternate_name]}_#{bt_assoc_name}" unless assoc_hm[:alternate_name] == bt_assoc_name
|
1327
1288
|
assoc_hm[:inverse] = assoc_bt
|
1328
1289
|
else
|
1329
|
-
assoc_hm = hms[hm_cnstr_name] = { is_bt: false, fk: fk[2], assoc_name:
|
1290
|
+
assoc_hm = hms[hm_cnstr_name] = { is_bt: false, fk: fk[2], assoc_name: for_tbl.pluralize, alternate_name: bt_assoc_name, inverse_table: fk[1], inverse: assoc_bt }
|
1330
1291
|
assoc_hm[:polymorphic] = true if is_polymorphic
|
1331
1292
|
hm_counts = relation.fetch(:hm_counts) { relation[:hm_counts] = {} }
|
1332
1293
|
hm_counts[fk[1]] = hm_counts.fetch(fk[1]) { 0 } + 1
|
@@ -222,7 +222,7 @@ end %>"
|
|
222
222
|
if ['index', 'show', 'update'].include?(args.first)
|
223
223
|
poly_cols = []
|
224
224
|
css << "<% bts = { #{
|
225
|
-
bts.each_with_object([]) do |v, s|
|
225
|
+
bt_items = bts.each_with_object([]) do |v, s|
|
226
226
|
foreign_models = if v.last[2] # Polymorphic?
|
227
227
|
poly_cols << @_brick_model.reflect_on_association(v[1].first).foreign_type
|
228
228
|
v.last[1].each_with_object([]) { |x, s| s << "[#{x.name}, #{x.primary_key.inspect}]" }.join(', ')
|
@@ -230,7 +230,10 @@ end %>"
|
|
230
230
|
"[#{v.last[1].name}, #{v.last[1].primary_key.inspect}]"
|
231
231
|
end
|
232
232
|
s << "#{v.first.inspect} => [#{v.last.first.inspect}, [#{foreign_models}], #{v.last[2].inspect}]"
|
233
|
-
end
|
233
|
+
end
|
234
|
+
# # %%% Need to fix poly going to an STI class
|
235
|
+
# binding.pry unless poly_cols.empty?
|
236
|
+
bt_items.join(', ')
|
234
237
|
} }
|
235
238
|
poly_cols = #{poly_cols.inspect} %>"
|
236
239
|
end
|
data/lib/brick/version_number.rb
CHANGED
data/lib/brick.rb
CHANGED
@@ -93,16 +93,19 @@ module Brick
|
|
93
93
|
attr_accessor :default_schema, :db_schemas
|
94
94
|
|
95
95
|
def set_db_schema(params)
|
96
|
-
schema = params['_brick_schema']
|
96
|
+
schema = params['_brick_schema'] || 'public'
|
97
97
|
ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?;", schema) if schema && ::Brick.db_schemas&.include?(schema)
|
98
98
|
end
|
99
99
|
|
100
100
|
# All tables and views (what Postgres calls "relations" including column and foreign key info)
|
101
101
|
def relations
|
102
|
-
connections = Brick.instance_variable_get(:@relations) ||
|
103
|
-
Brick.instance_variable_set(:@relations, (connections = {}))
|
104
102
|
# Key our list of relations for this connection off of the connection pool's object_id
|
105
|
-
(
|
103
|
+
(@relations ||= {})[ActiveRecord::Base.connection_pool.object_id] ||= Hash.new { |h, k| h[k] = Hash.new { |h, k| h[k] = {} } }
|
104
|
+
end
|
105
|
+
|
106
|
+
# If multitenancy is enabled, a list of non-tenanted "global" models
|
107
|
+
def non_tenanted_models
|
108
|
+
@pending_models ||= {}
|
106
109
|
end
|
107
110
|
|
108
111
|
def get_bts_and_hms(model)
|
@@ -314,7 +317,7 @@ module Brick
|
|
314
317
|
if ars
|
315
318
|
ars.each do |ar|
|
316
319
|
fk = ar.length < 5 ? [nil, +ar[0], ar[1], nil, +ar[2]] : [ar[0], +ar[1], ar[2], ar[3], +ar[4], ar[5]]
|
317
|
-
::Brick._add_bt_and_hm(fk, relations)
|
320
|
+
::Brick._add_bt_and_hm(fk, relations, false, true)
|
318
321
|
end
|
319
322
|
end
|
320
323
|
if (polys = ::Brick.config.polymorphics)
|
@@ -327,7 +330,7 @@ module Brick
|
|
327
330
|
v ||= ActiveRecord::Base.execute_sql("SELECT DISTINCT #{poly}_type AS typ FROM #{table_name}").each_with_object([]) { |result, s| s << result['typ'] if result['typ'] }
|
328
331
|
v.each do |type|
|
329
332
|
if relations.key?(primary_table = type.underscore.pluralize)
|
330
|
-
::Brick._add_bt_and_hm([nil, table_name, poly, nil, primary_table, "(brick) #{table_name}_#{poly}"], relations, true)
|
333
|
+
::Brick._add_bt_and_hm([nil, table_name, poly, nil, primary_table, "(brick) #{table_name}_#{poly}"], relations, true, true)
|
331
334
|
else
|
332
335
|
missing_stis[primary_table] = type unless ::Brick.existing_stis.key?(type)
|
333
336
|
end
|
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.32
|
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
|
+
date: 2022-06-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|