brick 1.0.30 → 1.0.33

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: ad31136f451e04eabcc67ee09980d45d25aa8e35453aaf4c160ffc9aef3f774c
4
- data.tar.gz: 5e3b705dfad389391b291d26bd3c0571cf0579abce3ed00c2840805fd5e05353
3
+ metadata.gz: 7bd78c8878b1ce8e8e58fb4d8463322269d2ac065c5ed12079dcc64360dc30d5
4
+ data.tar.gz: e6926fdc0ec59ad609be39361dbfe0ccea24c1b08a01ccf0a4ae8eb1aa9e2ba2
5
5
  SHA512:
6
- metadata.gz: ac2403152cae57e54ae1840920aa47e5c64297ff7698f691c0a752ae063d824073f24d040aa66580ef0e5eddac0ec164cf720bc720ad73a09ef7862116608476
7
- data.tar.gz: b529668cde573b3c371af30b7f141a1e313d477281bc2ef5453c9751a67abea39a4d8a57e3632f6ef8789ea04ab9d4e01170fb29f096fa6ab8772df77f60f564
6
+ metadata.gz: 5b6ac439519209c3097c2b2651ed66f319b9c04072d2cee7262aad6c4d9907d61b5b45ee1812c1f6cea1e5e1eed2082eb15953e4e3097c9c12b4f35a1f3af703
7
+ data.tar.gz: 5bb559ada7035abb062b0441578d5406e64f87b9f11fde1d5b3b880179098bafe9507a1a6cdd8e1af1cd1fb4defc1d339e0054896ca0e9c507ea831f24a0cb96
@@ -86,7 +86,11 @@ module ActiveRecord
86
86
  descrip_col = (columns.map(&:name) - _brick_get_fks -
87
87
  (::Brick.config.metadata_columns || []) -
88
88
  [primary_key]).first
89
- dsl = ::Brick.config.model_descrips[name] = "[#{descrip_col}]" if descrip_col
89
+ dsl = ::Brick.config.model_descrips[name] = if descrip_col
90
+ "[#{descrip_col}]"
91
+ elsif (pk_parts = self.primary_key.is_a?(Array) ? self.primary_key : [self.primary_key])
92
+ "#{name} ##{pk_parts.map { |pk_part| "[#{pk_part}]" }.join(', ')}"
93
+ end
90
94
  end
91
95
  dsl
92
96
  end
@@ -104,7 +108,8 @@ module ActiveRecord
104
108
  if ch == ']' # Time to process a bracketed thing?
105
109
  parts = bracket_name.split('.')
106
110
  first_parts = parts[0..-2].map do |part|
107
- klass = klass.reflect_on_association(part_sym = part.to_sym).klass
111
+ klass = (orig_class = klass).reflect_on_association(part_sym = part.to_sym)&.klass
112
+ puts "Couldn't reference #{orig_class.name}##{part} that's part of the DSL \"#{dsl}\"." if klass.nil?
108
113
  part_sym
109
114
  end
110
115
  parts = prefix + first_parts + [parts[-1]]
@@ -128,7 +133,7 @@ module ActiveRecord
128
133
  end
129
134
  else # With no DSL available, still put this prefix into the JoinArray so we can get primary key (ID) info from this table
130
135
  x = prefix.each_with_object(build_array) { |v, s| s[v.to_sym] }
131
- x[prefix[-1]] = nil unless prefix.empty? # Using []= will "hydrate" any missing part(s) in our whole series
136
+ x[prefix.last] = nil unless prefix.empty? # Using []= will "hydrate" any missing part(s) in our whole series
132
137
  end
133
138
  members
134
139
  end
@@ -251,13 +256,12 @@ module ActiveRecord
251
256
  names << [piece.right._arel_table_type, (piece.right.table_alias || piece.right.name)]
252
257
  else # "Normal" setup, fed from a JoinSource which has an array of JOINs
253
258
  # The left side is the "JOIN" table
254
- names += _recurse_arel(piece.left)
259
+ names += _recurse_arel(table = piece.left)
255
260
  # The expression on the right side is the "ON" clause
256
261
  # on = piece.right.expr
257
262
  # # Find the table which is not ourselves, and thus must be the "path" that led us here
258
263
  # parent = piece.left == on.left.relation ? on.right.relation : on.left.relation
259
264
  # binding.pry if piece.left.is_a?(Arel::Nodes::TableAlias)
260
- table = piece.left
261
265
  if table.is_a?(Arel::Nodes::TableAlias)
262
266
  alias_name = table.right
263
267
  table = table.left
@@ -276,7 +280,8 @@ module ActiveRecord
276
280
  @_brick_chains = {}
277
281
  # The left side is the "FROM" table
278
282
  # names += _recurse_arel(piece.left)
279
- names << [piece.left._arel_table_type, (piece.left.table_alias || piece.left.name)]
283
+ names << (this_name = [piece.left._arel_table_type, (piece.left.table_alias || piece.left.name)])
284
+ (_brick_chains[this_name.first] ||= []) << this_name.last
280
285
  # The right side is an array of all JOINs
281
286
  piece.right.each { |join| names << _recurse_arel(join) }
282
287
  end
@@ -348,6 +353,7 @@ module ActiveRecord
348
353
  next unless klass.reflect_on_association(assoc_name)&.klass&.column_names&.any?(ks.last)
349
354
 
350
355
  join_array[assoc_name] = nil # Store this relation name in our special collection for .joins()
356
+ distinct!
351
357
  end
352
358
  wheres[k] = v.split(',')
353
359
  end
@@ -397,6 +403,7 @@ module ActiveRecord
397
403
  hm_counts.each do |k, hm|
398
404
  associative = nil
399
405
  count_column = if hm.options[:through]
406
+ # binding.pry if associatives[hm.name].nil?
400
407
  fk_col = (associative = associatives[hm.name]).foreign_key
401
408
  hm.foreign_key
402
409
  else
@@ -420,11 +427,13 @@ module ActiveRecord
420
427
  on_clause << "#{tbl_alias}.#{poly_type} = '#{name}'"
421
428
  end
422
429
  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}"
430
+ JOIN (SELECT #{selects.join(', ')}, COUNT(#{'DISTINCT ' if hm.options[:through]}#{count_column
431
+ }) AS _ct_ FROM #{associative&.table_name || hm.klass.table_name
432
+ } GROUP BY #{(1..selects.length).to_a.join(', ')}) AS #{tbl_alias}"
424
433
  joins!("#{join_clause} ON #{on_clause.join(' AND ')}")
425
434
  end
426
435
  where!(wheres) unless wheres.empty?
427
- limit(1000) # Don't want to get too carried away just yet
436
+ limit!(1000) # Don't want to get too carried away just yet
428
437
  wheres unless wheres.empty? # Return the specific parameters that we did use
429
438
  end
430
439
 
@@ -485,7 +494,9 @@ if ActiveSupport::Dependencies.respond_to?(:autoload_module!) # %%% Only works w
485
494
  alias _brick_autoload_module! autoload_module!
486
495
  def autoload_module!(*args)
487
496
  into, const_name, qualified_name, path_suffix = args
488
- if (base_class = ::Brick.config.sti_namespace_prefixes&.fetch("::#{into.name}::", nil)&.constantize)
497
+ base_class_name = ::Brick.config.sti_namespace_prefixes&.fetch("::#{into.name}::", nil)
498
+ base_class_name = "::#{base_class_name}" unless base_class_name.start_with?('::')
499
+ if (base_class = base_class_name&.constantize)
489
500
  ::Brick.sti_models[qualified_name] = { base: base_class }
490
501
  # Build subclass and place it into the specially STI-namespaced module
491
502
  into.const_set(const_name.to_sym, klass = Class.new(base_class))
@@ -506,8 +517,13 @@ end
506
517
  Module.class_exec do
507
518
  alias _brick_const_missing const_missing
508
519
  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)
520
+ if (self.const_defined?(args.first) && (possible = self.const_get(args.first)) && possible != self) ||
521
+ (self != Object && Object.const_defined?(args.first) &&
522
+ (
523
+ (possible = Object.const_get(args.first)) &&
524
+ (possible != self || (possible == self && possible.is_a?(Class)))
525
+ )
526
+ )
511
527
  return possible
512
528
  end
513
529
  class_name = args.first.to_s
@@ -533,22 +549,23 @@ Module.class_exec do
533
549
  result = if ::Brick.enable_controllers? && class_name.end_with?('Controller') && (plural_class_name = class_name[0..-11]).length.positive?
534
550
  # Otherwise now it's up to us to fill in the gaps
535
551
  # (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)
552
+ # Vabc instead of VABC)
537
553
  full_class_name = +''
538
554
  full_class_name << "::#{self.name}" unless self == Object
539
555
  full_class_name << "::#{plural_class_name.underscore.singularize.camelize}"
540
- if (model = self.const_get(full_class_name))
556
+ if (plural_class_name == 'BrickSwagger' || model = self.const_get(full_class_name))
541
557
  # 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.
542
558
  Object.send(:build_controller, self, class_name, plural_class_name, model, relations)
543
559
  end
544
560
  elsif (::Brick.enable_models? || ::Brick.enable_controllers?) && # Schema match?
545
561
  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) }
562
+ (schema_name = [(singular_table_name = class_name.underscore),
563
+ (table_name = singular_table_name.pluralize),
564
+ class_name,
565
+ (plural_class_name = class_name.pluralize)].find { |s| Brick.db_schemas.include?(s) }&.camelize ||
566
+ (::Brick.config.sti_namespace_prefixes&.key?("::#{class_name}::") && class_name))
550
567
  # Build out a module for the schema if it's namespaced
551
- schema_name = schema_name.camelize
568
+ # schema_name = schema_name.camelize
552
569
  self.const_set(schema_name.to_sym, (built_module = Module.new))
553
570
 
554
571
  [built_module, "module #{schema_name}; end\n"]
@@ -557,27 +574,37 @@ Module.class_exec do
557
574
  # See if a file is there in the same way that ActiveSupport::Dependencies#load_missing_constant
558
575
  # checks for it in ~/.rvm/gems/ruby-2.7.5/gems/activesupport-5.2.6.2/lib/active_support/dependencies.rb
559
576
 
560
- unless self == Object # Are we in some namespace?
577
+ if self != Object || # Are we in some namespace? ...
578
+ (base_model = ::Brick.config.sti_namespace_prefixes&.fetch("::#{name}::", nil)&.constantize) # ... or part of an auto-STI namespace?
561
579
  schema_name = [(singular_schema_name = name.underscore),
562
580
  (schema_name = singular_schema_name.pluralize),
563
581
  name,
564
582
  name.pluralize].find { |s| Brick.db_schemas.include?(s) }
565
583
  end
566
-
567
584
  plural_class_name = ActiveSupport::Inflector.pluralize(model_name = class_name)
568
585
  # 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)
586
+ singular_table_name = ActiveSupport::Inflector.underscore(model_name).gsub('/', '.')
587
+
588
+ if base_model
589
+ schema_name = name.underscore # For the auto-STI namespace models
590
+ table_name = base_model.table_name
591
+ Object.send(:build_model, self, model_name, singular_table_name, table_name, relations, table_name)
592
+ else
593
+ # Adjust for STI if we know of a base model for the requested model name
594
+ # %%% Does not yet work with namespaced model names. Perhaps prefix with plural_class_name when doing the lookups here.
595
+ table_name = if (base_model = ::Brick.sti_models[model_name]&.fetch(:base, nil) || ::Brick.existing_stis[model_name]&.constantize)
596
+ base_model.table_name
597
+ else
598
+ ActiveSupport::Inflector.pluralize(singular_table_name)
599
+ end
600
+ if ::Brick.config.schema_behavior[:multitenant] && Object.const_defined?('Apartment') &&
601
+ Apartment.excluded_models.include?(table_name.singularize.camelize)
602
+ schema_name = Apartment.default_schema
603
+ end
604
+ # Maybe, just maybe there's a database table that will satisfy this need
605
+ if (matching = [table_name, singular_table_name, plural_class_name, model_name].find { |m| relations.key?(schema_name ? "#{schema_name}.#{m}" : m) })
606
+ Object.send(:build_model, schema_name, model_name, singular_table_name, table_name, relations, matching)
607
+ end
581
608
  end
582
609
  end
583
610
  if result
@@ -593,7 +620,7 @@ Module.class_exec do
593
620
  elsif self != Object
594
621
  module_parent.const_missing(*args)
595
622
  else
596
- puts "MISSING! mod #{self.name} #{args.inspect} #{table_name}"
623
+ puts "MISSING! #{self.name} #{args.inspect} #{table_name}"
597
624
  self._brick_const_missing(*args)
598
625
  end
599
626
  end
@@ -601,117 +628,31 @@ end
601
628
 
602
629
  class Object
603
630
  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
631
 
698
632
  private
699
633
 
700
634
  def build_model(schema_name, model_name, singular_table_name, table_name, relations, matching)
701
- full_name = if schema_name.blank?
635
+ full_name = if (::Brick.config.schema_behavior[:multitenant] && Object.const_defined?('Apartment') && schema_name == Apartment.default_schema)
636
+ relation = relations["#{schema_name}.#{matching}"]
637
+ model_name
638
+ elsif schema_name.blank?
702
639
  model_name
703
640
  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}"
641
+ schema_module = if schema_name.instance_of?(Module) # from an auto-STI namespace?
642
+ schema_name
643
+ else
644
+ matching = "#{schema_name}.#{matching}"
645
+ (Brick.db_schemas[schema_name] ||= self.const_get(schema_name.singularize.camelize))
646
+ end
706
647
  "#{schema_module&.name}::#{model_name}"
707
648
  end
708
649
 
709
- return if ((is_view = (relation = relations[matching]).key?(:isView)) && ::Brick.config.skip_database_views) ||
650
+ return if ((is_view = (relation ||= relations[matching]).key?(:isView)) && ::Brick.config.skip_database_views) ||
710
651
  ::Brick.config.exclude_tables.include?(matching)
711
652
 
712
653
  # Are they trying to use a pluralised class name such as "Employees" instead of "Employee"?
713
654
  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}::")
655
+ unless ::Brick.config.sti_namespace_prefixes&.key?("::#{singular_table_name.camelize}::")
715
656
  puts "Warning: Class name for a model that references table \"#{matching}\" should be \"#{ActiveSupport::Inflector.singularize(model_name)}\"."
716
657
  end
717
658
  return
@@ -755,6 +696,9 @@ class Object
755
696
  code << " self.primary_key = #{pk_sym.inspect}\n"
756
697
  end
757
698
  _brick_primary_key(relation) # Set the newly-found PK in the instance variable
699
+ elsif (possible_pk = ActiveRecord::Base.get_primary_key(base_class.name)) && relation[:cols][possible_pk]
700
+ new_model_class.primary_key = (possible_pk = possible_pk.to_sym)
701
+ code << " self.primary_key = #{possible_pk.inspect}\n"
758
702
  else
759
703
  code << " # Could not identify any column(s) to use as a primary key\n" unless is_view
760
704
  end
@@ -792,10 +736,11 @@ class Object
792
736
  end # class definition
793
737
  # Having this separate -- will this now work out better?
794
738
  built_model.class_exec do
795
- hmts.each do |hmt_fk, fks|
739
+ hmts&.each do |hmt_fk, fks|
796
740
  hmt_fk = hmt_fk.tr('.', '_')
797
741
  fks.each do |fk|
798
- through = fk.first[:assoc_name]
742
+ # %%% Will not work with custom has_many name
743
+ through = ::Brick.config.schema_behavior[:multitenant] ? fk.first[:assoc_name] : fk.first[:inverse_table].tr('.', '_').pluralize
799
744
  hmt_name = if fks.length > 1
800
745
  if fks[0].first[:inverse][:assoc_name] == fks[1].first[:inverse][:assoc_name] # Same BT names pointing back to us? (Most common scenario)
801
746
  "#{hmt_fk}_through_#{fk.first[:assoc_name]}"
@@ -807,10 +752,15 @@ class Object
807
752
  else
808
753
  hmt_fk
809
754
  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
755
+ options = { through: through.to_sym }
756
+ if relation[:fks].any? { |k, v| v[:assoc_name] == hmt_name }
757
+ hmt_name = "#{hmt_name.singularize}_#{fk.first[:assoc_name]}"
758
+ # binding.pry if relation[:fks].any? { |k, v| v[:assoc_name] == hmt_name }
759
+ options[:class_name] = fk.first[:inverse_table].singularize.camelize
760
+ options[:foreign_key] = fk.first[:fk].to_sym
761
+ end
762
+ options[:source] = fk.last.to_sym unless hmt_name.singularize == fk.last
763
+ code << " has_many :#{hmt_name}#{options.map { |opt| ", #{opt.first}: #{opt.last.inspect}" }.join}\n"
814
764
  self.send(:has_many, hmt_name.to_sym, **options)
815
765
  end
816
766
  end
@@ -832,6 +782,7 @@ class Object
832
782
  else
833
783
  assoc[:assoc_name]
834
784
  end
785
+ options[:optional] = true if assoc.key?(:optional)
835
786
  if assoc.key?(:polymorphic)
836
787
  options[:polymorphic] = true
837
788
  else
@@ -875,7 +826,11 @@ class Object
875
826
  end
876
827
  # Figure out if we need to specially call out the class_name and/or foreign key
877
828
  # (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
829
+ if (singular_table_parts = singular_table_name.split('.')).length > 1 &&
830
+ ::Brick.config.schema_behavior[:multitenant] && singular_table_parts.first == 'public'
831
+ singular_table_parts.shift
832
+ end
833
+ options[:class_name] = "::#{assoc[:primary_class]&.name || singular_table_parts.map(&:camelize).join('::')}" if need_class_name
879
834
  # Work around a bug in CPK where self-referencing belongs_to associations double up their foreign keys
880
835
  if need_fk # Funky foreign key?
881
836
  options[:foreign_key] = if assoc[:fk].is_a?(Array)
@@ -889,7 +844,6 @@ class Object
889
844
 
890
845
  # Prepare a list of entries for "has_many :through"
891
846
  if macro == :has_many
892
- puts [inverse_table, relations[inverse_table].length].inspect
893
847
  relations[inverse_table][:hmt_fks].each do |k, hmt_fk|
894
848
  next if k == assoc[:fk]
895
849
 
@@ -905,19 +859,70 @@ class Object
905
859
  def build_controller(namespace, class_name, plural_class_name, model, relations)
906
860
  table_name = ActiveSupport::Inflector.underscore(plural_class_name)
907
861
  singular_table_name = ActiveSupport::Inflector.singularize(table_name)
908
- pk = model._brick_primary_key(relations.fetch(table_name, nil))
862
+ pk = model&._brick_primary_key(relations.fetch(table_name, nil))
909
863
 
910
864
  namespace_name = "#{namespace.name}::" if namespace
911
865
  code = +"class #{namespace_name}#{class_name} < ApplicationController\n"
912
866
  built_controller = Class.new(ActionController::Base) do |new_controller_class|
913
867
  (namespace || Object).const_set(class_name.to_sym, new_controller_class)
914
868
 
915
- code << " def index\n"
916
- code << " @#{table_name} = #{model.name}#{pk&.present? ? ".order(#{pk.inspect})" : '.all'}\n"
917
- code << " @#{table_name}.brick_select(params)\n"
918
- code << " end\n"
869
+ unless (is_swagger = plural_class_name == 'BrickSwagger') # && request.format == :json)
870
+ code << " def index\n"
871
+ code << " @#{table_name} = #{model.name}#{pk&.present? ? ".order(#{pk.inspect})" : '.all'}\n"
872
+ code << " @#{table_name}.brick_select(params)\n"
873
+ code << " end\n"
874
+ end
919
875
  self.protect_from_forgery unless: -> { self.request.format.js? }
920
876
  self.define_method :index do
877
+ if is_swagger
878
+ json = { 'openapi': '3.0.1', 'info': { 'title': 'API V1', 'version': 'v1' },
879
+ 'servers': [
880
+ { 'url': 'https://{defaultHost}', 'variables': { 'defaultHost': { 'default': 'www.example.com' } } }
881
+ ]
882
+ }
883
+ json['paths'] = relations.each_with_object({}) do |v, s|
884
+ # next if v.last[:is_view]
885
+
886
+ s["/api/v1/#{v.first}"] = {
887
+ 'get': {
888
+ 'summary': 'list #{v.first}',
889
+ 'parameters': v.last[:cols].map { |k, v| { 'name' => k, 'schema': { 'type': v.first } } },
890
+ 'responses': { '200': { 'description': 'successful' } }
891
+ }
892
+ }
893
+ s["/api/v1/#{v.first}/{id}"] = {
894
+ 'patch': {
895
+ 'summary': 'update a #{v.first.singularize}',
896
+ 'parameters': v.last[:cols].reject { |k, v| Brick.config.metadata_columns.include?(k) }.map do |k, v|
897
+ { 'name' => k, 'schema': { 'type': v.first } }
898
+ end,
899
+ 'responses': { '200': { 'description': 'successful' } }
900
+ }
901
+ # "/api/v1/books/{id}": {
902
+ # "parameters": [
903
+ # {
904
+ # "name": "id",
905
+ # "in": "path",
906
+ # "description": "id",
907
+ # "required": true,
908
+ # "schema": {
909
+ # "type": "string"
910
+ # }
911
+ # },
912
+ # {
913
+ # "name": "Authorization",
914
+ # "in": "header",
915
+ # "schema": {
916
+ # "type": "string"
917
+ # }
918
+ # }
919
+ # ],
920
+ }
921
+ end
922
+ # binding.pry
923
+ render inline: json.to_json, content_type: request.format
924
+ return
925
+ end
921
926
  ::Brick.set_db_schema(params)
922
927
  if request.format == :csv # Asking for a template?
923
928
  require 'csv'
@@ -946,7 +951,7 @@ class Object
946
951
  @_brick_join_array = join_array
947
952
  end
948
953
 
949
- if model.primary_key
954
+ if model&.primary_key
950
955
  code << " def show\n"
951
956
  code << (find_by_id = " id = params[:id]&.split(/[\\/,_]/)
952
957
  id = id.first if id.is_a?(Array) && id.length == 1
@@ -961,7 +966,7 @@ class Object
961
966
  end
962
967
 
963
968
  # By default, views get marked as read-only
964
- unless false # model.readonly # (relation = relations[model.table_name]).key?(:isView)
969
+ unless is_swagger # model.readonly # (relation = relations[model.table_name]).key?(:isView)
965
970
  code << " # (Define :new, :create)\n"
966
971
 
967
972
  if model.primary_key
@@ -993,7 +998,9 @@ class Object
993
998
  # return
994
999
  end
995
1000
 
996
- instance_variable_set("@#{singular_table_name}".to_sym, (obj = model.find(params[:id].split(','))))
1001
+ id = params[:id]&.split(/[\/,_]/)
1002
+ id = id.first if id.is_a?(Array) && id.length == 1
1003
+ instance_variable_set("@#{singular_table_name}".to_sym, (obj = model.find(id)))
997
1004
  obj = obj.first if obj.is_a?(Array)
998
1005
  obj.send(:update, send(params_name = params_name.to_sym))
999
1006
  end
@@ -1018,10 +1025,23 @@ class Object
1018
1025
 
1019
1026
  def _brick_get_hm_assoc_name(relation, hm_assoc)
1020
1027
  if relation[:hm_counts][hm_assoc[:assoc_name]]&.> 1
1028
+ # binding.pry if (same_name = relation[:fks].find { |x| x.last[:assoc_name] == hm_assoc[:assoc_name] && x.last != hm_assoc }) #&&
1029
+ # x.last[:alternate_name] == hm_assoc[:alternate_name] })
1030
+ # relation[:fks].any? { |k, v| v[:assoc_name] == new_alt_name }
1021
1031
  plural = ActiveSupport::Inflector.pluralize(hm_assoc[:alternate_name])
1022
- [hm_assoc[:alternate_name] == name.underscore ? "#{hm_assoc[:assoc_name].singularize}_#{plural}" : plural, true]
1032
+ # binding.pry if hm_assoc[:assoc_name] == 'issue_issue_duplicates'
1033
+ new_alt_name = (hm_assoc[:alternate_name] == name.underscore) ? "#{hm_assoc[:assoc_name].singularize}_#{plural}" : plural
1034
+ # uniq = 1
1035
+ # while same_name = relation[:fks].find { |x| x.last[:assoc_name] == hm_assoc[:assoc_name] && x.last != hm_assoc }
1036
+ # hm_assoc[:assoc_name] = "#{hm_assoc_name}_#{uniq += 1}"
1037
+ # end
1038
+ # puts new_alt_name
1039
+ # binding.pry if new_alt_name == 'issue_duplicates'
1040
+ # hm_assoc[:assoc_name] = new_alt_name
1041
+ [new_alt_name, true]
1023
1042
  else
1024
1043
  assoc_name = hm_assoc[:inverse_table].pluralize
1044
+ # hm_assoc[:assoc_name] = assoc_name
1025
1045
  [assoc_name, assoc_name.include?('.')]
1026
1046
  end
1027
1047
  end
@@ -1040,16 +1060,30 @@ module ActiveRecord::ConnectionHandling
1040
1060
  conn
1041
1061
  end
1042
1062
 
1063
+ # This is done separately so that during testing it can be called right after a migration
1064
+ # in order to make sure everything is good.
1043
1065
  def _brick_reflect_tables
1066
+ initializer_loaded = false
1044
1067
  if (relations = ::Brick.relations).empty?
1045
- load Rails.root.join('config/initializers/brick.rb') # Hopefully our initializer is named exactly this!
1068
+ # If there's schema things configured then we only expect our initializer to be named exactly this
1069
+ if File.exist?(brick_initializer = Rails.root.join('config/initializers/brick.rb'))
1070
+ initializer_loaded = load brick_initializer
1071
+ end
1072
+ # Load the initializer for the Apartment gem a little early so that if .excluded_models and
1073
+ # .default_schema are specified then we can work with non-tenanted models more appropriately
1074
+ if Object.const_defined?('Apartment') && File.exist?(apartment_initializer = Rails.root.join('config/initializers/apartment.rb'))
1075
+ load apartment_initializer
1076
+ apartment_excluded = Apartment.excluded_models
1077
+ end
1046
1078
  # Only for Postgres? (Doesn't work in sqlite3)
1047
1079
  # puts ActiveRecord::Base.execute_sql("SELECT current_setting('SEARCH_PATH')").to_a.inspect
1048
1080
 
1049
1081
  schema_sql = 'SELECT NULL AS table_schema;'
1050
1082
  case ActiveRecord::Base.connection.adapter_name
1051
1083
  when 'PostgreSQL'
1052
- if (::Brick.default_schema = schema = ::Brick.config.schema_behavior&.[](:multitenant)&.[](:schema_to_analyse))
1084
+ if (is_multitenant = (multitenancy = ::Brick.config.schema_behavior[:multitenant]) &&
1085
+ (sta = multitenancy[:schema_to_analyse]) != 'public')
1086
+ ::Brick.default_schema = schema = sta
1053
1087
  ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?", schema)
1054
1088
  end
1055
1089
  schema_sql = 'SELECT DISTINCT table_schema FROM INFORMATION_SCHEMA.tables;'
@@ -1067,6 +1101,28 @@ module ActiveRecord::ConnectionHandling
1067
1101
  puts "Unfamiliar with connection adapter #{ActiveRecord::Base.connection.adapter_name}"
1068
1102
  end
1069
1103
 
1104
+ unless (db_schemas = ActiveRecord::Base.execute_sql(schema_sql)).is_a?(Array)
1105
+ db_schemas = db_schemas.to_a
1106
+ end
1107
+ unless db_schemas.empty?
1108
+ ::Brick.db_schemas = db_schemas.each_with_object({}) do |row, s|
1109
+ row = row.is_a?(String) ? row : row['table_schema']
1110
+ # Remove any system schemas
1111
+ s[row] = nil unless ['information_schema', 'pg_catalog'].include?(row)
1112
+ end
1113
+ end
1114
+
1115
+ if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
1116
+ if (possible_schema = ::Brick.config.schema_behavior&.[](:multitenant)&.[](:schema_to_analyse))
1117
+ if ::Brick.db_schemas.key?(possible_schema)
1118
+ ::Brick.default_schema = schema = possible_schema
1119
+ ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?", schema)
1120
+ else
1121
+ puts "*** In the brick.rb initializer the line \"::Brick.schema_behavior = ...\" refers to a schema called \"#{possible_schema}\". This schema does not exist. ***"
1122
+ end
1123
+ end
1124
+ end
1125
+
1070
1126
  sql ||= "SELECT t.table_schema AS schema, t.table_name AS relation_name, t.table_type,
1071
1127
  c.column_name, c.data_type,
1072
1128
  COALESCE(c.character_maximum_length, c.numeric_precision) AS max_length,
@@ -1093,14 +1149,16 @@ module ActiveRecord::ConnectionHandling
1093
1149
  measures = []
1094
1150
  case ActiveRecord::Base.connection.adapter_name
1095
1151
  when 'PostgreSQL', 'SQLite' # These bring back a hash for each row because the query uses column aliases
1096
- schema ||= 'public' if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
1152
+ # schema ||= 'public' if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
1097
1153
  ActiveRecord::Base.execute_sql(sql).each do |r|
1098
- # next if internal_views.include?(r['relation_name']) # Skip internal views such as v_all_assessments
1099
- relation_name = if r['schema'] != schema
1100
- "#{schema_name = r['schema']}.#{r['relation_name']}"
1101
- else
1102
- r['relation_name']
1103
- end
1154
+ # If Apartment gem lists the table as being associated with a non-tenanted model then use whatever it thinks
1155
+ # is the default schema, usually 'public'.
1156
+ schema_name = if ::Brick.config.schema_behavior[:multitenant]
1157
+ Apartment.default_schema if apartment_excluded&.include?(r['relation_name'].singularize.camelize)
1158
+ elsif ![schema, 'public'].include?(r['schema'])
1159
+ r['schema']
1160
+ end
1161
+ relation_name = schema_name ? "#{schema_name}.#{r['relation_name']}" : r['relation_name']
1104
1162
  relation = relations[relation_name]
1105
1163
  relation[:isView] = true if r['table_type'] == 'VIEW'
1106
1164
  col_name = r['column_name']
@@ -1119,7 +1177,6 @@ module ActiveRecord::ConnectionHandling
1119
1177
  end
1120
1178
  else # MySQL2 acts a little differently, bringing back an array for each row
1121
1179
  ActiveRecord::Base.execute_sql(sql).each do |r|
1122
- # next if internal_views.include?(r['relation_name']) # Skip internal views such as v_all_assessments
1123
1180
  relation = relations[(relation_name = r[0])] # here relation represents a table or view from the database
1124
1181
  relation[:isView] = true if r[1] == 'VIEW' # table_type
1125
1182
  col_name = r[2]
@@ -1158,7 +1215,7 @@ module ActiveRecord::ConnectionHandling
1158
1215
  # end
1159
1216
  # end
1160
1217
  # end
1161
- schema = ::Brick.default_schema # Reset back for this next round of fun
1218
+ # schema = ::Brick.default_schema # Reset back for this next round of fun
1162
1219
  case ActiveRecord::Base.connection.adapter_name
1163
1220
  when 'PostgreSQL', 'Mysql2'
1164
1221
  sql = "SELECT kcu1.CONSTRAINT_SCHEMA, kcu1.TABLE_NAME, kcu1.COLUMN_NAME,
@@ -1173,7 +1230,7 @@ module ActiveRecord::ConnectionHandling
1173
1230
  AND kcu2.CONSTRAINT_SCHEMA = rc.UNIQUE_CONSTRAINT_SCHEMA
1174
1231
  AND kcu2.CONSTRAINT_NAME = rc.UNIQUE_CONSTRAINT_NAME
1175
1232
  AND kcu2.ORDINAL_POSITION = kcu1.ORDINAL_POSITION#{"
1176
- WHERE kcu1.CONSTRAINT_SCHEMA = COALESCE(current_setting('SEARCH_PATH'), 'public')" if schema }"
1233
+ WHERE kcu1.CONSTRAINT_SCHEMA = COALESCE(current_setting('SEARCH_PATH'), 'public')" if schema }"
1177
1234
  # AND kcu2.TABLE_NAME = ?;", Apartment::Tenant.current, table_name
1178
1235
  when 'SQLite'
1179
1236
  sql = "SELECT m.name, fkl.\"from\", fkl.\"table\", m.name || '_' || fkl.\"from\" AS constraint_name
@@ -1183,19 +1240,20 @@ module ActiveRecord::ConnectionHandling
1183
1240
  else
1184
1241
  end
1185
1242
  if sql
1186
- ::Brick.default_schema ||= schema ||= 'public' if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
1187
- unless (db_schemas = ActiveRecord::Base.execute_sql(schema_sql)).is_a?(Array)
1188
- db_schemas = db_schemas.to_a
1189
- end
1190
- unless db_schemas.empty?
1191
- ::Brick.db_schemas = db_schemas.each_with_object({}) do |row, s|
1192
- row = row.is_a?(String) ? row : row['table_schema']
1193
- # Remove whatever default schema we're using and other system schemas
1194
- s[row] = nil unless ['information_schema', 'pg_catalog', schema].include?(row)
1195
- end
1196
- end
1243
+ # ::Brick.default_schema ||= schema ||= 'public' if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
1197
1244
  ActiveRecord::Base.execute_sql(sql).each do |fk|
1198
1245
  fk = fk.values unless fk.is_a?(Array)
1246
+ # Multitenancy makes things a little more general overall, except for non-tenanted tables
1247
+ if apartment_excluded&.include?(fk[1].singularize.camelize)
1248
+ fk[0] = Apartment.default_schema
1249
+ elsif fk[0] == 'public' || (is_multitenant && fk[0] == schema)
1250
+ fk[0] = nil
1251
+ end
1252
+ if apartment_excluded&.include?(fk[4].singularize.camelize)
1253
+ fk[3] = Apartment.default_schema
1254
+ elsif fk[3] == 'public' || (is_multitenant && fk[3] == schema)
1255
+ fk[3] = nil
1256
+ end
1199
1257
  ::Brick._add_bt_and_hm(fk, relations)
1200
1258
  end
1201
1259
  end
@@ -1208,11 +1266,7 @@ module ActiveRecord::ConnectionHandling
1208
1266
  views.keys.each { |k| puts ActiveSupport::Inflector.singularize(k).camelize }
1209
1267
  end
1210
1268
 
1211
- # Try to load the initializer pretty danged early
1212
- if File.exist?(brick_initialiser = Rails.root.join('config/initializers/brick.rb'))
1213
- load brick_initialiser
1214
- ::Brick.load_additional_references
1215
- end
1269
+ ::Brick.load_additional_references if initializer_loaded
1216
1270
  end
1217
1271
  end
1218
1272
 
@@ -1239,31 +1293,51 @@ module Brick
1239
1293
  # rubocop:enable Style/CommentedKeyword
1240
1294
 
1241
1295
  class << self
1242
- def _add_bt_and_hm(fk, relations, is_polymorphic = false)
1243
- if (bt_assoc_name = fk[2].underscore).end_with?('_id')
1244
- bt_assoc_name = bt_assoc_name[0..-4]
1245
- 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
1246
- bt_assoc_name = bt_assoc_name[0..-3]
1247
- else
1248
- bt_assoc_name = "#{bt_assoc_name}_bt"
1296
+ def _add_bt_and_hm(fk, relations, is_polymorphic = false, is_optional = false)
1297
+ bt_assoc_name = fk[2]
1298
+ unless is_polymorphic
1299
+ bt_assoc_name = if bt_assoc_name.underscore.end_with?('_id')
1300
+ bt_assoc_name[0..-4]
1301
+ elsif bt_assoc_name.downcase.end_with?('id') && bt_assoc_name.exclude?('_')
1302
+ bt_assoc_name[0..-3] # Make the bold assumption that we can just peel off any final ID part
1303
+ else
1304
+ "#{bt_assoc_name}_bt"
1305
+ end
1249
1306
  end
1250
1307
  # %%% Temporary schema patch
1251
- fk[1] = "#{fk[0]}.#{for_tbl = fk[1]}" if fk[0] && fk[0] != ::Brick.default_schema
1252
- for_tbl << '_' if for_tbl
1308
+ for_tbl = fk[1]
1309
+ fk[0] = Apartment.default_schema if Object.const_defined?('Apartment') && Apartment.excluded_models.include?(for_tbl.singularize.camelize)
1310
+ fk[1] = "#{fk[0]}.#{fk[1]}" if fk[0] # && fk[0] != ::Brick.default_schema
1253
1311
  bts = (relation = relations.fetch(fk[1], nil))&.fetch(:fks) { relation[:fks] = {} }
1312
+
1254
1313
  # %%% Do we miss out on has_many :through or even HM based on constantizing this model early?
1255
1314
  # Maybe it's already gotten this info because we got as far as to say there was a unique class
1256
1315
  primary_table = if (is_class = fk[4].is_a?(Hash) && fk[4].key?(:class))
1257
1316
  pri_tbl = (primary_class = fk[4][:class].constantize).table_name
1317
+ if (pri_tbl_parts = pri_tbl.split('.')).length > 1
1318
+ fk[3] = pri_tbl_parts.first
1319
+ end
1258
1320
  else
1321
+ is_schema = if ::Brick.config.schema_behavior[:multitenant]
1322
+ # If Apartment gem lists the primary table as being associated with a non-tenanted model
1323
+ # then use 'public' schema for the primary table
1324
+ if Object.const_defined?('Apartment') && Apartment.excluded_models.include?(fk[4].singularize.camelize)
1325
+ fk[3] = Apartment.default_schema
1326
+ true
1327
+ end
1328
+ else
1329
+ fk[3] && fk[3] != ::Brick.default_schema && fk[3] != 'public'
1330
+ end
1259
1331
  pri_tbl = fk[4]
1260
- (fk[3] && fk[3] != ::Brick.default_schema) ? "#{fk[3]}.#{pri_tbl}" : pri_tbl
1332
+ is_schema ? "#{fk[3]}.#{pri_tbl}" : pri_tbl
1261
1333
  end
1262
1334
  hms = (relation = relations.fetch(primary_table, nil))&.fetch(:fks) { relation[:fks] = {} } unless is_class
1263
1335
 
1264
1336
  unless (cnstr_name = fk[5])
1265
1337
  # For any appended references (those that come from config), arrive upon a definitely unique constraint name
1266
- cnstr_base = cnstr_name = "(brick) #{for_tbl}#{is_class ? fk[4][:class].underscore : pri_tbl}"
1338
+ pri_tbl = is_class ? fk[4][:class].underscore : pri_tbl
1339
+ pri_tbl = "#{bt_assoc_name}_#{pri_tbl}" if pri_tbl.singularize != bt_assoc_name
1340
+ cnstr_base = cnstr_name = "(brick) #{for_tbl}_#{pri_tbl}"
1267
1341
  cnstr_added_num = 1
1268
1342
  cnstr_name = "#{cnstr_base}_#{cnstr_added_num += 1}" while bts&.key?(cnstr_name) || hms&.key?(cnstr_name)
1269
1343
  missing = []
@@ -1303,6 +1377,7 @@ module Brick
1303
1377
  else
1304
1378
  inverse_table = [primary_table] if is_polymorphic
1305
1379
  assoc_bt = bts[cnstr_name] = { is_bt: true, fk: fk[2], assoc_name: bt_assoc_name, inverse_table: inverse_table || primary_table }
1380
+ assoc_bt[:optional] = true if is_optional
1306
1381
  assoc_bt[:polymorphic] = true if is_polymorphic
1307
1382
  end
1308
1383
  if is_class
@@ -1323,7 +1398,14 @@ module Brick
1323
1398
  assoc_hm[:alternate_name] = "#{assoc_hm[:alternate_name]}_#{bt_assoc_name}" unless assoc_hm[:alternate_name] == bt_assoc_name
1324
1399
  assoc_hm[:inverse] = assoc_bt
1325
1400
  else
1326
- 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 }
1401
+ inv_tbl = if ::Brick.config.schema_behavior[:multitenant] && Object.const_defined?('Apartment') && fk[0] == Apartment.default_schema
1402
+ for_tbl
1403
+ else
1404
+ fk[1]
1405
+ end
1406
+ # binding.pry if inv_tbl == 'issue_issue_duplicates' # inverse_table goofed?
1407
+ assoc_hm = hms[hm_cnstr_name] = { is_bt: false, fk: fk[2], assoc_name: for_tbl.pluralize, alternate_name: bt_assoc_name,
1408
+ inverse_table: inv_tbl, inverse: assoc_bt }
1327
1409
  assoc_hm[:polymorphic] = true if is_polymorphic
1328
1410
  hm_counts = relation.fetch(:hm_counts) { relation[:hm_counts] = {} }
1329
1411
  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
@@ -404,7 +407,7 @@ function changeout(href, param, value) {
404
407
  origin = (key_parts = k.split('.')).length == 1 ? #{model_name} : #{model_name}.reflect_on_association(key_parts.first).klass
405
408
  # binding.pry
406
409
  if (destination_fk = Brick.relations[origin.table_name][:fks].values.find { |fk| puts fk.inspect; fk[:fk] == key_parts.last }) &&
407
- (obj = (destination = origin.reflect_on_association(destination_fk[:assoc_name])&.klass)&.find(id)) %>
410
+ (obj = (destination = origin.reflect_on_association(destination_fk[:assoc_name])&.klass)&.find(id)) %>
408
411
  <h3>for <%= link_to \"#{"#\{obj.brick_descrip\} (#\{destination.name\})\""}, send(\"#\{destination.name.underscore.tr('/', '_')\}_path\".to_sym, id) %></h3><%
409
412
  end
410
413
  end %>
@@ -5,7 +5,7 @@ module Brick
5
5
  module VERSION
6
6
  MAJOR = 1
7
7
  MINOR = 0
8
- TINY = 30
8
+ TINY = 33
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)
@@ -129,6 +132,7 @@ module Brick
129
132
  associatives = hms.each_with_object({}) do |hmt, s|
130
133
  if (through = hmt.last.options[:through])
131
134
  skip_hms[through] = nil
135
+ # binding.pry if hmt.first == :issue_issues
132
136
  s[hmt.first] = hms[through] # End up with a hash of HMT names pointing to join-table associations
133
137
  elsif hmt.last.inverse_of.nil?
134
138
  puts "SKIPPING #{hmt.last.name.inspect}"
@@ -314,7 +318,7 @@ module Brick
314
318
  if ars
315
319
  ars.each do |ar|
316
320
  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)
321
+ ::Brick._add_bt_and_hm(fk, relations, false, true)
318
322
  end
319
323
  end
320
324
  if (polys = ::Brick.config.polymorphics)
@@ -327,7 +331,7 @@ module Brick
327
331
  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
332
  v.each do |type|
329
333
  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)
334
+ ::Brick._add_bt_and_hm([nil, table_name, poly, nil, primary_table, "(brick) #{table_name}_#{poly}"], relations, true, true)
331
335
  else
332
336
  missing_stis[primary_table] = type unless ::Brick.existing_stis.key?(type)
333
337
  end
@@ -406,6 +410,7 @@ In config/initializers/brick.rb appropriate entries would look something like:
406
410
  ::Brick.relations.each do |rel_name, v|
407
411
  rel_name = rel_name.split('.').map(&:underscore)
408
412
  schema_names = rel_name[0..-2]
413
+ schema_names.shift if ::Brick.config.schema_behavior[:multitenant] && Object.const_defined?('Apartment') && schema_names.first == Apartment.default_schema
409
414
  k = rel_name.last
410
415
  unless existing_controllers.key?(controller_name = k.pluralize)
411
416
  options = {}
@@ -419,6 +424,7 @@ In config/initializers/brick.rb appropriate entries would look something like:
419
424
  end
420
425
  end
421
426
  end
427
+ send(:get, '/api-docs/v1/swagger.json', { to: 'brick_swagger#index' }) if Object.const_defined?('Rswag::Ui')
422
428
  end
423
429
  super
424
430
  end
@@ -28,7 +28,7 @@ module Brick
28
28
  col_down = col.downcase
29
29
 
30
30
  if (is_possible_poly = ['character varying', 'text'].include?(type.first))
31
- if col_down.end_with?('_type') &&
31
+ if col_down.end_with?('_type')
32
32
  poly_type_cut_length = -6
33
33
  col_down = col_down[0..-6]
34
34
  elsif col_down.end_with?('type')
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.30
4
+ version: 1.0.33
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-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord