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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f529097f82af103af54ae472e226244ce8932b64cf974b836d1d23be6cedfbce
4
- data.tar.gz: 1561bea9cdcdd366d2ef9798cff36f3a74f58768f1f14be5c2bfbf4f40feac8d
3
+ metadata.gz: 8483d9a50013e440cce28f648d09d5322985702f952520cb83c69ad0e5742ce4
4
+ data.tar.gz: 40072d0504f4d2b1cc9a59e62fef305b939dd977da0e794445adf003dbfb8b7e
5
5
  SHA512:
6
- metadata.gz: a5d812af4ba7eadae06e28bb9686ea60b79f9df721a51cbddbb0ce87d6abfe78e0675b45446768bd2460f00c1f02bba5276d21d9ec96345dcffa8079d87f2ec0
7
- data.tar.gz: b7ff59c6229dac732eb06a01c59d4a2913675b02a0a8a54106c2dfdcf965ea58cff5dc6d8dfb1c54fb8d9569fa31f2d8217ed281593bdbc167e89d42178225dd
6
+ metadata.gz: 9740778159acb464652322ef36eff763a55b38c6579fe0a7550a5797db44928ad6832a8bccc0d924a4d4c5a14dc02658e61bf4220cecff08fc348d540b67e41c
7
+ data.tar.gz: 1b09db07a284512821b604f1d82f64c4be0c11db7f8a8973f36ab3f78b21804a7fec1eb641bcf1ac5c2ec118cb477c8086c124017c66550753ec4f9beddbd413
@@ -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).klass
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(#{count_column}) AS _ct_ FROM #{associative&.table_name || hm.klass.table_name} GROUP BY #{(1..selects.length).to_a.join(', ')}) AS #{tbl_alias}"
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
- if (base_class = ::Brick.config.sti_namespace_prefixes&.fetch("::#{into.name}::", nil)&.constantize)
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) && (possible = Object.const_get(args.first)) != self)
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
- # VAbc instead of VABC)
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
- (table_name = singular_table_name.pluralize),
548
- class_name,
549
- (plural_class_name = class_name.pluralize)].find { |s| Brick.db_schemas.include?(s) }
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
- unless self == Object # Are we in some namespace?
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
- # Adjust for STI if we know of a base model for the requested model name
572
- table_name = if (base_model = ::Brick.sti_models[model_name]&.fetch(:base, nil) || ::Brick.existing_stis[model_name]&.constantize)
573
- base_model.table_name
574
- else
575
- ActiveSupport::Inflector.pluralize(singular_table_name)
576
- end
577
-
578
- # Maybe, just maybe there's a database table that will satisfy this need
579
- if (matching = [table_name, singular_table_name, plural_class_name, model_name].find { |m| relations.key?(schema_name ? "#{schema_name}.#{m}" : m) })
580
- Object.send(:build_model, schema_name, model_name, singular_table_name, table_name, relations, matching)
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! mod #{self.name} #{args.inspect} #{table_name}"
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 = (Brick.db_schemas[schema_name] ||= self.const_get(schema_name.singularize.camelize))
705
- matching = "#{schema_name}.#{matching}"
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.titleize}::")
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.each do |hmt_fk, fks|
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
- source = fk.last unless hmt_name.singularize == fk.last
811
- code << " has_many :#{hmt_name}, through: #{(assoc_name = through.to_sym).to_sym.inspect}#{", source: :#{source}" if source}\n"
812
- options = { through: assoc_name }
813
- options[:source] = source.to_sym if source
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
- options[:class_name] = "::#{assoc[:primary_class]&.name || singular_table_name.split('.').map(&:camelize).join('::')}" if need_class_name
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
- instance_variable_set("@#{singular_table_name}".to_sym, (obj = model.find(params[:id].split(','))))
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
- # Hopefully our initializer is named exactly this!
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 (::Brick.default_schema = schema = ::Brick.config.schema_behavior&.[](:multitenant)&.[](:schema_to_analyse))
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
- # next if internal_views.include?(r['relation_name']) # Skip internal views such as v_all_assessments
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
- WHERE kcu1.CONSTRAINT_SCHEMA = COALESCE(current_setting('SEARCH_PATH'), 'public')" if schema }"
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
- # Try to load the initializer pretty danged early
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
- if (bt_assoc_name = fk[2].underscore).end_with?('_id')
1247
- bt_assoc_name = bt_assoc_name[0..-4]
1248
- elsif bt_assoc_name.end_with?('id') && bt_assoc_name.exclude?('_') # Make the bold assumption that we can just peel off the final ID part
1249
- bt_assoc_name = bt_assoc_name[0..-3]
1250
- else
1251
- bt_assoc_name = "#{bt_assoc_name}_bt"
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
- fk[1] = "#{fk[0]}.#{for_tbl = fk[1]}" if fk[0] && fk[0] != ::Brick.default_schema
1255
- for_tbl << '_' if for_tbl
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
- (fk[3] && fk[3] != ::Brick.default_schema) ? "#{fk[3]}.#{pri_tbl}" : pri_tbl
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
- cnstr_base = cnstr_name = "(brick) #{for_tbl}#{is_class ? fk[4][:class].underscore : pri_tbl}"
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: fk[1].tr('.', '_').pluralize, alternate_name: bt_assoc_name, inverse_table: fk[1], inverse: assoc_bt }
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.join(', ')
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
@@ -5,7 +5,7 @@ module Brick
5
5
  module VERSION
6
6
  MAJOR = 1
7
7
  MINOR = 0
8
- TINY = 31
8
+ TINY = 32
9
9
 
10
10
  # PRE is nil unless it's a pre-release (beta, RC, etc.)
11
11
  PRE = nil
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
- (connections[ActiveRecord::Base.connection_pool.object_id] ||= Hash.new { |h, k| h[k] = Hash.new { |h, k| h[k] = {} } })
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.31
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-05-29 00:00:00.000000000 Z
11
+ date: 2022-06-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord