brick 1.0.29 → 1.0.30

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: 1a312c2fd5baf0793b4bb4143167b4399c70a514991206a611d525d0784de95b
4
- data.tar.gz: bb1af47d2d073dab2c046ff9757fb5db9447f02dd0688330ad7ac8ad678d6751
3
+ metadata.gz: ad31136f451e04eabcc67ee09980d45d25aa8e35453aaf4c160ffc9aef3f774c
4
+ data.tar.gz: 5e3b705dfad389391b291d26bd3c0571cf0579abce3ed00c2840805fd5e05353
5
5
  SHA512:
6
- metadata.gz: dbfabaf60608b2094e9903cb538bab00706f434f94289b9647320bb128adf0b34317a6404ace34897cf4b2f91700ebd7c028a12b4072ba1ebf9de3380f75add3
7
- data.tar.gz: 6e5444ac763c71de71fc1aca64cdb41db6276f1df57f5b556b03bad0b743373f2720865ba7c6ce36f9887d77ffee3b091f32d233121742c4786ca21e8d512705
6
+ metadata.gz: ac2403152cae57e54ae1840920aa47e5c64297ff7698f691c0a752ae063d824073f24d040aa66580ef0e5eddac0ec164cf720bc720ad73a09ef7862116608476
7
+ data.tar.gz: b529668cde573b3c371af30b7f141a1e313d477281bc2ef5453c9751a67abea39a4d8a57e3632f6ef8789ea04ab9d4e01170fb29f096fa6ab8772df77f60f564
data/lib/brick/config.rb CHANGED
@@ -122,12 +122,32 @@ module Brick
122
122
  @mutex.synchronize { @sti_namespace_prefixes = prefixes }
123
123
  end
124
124
 
125
- def schema_to_analyse
126
- @mutex.synchronize { @schema_to_analyse }
125
+ def schema_behavior
126
+ @mutex.synchronize { @schema_behavior ||= {} }
127
127
  end
128
128
 
129
- def schema_to_analyse=(schema)
130
- @mutex.synchronize { @schema_to_analyse = schema }
129
+ def schema_behavior=(schema)
130
+ @mutex.synchronize { @schema_behavior = schema }
131
+ end
132
+
133
+ def sti_type_column
134
+ @mutex.synchronize { @sti_type_column ||= {} }
135
+ end
136
+
137
+ def sti_type_column=(type_col)
138
+ @mutex.synchronize do
139
+ (@sti_type_column = type_col).each_with_object({}) do |v, s|
140
+ if v.last.nil?
141
+ # Set an STI type column generally
142
+ ActiveRecord::Base.inheritance_column = v.first
143
+ else
144
+ # Custom STI type columns for models built from specific tables
145
+ (v.last.is_a?(Array) ? v.last : [v.last]).each do |table|
146
+ ::Brick.relations[table][:sti_col] = v.first
147
+ end
148
+ end
149
+ end
150
+ end
131
151
  end
132
152
 
133
153
  def default_route_fallback
@@ -196,7 +196,7 @@ module ActiveRecord
196
196
  def self.bt_link(assoc_name)
197
197
  model_underscore = name.underscore
198
198
  assoc_name = CGI.escapeHTML(assoc_name.to_s)
199
- model_path = Rails.application.routes.url_helpers.send("#{model_underscore.pluralize}_path".to_sym)
199
+ model_path = Rails.application.routes.url_helpers.send("#{model_underscore.tr('/', '_').pluralize}_path".to_sym)
200
200
  link = Class.new.extend(ActionView::Helpers::UrlHelper).link_to(name, model_path)
201
201
  model_underscore == assoc_name ? link : "#{assoc_name}-#{link}".html_safe
202
202
  end
@@ -306,8 +306,12 @@ module ActiveRecord
306
306
 
307
307
  # %%% Skip the metadata columns
308
308
  if selects&.empty? # Default to all columns
309
+ tbl_no_schema = table.name.split('.').last
309
310
  columns.each do |col|
310
- selects << "\"#{table.name}\".\"#{col.name}\""
311
+ if (col_name = col.name) == 'class'
312
+ col_alias = ' AS _class'
313
+ end
314
+ selects << "\"#{tbl_no_schema}\".\"#{col_name}\"#{col_alias}"
311
315
  end
312
316
  end
313
317
 
@@ -349,7 +353,7 @@ module ActiveRecord
349
353
  end
350
354
 
351
355
  if join_array.present?
352
- left_outer_joins!(join_array) # joins!(join_array)
356
+ left_outer_joins!(join_array)
353
357
  # Without working from a duplicate, touching the AREL ast tree sets the @arel instance variable, which causes the relation to be immutable.
354
358
  (rel_dupe = dup)._arel_alias_names
355
359
  core_selects = selects.dup
@@ -360,10 +364,10 @@ module ActiveRecord
360
364
  v.last.each do |k1, v1| # k1 is class, v1 is array of columns to snag
361
365
  next if chains[k1].nil?
362
366
 
363
- tbl_name = field_tbl_names[v.first][k1] ||= shift_or_first(chains[k1])
367
+ tbl_name = (field_tbl_names[v.first][k1] ||= shift_or_first(chains[k1])).split('.').last
364
368
  field_tbl_name = nil
365
369
  v1.map { |x| [translations[x[0..-2].map(&:to_s).join('.')], x.last] }.each_with_index do |sel_col, idx|
366
- field_tbl_name = field_tbl_names[v.first][sel_col.first] ||= shift_or_first(chains[sel_col.first])
370
+ field_tbl_name = (field_tbl_names[v.first][sel_col.first] ||= shift_or_first(chains[sel_col.first])).split('.').last
367
371
 
368
372
  selects << "#{"\"#{field_tbl_name}\".\"#{sel_col.last}\""} AS \"#{(col_alias = "_brfk_#{v.first}__#{sel_col.last}")}\""
369
373
  v1[idx] << col_alias
@@ -420,6 +424,7 @@ JOIN (SELECT #{selects.join(', ')}, COUNT(#{count_column}) AS _ct_ FROM #{associ
420
424
  joins!("#{join_clause} ON #{on_clause.join(' AND ')}")
421
425
  end
422
426
  where!(wheres) unless wheres.empty?
427
+ limit(1000) # Don't want to get too carried away just yet
423
428
  wheres unless wheres.empty? # Return the specific parameters that we did use
424
429
  end
425
430
 
@@ -484,7 +489,7 @@ if ActiveSupport::Dependencies.respond_to?(:autoload_module!) # %%% Only works w
484
489
  ::Brick.sti_models[qualified_name] = { base: base_class }
485
490
  # Build subclass and place it into the specially STI-namespaced module
486
491
  into.const_set(const_name.to_sym, klass = Class.new(base_class))
487
- # %%% used to also have: autoload_once_paths.include?(base_path) ||
492
+ # %%% used to also have: autoload_once_paths.include?(base_path) ||
488
493
  autoloaded_constants << qualified_name unless autoloaded_constants.include?(qualified_name)
489
494
  klass
490
495
  elsif (base_class = ::Brick.config.sti_namespace_prefixes&.fetch("::#{const_name}", nil)&.constantize)
@@ -498,75 +503,209 @@ if ActiveSupport::Dependencies.respond_to?(:autoload_module!) # %%% Only works w
498
503
  end
499
504
  end
500
505
 
501
- class Object
502
- class << self
503
- alias _brick_const_missing const_missing
504
- def const_missing(*args)
505
- return self.const_get(args.first) if self.const_defined?(args.first)
506
- return Object.const_get(args.first) if Object.const_defined?(args.first) unless self == Object
507
-
508
- class_name = args.first.to_s
509
- # See if a file is there in the same way that ActiveSupport::Dependencies#load_missing_constant
510
- # checks for it in ~/.rvm/gems/ruby-2.7.5/gems/activesupport-5.2.6.2/lib/active_support/dependencies.rb
511
- # that is, checking #qualified_name_for with: from_mod, const_name
512
- # If we want to support namespacing in the future, might have to utilise something like this:
513
- # path_suffix = ActiveSupport::Dependencies.qualified_name_for(Object, args.first).underscore
514
- # return self._brick_const_missing(*args) if ActiveSupport::Dependencies.search_for_file(path_suffix)
515
- # If the file really exists, go and snag it:
516
- if !(is_found = ActiveSupport::Dependencies.search_for_file(class_name.underscore)) && (filepath = (self.name || class_name)&.split('::'))
517
- filepath = (filepath[0..-2] + [class_name]).join('/').underscore + '.rb'
518
- end
519
- if is_found
520
- return self._brick_const_missing(*args)
521
- elsif ActiveSupport::Dependencies.search_for_file(filepath) # Last-ditch effort to pick this thing up before we fill in the gaps on our own
522
- my_const = parent.const_missing(class_name) # ends up having: MyModule::MyClass
523
- return my_const
524
- end
506
+ Module.class_exec do
507
+ alias _brick_const_missing const_missing
508
+ 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)
511
+ return possible
512
+ end
513
+ class_name = args.first.to_s
514
+ # See if a file is there in the same way that ActiveSupport::Dependencies#load_missing_constant
515
+ # checks for it in ~/.rvm/gems/ruby-2.7.5/gems/activesupport-5.2.6.2/lib/active_support/dependencies.rb
516
+ # that is, checking #qualified_name_for with: from_mod, const_name
517
+ # If we want to support namespacing in the future, might have to utilise something like this:
518
+ # path_suffix = ActiveSupport::Dependencies.qualified_name_for(Object, args.first).underscore
519
+ # return self._brick_const_missing(*args) if ActiveSupport::Dependencies.search_for_file(path_suffix)
520
+ # If the file really exists, go and snag it:
521
+ if !(is_found = ActiveSupport::Dependencies.search_for_file(class_name.underscore)) && (filepath = (self.name || class_name)&.split('::'))
522
+ filepath = (filepath[0..-2] + [class_name]).join('/').underscore + '.rb'
523
+ end
524
+ if is_found
525
+ return self._brick_const_missing(*args)
526
+ # elsif ActiveSupport::Dependencies.search_for_file(filepath) # Last-ditch effort to pick this thing up before we fill in the gaps on our own
527
+ # my_const = parent.const_missing(class_name) # ends up having: MyModule::MyClass
528
+ # return my_const
529
+ end
525
530
 
526
- relations = ::Brick.instance_variable_get(:@relations)[ActiveRecord::Base.connection_pool.object_id] || {}
527
- result = if ::Brick.enable_controllers? && class_name.end_with?('Controller') && (plural_class_name = class_name[0..-11]).length.positive?
528
- # Otherwise now it's up to us to fill in the gaps
529
- if (model = plural_class_name.singularize.constantize)
530
- # 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.
531
- build_controller(class_name, plural_class_name, model, relations)
532
- end
533
- elsif ::Brick.enable_models?
534
- # See if a file is there in the same way that ActiveSupport::Dependencies#load_missing_constant
535
- # checks for it in ~/.rvm/gems/ruby-2.7.5/gems/activesupport-5.2.6.2/lib/active_support/dependencies.rb
536
- plural_class_name = ActiveSupport::Inflector.pluralize(model_name = class_name)
537
- singular_table_name = ActiveSupport::Inflector.underscore(model_name)
538
-
539
- # Adjust for STI if we know of a base model for the requested model name
540
- table_name = if (base_model = ::Brick.sti_models[model_name]&.fetch(:base, nil) || ::Brick.existing_stis[model_name]&.constantize)
541
- base_model.table_name
542
- else
543
- ActiveSupport::Inflector.pluralize(singular_table_name)
544
- end
545
-
546
- # Maybe, just maybe there's a database table that will satisfy this need
547
- if (matching = [table_name, singular_table_name, plural_class_name, model_name].find { |m| relations.key?(m) })
548
- build_model(model_name, singular_table_name, table_name, relations, matching)
549
- end
531
+ relations = ::Brick.instance_variable_get(:@relations)[ActiveRecord::Base.connection_pool.object_id] || {}
532
+ # puts "ON OBJECT: #{args.inspect}" if self.module_parent == Object
533
+ result = if ::Brick.enable_controllers? && class_name.end_with?('Controller') && (plural_class_name = class_name[0..-11]).length.positive?
534
+ # Otherwise now it's up to us to fill in the gaps
535
+ # (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)
537
+ full_class_name = +''
538
+ full_class_name << "::#{self.name}" unless self == Object
539
+ full_class_name << "::#{plural_class_name.underscore.singularize.camelize}"
540
+ if (model = self.const_get(full_class_name))
541
+ # 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
+ Object.send(:build_controller, self, class_name, plural_class_name, model, relations)
550
543
  end
551
- if result
552
- built_class, code = result
553
- puts "\n#{code}"
554
- built_class
555
- elsif ::Brick.config.sti_namespace_prefixes&.key?("::#{class_name}")
544
+ elsif (::Brick.enable_models? || ::Brick.enable_controllers?) && # Schema match?
545
+ 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) }
550
+ # Build out a module for the schema if it's namespaced
551
+ schema_name = schema_name.camelize
552
+ self.const_set(schema_name.to_sym, (built_module = Module.new))
553
+
554
+ [built_module, "module #{schema_name}; end\n"]
555
+ # # %%% Perhaps an option to use the first module just as schema, and additional modules as namespace with a table name prefix applied
556
+ elsif ::Brick.enable_models?
557
+ # See if a file is there in the same way that ActiveSupport::Dependencies#load_missing_constant
558
+ # checks for it in ~/.rvm/gems/ruby-2.7.5/gems/activesupport-5.2.6.2/lib/active_support/dependencies.rb
559
+
560
+ unless self == Object # Are we in some namespace?
561
+ schema_name = [(singular_schema_name = name.underscore),
562
+ (schema_name = singular_schema_name.pluralize),
563
+ name,
564
+ name.pluralize].find { |s| Brick.db_schemas.include?(s) }
565
+ end
566
+
567
+ plural_class_name = ActiveSupport::Inflector.pluralize(model_name = class_name)
568
+ # 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
+ end
582
+ end
583
+ if result
584
+ built_class, code = result
585
+ puts "\n#{code}"
586
+ built_class
587
+ elsif ::Brick.config.sti_namespace_prefixes&.key?("::#{class_name}") && !schema_name
556
588
  # module_prefixes = type_name.split('::')
557
589
  # path = self.name.split('::')[0..-2] + []
558
590
  # module_prefixes.unshift('') unless module_prefixes.first.blank?
559
591
  # candidate_file = Rails.root.join('app/models' + module_prefixes.map(&:underscore).join('/') + '.rb')
560
- self._brick_const_missing(*args)
561
- else
562
- puts "MISSING! #{self.name} #{args.inspect} #{table_name}"
563
- self._brick_const_missing(*args)
564
- end
592
+ self._brick_const_missing(*args)
593
+ elsif self != Object
594
+ module_parent.const_missing(*args)
595
+ else
596
+ puts "MISSING! mod #{self.name} #{args.inspect} #{table_name}"
597
+ self._brick_const_missing(*args)
565
598
  end
599
+ end
600
+ end
601
+
602
+ class Object
603
+ 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
566
697
 
567
698
  private
568
699
 
569
- def build_model(model_name, singular_table_name, table_name, relations, matching)
700
+ def build_model(schema_name, model_name, singular_table_name, table_name, relations, matching)
701
+ full_name = if schema_name.blank?
702
+ model_name
703
+ 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}"
706
+ "#{schema_module&.name}::#{model_name}"
707
+ end
708
+
570
709
  return if ((is_view = (relation = relations[matching]).key?(:isView)) && ::Brick.config.skip_database_views) ||
571
710
  ::Brick.config.exclude_tables.include?(matching)
572
711
 
@@ -578,14 +717,15 @@ class Object
578
717
  return
579
718
  end
580
719
 
581
- if (base_model = ::Brick.sti_models[model_name]&.fetch(:base, nil) || ::Brick.existing_stis[model_name]&.constantize)
720
+ if (base_model = ::Brick.sti_models[full_name]&.fetch(:base, nil) || ::Brick.existing_stis[full_name]&.constantize)
582
721
  is_sti = true
583
722
  else
584
723
  base_model = ::Brick.config.models_inherit_from || ActiveRecord::Base
585
724
  end
586
- code = +"class #{model_name} < #{base_model.name}\n"
725
+ hmts = nil
726
+ code = +"class #{full_name} < #{base_model.name}\n"
587
727
  built_model = Class.new(base_model) do |new_model_class|
588
- Object.const_set(model_name.to_sym, new_model_class)
728
+ (schema_module || Object).const_set(model_name.to_sym, new_model_class)
589
729
  # Accommodate singular or camel-cased table names such as "order_detail" or "OrderDetails"
590
730
  code << " self.table_name = '#{self.table_name = matching}'\n" unless table_name == matching
591
731
 
@@ -618,6 +758,10 @@ class Object
618
758
  else
619
759
  code << " # Could not identify any column(s) to use as a primary key\n" unless is_view
620
760
  end
761
+ if (sti_col = relation.fetch(:sti_col, nil))
762
+ new_model_class.send(:'inheritance_column=', sti_col)
763
+ code << " self.inheritance_column = #{sti_col.inspect}\n"
764
+ end
621
765
 
622
766
  unless is_sti
623
767
  fks = relation[:fks] || {}
@@ -635,7 +779,21 @@ class Object
635
779
  build_bt_or_hm(relations, model_name, relation, hmts, assoc, inverse_assoc_name, invs, code) unless invs.is_a?(Array)
636
780
  hmts
637
781
  end
782
+ # # Not NULLables
783
+ # # %%% For the minute we've had to pull this out because it's been troublesome implementing the NotNull validator
784
+ # relation[:cols].each do |col, datatype|
785
+ # if (datatype[3] && _brick_primary_key.exclude?(col) && ::Brick.config.metadata_columns.exclude?(col)) ||
786
+ # ::Brick.config.not_nullables.include?("#{matching}.#{col}")
787
+ # code << " validates :#{col}, not_null: true\n"
788
+ # self.send(:validates, col.to_sym, { not_null: true })
789
+ # end
790
+ # end
791
+ end
792
+ end # class definition
793
+ # Having this separate -- will this now work out better?
794
+ built_model.class_exec do
638
795
  hmts.each do |hmt_fk, fks|
796
+ hmt_fk = hmt_fk.tr('.', '_')
639
797
  fks.each do |fk|
640
798
  through = fk.first[:assoc_name]
641
799
  hmt_name = if fks.length > 1
@@ -656,18 +814,8 @@ class Object
656
814
  self.send(:has_many, hmt_name.to_sym, **options)
657
815
  end
658
816
  end
659
- # # Not NULLables
660
- # # %%% For the minute we've had to pull this out because it's been troublesome implementing the NotNull validator
661
- # relation[:cols].each do |col, datatype|
662
- # if (datatype[3] && _brick_primary_key.exclude?(col) && ::Brick.config.metadata_columns.exclude?(col)) ||
663
- # ::Brick.config.not_nullables.include?("#{matching}.#{col}")
664
- # code << " validates :#{col}, not_null: true\n"
665
- # self.send(:validates, col.to_sym, { not_null: true })
666
- # end
667
- # end
668
817
  end
669
- code << "end # model #{model_name}\n\n"
670
- end # class definition
818
+ code << "end # model #{full_name}\n\n"
671
819
  [built_model, code]
672
820
  end
673
821
 
@@ -678,7 +826,7 @@ class Object
678
826
  # Try to take care of screwy names if this is a belongs_to going to an STI subclass
679
827
  assoc_name = if (primary_class = assoc.fetch(:primary_class, nil)) &&
680
828
  sti_inverse_assoc = primary_class.reflect_on_all_associations.find do |a|
681
- a.macro == :has_many && a.options[:class_name] == self.name && assoc[:fk] = a.foreign_key
829
+ a.macro == :has_many && a.options[:class_name] == self.name && assoc[:fk] == a.foreign_key
682
830
  end
683
831
  sti_inverse_assoc.options[:inverse_of]&.to_s || assoc_name
684
832
  else
@@ -710,7 +858,7 @@ class Object
710
858
  if assoc.key?(:polymorphic)
711
859
  options[:as] = assoc[:fk].to_sym
712
860
  else
713
- need_fk = "#{ActiveSupport::Inflector.singularize(assoc[:inverse][:inverse_table])}_id" != assoc[:fk]
861
+ need_fk = "#{ActiveSupport::Inflector.singularize(assoc[:inverse][:inverse_table].split('.').last)}_id" != assoc[:fk]
714
862
  end
715
863
  # fks[table_name].find { |other_assoc| other_assoc.object_id != assoc.object_id && other_assoc[:assoc_name] == assoc[assoc_name] }
716
864
  if (has_ones = ::Brick.config.has_ones&.fetch(model_name, nil))&.key?(singular_assoc_name = ActiveSupport::Inflector.singularize(assoc_name))
@@ -727,7 +875,7 @@ class Object
727
875
  end
728
876
  # Figure out if we need to specially call out the class_name and/or foreign key
729
877
  # (and if either of those then definitely also a specific inverse_of)
730
- options[:class_name] = assoc[:primary_class]&.name || singular_table_name.camelize if need_class_name
878
+ options[:class_name] = "::#{assoc[:primary_class]&.name || singular_table_name.split('.').map(&:camelize).join('::')}" if need_class_name
731
879
  # Work around a bug in CPK where self-referencing belongs_to associations double up their foreign keys
732
880
  if need_fk # Funky foreign key?
733
881
  options[:foreign_key] = if assoc[:fk].is_a?(Array)
@@ -737,10 +885,11 @@ class Object
737
885
  assoc[:fk].to_sym
738
886
  end
739
887
  end
740
- options[:inverse_of] = inverse_assoc_name.to_sym if inverse_assoc_name && (need_class_name || need_fk || need_inverse_of)
888
+ options[:inverse_of] = inverse_assoc_name.tr('.', '_').to_sym if inverse_assoc_name && (need_class_name || need_fk || need_inverse_of)
741
889
 
742
890
  # Prepare a list of entries for "has_many :through"
743
891
  if macro == :has_many
892
+ puts [inverse_table, relations[inverse_table].length].inspect
744
893
  relations[inverse_table][:hmt_fks].each do |k, hmt_fk|
745
894
  next if k == assoc[:fk]
746
895
 
@@ -748,19 +897,20 @@ class Object
748
897
  end
749
898
  end
750
899
  # And finally create a has_one, has_many, or belongs_to for this association
751
- assoc_name = assoc_name.to_sym
900
+ assoc_name = assoc_name.tr('.', '_').to_sym
752
901
  code << " #{macro} #{assoc_name.inspect}#{options.map { |k, v| ", #{k}: #{v.inspect}" }.join}\n"
753
902
  self.send(macro, assoc_name, **options)
754
903
  end
755
904
 
756
- def build_controller(class_name, plural_class_name, model, relations)
905
+ def build_controller(namespace, class_name, plural_class_name, model, relations)
757
906
  table_name = ActiveSupport::Inflector.underscore(plural_class_name)
758
907
  singular_table_name = ActiveSupport::Inflector.singularize(table_name)
759
908
  pk = model._brick_primary_key(relations.fetch(table_name, nil))
760
909
 
761
- code = +"class #{class_name} < ApplicationController\n"
910
+ namespace_name = "#{namespace.name}::" if namespace
911
+ code = +"class #{namespace_name}#{class_name} < ApplicationController\n"
762
912
  built_controller = Class.new(ActionController::Base) do |new_controller_class|
763
- Object.const_set(class_name.to_sym, new_controller_class)
913
+ (namespace || Object).const_set(class_name.to_sym, new_controller_class)
764
914
 
765
915
  code << " def index\n"
766
916
  code << " @#{table_name} = #{model.name}#{pk&.present? ? ".order(#{pk.inspect})" : '.all'}\n"
@@ -787,9 +937,10 @@ class Object
787
937
  # %%% Add custom HM count columns
788
938
  # %%% What happens when the PK is composite?
789
939
  counts = hm_counts.each_with_object([]) { |v, s| s << "_br_#{v.first}._ct_ AS _br_#{v.first}_ct" }
790
- # *selects,
791
940
  instance_variable_set("@#{table_name}".to_sym, ar_relation.dup._select!(*selects, *counts))
792
- # binding.pry
941
+ if namespace && (idx = lookup_context.prefixes.index(table_name))
942
+ lookup_context.prefixes[idx] = "#{namespace.name.underscore}/#{lookup_context.prefixes[idx]}"
943
+ end
793
944
  @_brick_bt_descrip = bt_descrip
794
945
  @_brick_hm_counts = hm_counts
795
946
  @_brick_join_array = join_array
@@ -814,9 +965,9 @@ class Object
814
965
  code << " # (Define :new, :create)\n"
815
966
 
816
967
  if model.primary_key
817
- if (schema = ::Brick.config.schema_to_analyse) && ::Brick.db_schemas&.include?(schema)
818
- ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?;", schema)
819
- end
968
+ # if (schema = ::Brick.config.schema_behavior[:multitenant]&.fetch(:schema_to_analyse, nil)) && ::Brick.db_schemas&.include?(schema)
969
+ # ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?;", schema)
970
+ # end
820
971
 
821
972
  is_need_params = true
822
973
  # code << " # (Define :edit, and :destroy)\n"
@@ -827,7 +978,6 @@ class Object
827
978
  code << " end\n"
828
979
  self.define_method :update do
829
980
  ::Brick.set_db_schema(params)
830
-
831
981
  if request.format == :csv # Importing CSV?
832
982
  require 'csv'
833
983
  # See if internally it's likely a TSV file (tab-separated)
@@ -851,7 +1001,7 @@ class Object
851
1001
 
852
1002
  if is_need_params
853
1003
  code << "private\n"
854
- code << " def params\n"
1004
+ code << " def #{params_name}\n"
855
1005
  code << " params.require(:#{singular_table_name}).permit(#{model.columns_hash.keys.map { |c| c.to_sym.inspect }.join(', ')})\n"
856
1006
  code << " end\n"
857
1007
  self.define_method(params_name) do
@@ -861,7 +1011,7 @@ class Object
861
1011
  # Get column names for params from relations[model.table_name][:cols].keys
862
1012
  end
863
1013
  end
864
- code << "end # #{class_name}\n\n"
1014
+ code << "end # #{namespace_name}#{class_name}\n\n"
865
1015
  end # class definition
866
1016
  [built_controller, code]
867
1017
  end
@@ -871,7 +1021,8 @@ class Object
871
1021
  plural = ActiveSupport::Inflector.pluralize(hm_assoc[:alternate_name])
872
1022
  [hm_assoc[:alternate_name] == name.underscore ? "#{hm_assoc[:assoc_name].singularize}_#{plural}" : plural, true]
873
1023
  else
874
- [ActiveSupport::Inflector.pluralize(hm_assoc[:inverse_table]), nil]
1024
+ assoc_name = hm_assoc[:inverse_table].pluralize
1025
+ [assoc_name, assoc_name.include?('.')]
875
1026
  end
876
1027
  end
877
1028
  end
@@ -891,16 +1042,19 @@ module ActiveRecord::ConnectionHandling
891
1042
 
892
1043
  def _brick_reflect_tables
893
1044
  if (relations = ::Brick.relations).empty?
894
- # Only for Postgres? (Doesn't work in sqlite3)
895
- # puts ActiveRecord::Base.execute_sql("SELECT current_setting('SEARCH_PATH')").to_a.inspect
1045
+ load Rails.root.join('config/initializers/brick.rb') # Hopefully our initializer is named exactly this!
1046
+ # Only for Postgres? (Doesn't work in sqlite3)
1047
+ # puts ActiveRecord::Base.execute_sql("SELECT current_setting('SEARCH_PATH')").to_a.inspect
896
1048
 
897
- schema_sql = 'SELECT NULL AS table_schema;'
898
- case ActiveRecord::Base.connection.adapter_name
1049
+ schema_sql = 'SELECT NULL AS table_schema;'
1050
+ case ActiveRecord::Base.connection.adapter_name
899
1051
  when 'PostgreSQL'
900
- schema = 'public' # Too early at this point to be able to pick up: Brick.config.schema_to_analyse
1052
+ if (::Brick.default_schema = schema = ::Brick.config.schema_behavior&.[](:multitenant)&.[](:schema_to_analyse))
1053
+ ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?", schema)
1054
+ end
901
1055
  schema_sql = 'SELECT DISTINCT table_schema FROM INFORMATION_SCHEMA.tables;'
902
1056
  when 'Mysql2'
903
- schema = ActiveRecord::Base.connection.current_database
1057
+ ::Brick.default_schema = schema = ActiveRecord::Base.connection.current_database
904
1058
  when 'SQLite'
905
1059
  sql = "SELECT m.name AS relation_name, UPPER(m.type) AS table_type,
906
1060
  p.name AS column_name, p.type AS data_type,
@@ -913,8 +1067,7 @@ module ActiveRecord::ConnectionHandling
913
1067
  puts "Unfamiliar with connection adapter #{ActiveRecord::Base.connection.adapter_name}"
914
1068
  end
915
1069
 
916
- sql ||= ActiveRecord::Base.send(:sanitize_sql_array, [
917
- "SELECT t.table_name AS relation_name, t.table_type,
1070
+ sql ||= "SELECT t.table_schema AS schema, t.table_name AS relation_name, t.table_type,
918
1071
  c.column_name, c.data_type,
919
1072
  COALESCE(c.character_maximum_length, c.numeric_precision) AS max_length,
920
1073
  tc.constraint_type AS const, kcu.constraint_name AS \"key\",
@@ -932,18 +1085,23 @@ module ActiveRecord::ConnectionHandling
932
1085
  ON kcu.CONSTRAINT_SCHEMA = tc.CONSTRAINT_SCHEMA
933
1086
  AND kcu.TABLE_NAME = tc.TABLE_NAME
934
1087
  AND kcu.CONSTRAINT_NAME = tc.constraint_name
935
- WHERE t.table_schema = ? -- COALESCE(current_setting('SEARCH_PATH'), 'public')
1088
+ WHERE t.table_schema NOT IN ('information_schema', 'pg_catalog')#{"
1089
+ AND t.table_schema = COALESCE(current_setting('SEARCH_PATH'), 'public')" if schema }
936
1090
  -- AND t.table_type IN ('VIEW') -- 'BASE TABLE', 'FOREIGN TABLE'
937
1091
  AND t.table_name NOT IN ('pg_stat_statements', 'ar_internal_metadata', 'schema_migrations')
938
- ORDER BY 1, t.table_type DESC, c.ordinal_position", schema
939
- ])
940
-
1092
+ ORDER BY 1, t.table_type DESC, c.ordinal_position"
941
1093
  measures = []
942
1094
  case ActiveRecord::Base.connection.adapter_name
943
1095
  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'
944
1097
  ActiveRecord::Base.execute_sql(sql).each do |r|
945
1098
  # next if internal_views.include?(r['relation_name']) # Skip internal views such as v_all_assessments
946
- relation = relations[(relation_name = r['relation_name'])]
1099
+ relation_name = if r['schema'] != schema
1100
+ "#{schema_name = r['schema']}.#{r['relation_name']}"
1101
+ else
1102
+ r['relation_name']
1103
+ end
1104
+ relation = relations[relation_name]
947
1105
  relation[:isView] = true if r['table_type'] == 'VIEW'
948
1106
  col_name = r['column_name']
949
1107
  key = case r['const']
@@ -1000,11 +1158,11 @@ module ActiveRecord::ConnectionHandling
1000
1158
  # end
1001
1159
  # end
1002
1160
  # end
1003
-
1161
+ schema = ::Brick.default_schema # Reset back for this next round of fun
1004
1162
  case ActiveRecord::Base.connection.adapter_name
1005
1163
  when 'PostgreSQL', 'Mysql2'
1006
- sql = ActiveRecord::Base.send(:sanitize_sql_array, [
1007
- "SELECT kcu1.TABLE_NAME, kcu1.COLUMN_NAME, kcu2.TABLE_NAME AS primary_table, kcu1.CONSTRAINT_NAME
1164
+ sql = "SELECT kcu1.CONSTRAINT_SCHEMA, kcu1.TABLE_NAME, kcu1.COLUMN_NAME,
1165
+ kcu2.CONSTRAINT_SCHEMA AS primary_schema, kcu2.TABLE_NAME AS primary_table, kcu1.CONSTRAINT_NAME AS CONSTRAINT_SCHEMA_FK
1008
1166
  FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS AS rc
1009
1167
  INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS kcu1
1010
1168
  ON kcu1.CONSTRAINT_CATALOG = rc.CONSTRAINT_CATALOG
@@ -1014,10 +1172,9 @@ module ActiveRecord::ConnectionHandling
1014
1172
  ON kcu2.CONSTRAINT_CATALOG = rc.UNIQUE_CONSTRAINT_CATALOG
1015
1173
  AND kcu2.CONSTRAINT_SCHEMA = rc.UNIQUE_CONSTRAINT_SCHEMA
1016
1174
  AND kcu2.CONSTRAINT_NAME = rc.UNIQUE_CONSTRAINT_NAME
1017
- AND kcu2.ORDINAL_POSITION = kcu1.ORDINAL_POSITION
1018
- WHERE kcu1.CONSTRAINT_SCHEMA = ? -- COALESCE(current_setting('SEARCH_PATH'), 'public')", schema
1175
+ AND kcu2.ORDINAL_POSITION = kcu1.ORDINAL_POSITION#{"
1176
+ WHERE kcu1.CONSTRAINT_SCHEMA = COALESCE(current_setting('SEARCH_PATH'), 'public')" if schema }"
1019
1177
  # AND kcu2.TABLE_NAME = ?;", Apartment::Tenant.current, table_name
1020
- ])
1021
1178
  when 'SQLite'
1022
1179
  sql = "SELECT m.name, fkl.\"from\", fkl.\"table\", m.name || '_' || fkl.\"from\" AS constraint_name
1023
1180
  FROM sqlite_master m
@@ -1026,10 +1183,17 @@ module ActiveRecord::ConnectionHandling
1026
1183
  else
1027
1184
  end
1028
1185
  if sql
1029
- ::Brick.db_schemas = ActiveRecord::Base.execute_sql(schema_sql)
1030
- ::Brick.db_schemas = ::Brick.db_schemas.to_a unless ::Brick.db_schemas.is_a?(Array)
1031
- ::Brick.db_schemas.map! { |row| row['table_schema'] } unless ::Brick.db_schemas.empty? || ::Brick.db_schemas.first.is_a?(String)
1032
- ::Brick.db_schemas -= ['information_schema', 'pg_catalog']
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
1033
1197
  ActiveRecord::Base.execute_sql(sql).each do |fk|
1034
1198
  fk = fk.values unless fk.is_a?(Array)
1035
1199
  ::Brick._add_bt_and_hm(fk, relations)
@@ -1076,34 +1240,46 @@ module Brick
1076
1240
 
1077
1241
  class << self
1078
1242
  def _add_bt_and_hm(fk, relations, is_polymorphic = false)
1079
- bt_assoc_name = fk[1].underscore
1080
- bt_assoc_name = bt_assoc_name[0..-4] if bt_assoc_name.end_with?('_id')
1081
-
1082
- bts = (relation = relations.fetch(fk[0], nil))&.fetch(:fks) { relation[:fks] = {} }
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"
1249
+ end
1250
+ # %%% 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
1253
+ bts = (relation = relations.fetch(fk[1], nil))&.fetch(:fks) { relation[:fks] = {} }
1083
1254
  # %%% Do we miss out on has_many :through or even HM based on constantizing this model early?
1084
1255
  # Maybe it's already gotten this info because we got as far as to say there was a unique class
1085
- primary_table = (is_class = fk[2].is_a?(Hash) && fk[2].key?(:class)) ? (primary_class = fk[2][:class].constantize).table_name : fk[2]
1256
+ primary_table = if (is_class = fk[4].is_a?(Hash) && fk[4].key?(:class))
1257
+ pri_tbl = (primary_class = fk[4][:class].constantize).table_name
1258
+ else
1259
+ pri_tbl = fk[4]
1260
+ (fk[3] && fk[3] != ::Brick.default_schema) ? "#{fk[3]}.#{pri_tbl}" : pri_tbl
1261
+ end
1086
1262
  hms = (relation = relations.fetch(primary_table, nil))&.fetch(:fks) { relation[:fks] = {} } unless is_class
1087
1263
 
1088
- unless (cnstr_name = fk[3])
1264
+ unless (cnstr_name = fk[5])
1089
1265
  # For any appended references (those that come from config), arrive upon a definitely unique constraint name
1090
- cnstr_base = cnstr_name = "(brick) #{fk[0]}_#{is_class ? fk[2][:class].underscore : fk[2]}"
1266
+ cnstr_base = cnstr_name = "(brick) #{for_tbl}#{is_class ? fk[4][:class].underscore : pri_tbl}"
1091
1267
  cnstr_added_num = 1
1092
1268
  cnstr_name = "#{cnstr_base}_#{cnstr_added_num += 1}" while bts&.key?(cnstr_name) || hms&.key?(cnstr_name)
1093
1269
  missing = []
1094
- missing << fk[0] unless relations.key?(fk[0])
1270
+ missing << fk[1] unless relations.key?(fk[1])
1095
1271
  missing << primary_table unless is_class || relations.key?(primary_table)
1096
1272
  unless missing.empty?
1097
1273
  tables = relations.reject { |_k, v| v.fetch(:isView, nil) }.keys.sort
1098
1274
  puts "Brick: Additional reference #{fk.inspect} refers to non-existent #{'table'.pluralize(missing.length)} #{missing.join(' and ')}. (Available tables include #{tables.join(', ')}.)"
1099
1275
  return
1100
1276
  end
1101
- unless (cols = relations[fk[0]][:cols]).key?(fk[1]) || (is_polymorphic && cols.key?("#{fk[1]}_id") && cols.key?("#{fk[1]}_type"))
1277
+ unless (cols = relations[fk[1]][:cols]).key?(fk[2]) || (is_polymorphic && cols.key?("#{fk[2]}_id") && cols.key?("#{fk[2]}_type"))
1102
1278
  columns = cols.map { |k, v| "#{k} (#{v.first.split(' ').first})" }
1103
- puts "Brick: Additional reference #{fk.inspect} refers to non-existent column #{fk[1]}. (Columns present in #{fk[0]} are #{columns.join(', ')}.)"
1279
+ puts "Brick: Additional reference #{fk.inspect} refers to non-existent column #{fk[2]}. (Columns present in #{fk[1]} are #{columns.join(', ')}.)"
1104
1280
  return
1105
1281
  end
1106
- if (redundant = bts.find { |_k, v| v[:inverse]&.fetch(:inverse_table, nil) == fk[0] && v[:fk] == fk[1] && v[:inverse_table] == primary_table })
1282
+ if (redundant = bts.find { |_k, v| v[:inverse]&.fetch(:inverse_table, nil) == fk[1] && v[:fk] == fk[2] && v[:inverse_table] == primary_table })
1107
1283
  if is_class && !redundant.last.key?(:class)
1108
1284
  redundant.last[:primary_class] = primary_class # Round out this BT so it can find the proper :source for a HMT association that references an STI subclass
1109
1285
  else
@@ -1115,18 +1291,18 @@ module Brick
1115
1291
  if (assoc_bt = bts[cnstr_name])
1116
1292
  if is_polymorphic
1117
1293
  # Assuming same fk (don't yet support composite keys for polymorphics)
1118
- assoc_bt[:inverse_table] << fk[2]
1294
+ assoc_bt[:inverse_table] << fk[4]
1119
1295
  else # Expect we could have a composite key going
1120
1296
  if assoc_bt[:fk].is_a?(String)
1121
- assoc_bt[:fk] = [assoc_bt[:fk], fk[1]] unless fk[1] == assoc_bt[:fk]
1122
- elsif assoc_bt[:fk].exclude?(fk[1])
1123
- assoc_bt[:fk] << fk[1]
1297
+ assoc_bt[:fk] = [assoc_bt[:fk], fk[2]] unless fk[2] == assoc_bt[:fk]
1298
+ elsif assoc_bt[:fk].exclude?(fk[2])
1299
+ assoc_bt[:fk] << fk[2]
1124
1300
  end
1125
- assoc_bt[:assoc_name] = "#{assoc_bt[:assoc_name]}_#{fk[1]}"
1301
+ assoc_bt[:assoc_name] = "#{assoc_bt[:assoc_name]}_#{fk[2]}"
1126
1302
  end
1127
1303
  else
1128
1304
  inverse_table = [primary_table] if is_polymorphic
1129
- assoc_bt = bts[cnstr_name] = { is_bt: true, fk: fk[1], assoc_name: bt_assoc_name, inverse_table: inverse_table || primary_table }
1305
+ assoc_bt = bts[cnstr_name] = { is_bt: true, fk: fk[2], assoc_name: bt_assoc_name, inverse_table: inverse_table || primary_table }
1130
1306
  assoc_bt[:polymorphic] = true if is_polymorphic
1131
1307
  end
1132
1308
  if is_class
@@ -1136,21 +1312,21 @@ module Brick
1136
1312
  # assoc_bt[:inverse_of] = primary_class.reflect_on_all_associations.find { |a| a.foreign_key == bt[1] }
1137
1313
  end
1138
1314
 
1139
- return if is_class || ::Brick.config.exclude_hms&.any? { |exclusion| fk[0] == exclusion[0] && fk[1] == exclusion[1] && primary_table == exclusion[2] }
1315
+ return if is_class || ::Brick.config.exclude_hms&.any? { |exclusion| fk[1] == exclusion[0] && fk[2] == exclusion[1] && primary_table == exclusion[2] } || hms.nil?
1140
1316
 
1141
1317
  if (assoc_hm = hms.fetch((hm_cnstr_name = "hm_#{cnstr_name}"), nil))
1142
1318
  if assoc_hm[:fk].is_a?(String)
1143
- assoc_hm[:fk] = [assoc_hm[:fk], fk[1]] unless fk[1] == assoc_hm[:fk]
1144
- elsif assoc_hm[:fk].exclude?(fk[1])
1145
- assoc_hm[:fk] << fk[1]
1319
+ assoc_hm[:fk] = [assoc_hm[:fk], fk[2]] unless fk[2] == assoc_hm[:fk]
1320
+ elsif assoc_hm[:fk].exclude?(fk[2])
1321
+ assoc_hm[:fk] << fk[2]
1146
1322
  end
1147
1323
  assoc_hm[:alternate_name] = "#{assoc_hm[:alternate_name]}_#{bt_assoc_name}" unless assoc_hm[:alternate_name] == bt_assoc_name
1148
1324
  assoc_hm[:inverse] = assoc_bt
1149
1325
  else
1150
- assoc_hm = hms[hm_cnstr_name] = { is_bt: false, fk: fk[1], assoc_name: fk[0], alternate_name: bt_assoc_name, inverse_table: fk[0], inverse: assoc_bt }
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 }
1151
1327
  assoc_hm[:polymorphic] = true if is_polymorphic
1152
1328
  hm_counts = relation.fetch(:hm_counts) { relation[:hm_counts] = {} }
1153
- hm_counts[fk[0]] = hm_counts.fetch(fk[0]) { 0 } + 1
1329
+ hm_counts[fk[1]] = hm_counts.fetch(fk[1]) { 0 } + 1
1154
1330
  end
1155
1331
  assoc_bt[:inverse] = assoc_hm
1156
1332
  end
@@ -55,7 +55,9 @@ module Brick
55
55
  unless (is_template_exists = _brick_template_exists?(*args, **options))
56
56
  # Need to return true if we can fill in the blanks for a missing one
57
57
  # args will be something like: ["index", ["categories"]]
58
- model = args[1].map(&:camelize).join('::').singularize.constantize
58
+ args[1] = args[1].each_with_object([]) { |a, s| s.concat(a.split('/')) }
59
+ args[1][args[1].length - 1] = args[1].last.singularize # Make sure the last item, defining the class name, is singular
60
+ model = args[1].map(&:camelize).join('::').constantize
59
61
  if is_template_exists = model && (
60
62
  ['index', 'show'].include?(args.first) || # Everything has index and show
61
63
  # Only CUD stuff has create / update / destroy
@@ -72,9 +74,9 @@ module Brick
72
74
  fk_name.zip(pk.map { |pk_part| "#{obj_name}.#{pk_part}" })
73
75
  else
74
76
  pk = pk.each_with_object([]) { |pk_part, s| s << "#{obj_name}.#{pk_part}" }
75
- [[fk_name, "#{pk.length == 1 ? pk.first : pk.inspect}"]]
77
+ [[fk_name, pk.length == 1 ? pk.first : pk.inspect]]
76
78
  end
77
- keys << [hm_assoc.inverse_of.foreign_type, "#{hm_assoc.active_record.name}"] if hm_assoc.options.key?(:as)
79
+ keys << [hm_assoc.inverse_of.foreign_type, hm_assoc.active_record.name] if hm_assoc.options.key?(:as)
78
80
  keys.map { |x| "#{x.first}: #{x.last}"}.join(', ')
79
81
  end
80
82
 
@@ -84,8 +86,9 @@ module Brick
84
86
 
85
87
  model_name = @_brick_model.name
86
88
  pk = @_brick_model._brick_primary_key(::Brick.relations.fetch(model_name, nil))
87
- obj_name = model_name.underscore
88
- table_name = model_name.pluralize.underscore
89
+ obj_name = model_name.split('::').last.underscore
90
+ path_obj_name = model_name.underscore.tr('/', '_')
91
+ table_name = obj_name.pluralize
89
92
  template_link = nil
90
93
  bts, hms, associatives = ::Brick.get_bts_and_hms(@_brick_model) # This gets BT and HM and also has_many :through (HMT)
91
94
  hms_columns = [] # Used for 'index'
@@ -108,21 +111,21 @@ module Brick
108
111
  "#{obj_name}.#{attrib_name} || 0"
109
112
  end
110
113
  "<%= ct = #{set_ct}
111
- link_to \"#\{ct || 'View'\} #{assoc_name}\", #{hm_assoc.klass.name.underscore.pluralize}_path({ #{path_keys(hm_assoc, hm_fk_name, obj_name, pk)} }) unless ct&.zero? %>\n"
114
+ link_to \"#\{ct || 'View'\} #{assoc_name}\", #{hm_assoc.klass.name.underscore.tr('/', '_').pluralize}_path({ #{path_keys(hm_assoc, hm_fk_name, obj_name, pk)} }) unless ct&.zero? %>\n"
112
115
  else # has_one
113
116
  "<%= obj = #{obj_name}.#{hm.first}; link_to(obj.brick_descrip, obj) if obj %>\n"
114
117
  end
115
118
  elsif args.first == 'show'
116
- hm_stuff << "<%= link_to '#{assoc_name}', #{hm_assoc.klass.name.underscore.pluralize}_path({ #{path_keys(hm_assoc, hm_fk_name, "@#{obj_name}", pk)} }) %>\n"
119
+ hm_stuff << "<%= link_to '#{assoc_name}', #{hm_assoc.klass.name.underscore.tr('/', '_').pluralize}_path({ #{path_keys(hm_assoc, hm_fk_name, "@#{obj_name}", pk)} }) %>\n"
117
120
  end
118
121
  s << hm_stuff
119
122
  end
120
123
 
121
- schema_options = ::Brick.db_schemas.each_with_object(+'') { |v, s| s << "<option value=\"#{v}\">#{v}</option>" }.html_safe
124
+ schema_options = ::Brick.db_schemas.keys.each_with_object(+'') { |v, s| s << "<option value=\"#{v}\">#{v}</option>" }.html_safe
122
125
  # %%% If we are not auto-creating controllers (or routes) then omit by default, and if enabled anyway, such as in a development
123
126
  # environment or whatever, then get either the controllers or routes list instead
124
- table_options = (::Brick.relations.keys - ::Brick.config.exclude_tables)
125
- .each_with_object(+'') { |v, s| s << "<option value=\"#{v.underscore.pluralize}\">#{v}</option>" }.html_safe
127
+ table_options = (::Brick.relations.keys - ::Brick.config.exclude_tables).sort
128
+ .each_with_object(+'') { |v, s| s << "<option value=\"#{v.underscore.gsub('.', '/').pluralize}\">#{v}</option>" }.html_safe
126
129
  css = +"<style>
127
130
  #dropper {
128
131
  background-color: #eee;
@@ -261,7 +264,8 @@ if (schemaSelect) {
261
264
 
262
265
  var tblSelect = document.getElementById(\"tbl\");
263
266
  if (tblSelect) {
264
- tblSelect.value = changeout(location.href);
267
+ tblSelect.value = changeout(location.href)[0];
268
+ if (tblSelect.selectedIndex < 0) tblSelect.value = changeout(location.href)[1];
265
269
  tblSelect.addEventListener(\"change\", function () {
266
270
  var lhr = changeout(location.href, null, this.value);
267
271
  if (brickSchema)
@@ -276,7 +280,8 @@ function changeout(href, param, value) {
276
280
  hrefParts = hrefParts[0].split(\"://\");
277
281
  var pathParts = hrefParts[hrefParts.length - 1].split(\"/\");
278
282
  if (value === undefined)
279
- return pathParts[1];
283
+ // A couple possibilities if it's namespaced, starting with two parts in the path -- and then try just one
284
+ return [pathParts.slice(1, 3).join('/'), pathParts.slice(1, 2)];
280
285
  else
281
286
  return hrefParts[0] + \"://\" + pathParts[0] + \"/\" + value;
282
287
  }
@@ -313,7 +318,7 @@ function changeout(href, param, value) {
313
318
  btnImport.style.display = droppedTSV.length > 0 ? \"block\" : \"none\";
314
319
  });
315
320
  btnImport.addEventListener(\"click\", function () {
316
- fetch(changeout(<%= #{obj_name}_path(-1, format: :csv).inspect.html_safe %>, \"_brick_schema\", brickSchema), {
321
+ fetch(changeout(<%= #{path_obj_name}_path(-1, format: :csv).inspect.html_safe %>, \"_brick_schema\", brickSchema), {
317
322
  method: 'PATCH',
318
323
  headers: { 'Content-Type': 'text/tab-separated-values' },
319
324
  body: droppedTSV
@@ -384,17 +389,32 @@ function changeout(href, param, value) {
384
389
  <script async defer src=\"https://apis.google.com/js/api.js\" onload=\"gapiLoaded()\"></script>
385
390
  "
386
391
  end
392
+ # %%% Instead of our current "for Janet Leverling (Employee)" kind of link we previously had this code that did a "where x = 123" thing:
393
+ # (where <%= @_brick_params.each_with_object([]) { |v, s| s << \"#\{v.first\} = #\{v.last.inspect\}\" }.join(', ') %>)
387
394
  "#{css}
388
395
  <p style=\"color: green\"><%= notice %></p>#{"
389
- <select id=\"schema\">#{schema_options}</select>" if ::Brick.db_schemas.length > 1}
396
+ <select id=\"schema\">#{schema_options}</select>" if ::Brick.config.schema_behavior[:multitenant] && ::Brick.db_schemas.length > 1}
390
397
  <select id=\"tbl\">#{table_options}</select>
391
- <h1>#{model_name.pluralize}</h1>#{template_link}
392
-
393
- <% if @_brick_params&.present? %><h3>where <%= @_brick_params.each_with_object([]) { |v, s| s << \"#\{v.first\} = #\{v.last.inspect\}\" }.join(', ') %></h3><% end %>
398
+ <h1>#{model_plural = model_name.pluralize}</h1>#{template_link}
399
+
400
+ <% if @_brick_params&.present? %>
401
+ <% if @_brick_params.length == 1 # %%% Does not yet work with composite keys
402
+ k, id = @_brick_params.first
403
+ id = id.first if id.is_a?(Array) && id.length == 1
404
+ origin = (key_parts = k.split('.')).length == 1 ? #{model_name} : #{model_name}.reflect_on_association(key_parts.first).klass
405
+ # binding.pry
406
+ 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)) %>
408
+ <h3>for <%= link_to \"#{"#\{obj.brick_descrip\} (#\{destination.name\})\""}, send(\"#\{destination.name.underscore.tr('/', '_')\}_path\".to_sym, id) %></h3><%
409
+ end
410
+ end %>
411
+ (<%= link_to 'See all #{model_plural.split('::').last}', #{path_obj_name.pluralize}_path %>)
412
+ <% end %>
394
413
  <table id=\"#{table_name}\">
395
414
  <thead><tr>#{'<th></th>' if pk.present?}
396
415
  <% @#{table_name}.columns.map(&:name).each do |col| %>
397
- <% next if #{(pk || []).inspect}.include?(col) || ::Brick.config.metadata_columns.include?(col) || poly_cols.include?(col) %>
416
+ <% next if (#{(pk || []).inspect}.include?(col) && #{model_name}.column_for_attribute(col).type == :integer && !bts.key?(col)) ||
417
+ ::Brick.config.metadata_columns.include?(col) || poly_cols.include?(col) %>
398
418
  <th>
399
419
  <% if (bt = bts[col]) %>
400
420
  BT <%
@@ -407,15 +427,16 @@ function changeout(href, param, value) {
407
427
  </th>
408
428
  <% end %>
409
429
  <%# Consider getting the name from the association -- h.first.name -- if a more \"friendly\" alias should be used for a screwy table name %>
410
- #{hms_headers.map { |h| "<th>#{h[1]} <%= link_to('#{h[2]}', #{h.first.klass.name.underscore.pluralize}_path) %></th>\n" }.join}
430
+ #{hms_headers.map { |h| "<th>#{h[1]} <%= link_to('#{h[2]}', #{h.first.klass.name.underscore.tr('/', '_').pluralize}_path) %></th>\n" }.join}
411
431
  </tr></thead>
412
432
 
413
433
  <tbody>
414
434
  <% @#{table_name}.each do |#{obj_name}| %>
415
435
  <tr>#{"
416
- <td><%= link_to '⇛', #{obj_name}_path(#{obj_pk}), { class: 'big-arrow' } %></td>" if obj_pk}
436
+ <td><%= link_to '⇛', #{path_obj_name}_path(#{obj_pk}), { class: 'big-arrow' } %></td>" if obj_pk}
417
437
  <% #{obj_name}.attributes.each do |k, val| %>
418
- <% next if #{(obj_pk || []).inspect}.include?(k) || ::Brick.config.metadata_columns.include?(k) || poly_cols.include?(k) || k.start_with?('_brfk_') || (k.start_with?('_br_') && (k.length == 63 || k.end_with?('_ct'))) %>
438
+ <% next if (#{(obj_pk || []).inspect}.include?(k) && #{model_name}.column_for_attribute(k).type == :integer && !bts.key?(k)) ||
439
+ ::Brick.config.metadata_columns.include?(k) || poly_cols.include?(k) || k.start_with?('_brfk_') || (k.start_with?('_br_') && (k.length == 63 || k.end_with?('_ct'))) %>
419
440
  <td>
420
441
  <% if (bt = bts[k]) %>
421
442
  <%# binding.pry # Postgres column names are limited to 63 characters %>
@@ -430,7 +451,7 @@ function changeout(href, param, value) {
430
451
  #{obj_name}, (descrips = @_brick_bt_descrip[bt.first][bt_class])[0..-2].map { |z| #{obj_name}.send(z.last[0..62]) }, (bt_id_col = descrips.last)
431
452
  )
432
453
  bt_id = #{obj_name}.send(*bt_id_col) if bt_id_col&.present? %>
433
- <%= bt_id ? link_to(bt_txt, send(\"#\{bt_class.base_class.name.underscore\}_path\".to_sym, bt_id)) : bt_txt %>
454
+ <%= bt_id ? link_to(bt_txt, send(\"#\{bt_class.base_class.name.underscore.tr('/', '_')\}_path\".to_sym, bt_id)) : bt_txt %>
434
455
  <%#= Previously was: bt_obj = bt[1].first.first.find_by(bt[2] => val); link_to(bt_obj.brick_descrip, send(\"#\{bt[1].first.first.name.underscore\}_path\".to_sym, bt_obj.send(bt[1].first.first.primary_key.to_sym))) if bt_obj %>
435
456
  <% end %>
436
457
  <% else %>
@@ -440,19 +461,19 @@ function changeout(href, param, value) {
440
461
  <% end %>
441
462
  #{hms_columns.each_with_object(+'') { |hm_col, s| s << "<td>#{hm_col}</td>" }}
442
463
  </tr>
443
- </tbody>
444
464
  <% end %>
465
+ </tbody>
445
466
  </table>
446
467
 
447
- #{"<hr><%= link_to \"New #{obj_name}\", new_#{obj_name}_path %>" unless @_brick_model.is_view?}
468
+ #{"<hr><%= link_to \"New #{obj_name}\", new_#{path_obj_name}_path %>" unless @_brick_model.is_view?}
448
469
  #{script}"
449
470
  when 'show', 'update'
450
471
  "#{css}
451
472
  <p style=\"color: green\"><%= notice %></p>#{"
452
- <select id=\"schema\">#{schema_options}</select>" if ::Brick.db_schemas.length > 1}
473
+ <select id=\"schema\">#{schema_options}</select>" if ::Brick.config.schema_behavior[:multitenant] && ::Brick.db_schemas.length > 1}
453
474
  <select id=\"tbl\">#{table_options}</select>
454
475
  <h1>#{model_name}: <%= (obj = @#{obj_name})&.brick_descrip || controller_name %></h1>
455
- <%= link_to '(See all #{obj_name.pluralize})', #{table_name}_path %>
476
+ <%= link_to '(See all #{obj_name.pluralize})', #{path_obj_name.pluralize}_path %>
456
477
  <% if obj %>
457
478
  <%= # path_options = [obj.#{pk}]
458
479
  # path_options << { '_brick_schema': } if
@@ -462,7 +483,8 @@ function changeout(href, param, value) {
462
483
  <% has_fields = false
463
484
  @#{obj_name}.attributes.each do |k, val| %>
464
485
  <tr>
465
- <% next if #{(pk || []).inspect}.include?(k) || ::Brick.config.metadata_columns.include?(k) %>
486
+ <% next if (#{(pk || []).inspect}.include?(k) && !bts.key?(k)) ||
487
+ ::Brick.config.metadata_columns.include?(k) %>
466
488
  <th class=\"show-field\">
467
489
  <% has_fields = true
468
490
  if (bt = bts[k])
@@ -504,7 +526,7 @@ function changeout(href, param, value) {
504
526
  html_options = { prompt: \"Select #\{bt_name\}\" }
505
527
  html_options[:class] = 'dimmed' unless val %>
506
528
  <%= f.select k.to_sym, bt[3], { value: val || '^^^brick_NULL^^^' }, html_options %>
507
- <%= bt_obj = bt_class&.find_by(bt_pair[1] => val); link_to('⇛', send(\"#\{bt_class.base_class.name.underscore\}_path\".to_sym, bt_obj.send(bt_class.primary_key.to_sym)), { class: 'show-arrow' }) if bt_obj %>
529
+ <%= bt_obj = bt_class&.find_by(bt_pair[1] => val); link_to('⇛', send(\"#\{bt_class.base_class.name.underscore.tr('/', '_')\}_path\".to_sym, bt_obj.send(bt_class.primary_key.to_sym)), { class: 'show-arrow' }) if bt_obj %>
508
530
  <% else case #{model_name}.column_for_attribute(k).type
509
531
  when :string, :text %>
510
532
  <% if is_bcrypt?(val) # || .readonly? %>
@@ -546,7 +568,7 @@ function changeout(href, param, value) {
546
568
  <tr><td>(none)</td></tr>
547
569
  <% else %>
548
570
  <% collection.uniq.each do |#{hm_singular_name}| %>
549
- <tr><td><%= link_to(#{hm_singular_name}.brick_descrip, #{hm.first.klass.name.underscore}_path([#{obj_pk}])) %></td></tr>
571
+ <tr><td><%= link_to(#{hm_singular_name}.brick_descrip, #{hm.first.klass.name.underscore.tr('/', '_')}_path([#{obj_pk}])) %></td></tr>
550
572
  <% end %>
551
573
  <% end %>
552
574
  </table>"
@@ -5,7 +5,7 @@ module Brick
5
5
  module VERSION
6
6
  MAJOR = 1
7
7
  MINOR = 0
8
- TINY = 29
8
+ TINY = 30
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
@@ -90,10 +90,10 @@ module Brick
90
90
  end
91
91
 
92
92
  class << self
93
- attr_accessor :db_schemas
93
+ attr_accessor :default_schema, :db_schemas
94
94
 
95
95
  def set_db_schema(params)
96
- schema = params['_brick_schema'] || 'public'
96
+ schema = params['_brick_schema']
97
97
  ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?;", schema) if schema && ::Brick.db_schemas&.include?(schema)
98
98
  end
99
99
 
@@ -287,8 +287,16 @@ module Brick
287
287
  # Database schema to use when analysing existing data, such as deriving a list of polymorphic classes
288
288
  # for polymorphics in which it wasn't originally specified.
289
289
  # @api public
290
- def schema_to_analyse=(schema)
291
- Brick.config.schema_to_analyse = schema
290
+ def schema_behavior=(behavior)
291
+ Brick.config.schema_behavior = (behavior.is_a?(Symbol) ? { behavior => nil } : behavior)
292
+ end
293
+ # For any Brits out there
294
+ def schema_behaviour=(behavior)
295
+ Brick.schema_behavior = behavior
296
+ end
297
+
298
+ def sti_type_column=(type_col)
299
+ Brick.config.sti_type_column = (type_col.is_a?(String) ? { type_col => nil } : type_col)
292
300
  end
293
301
 
294
302
  def default_route_fallback=(resource_name)
@@ -303,18 +311,23 @@ module Brick
303
311
 
304
312
  relations = ::Brick.relations
305
313
  if (ars = ::Brick.config.additional_references) || ::Brick.config.polymorphics
306
- ars.each { |fk| ::Brick._add_bt_and_hm(fk[0..2], relations) } if ars
314
+ if ars
315
+ ars.each do |ar|
316
+ 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)
318
+ end
319
+ end
307
320
  if (polys = ::Brick.config.polymorphics)
308
- if (schema = ::Brick.config.schema_to_analyse) && ::Brick.db_schemas&.include?(schema)
321
+ if (schema = ::Brick.config.schema_behavior[:multitenant]&.fetch(:schema_to_analyse, nil)) && ::Brick.db_schemas&.include?(schema)
309
322
  ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?;", schema)
310
323
  end
311
324
  missing_stis = {}
312
325
  polys.each do |k, v|
313
326
  table_name, poly = k.split('.')
314
- v ||= ActiveRecord::Base.execute_sql("SELECT DISTINCT #{poly}_type AS typ FROM #{table_name}").map { |result| result['typ'] }
327
+ 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'] }
315
328
  v.each do |type|
316
329
  if relations.key?(primary_table = type.underscore.pluralize)
317
- ::Brick._add_bt_and_hm([table_name, poly, primary_table, "(brick) #{table_name}_#{poly}"], relations, true)
330
+ ::Brick._add_bt_and_hm([nil, table_name, poly, nil, primary_table, "(brick) #{table_name}_#{poly}"], relations, true)
318
331
  else
319
332
  missing_stis[primary_table] = type unless ::Brick.existing_stis.key?(type)
320
333
  end
@@ -390,11 +403,20 @@ In config/initializers/brick.rb appropriate entries would look something like:
390
403
  end
391
404
  # %%% TODO: If no auto-controllers then enumerate the controllers folder in order to build matching routes
392
405
  # If auto-controllers and auto-models are both enabled then this makes sense:
393
- ::Brick.relations.each do |k, v|
394
- unless existing_controllers.key?(controller_name = k.underscore.pluralize)
406
+ ::Brick.relations.each do |rel_name, v|
407
+ rel_name = rel_name.split('.').map(&:underscore)
408
+ schema_names = rel_name[0..-2]
409
+ k = rel_name.last
410
+ unless existing_controllers.key?(controller_name = k.pluralize)
395
411
  options = {}
396
412
  options[:only] = [:index, :show] if v.key?(:isView)
397
- send(:resources, controller_name.to_sym, **options)
413
+ if schema_names.present? # && !Object.const_defined('Apartment')
414
+ send(:namespace, schema_names.first) do
415
+ send(:resources, controller_name.to_sym, **options)
416
+ end
417
+ else
418
+ send(:resources, controller_name.to_sym, **options)
419
+ end
398
420
  end
399
421
  end
400
422
  end
@@ -215,9 +215,15 @@ module Brick
215
215
  # Brick.sti_namespace_prefixes = { '::Animals::' => 'Animal',
216
216
  # '::Snake' => 'Reptile' }
217
217
 
218
+ # # Custom inheritance_column to be used for STI. This is by default \"type\", and applies to all models. With this
219
+ # # option you can change this either for specific models, or apply a new overall name generally:
220
+ # Brick.sti_type_column = 'sti_type'
221
+ # Brick.sti_type_column = { 'rails_type' => ['sales.specialoffer'] }
222
+
218
223
  # # Database schema to use when analysing existing data, such as deriving a list of polymorphic classes in the case that
219
224
  # # it wasn't originally specified.
220
- # Brick.schema_to_analyse = 'engineering'
225
+ # Brick.schema_behavior = :namespaced
226
+ # Brick.schema_behavior = { multitenant: { schema_to_analyse: 'engineering' } }
221
227
 
222
228
  # # Polymorphic associations are set up by providing a model name and polymorphic association name#{poly}
223
229
 
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.29
4
+ version: 1.0.30
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-24 00:00:00.000000000 Z
11
+ date: 2022-05-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord