brick 1.0.31 → 1.0.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/brick/extensions.rb +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
|