brick 1.0.28 → 1.0.31

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: ab7e6a8198638a0e6d3cde289db17d107e699fad2b4b5a200703cb1982d19569
4
- data.tar.gz: 708360bb9463e45c8f7fece60dc762dc65b2b3893d8044ffce9ee87e33f63b80
3
+ metadata.gz: f529097f82af103af54ae472e226244ce8932b64cf974b836d1d23be6cedfbce
4
+ data.tar.gz: 1561bea9cdcdd366d2ef9798cff36f3a74f58768f1f14be5c2bfbf4f40feac8d
5
5
  SHA512:
6
- metadata.gz: 92229661eff9aac30ec7fad96603bc649a122131956e8632562e22d11c8bae67db0272ec5453ab2cc233bd11f7ab72222c3520f2acceecb83e35d4c54cbd8f4d
7
- data.tar.gz: ddc169b2effaf58e60861d0153f191f6f1e2f11d459363a000da2c1f44c1583c8cd037f78269c238a84de58e04d7633c7a7ecbf046c08ef48fdaf5fb01de76a3
6
+ metadata.gz: a5d812af4ba7eadae06e28bb9686ea60b79f9df721a51cbddbb0ce87d6abfe78e0675b45446768bd2460f00c1f02bba5276d21d9ec96345dcffa8079d87f2ec0
7
+ data.tar.gz: b7ff59c6229dac732eb06a01c59d4a2913675b02a0a8a54106c2dfdcf965ea58cff5dc6d8dfb1c54fb8d9569fa31f2d8217ed281593bdbc167e89d42178225dd
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
@@ -72,7 +72,11 @@ module ActiveRecord
72
72
 
73
73
  pk = primary_key.is_a?(String) ? [primary_key] : primary_key || []
74
74
  # Just return [] if we're missing any part of the primary key. (PK is usually just "id")
75
- @_brick_primary_key ||= pk.any? { |pk_part| !relation[:cols].key?(pk_part) } ? [] : pk
75
+ if relation && pk.present?
76
+ @_brick_primary_key ||= pk.any? { |pk_part| !relation[:cols].key?(pk_part) } ? [] : pk
77
+ else # No definitive key yet, so return what we can without setting the instance variable
78
+ pk
79
+ end
76
80
  end
77
81
 
78
82
  # Used to show a little prettier name for an object
@@ -192,7 +196,7 @@ module ActiveRecord
192
196
  def self.bt_link(assoc_name)
193
197
  model_underscore = name.underscore
194
198
  assoc_name = CGI.escapeHTML(assoc_name.to_s)
195
- 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)
196
200
  link = Class.new.extend(ActionView::Helpers::UrlHelper).link_to(name, model_path)
197
201
  model_underscore == assoc_name ? link : "#{assoc_name}-#{link}".html_safe
198
202
  end
@@ -302,8 +306,12 @@ module ActiveRecord
302
306
 
303
307
  # %%% Skip the metadata columns
304
308
  if selects&.empty? # Default to all columns
309
+ tbl_no_schema = table.name.split('.').last
305
310
  columns.each do |col|
306
- 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}"
307
315
  end
308
316
  end
309
317
 
@@ -345,7 +353,7 @@ module ActiveRecord
345
353
  end
346
354
 
347
355
  if join_array.present?
348
- left_outer_joins!(join_array) # joins!(join_array)
356
+ left_outer_joins!(join_array)
349
357
  # Without working from a duplicate, touching the AREL ast tree sets the @arel instance variable, which causes the relation to be immutable.
350
358
  (rel_dupe = dup)._arel_alias_names
351
359
  core_selects = selects.dup
@@ -356,12 +364,12 @@ module ActiveRecord
356
364
  v.last.each do |k1, v1| # k1 is class, v1 is array of columns to snag
357
365
  next if chains[k1].nil?
358
366
 
359
- 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
360
368
  field_tbl_name = nil
361
369
  v1.map { |x| [translations[x[0..-2].map(&:to_s).join('.')], x.last] }.each_with_index do |sel_col, idx|
362
- 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
363
371
 
364
- selects << "#{"#{field_tbl_name}.#{sel_col.last}"} AS \"#{(col_alias = "_brfk_#{v.first}__#{sel_col.last}")}\""
372
+ selects << "#{"\"#{field_tbl_name}\".\"#{sel_col.last}\""} AS \"#{(col_alias = "_brfk_#{v.first}__#{sel_col.last}")}\""
365
373
  v1[idx] << col_alias
366
374
  end
367
375
 
@@ -369,7 +377,7 @@ module ActiveRecord
369
377
  # Accommodate composite primary key by allowing id_col to come in as an array
370
378
  ((id_col = k1.primary_key).is_a?(Array) ? id_col : [id_col]).each do |id_part|
371
379
  id_for_tables[v.first] << if id_part
372
- selects << "#{"#{tbl_name}.#{id_part}"} AS \"#{(id_alias = "_brfk_#{v.first}__#{id_part}")}\""
380
+ selects << "#{"\"#{tbl_name}\".\"#{id_part}\""} AS \"#{(id_alias = "_brfk_#{v.first}__#{id_part}")}\""
373
381
  id_alias
374
382
  end
375
383
  end
@@ -416,6 +424,7 @@ JOIN (SELECT #{selects.join(', ')}, COUNT(#{count_column}) AS _ct_ FROM #{associ
416
424
  joins!("#{join_clause} ON #{on_clause.join(' AND ')}")
417
425
  end
418
426
  where!(wheres) unless wheres.empty?
427
+ limit(1000) # Don't want to get too carried away just yet
419
428
  wheres unless wheres.empty? # Return the specific parameters that we did use
420
429
  end
421
430
 
@@ -480,7 +489,7 @@ if ActiveSupport::Dependencies.respond_to?(:autoload_module!) # %%% Only works w
480
489
  ::Brick.sti_models[qualified_name] = { base: base_class }
481
490
  # Build subclass and place it into the specially STI-namespaced module
482
491
  into.const_set(const_name.to_sym, klass = Class.new(base_class))
483
- # %%% used to also have: autoload_once_paths.include?(base_path) ||
492
+ # %%% used to also have: autoload_once_paths.include?(base_path) ||
484
493
  autoloaded_constants << qualified_name unless autoloaded_constants.include?(qualified_name)
485
494
  klass
486
495
  elsif (base_class = ::Brick.config.sti_namespace_prefixes&.fetch("::#{const_name}", nil)&.constantize)
@@ -494,75 +503,209 @@ if ActiveSupport::Dependencies.respond_to?(:autoload_module!) # %%% Only works w
494
503
  end
495
504
  end
496
505
 
497
- class Object
498
- class << self
499
- alias _brick_const_missing const_missing
500
- def const_missing(*args)
501
- return self.const_get(args.first) if self.const_defined?(args.first)
502
- return Object.const_get(args.first) if Object.const_defined?(args.first) unless self == Object
503
-
504
- class_name = args.first.to_s
505
- # See if a file is there in the same way that ActiveSupport::Dependencies#load_missing_constant
506
- # checks for it in ~/.rvm/gems/ruby-2.7.5/gems/activesupport-5.2.6.2/lib/active_support/dependencies.rb
507
- # that is, checking #qualified_name_for with: from_mod, const_name
508
- # If we want to support namespacing in the future, might have to utilise something like this:
509
- # path_suffix = ActiveSupport::Dependencies.qualified_name_for(Object, args.first).underscore
510
- # return self._brick_const_missing(*args) if ActiveSupport::Dependencies.search_for_file(path_suffix)
511
- # If the file really exists, go and snag it:
512
- if !(is_found = ActiveSupport::Dependencies.search_for_file(class_name.underscore)) && (filepath = (self.name || class_name)&.split('::'))
513
- filepath = (filepath[0..-2] + [class_name]).join('/').underscore + '.rb'
514
- end
515
- if is_found
516
- return self._brick_const_missing(*args)
517
- elsif ActiveSupport::Dependencies.search_for_file(filepath) # Last-ditch effort to pick this thing up before we fill in the gaps on our own
518
- my_const = parent.const_missing(class_name) # ends up having: MyModule::MyClass
519
- return my_const
520
- 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
530
+
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)
543
+ end
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
521
577
 
522
- relations = ::Brick.instance_variable_get(:@relations)[ActiveRecord::Base.connection_pool.object_id] || {}
523
- result = if ::Brick.enable_controllers? && class_name.end_with?('Controller') && (plural_class_name = class_name[0..-11]).length.positive?
524
- # Otherwise now it's up to us to fill in the gaps
525
- if (model = plural_class_name.singularize.constantize)
526
- # 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.
527
- build_controller(class_name, plural_class_name, model, relations)
528
- end
529
- elsif ::Brick.enable_models?
530
- # See if a file is there in the same way that ActiveSupport::Dependencies#load_missing_constant
531
- # checks for it in ~/.rvm/gems/ruby-2.7.5/gems/activesupport-5.2.6.2/lib/active_support/dependencies.rb
532
- plural_class_name = ActiveSupport::Inflector.pluralize(model_name = class_name)
533
- singular_table_name = ActiveSupport::Inflector.underscore(model_name)
534
-
535
- # Adjust for STI if we know of a base model for the requested model name
536
- table_name = if (base_model = ::Brick.sti_models[model_name]&.fetch(:base, nil) || ::Brick.existing_stis[model_name]&.constantize)
537
- base_model.table_name
538
- else
539
- ActiveSupport::Inflector.pluralize(singular_table_name)
540
- end
541
-
542
- # Maybe, just maybe there's a database table that will satisfy this need
543
- if (matching = [table_name, singular_table_name, plural_class_name, model_name].find { |m| relations.key?(m) })
544
- build_model(model_name, singular_table_name, table_name, relations, matching)
545
- end
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)
546
581
  end
547
- if result
548
- built_class, code = result
549
- puts "\n#{code}"
550
- built_class
551
- elsif ::Brick.config.sti_namespace_prefixes&.key?("::#{class_name}")
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
552
588
  # module_prefixes = type_name.split('::')
553
589
  # path = self.name.split('::')[0..-2] + []
554
590
  # module_prefixes.unshift('') unless module_prefixes.first.blank?
555
591
  # candidate_file = Rails.root.join('app/models' + module_prefixes.map(&:underscore).join('/') + '.rb')
556
- self._brick_const_missing(*args)
557
- else
558
- puts "MISSING! #{self.name} #{args.inspect} #{table_name}"
559
- self._brick_const_missing(*args)
560
- 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)
561
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
562
697
 
563
698
  private
564
699
 
565
- 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
+
566
709
  return if ((is_view = (relation = relations[matching]).key?(:isView)) && ::Brick.config.skip_database_views) ||
567
710
  ::Brick.config.exclude_tables.include?(matching)
568
711
 
@@ -574,14 +717,15 @@ class Object
574
717
  return
575
718
  end
576
719
 
577
- 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)
578
721
  is_sti = true
579
722
  else
580
723
  base_model = ::Brick.config.models_inherit_from || ActiveRecord::Base
581
724
  end
582
- code = +"class #{model_name} < #{base_model.name}\n"
725
+ hmts = nil
726
+ code = +"class #{full_name} < #{base_model.name}\n"
583
727
  built_model = Class.new(base_model) do |new_model_class|
584
- Object.const_set(model_name.to_sym, new_model_class)
728
+ (schema_module || Object).const_set(model_name.to_sym, new_model_class)
585
729
  # Accommodate singular or camel-cased table names such as "order_detail" or "OrderDetails"
586
730
  code << " self.table_name = '#{self.table_name = matching}'\n" unless table_name == matching
587
731
 
@@ -610,9 +754,14 @@ class Object
610
754
  new_model_class.primary_key = (pk_sym = our_pks.first.to_sym)
611
755
  code << " self.primary_key = #{pk_sym.inspect}\n"
612
756
  end
757
+ _brick_primary_key(relation) # Set the newly-found PK in the instance variable
613
758
  else
614
759
  code << " # Could not identify any column(s) to use as a primary key\n" unless is_view
615
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
616
765
 
617
766
  unless is_sti
618
767
  fks = relation[:fks] || {}
@@ -630,7 +779,21 @@ class Object
630
779
  build_bt_or_hm(relations, model_name, relation, hmts, assoc, inverse_assoc_name, invs, code) unless invs.is_a?(Array)
631
780
  hmts
632
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
633
795
  hmts.each do |hmt_fk, fks|
796
+ hmt_fk = hmt_fk.tr('.', '_')
634
797
  fks.each do |fk|
635
798
  through = fk.first[:assoc_name]
636
799
  hmt_name = if fks.length > 1
@@ -651,18 +814,8 @@ class Object
651
814
  self.send(:has_many, hmt_name.to_sym, **options)
652
815
  end
653
816
  end
654
- # # Not NULLables
655
- # # %%% For the minute we've had to pull this out because it's been troublesome implementing the NotNull validator
656
- # relation[:cols].each do |col, datatype|
657
- # if (datatype[3] && _brick_primary_key.exclude?(col) && ::Brick.config.metadata_columns.exclude?(col)) ||
658
- # ::Brick.config.not_nullables.include?("#{matching}.#{col}")
659
- # code << " validates :#{col}, not_null: true\n"
660
- # self.send(:validates, col.to_sym, { not_null: true })
661
- # end
662
- # end
663
817
  end
664
- code << "end # model #{model_name}\n\n"
665
- end # class definition
818
+ code << "end # model #{full_name}\n\n"
666
819
  [built_model, code]
667
820
  end
668
821
 
@@ -673,7 +826,7 @@ class Object
673
826
  # Try to take care of screwy names if this is a belongs_to going to an STI subclass
674
827
  assoc_name = if (primary_class = assoc.fetch(:primary_class, nil)) &&
675
828
  sti_inverse_assoc = primary_class.reflect_on_all_associations.find do |a|
676
- 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
677
830
  end
678
831
  sti_inverse_assoc.options[:inverse_of]&.to_s || assoc_name
679
832
  else
@@ -705,7 +858,7 @@ class Object
705
858
  if assoc.key?(:polymorphic)
706
859
  options[:as] = assoc[:fk].to_sym
707
860
  else
708
- 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]
709
862
  end
710
863
  # fks[table_name].find { |other_assoc| other_assoc.object_id != assoc.object_id && other_assoc[:assoc_name] == assoc[assoc_name] }
711
864
  if (has_ones = ::Brick.config.has_ones&.fetch(model_name, nil))&.key?(singular_assoc_name = ActiveSupport::Inflector.singularize(assoc_name))
@@ -722,7 +875,7 @@ class Object
722
875
  end
723
876
  # Figure out if we need to specially call out the class_name and/or foreign key
724
877
  # (and if either of those then definitely also a specific inverse_of)
725
- 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
726
879
  # Work around a bug in CPK where self-referencing belongs_to associations double up their foreign keys
727
880
  if need_fk # Funky foreign key?
728
881
  options[:foreign_key] = if assoc[:fk].is_a?(Array)
@@ -732,10 +885,11 @@ class Object
732
885
  assoc[:fk].to_sym
733
886
  end
734
887
  end
735
- 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)
736
889
 
737
890
  # Prepare a list of entries for "has_many :through"
738
891
  if macro == :has_many
892
+ puts [inverse_table, relations[inverse_table].length].inspect
739
893
  relations[inverse_table][:hmt_fks].each do |k, hmt_fk|
740
894
  next if k == assoc[:fk]
741
895
 
@@ -743,19 +897,20 @@ class Object
743
897
  end
744
898
  end
745
899
  # And finally create a has_one, has_many, or belongs_to for this association
746
- assoc_name = assoc_name.to_sym
900
+ assoc_name = assoc_name.tr('.', '_').to_sym
747
901
  code << " #{macro} #{assoc_name.inspect}#{options.map { |k, v| ", #{k}: #{v.inspect}" }.join}\n"
748
902
  self.send(macro, assoc_name, **options)
749
903
  end
750
904
 
751
- def build_controller(class_name, plural_class_name, model, relations)
905
+ def build_controller(namespace, class_name, plural_class_name, model, relations)
752
906
  table_name = ActiveSupport::Inflector.underscore(plural_class_name)
753
907
  singular_table_name = ActiveSupport::Inflector.singularize(table_name)
754
- pk = model._brick_primary_key(relations[table_name])
908
+ pk = model._brick_primary_key(relations.fetch(table_name, nil))
755
909
 
756
- code = +"class #{class_name} < ApplicationController\n"
910
+ namespace_name = "#{namespace.name}::" if namespace
911
+ code = +"class #{namespace_name}#{class_name} < ApplicationController\n"
757
912
  built_controller = Class.new(ActionController::Base) do |new_controller_class|
758
- Object.const_set(class_name.to_sym, new_controller_class)
913
+ (namespace || Object).const_set(class_name.to_sym, new_controller_class)
759
914
 
760
915
  code << " def index\n"
761
916
  code << " @#{table_name} = #{model.name}#{pk&.present? ? ".order(#{pk.inspect})" : '.all'}\n"
@@ -782,9 +937,10 @@ class Object
782
937
  # %%% Add custom HM count columns
783
938
  # %%% What happens when the PK is composite?
784
939
  counts = hm_counts.each_with_object([]) { |v, s| s << "_br_#{v.first}._ct_ AS _br_#{v.first}_ct" }
785
- # *selects,
786
940
  instance_variable_set("@#{table_name}".to_sym, ar_relation.dup._select!(*selects, *counts))
787
- # 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
788
944
  @_brick_bt_descrip = bt_descrip
789
945
  @_brick_hm_counts = hm_counts
790
946
  @_brick_join_array = join_array
@@ -809,9 +965,9 @@ class Object
809
965
  code << " # (Define :new, :create)\n"
810
966
 
811
967
  if model.primary_key
812
- if (schema = ::Brick.config.schema_to_analyse) && ::Brick.db_schemas&.include?(schema)
813
- ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?;", schema)
814
- 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
815
971
 
816
972
  is_need_params = true
817
973
  # code << " # (Define :edit, and :destroy)\n"
@@ -822,7 +978,6 @@ class Object
822
978
  code << " end\n"
823
979
  self.define_method :update do
824
980
  ::Brick.set_db_schema(params)
825
-
826
981
  if request.format == :csv # Importing CSV?
827
982
  require 'csv'
828
983
  # See if internally it's likely a TSV file (tab-separated)
@@ -846,7 +1001,7 @@ class Object
846
1001
 
847
1002
  if is_need_params
848
1003
  code << "private\n"
849
- code << " def params\n"
1004
+ code << " def #{params_name}\n"
850
1005
  code << " params.require(:#{singular_table_name}).permit(#{model.columns_hash.keys.map { |c| c.to_sym.inspect }.join(', ')})\n"
851
1006
  code << " end\n"
852
1007
  self.define_method(params_name) do
@@ -856,7 +1011,7 @@ class Object
856
1011
  # Get column names for params from relations[model.table_name][:cols].keys
857
1012
  end
858
1013
  end
859
- code << "end # #{class_name}\n\n"
1014
+ code << "end # #{namespace_name}#{class_name}\n\n"
860
1015
  end # class definition
861
1016
  [built_controller, code]
862
1017
  end
@@ -866,7 +1021,8 @@ class Object
866
1021
  plural = ActiveSupport::Inflector.pluralize(hm_assoc[:alternate_name])
867
1022
  [hm_assoc[:alternate_name] == name.underscore ? "#{hm_assoc[:assoc_name].singularize}_#{plural}" : plural, true]
868
1023
  else
869
- [ActiveSupport::Inflector.pluralize(hm_assoc[:inverse_table]), nil]
1024
+ assoc_name = hm_assoc[:inverse_table].pluralize
1025
+ [assoc_name, assoc_name.include?('.')]
870
1026
  end
871
1027
  end
872
1028
  end
@@ -886,16 +1042,22 @@ module ActiveRecord::ConnectionHandling
886
1042
 
887
1043
  def _brick_reflect_tables
888
1044
  if (relations = ::Brick.relations).empty?
889
- # Only for Postgres? (Doesn't work in sqlite3)
890
- # puts ActiveRecord::Base.execute_sql("SELECT current_setting('SEARCH_PATH')").to_a.inspect
1045
+ # Hopefully our initializer is named exactly this!
1046
+ if File.exist?(brick_initializer = Rails.root.join('config/initializers/brick.rb'))
1047
+ load brick_initializer
1048
+ end
1049
+ # Only for Postgres? (Doesn't work in sqlite3)
1050
+ # puts ActiveRecord::Base.execute_sql("SELECT current_setting('SEARCH_PATH')").to_a.inspect
891
1051
 
892
- schema_sql = 'SELECT NULL AS table_schema;'
893
- case ActiveRecord::Base.connection.adapter_name
1052
+ schema_sql = 'SELECT NULL AS table_schema;'
1053
+ case ActiveRecord::Base.connection.adapter_name
894
1054
  when 'PostgreSQL'
895
- schema = 'public' # Too early at this point to be able to pick up: Brick.config.schema_to_analyse
1055
+ if (::Brick.default_schema = schema = ::Brick.config.schema_behavior&.[](:multitenant)&.[](:schema_to_analyse))
1056
+ ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?", schema)
1057
+ end
896
1058
  schema_sql = 'SELECT DISTINCT table_schema FROM INFORMATION_SCHEMA.tables;'
897
1059
  when 'Mysql2'
898
- schema = ActiveRecord::Base.connection.current_database
1060
+ ::Brick.default_schema = schema = ActiveRecord::Base.connection.current_database
899
1061
  when 'SQLite'
900
1062
  sql = "SELECT m.name AS relation_name, UPPER(m.type) AS table_type,
901
1063
  p.name AS column_name, p.type AS data_type,
@@ -908,8 +1070,7 @@ module ActiveRecord::ConnectionHandling
908
1070
  puts "Unfamiliar with connection adapter #{ActiveRecord::Base.connection.adapter_name}"
909
1071
  end
910
1072
 
911
- sql ||= ActiveRecord::Base.send(:sanitize_sql_array, [
912
- "SELECT t.table_name AS relation_name, t.table_type,
1073
+ sql ||= "SELECT t.table_schema AS schema, t.table_name AS relation_name, t.table_type,
913
1074
  c.column_name, c.data_type,
914
1075
  COALESCE(c.character_maximum_length, c.numeric_precision) AS max_length,
915
1076
  tc.constraint_type AS const, kcu.constraint_name AS \"key\",
@@ -927,18 +1088,23 @@ module ActiveRecord::ConnectionHandling
927
1088
  ON kcu.CONSTRAINT_SCHEMA = tc.CONSTRAINT_SCHEMA
928
1089
  AND kcu.TABLE_NAME = tc.TABLE_NAME
929
1090
  AND kcu.CONSTRAINT_NAME = tc.constraint_name
930
- WHERE t.table_schema = ? -- COALESCE(current_setting('SEARCH_PATH'), 'public')
1091
+ WHERE t.table_schema NOT IN ('information_schema', 'pg_catalog')#{"
1092
+ AND t.table_schema = COALESCE(current_setting('SEARCH_PATH'), 'public')" if schema }
931
1093
  -- AND t.table_type IN ('VIEW') -- 'BASE TABLE', 'FOREIGN TABLE'
932
1094
  AND t.table_name NOT IN ('pg_stat_statements', 'ar_internal_metadata', 'schema_migrations')
933
- ORDER BY 1, t.table_type DESC, c.ordinal_position", schema
934
- ])
935
-
1095
+ ORDER BY 1, t.table_type DESC, c.ordinal_position"
936
1096
  measures = []
937
1097
  case ActiveRecord::Base.connection.adapter_name
938
1098
  when 'PostgreSQL', 'SQLite' # These bring back a hash for each row because the query uses column aliases
1099
+ schema ||= 'public' if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
939
1100
  ActiveRecord::Base.execute_sql(sql).each do |r|
940
1101
  # next if internal_views.include?(r['relation_name']) # Skip internal views such as v_all_assessments
941
- relation = relations[(relation_name = r['relation_name'])]
1102
+ relation_name = if r['schema'] != schema
1103
+ "#{schema_name = r['schema']}.#{r['relation_name']}"
1104
+ else
1105
+ r['relation_name']
1106
+ end
1107
+ relation = relations[relation_name]
942
1108
  relation[:isView] = true if r['table_type'] == 'VIEW'
943
1109
  col_name = r['column_name']
944
1110
  key = case r['const']
@@ -995,11 +1161,11 @@ module ActiveRecord::ConnectionHandling
995
1161
  # end
996
1162
  # end
997
1163
  # end
998
-
1164
+ schema = ::Brick.default_schema # Reset back for this next round of fun
999
1165
  case ActiveRecord::Base.connection.adapter_name
1000
1166
  when 'PostgreSQL', 'Mysql2'
1001
- sql = ActiveRecord::Base.send(:sanitize_sql_array, [
1002
- "SELECT kcu1.TABLE_NAME, kcu1.COLUMN_NAME, kcu2.TABLE_NAME AS primary_table, kcu1.CONSTRAINT_NAME
1167
+ sql = "SELECT kcu1.CONSTRAINT_SCHEMA, kcu1.TABLE_NAME, kcu1.COLUMN_NAME,
1168
+ kcu2.CONSTRAINT_SCHEMA AS primary_schema, kcu2.TABLE_NAME AS primary_table, kcu1.CONSTRAINT_NAME AS CONSTRAINT_SCHEMA_FK
1003
1169
  FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS AS rc
1004
1170
  INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS kcu1
1005
1171
  ON kcu1.CONSTRAINT_CATALOG = rc.CONSTRAINT_CATALOG
@@ -1009,10 +1175,9 @@ module ActiveRecord::ConnectionHandling
1009
1175
  ON kcu2.CONSTRAINT_CATALOG = rc.UNIQUE_CONSTRAINT_CATALOG
1010
1176
  AND kcu2.CONSTRAINT_SCHEMA = rc.UNIQUE_CONSTRAINT_SCHEMA
1011
1177
  AND kcu2.CONSTRAINT_NAME = rc.UNIQUE_CONSTRAINT_NAME
1012
- AND kcu2.ORDINAL_POSITION = kcu1.ORDINAL_POSITION
1013
- WHERE kcu1.CONSTRAINT_SCHEMA = ? -- COALESCE(current_setting('SEARCH_PATH'), 'public')", schema
1178
+ AND kcu2.ORDINAL_POSITION = kcu1.ORDINAL_POSITION#{"
1179
+ WHERE kcu1.CONSTRAINT_SCHEMA = COALESCE(current_setting('SEARCH_PATH'), 'public')" if schema }"
1014
1180
  # AND kcu2.TABLE_NAME = ?;", Apartment::Tenant.current, table_name
1015
- ])
1016
1181
  when 'SQLite'
1017
1182
  sql = "SELECT m.name, fkl.\"from\", fkl.\"table\", m.name || '_' || fkl.\"from\" AS constraint_name
1018
1183
  FROM sqlite_master m
@@ -1021,10 +1186,17 @@ module ActiveRecord::ConnectionHandling
1021
1186
  else
1022
1187
  end
1023
1188
  if sql
1024
- ::Brick.db_schemas = ActiveRecord::Base.execute_sql(schema_sql)
1025
- ::Brick.db_schemas = ::Brick.db_schemas.to_a unless ::Brick.db_schemas.is_a?(Array)
1026
- ::Brick.db_schemas.map! { |row| row['table_schema'] } unless ::Brick.db_schemas.empty? || ::Brick.db_schemas.first.is_a?(String)
1027
- ::Brick.db_schemas -= ['information_schema', 'pg_catalog']
1189
+ ::Brick.default_schema ||= schema ||= 'public' if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
1190
+ unless (db_schemas = ActiveRecord::Base.execute_sql(schema_sql)).is_a?(Array)
1191
+ db_schemas = db_schemas.to_a
1192
+ end
1193
+ unless db_schemas.empty?
1194
+ ::Brick.db_schemas = db_schemas.each_with_object({}) do |row, s|
1195
+ row = row.is_a?(String) ? row : row['table_schema']
1196
+ # Remove whatever default schema we're using and other system schemas
1197
+ s[row] = nil unless ['information_schema', 'pg_catalog', schema].include?(row)
1198
+ end
1199
+ end
1028
1200
  ActiveRecord::Base.execute_sql(sql).each do |fk|
1029
1201
  fk = fk.values unless fk.is_a?(Array)
1030
1202
  ::Brick._add_bt_and_hm(fk, relations)
@@ -1071,34 +1243,46 @@ module Brick
1071
1243
 
1072
1244
  class << self
1073
1245
  def _add_bt_and_hm(fk, relations, is_polymorphic = false)
1074
- bt_assoc_name = fk[1].underscore
1075
- bt_assoc_name = bt_assoc_name[0..-4] if bt_assoc_name.end_with?('_id')
1076
-
1077
- bts = (relation = relations.fetch(fk[0], nil))&.fetch(:fks) { relation[:fks] = {} }
1246
+ if (bt_assoc_name = fk[2].underscore).end_with?('_id')
1247
+ bt_assoc_name = bt_assoc_name[0..-4]
1248
+ elsif bt_assoc_name.end_with?('id') && bt_assoc_name.exclude?('_') # Make the bold assumption that we can just peel off the final ID part
1249
+ bt_assoc_name = bt_assoc_name[0..-3]
1250
+ else
1251
+ bt_assoc_name = "#{bt_assoc_name}_bt"
1252
+ end
1253
+ # %%% Temporary schema patch
1254
+ fk[1] = "#{fk[0]}.#{for_tbl = fk[1]}" if fk[0] && fk[0] != ::Brick.default_schema
1255
+ for_tbl << '_' if for_tbl
1256
+ bts = (relation = relations.fetch(fk[1], nil))&.fetch(:fks) { relation[:fks] = {} }
1078
1257
  # %%% Do we miss out on has_many :through or even HM based on constantizing this model early?
1079
1258
  # Maybe it's already gotten this info because we got as far as to say there was a unique class
1080
- primary_table = (is_class = fk[2].is_a?(Hash) && fk[2].key?(:class)) ? (primary_class = fk[2][:class].constantize).table_name : fk[2]
1259
+ primary_table = if (is_class = fk[4].is_a?(Hash) && fk[4].key?(:class))
1260
+ pri_tbl = (primary_class = fk[4][:class].constantize).table_name
1261
+ else
1262
+ pri_tbl = fk[4]
1263
+ (fk[3] && fk[3] != ::Brick.default_schema) ? "#{fk[3]}.#{pri_tbl}" : pri_tbl
1264
+ end
1081
1265
  hms = (relation = relations.fetch(primary_table, nil))&.fetch(:fks) { relation[:fks] = {} } unless is_class
1082
1266
 
1083
- unless (cnstr_name = fk[3])
1267
+ unless (cnstr_name = fk[5])
1084
1268
  # For any appended references (those that come from config), arrive upon a definitely unique constraint name
1085
- cnstr_base = cnstr_name = "(brick) #{fk[0]}_#{is_class ? fk[2][:class].underscore : fk[2]}"
1269
+ cnstr_base = cnstr_name = "(brick) #{for_tbl}#{is_class ? fk[4][:class].underscore : pri_tbl}"
1086
1270
  cnstr_added_num = 1
1087
1271
  cnstr_name = "#{cnstr_base}_#{cnstr_added_num += 1}" while bts&.key?(cnstr_name) || hms&.key?(cnstr_name)
1088
1272
  missing = []
1089
- missing << fk[0] unless relations.key?(fk[0])
1273
+ missing << fk[1] unless relations.key?(fk[1])
1090
1274
  missing << primary_table unless is_class || relations.key?(primary_table)
1091
1275
  unless missing.empty?
1092
1276
  tables = relations.reject { |_k, v| v.fetch(:isView, nil) }.keys.sort
1093
1277
  puts "Brick: Additional reference #{fk.inspect} refers to non-existent #{'table'.pluralize(missing.length)} #{missing.join(' and ')}. (Available tables include #{tables.join(', ')}.)"
1094
1278
  return
1095
1279
  end
1096
- unless (cols = relations[fk[0]][:cols]).key?(fk[1]) || (is_polymorphic && cols.key?("#{fk[1]}_id") && cols.key?("#{fk[1]}_type"))
1280
+ unless (cols = relations[fk[1]][:cols]).key?(fk[2]) || (is_polymorphic && cols.key?("#{fk[2]}_id") && cols.key?("#{fk[2]}_type"))
1097
1281
  columns = cols.map { |k, v| "#{k} (#{v.first.split(' ').first})" }
1098
- puts "Brick: Additional reference #{fk.inspect} refers to non-existent column #{fk[1]}. (Columns present in #{fk[0]} are #{columns.join(', ')}.)"
1282
+ puts "Brick: Additional reference #{fk.inspect} refers to non-existent column #{fk[2]}. (Columns present in #{fk[1]} are #{columns.join(', ')}.)"
1099
1283
  return
1100
1284
  end
1101
- if (redundant = bts.find { |_k, v| v[:inverse]&.fetch(:inverse_table, nil) == fk[0] && v[:fk] == fk[1] && v[:inverse_table] == primary_table })
1285
+ if (redundant = bts.find { |_k, v| v[:inverse]&.fetch(:inverse_table, nil) == fk[1] && v[:fk] == fk[2] && v[:inverse_table] == primary_table })
1102
1286
  if is_class && !redundant.last.key?(:class)
1103
1287
  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
1104
1288
  else
@@ -1110,18 +1294,18 @@ module Brick
1110
1294
  if (assoc_bt = bts[cnstr_name])
1111
1295
  if is_polymorphic
1112
1296
  # Assuming same fk (don't yet support composite keys for polymorphics)
1113
- assoc_bt[:inverse_table] << fk[2]
1297
+ assoc_bt[:inverse_table] << fk[4]
1114
1298
  else # Expect we could have a composite key going
1115
1299
  if assoc_bt[:fk].is_a?(String)
1116
- assoc_bt[:fk] = [assoc_bt[:fk], fk[1]] unless fk[1] == assoc_bt[:fk]
1117
- elsif assoc_bt[:fk].exclude?(fk[1])
1118
- assoc_bt[:fk] << fk[1]
1300
+ assoc_bt[:fk] = [assoc_bt[:fk], fk[2]] unless fk[2] == assoc_bt[:fk]
1301
+ elsif assoc_bt[:fk].exclude?(fk[2])
1302
+ assoc_bt[:fk] << fk[2]
1119
1303
  end
1120
- assoc_bt[:assoc_name] = "#{assoc_bt[:assoc_name]}_#{fk[1]}"
1304
+ assoc_bt[:assoc_name] = "#{assoc_bt[:assoc_name]}_#{fk[2]}"
1121
1305
  end
1122
1306
  else
1123
1307
  inverse_table = [primary_table] if is_polymorphic
1124
- assoc_bt = bts[cnstr_name] = { is_bt: true, fk: fk[1], assoc_name: bt_assoc_name, inverse_table: inverse_table || primary_table }
1308
+ assoc_bt = bts[cnstr_name] = { is_bt: true, fk: fk[2], assoc_name: bt_assoc_name, inverse_table: inverse_table || primary_table }
1125
1309
  assoc_bt[:polymorphic] = true if is_polymorphic
1126
1310
  end
1127
1311
  if is_class
@@ -1131,21 +1315,21 @@ module Brick
1131
1315
  # assoc_bt[:inverse_of] = primary_class.reflect_on_all_associations.find { |a| a.foreign_key == bt[1] }
1132
1316
  end
1133
1317
 
1134
- return if is_class || ::Brick.config.exclude_hms&.any? { |exclusion| fk[0] == exclusion[0] && fk[1] == exclusion[1] && primary_table == exclusion[2] }
1318
+ return if is_class || ::Brick.config.exclude_hms&.any? { |exclusion| fk[1] == exclusion[0] && fk[2] == exclusion[1] && primary_table == exclusion[2] } || hms.nil?
1135
1319
 
1136
1320
  if (assoc_hm = hms.fetch((hm_cnstr_name = "hm_#{cnstr_name}"), nil))
1137
1321
  if assoc_hm[:fk].is_a?(String)
1138
- assoc_hm[:fk] = [assoc_hm[:fk], fk[1]] unless fk[1] == assoc_hm[:fk]
1139
- elsif assoc_hm[:fk].exclude?(fk[1])
1140
- assoc_hm[:fk] << fk[1]
1322
+ assoc_hm[:fk] = [assoc_hm[:fk], fk[2]] unless fk[2] == assoc_hm[:fk]
1323
+ elsif assoc_hm[:fk].exclude?(fk[2])
1324
+ assoc_hm[:fk] << fk[2]
1141
1325
  end
1142
1326
  assoc_hm[:alternate_name] = "#{assoc_hm[:alternate_name]}_#{bt_assoc_name}" unless assoc_hm[:alternate_name] == bt_assoc_name
1143
1327
  assoc_hm[:inverse] = assoc_bt
1144
1328
  else
1145
- 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 }
1329
+ assoc_hm = hms[hm_cnstr_name] = { is_bt: false, fk: fk[2], assoc_name: fk[1].tr('.', '_').pluralize, alternate_name: bt_assoc_name, inverse_table: fk[1], inverse: assoc_bt }
1146
1330
  assoc_hm[:polymorphic] = true if is_polymorphic
1147
1331
  hm_counts = relation.fetch(:hm_counts) { relation[:hm_counts] = {} }
1148
- hm_counts[fk[0]] = hm_counts.fetch(fk[0]) { 0 } + 1
1332
+ hm_counts[fk[1]] = hm_counts.fetch(fk[1]) { 0 } + 1
1149
1333
  end
1150
1334
  assoc_bt[:inverse] = assoc_hm
1151
1335
  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
 
@@ -83,9 +85,10 @@ module Brick
83
85
  return _brick_find_template(*args, **options) unless @_brick_model
84
86
 
85
87
  model_name = @_brick_model.name
86
- pk = @_brick_model._brick_primary_key(::Brick.relations[model_name])
87
- obj_name = model_name.underscore
88
- table_name = model_name.pluralize.underscore
88
+ pk = @_brick_model._brick_primary_key(::Brick.relations.fetch(model_name, nil))
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;
@@ -207,7 +210,13 @@ input[type=submit] {
207
210
  val.is_a?(String) && val.length == 60 && val.start_with?('$2a$')
208
211
  end
209
212
  def hide_bcrypt(val)
210
- is_bcrypt?(val) ? '(hidden)' : val
213
+ if is_bcrypt?(val)
214
+ '(hidden)'
215
+ elsif val.is_a?(String) && val.encoding.name != 'UTF-8'
216
+ val[0..1000].force_encoding('UTF-8')
217
+ else
218
+ val
219
+ end
211
220
  end %>"
212
221
 
213
222
  if ['index', 'show', 'update'].include?(args.first)
@@ -255,7 +264,8 @@ if (schemaSelect) {
255
264
 
256
265
  var tblSelect = document.getElementById(\"tbl\");
257
266
  if (tblSelect) {
258
- tblSelect.value = changeout(location.href);
267
+ tblSelect.value = changeout(location.href)[0];
268
+ if (tblSelect.selectedIndex < 0) tblSelect.value = changeout(location.href)[1];
259
269
  tblSelect.addEventListener(\"change\", function () {
260
270
  var lhr = changeout(location.href, null, this.value);
261
271
  if (brickSchema)
@@ -270,7 +280,8 @@ function changeout(href, param, value) {
270
280
  hrefParts = hrefParts[0].split(\"://\");
271
281
  var pathParts = hrefParts[hrefParts.length - 1].split(\"/\");
272
282
  if (value === undefined)
273
- 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)];
274
285
  else
275
286
  return hrefParts[0] + \"://\" + pathParts[0] + \"/\" + value;
276
287
  }
@@ -307,7 +318,7 @@ function changeout(href, param, value) {
307
318
  btnImport.style.display = droppedTSV.length > 0 ? \"block\" : \"none\";
308
319
  });
309
320
  btnImport.addEventListener(\"click\", function () {
310
- 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), {
311
322
  method: 'PATCH',
312
323
  headers: { 'Content-Type': 'text/tab-separated-values' },
313
324
  body: droppedTSV
@@ -378,17 +389,32 @@ function changeout(href, param, value) {
378
389
  <script async defer src=\"https://apis.google.com/js/api.js\" onload=\"gapiLoaded()\"></script>
379
390
  "
380
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(', ') %>)
381
394
  "#{css}
382
395
  <p style=\"color: green\"><%= notice %></p>#{"
383
- <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}
384
397
  <select id=\"tbl\">#{table_options}</select>
385
- <h1>#{model_name.pluralize}</h1>#{template_link}
386
-
387
- <% 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 %>
388
413
  <table id=\"#{table_name}\">
389
414
  <thead><tr>#{'<th></th>' if pk.present?}
390
415
  <% @#{table_name}.columns.map(&:name).each do |col| %>
391
- <% 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) %>
392
418
  <th>
393
419
  <% if (bt = bts[col]) %>
394
420
  BT <%
@@ -401,15 +427,16 @@ function changeout(href, param, value) {
401
427
  </th>
402
428
  <% end %>
403
429
  <%# Consider getting the name from the association -- h.first.name -- if a more \"friendly\" alias should be used for a screwy table name %>
404
- #{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}
405
431
  </tr></thead>
406
432
 
407
433
  <tbody>
408
434
  <% @#{table_name}.each do |#{obj_name}| %>
409
435
  <tr>#{"
410
- <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}
411
437
  <% #{obj_name}.attributes.each do |k, val| %>
412
- <% 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'))) %>
413
440
  <td>
414
441
  <% if (bt = bts[k]) %>
415
442
  <%# binding.pry # Postgres column names are limited to 63 characters %>
@@ -424,7 +451,7 @@ function changeout(href, param, value) {
424
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)
425
452
  )
426
453
  bt_id = #{obj_name}.send(*bt_id_col) if bt_id_col&.present? %>
427
- <%= 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 %>
428
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 %>
429
456
  <% end %>
430
457
  <% else %>
@@ -434,19 +461,19 @@ function changeout(href, param, value) {
434
461
  <% end %>
435
462
  #{hms_columns.each_with_object(+'') { |hm_col, s| s << "<td>#{hm_col}</td>" }}
436
463
  </tr>
437
- </tbody>
438
464
  <% end %>
465
+ </tbody>
439
466
  </table>
440
467
 
441
- #{"<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?}
442
469
  #{script}"
443
470
  when 'show', 'update'
444
471
  "#{css}
445
472
  <p style=\"color: green\"><%= notice %></p>#{"
446
- <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}
447
474
  <select id=\"tbl\">#{table_options}</select>
448
475
  <h1>#{model_name}: <%= (obj = @#{obj_name})&.brick_descrip || controller_name %></h1>
449
- <%= link_to '(See all #{obj_name.pluralize})', #{table_name}_path %>
476
+ <%= link_to '(See all #{obj_name.pluralize})', #{path_obj_name.pluralize}_path %>
450
477
  <% if obj %>
451
478
  <%= # path_options = [obj.#{pk}]
452
479
  # path_options << { '_brick_schema': } if
@@ -456,8 +483,8 @@ function changeout(href, param, value) {
456
483
  <% has_fields = false
457
484
  @#{obj_name}.attributes.each do |k, val| %>
458
485
  <tr>
459
- <%# %%% Accommodate composite keys %>
460
- <% next if #{pk}.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) %>
461
488
  <th class=\"show-field\">
462
489
  <% has_fields = true
463
490
  if (bt = bts[k])
@@ -499,7 +526,7 @@ function changeout(href, param, value) {
499
526
  html_options = { prompt: \"Select #\{bt_name\}\" }
500
527
  html_options[:class] = 'dimmed' unless val %>
501
528
  <%= f.select k.to_sym, bt[3], { value: val || '^^^brick_NULL^^^' }, html_options %>
502
- <%= 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 %>
503
530
  <% else case #{model_name}.column_for_attribute(k).type
504
531
  when :string, :text %>
505
532
  <% if is_bcrypt?(val) # || .readonly? %>
@@ -541,7 +568,7 @@ function changeout(href, param, value) {
541
568
  <tr><td>(none)</td></tr>
542
569
  <% else %>
543
570
  <% collection.uniq.each do |#{hm_singular_name}| %>
544
- <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>
545
572
  <% end %>
546
573
  <% end %>
547
574
  </table>"
@@ -5,7 +5,7 @@ module Brick
5
5
  module VERSION
6
6
  MAJOR = 1
7
7
  MINOR = 0
8
- TINY = 28
8
+ TINY = 31
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.28
4
+ version: 1.0.31
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