brick 1.0.29 → 1.0.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/brick/config.rb +24 -4
- data/lib/brick/extensions.rb +292 -152
- data/lib/brick/frameworks/rails/engine.rb +56 -31
- data/lib/brick/version_number.rb +1 -1
- data/lib/brick.rb +38 -13
- data/lib/generators/brick/install_generator.rb +7 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8483d9a50013e440cce28f648d09d5322985702f952520cb83c69ad0e5742ce4
|
4
|
+
data.tar.gz: 40072d0504f4d2b1cc9a59e62fef305b939dd977da0e794445adf003dbfb8b7e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9740778159acb464652322ef36eff763a55b38c6579fe0a7550a5797db44928ad6832a8bccc0d924a4d4c5a14dc02658e61bf4220cecff08fc348d540b67e41c
|
7
|
+
data.tar.gz: 1b09db07a284512821b604f1d82f64c4be0c11db7f8a8973f36ab3f78b21804a7fec1eb641bcf1ac5c2ec118cb477c8086c124017c66550753ec4f9beddbd413
|
data/lib/brick/config.rb
CHANGED
@@ -122,12 +122,32 @@ module Brick
|
|
122
122
|
@mutex.synchronize { @sti_namespace_prefixes = prefixes }
|
123
123
|
end
|
124
124
|
|
125
|
-
def
|
126
|
-
@mutex.synchronize { @
|
125
|
+
def schema_behavior
|
126
|
+
@mutex.synchronize { @schema_behavior ||= {} }
|
127
127
|
end
|
128
128
|
|
129
|
-
def
|
130
|
-
@mutex.synchronize { @
|
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
|
data/lib/brick/extensions.rb
CHANGED
@@ -104,7 +104,8 @@ module ActiveRecord
|
|
104
104
|
if ch == ']' # Time to process a bracketed thing?
|
105
105
|
parts = bracket_name.split('.')
|
106
106
|
first_parts = parts[0..-2].map do |part|
|
107
|
-
klass = klass.reflect_on_association(part_sym = part.to_sym)
|
107
|
+
klass = (orig_class = klass).reflect_on_association(part_sym = part.to_sym)&.klass
|
108
|
+
puts "Couldn't reference #{orig_class.name}##{part} that's part of the DSL \"#{dsl}\"." if klass.nil?
|
108
109
|
part_sym
|
109
110
|
end
|
110
111
|
parts = prefix + first_parts + [parts[-1]]
|
@@ -196,7 +197,7 @@ module ActiveRecord
|
|
196
197
|
def self.bt_link(assoc_name)
|
197
198
|
model_underscore = name.underscore
|
198
199
|
assoc_name = CGI.escapeHTML(assoc_name.to_s)
|
199
|
-
model_path = Rails.application.routes.url_helpers.send("#{model_underscore.pluralize}_path".to_sym)
|
200
|
+
model_path = Rails.application.routes.url_helpers.send("#{model_underscore.tr('/', '_').pluralize}_path".to_sym)
|
200
201
|
link = Class.new.extend(ActionView::Helpers::UrlHelper).link_to(name, model_path)
|
201
202
|
model_underscore == assoc_name ? link : "#{assoc_name}-#{link}".html_safe
|
202
203
|
end
|
@@ -306,8 +307,12 @@ module ActiveRecord
|
|
306
307
|
|
307
308
|
# %%% Skip the metadata columns
|
308
309
|
if selects&.empty? # Default to all columns
|
310
|
+
tbl_no_schema = table.name.split('.').last
|
309
311
|
columns.each do |col|
|
310
|
-
|
312
|
+
if (col_name = col.name) == 'class'
|
313
|
+
col_alias = ' AS _class'
|
314
|
+
end
|
315
|
+
selects << "\"#{tbl_no_schema}\".\"#{col_name}\"#{col_alias}"
|
311
316
|
end
|
312
317
|
end
|
313
318
|
|
@@ -344,12 +349,13 @@ module ActiveRecord
|
|
344
349
|
next unless klass.reflect_on_association(assoc_name)&.klass&.column_names&.any?(ks.last)
|
345
350
|
|
346
351
|
join_array[assoc_name] = nil # Store this relation name in our special collection for .joins()
|
352
|
+
distinct!
|
347
353
|
end
|
348
354
|
wheres[k] = v.split(',')
|
349
355
|
end
|
350
356
|
|
351
357
|
if join_array.present?
|
352
|
-
left_outer_joins!(join_array)
|
358
|
+
left_outer_joins!(join_array)
|
353
359
|
# Without working from a duplicate, touching the AREL ast tree sets the @arel instance variable, which causes the relation to be immutable.
|
354
360
|
(rel_dupe = dup)._arel_alias_names
|
355
361
|
core_selects = selects.dup
|
@@ -360,10 +366,10 @@ module ActiveRecord
|
|
360
366
|
v.last.each do |k1, v1| # k1 is class, v1 is array of columns to snag
|
361
367
|
next if chains[k1].nil?
|
362
368
|
|
363
|
-
tbl_name = field_tbl_names[v.first][k1] ||= shift_or_first(chains[k1])
|
369
|
+
tbl_name = (field_tbl_names[v.first][k1] ||= shift_or_first(chains[k1])).split('.').last
|
364
370
|
field_tbl_name = nil
|
365
371
|
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])
|
372
|
+
field_tbl_name = (field_tbl_names[v.first][sel_col.first] ||= shift_or_first(chains[sel_col.first])).split('.').last
|
367
373
|
|
368
374
|
selects << "#{"\"#{field_tbl_name}\".\"#{sel_col.last}\""} AS \"#{(col_alias = "_brfk_#{v.first}__#{sel_col.last}")}\""
|
369
375
|
v1[idx] << col_alias
|
@@ -416,10 +422,13 @@ module ActiveRecord
|
|
416
422
|
on_clause << "#{tbl_alias}.#{poly_type} = '#{name}'"
|
417
423
|
end
|
418
424
|
join_clause = "LEFT OUTER
|
419
|
-
JOIN (SELECT #{selects.join(', ')}, COUNT(#{
|
425
|
+
JOIN (SELECT #{selects.join(', ')}, COUNT(#{'DISTINCT ' if hm.options[:through]}#{count_column
|
426
|
+
}) AS _ct_ FROM #{associative&.table_name || hm.klass.table_name
|
427
|
+
} GROUP BY #{(1..selects.length).to_a.join(', ')}) AS #{tbl_alias}"
|
420
428
|
joins!("#{join_clause} ON #{on_clause.join(' AND ')}")
|
421
429
|
end
|
422
430
|
where!(wheres) unless wheres.empty?
|
431
|
+
limit!(1000) # Don't want to get too carried away just yet
|
423
432
|
wheres unless wheres.empty? # Return the specific parameters that we did use
|
424
433
|
end
|
425
434
|
|
@@ -480,11 +489,13 @@ if ActiveSupport::Dependencies.respond_to?(:autoload_module!) # %%% Only works w
|
|
480
489
|
alias _brick_autoload_module! autoload_module!
|
481
490
|
def autoload_module!(*args)
|
482
491
|
into, const_name, qualified_name, path_suffix = args
|
483
|
-
|
492
|
+
base_class_name = ::Brick.config.sti_namespace_prefixes&.fetch("::#{into.name}::", nil)
|
493
|
+
base_class_name = "::#{base_class_name}" unless base_class_name.start_with?('::')
|
494
|
+
if (base_class = base_class_name&.constantize)
|
484
495
|
::Brick.sti_models[qualified_name] = { base: base_class }
|
485
496
|
# Build subclass and place it into the specially STI-namespaced module
|
486
497
|
into.const_set(const_name.to_sym, klass = Class.new(base_class))
|
487
|
-
# %%% used to also have: autoload_once_paths.include?(base_path) ||
|
498
|
+
# %%% used to also have: autoload_once_paths.include?(base_path) ||
|
488
499
|
autoloaded_constants << qualified_name unless autoloaded_constants.include?(qualified_name)
|
489
500
|
klass
|
490
501
|
elsif (base_class = ::Brick.config.sti_namespace_prefixes&.fetch("::#{const_name}", nil)&.constantize)
|
@@ -498,94 +509,153 @@ if ActiveSupport::Dependencies.respond_to?(:autoload_module!) # %%% Only works w
|
|
498
509
|
end
|
499
510
|
end
|
500
511
|
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
512
|
+
Module.class_exec do
|
513
|
+
alias _brick_const_missing const_missing
|
514
|
+
def const_missing(*args)
|
515
|
+
if (self.const_defined?(args.first) && (possible = self.const_get(args.first)) && possible != self) ||
|
516
|
+
(self != Object && Object.const_defined?(args.first) &&
|
517
|
+
(
|
518
|
+
(possible = Object.const_get(args.first)) &&
|
519
|
+
(possible != self || (possible == self && possible.is_a?(Class)))
|
520
|
+
)
|
521
|
+
)
|
522
|
+
return possible
|
523
|
+
end
|
524
|
+
class_name = args.first.to_s
|
525
|
+
# See if a file is there in the same way that ActiveSupport::Dependencies#load_missing_constant
|
526
|
+
# checks for it in ~/.rvm/gems/ruby-2.7.5/gems/activesupport-5.2.6.2/lib/active_support/dependencies.rb
|
527
|
+
# that is, checking #qualified_name_for with: from_mod, const_name
|
528
|
+
# If we want to support namespacing in the future, might have to utilise something like this:
|
529
|
+
# path_suffix = ActiveSupport::Dependencies.qualified_name_for(Object, args.first).underscore
|
530
|
+
# return self._brick_const_missing(*args) if ActiveSupport::Dependencies.search_for_file(path_suffix)
|
531
|
+
# If the file really exists, go and snag it:
|
532
|
+
if !(is_found = ActiveSupport::Dependencies.search_for_file(class_name.underscore)) && (filepath = (self.name || class_name)&.split('::'))
|
533
|
+
filepath = (filepath[0..-2] + [class_name]).join('/').underscore + '.rb'
|
534
|
+
end
|
535
|
+
if is_found
|
536
|
+
return self._brick_const_missing(*args)
|
537
|
+
# elsif ActiveSupport::Dependencies.search_for_file(filepath) # Last-ditch effort to pick this thing up before we fill in the gaps on our own
|
538
|
+
# my_const = parent.const_missing(class_name) # ends up having: MyModule::MyClass
|
539
|
+
# return my_const
|
540
|
+
end
|
525
541
|
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
542
|
+
relations = ::Brick.instance_variable_get(:@relations)[ActiveRecord::Base.connection_pool.object_id] || {}
|
543
|
+
# puts "ON OBJECT: #{args.inspect}" if self.module_parent == Object
|
544
|
+
result = if ::Brick.enable_controllers? && class_name.end_with?('Controller') && (plural_class_name = class_name[0..-11]).length.positive?
|
545
|
+
# Otherwise now it's up to us to fill in the gaps
|
546
|
+
# (Go over to underscores for a moment so that if we have something come in like VABCsController then the model name ends up as
|
547
|
+
# Vabc instead of VABC)
|
548
|
+
full_class_name = +''
|
549
|
+
full_class_name << "::#{self.name}" unless self == Object
|
550
|
+
full_class_name << "::#{plural_class_name.underscore.singularize.camelize}"
|
551
|
+
if (model = self.const_get(full_class_name))
|
552
|
+
# 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.
|
553
|
+
Object.send(:build_controller, self, class_name, plural_class_name, model, relations)
|
554
|
+
end
|
555
|
+
elsif (::Brick.enable_models? || ::Brick.enable_controllers?) && # Schema match?
|
556
|
+
self == Object && # %%% This works for Person::Person -- but also limits us to not being able to allow more than one level of namespacing
|
557
|
+
(schema_name = [(singular_table_name = class_name.underscore),
|
558
|
+
(table_name = singular_table_name.pluralize),
|
559
|
+
class_name,
|
560
|
+
(plural_class_name = class_name.pluralize)].find { |s| Brick.db_schemas.include?(s) }&.camelize ||
|
561
|
+
(::Brick.config.sti_namespace_prefixes&.key?("::#{class_name}::") && class_name))
|
562
|
+
# Build out a module for the schema if it's namespaced
|
563
|
+
# schema_name = schema_name.camelize
|
564
|
+
self.const_set(schema_name.to_sym, (built_module = Module.new))
|
565
|
+
|
566
|
+
[built_module, "module #{schema_name}; end\n"]
|
567
|
+
# # %%% Perhaps an option to use the first module just as schema, and additional modules as namespace with a table name prefix applied
|
568
|
+
elsif ::Brick.enable_models?
|
569
|
+
# See if a file is there in the same way that ActiveSupport::Dependencies#load_missing_constant
|
570
|
+
# checks for it in ~/.rvm/gems/ruby-2.7.5/gems/activesupport-5.2.6.2/lib/active_support/dependencies.rb
|
571
|
+
|
572
|
+
if self != Object || # Are we in some namespace? ...
|
573
|
+
(base_model = ::Brick.config.sti_namespace_prefixes&.fetch("::#{name}::", nil)&.constantize) # ... or part of an auto-STI namespace?
|
574
|
+
schema_name = [(singular_schema_name = name.underscore),
|
575
|
+
(schema_name = singular_schema_name.pluralize),
|
576
|
+
name,
|
577
|
+
name.pluralize].find { |s| Brick.db_schemas.include?(s) }
|
578
|
+
end
|
579
|
+
plural_class_name = ActiveSupport::Inflector.pluralize(model_name = class_name)
|
580
|
+
# If it's namespaced then we turn the first part into what would be a schema name
|
581
|
+
singular_table_name = ActiveSupport::Inflector.underscore(model_name).gsub('/', '.')
|
582
|
+
|
583
|
+
if base_model
|
584
|
+
schema_name = name.underscore # For the auto-STI namespace models
|
585
|
+
table_name = base_model.table_name
|
586
|
+
Object.send(:build_model, self, model_name, singular_table_name, table_name, relations, table_name)
|
587
|
+
else
|
539
588
|
# Adjust for STI if we know of a base model for the requested model name
|
589
|
+
# %%% Does not yet work with namespaced model names. Perhaps prefix with plural_class_name when doing the lookups here.
|
540
590
|
table_name = if (base_model = ::Brick.sti_models[model_name]&.fetch(:base, nil) || ::Brick.existing_stis[model_name]&.constantize)
|
541
591
|
base_model.table_name
|
542
592
|
else
|
543
593
|
ActiveSupport::Inflector.pluralize(singular_table_name)
|
544
594
|
end
|
545
|
-
|
595
|
+
|
546
596
|
# 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
|
597
|
+
if (matching = [table_name, singular_table_name, plural_class_name, model_name].find { |m| relations.key?(schema_name ? "#{schema_name}.#{m}" : m) })
|
598
|
+
Object.send(:build_model, schema_name, model_name, singular_table_name, table_name, relations, matching)
|
549
599
|
end
|
550
600
|
end
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
601
|
+
end
|
602
|
+
if result
|
603
|
+
built_class, code = result
|
604
|
+
puts "\n#{code}"
|
605
|
+
built_class
|
606
|
+
elsif ::Brick.config.sti_namespace_prefixes&.key?("::#{class_name}") && !schema_name
|
556
607
|
# module_prefixes = type_name.split('::')
|
557
608
|
# path = self.name.split('::')[0..-2] + []
|
558
609
|
# module_prefixes.unshift('') unless module_prefixes.first.blank?
|
559
610
|
# candidate_file = Rails.root.join('app/models' + module_prefixes.map(&:underscore).join('/') + '.rb')
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
611
|
+
self._brick_const_missing(*args)
|
612
|
+
elsif self != Object
|
613
|
+
module_parent.const_missing(*args)
|
614
|
+
else
|
615
|
+
puts "MISSING! #{self.name} #{args.inspect} #{table_name}"
|
616
|
+
self._brick_const_missing(*args)
|
565
617
|
end
|
618
|
+
end
|
619
|
+
end
|
620
|
+
|
621
|
+
class Object
|
622
|
+
class << self
|
566
623
|
|
567
624
|
private
|
568
625
|
|
569
|
-
def build_model(model_name, singular_table_name, table_name, relations, matching)
|
626
|
+
def build_model(schema_name, model_name, singular_table_name, table_name, relations, matching)
|
627
|
+
full_name = if schema_name.blank?
|
628
|
+
model_name
|
629
|
+
else # Prefix the schema to the table name + prefix the schema namespace to the class name
|
630
|
+
schema_module = if schema_name.instance_of?(Module) # from an auto-STI namespace?
|
631
|
+
schema_name
|
632
|
+
else
|
633
|
+
matching = "#{schema_name}.#{matching}"
|
634
|
+
(Brick.db_schemas[schema_name] ||= self.const_get(schema_name.singularize.camelize))
|
635
|
+
end
|
636
|
+
"#{schema_module&.name}::#{model_name}"
|
637
|
+
end
|
638
|
+
|
570
639
|
return if ((is_view = (relation = relations[matching]).key?(:isView)) && ::Brick.config.skip_database_views) ||
|
571
640
|
::Brick.config.exclude_tables.include?(matching)
|
572
641
|
|
573
642
|
# Are they trying to use a pluralised class name such as "Employees" instead of "Employee"?
|
574
643
|
if table_name == singular_table_name && !ActiveSupport::Inflector.inflections.uncountable.include?(table_name)
|
575
|
-
unless ::Brick.config.sti_namespace_prefixes&.key?("::#{singular_table_name.
|
644
|
+
unless ::Brick.config.sti_namespace_prefixes&.key?("::#{singular_table_name.camelize}::")
|
576
645
|
puts "Warning: Class name for a model that references table \"#{matching}\" should be \"#{ActiveSupport::Inflector.singularize(model_name)}\"."
|
577
646
|
end
|
578
647
|
return
|
579
648
|
end
|
580
649
|
|
581
|
-
if (base_model = ::Brick.sti_models[
|
650
|
+
if (base_model = ::Brick.sti_models[full_name]&.fetch(:base, nil) || ::Brick.existing_stis[full_name]&.constantize)
|
582
651
|
is_sti = true
|
583
652
|
else
|
584
653
|
base_model = ::Brick.config.models_inherit_from || ActiveRecord::Base
|
585
654
|
end
|
586
|
-
|
655
|
+
hmts = nil
|
656
|
+
code = +"class #{full_name} < #{base_model.name}\n"
|
587
657
|
built_model = Class.new(base_model) do |new_model_class|
|
588
|
-
Object.const_set(model_name.to_sym, new_model_class)
|
658
|
+
(schema_module || Object).const_set(model_name.to_sym, new_model_class)
|
589
659
|
# Accommodate singular or camel-cased table names such as "order_detail" or "OrderDetails"
|
590
660
|
code << " self.table_name = '#{self.table_name = matching}'\n" unless table_name == matching
|
591
661
|
|
@@ -618,6 +688,10 @@ class Object
|
|
618
688
|
else
|
619
689
|
code << " # Could not identify any column(s) to use as a primary key\n" unless is_view
|
620
690
|
end
|
691
|
+
if (sti_col = relation.fetch(:sti_col, nil))
|
692
|
+
new_model_class.send(:'inheritance_column=', sti_col)
|
693
|
+
code << " self.inheritance_column = #{sti_col.inspect}\n"
|
694
|
+
end
|
621
695
|
|
622
696
|
unless is_sti
|
623
697
|
fks = relation[:fks] || {}
|
@@ -635,7 +709,21 @@ class Object
|
|
635
709
|
build_bt_or_hm(relations, model_name, relation, hmts, assoc, inverse_assoc_name, invs, code) unless invs.is_a?(Array)
|
636
710
|
hmts
|
637
711
|
end
|
638
|
-
|
712
|
+
# # Not NULLables
|
713
|
+
# # %%% For the minute we've had to pull this out because it's been troublesome implementing the NotNull validator
|
714
|
+
# relation[:cols].each do |col, datatype|
|
715
|
+
# if (datatype[3] && _brick_primary_key.exclude?(col) && ::Brick.config.metadata_columns.exclude?(col)) ||
|
716
|
+
# ::Brick.config.not_nullables.include?("#{matching}.#{col}")
|
717
|
+
# code << " validates :#{col}, not_null: true\n"
|
718
|
+
# self.send(:validates, col.to_sym, { not_null: true })
|
719
|
+
# end
|
720
|
+
# end
|
721
|
+
end
|
722
|
+
end # class definition
|
723
|
+
# Having this separate -- will this now work out better?
|
724
|
+
built_model.class_exec do
|
725
|
+
hmts&.each do |hmt_fk, fks|
|
726
|
+
hmt_fk = hmt_fk.tr('.', '_')
|
639
727
|
fks.each do |fk|
|
640
728
|
through = fk.first[:assoc_name]
|
641
729
|
hmt_name = if fks.length > 1
|
@@ -649,25 +737,19 @@ class Object
|
|
649
737
|
else
|
650
738
|
hmt_fk
|
651
739
|
end
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
740
|
+
options = { through: through.to_sym }
|
741
|
+
if relation[:fks].any? { |k, v| v[:assoc_name] == hmt_name }
|
742
|
+
hmt_name = "#{hmt_name.singularize}_#{fk.first[:assoc_name]}"
|
743
|
+
options[:class_name] = fk.first[:inverse_table].singularize.camelize
|
744
|
+
options[:foreign_key] = fk.first[:fk].to_sym
|
745
|
+
end
|
746
|
+
options[:source] = fk.last.to_sym unless hmt_name.singularize == fk.last
|
747
|
+
code << " has_many :#{hmt_name}#{options.map { |opt| ", #{opt.first}: #{opt.last.inspect}" }.join}\n"
|
656
748
|
self.send(:has_many, hmt_name.to_sym, **options)
|
657
749
|
end
|
658
750
|
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
751
|
end
|
669
|
-
code << "end # model #{
|
670
|
-
end # class definition
|
752
|
+
code << "end # model #{full_name}\n\n"
|
671
753
|
[built_model, code]
|
672
754
|
end
|
673
755
|
|
@@ -678,12 +760,13 @@ class Object
|
|
678
760
|
# Try to take care of screwy names if this is a belongs_to going to an STI subclass
|
679
761
|
assoc_name = if (primary_class = assoc.fetch(:primary_class, nil)) &&
|
680
762
|
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]
|
763
|
+
a.macro == :has_many && a.options[:class_name] == self.name && assoc[:fk] == a.foreign_key
|
682
764
|
end
|
683
765
|
sti_inverse_assoc.options[:inverse_of]&.to_s || assoc_name
|
684
766
|
else
|
685
767
|
assoc[:assoc_name]
|
686
768
|
end
|
769
|
+
options[:optional] = true if assoc.key?(:optional)
|
687
770
|
if assoc.key?(:polymorphic)
|
688
771
|
options[:polymorphic] = true
|
689
772
|
else
|
@@ -710,7 +793,7 @@ class Object
|
|
710
793
|
if assoc.key?(:polymorphic)
|
711
794
|
options[:as] = assoc[:fk].to_sym
|
712
795
|
else
|
713
|
-
need_fk = "#{ActiveSupport::Inflector.singularize(assoc[:inverse][:inverse_table])}_id" != assoc[:fk]
|
796
|
+
need_fk = "#{ActiveSupport::Inflector.singularize(assoc[:inverse][:inverse_table].split('.').last)}_id" != assoc[:fk]
|
714
797
|
end
|
715
798
|
# fks[table_name].find { |other_assoc| other_assoc.object_id != assoc.object_id && other_assoc[:assoc_name] == assoc[assoc_name] }
|
716
799
|
if (has_ones = ::Brick.config.has_ones&.fetch(model_name, nil))&.key?(singular_assoc_name = ActiveSupport::Inflector.singularize(assoc_name))
|
@@ -727,7 +810,11 @@ class Object
|
|
727
810
|
end
|
728
811
|
# Figure out if we need to specially call out the class_name and/or foreign key
|
729
812
|
# (and if either of those then definitely also a specific inverse_of)
|
730
|
-
|
813
|
+
if (singular_table_parts = singular_table_name.split('.')).length > 1 &&
|
814
|
+
::Brick.config.schema_behavior[:multitenant] && singular_table_parts.first == 'public'
|
815
|
+
singular_table_parts.shift
|
816
|
+
end
|
817
|
+
options[:class_name] = "::#{assoc[:primary_class]&.name || singular_table_parts.map(&:camelize).join('::')}" if need_class_name
|
731
818
|
# Work around a bug in CPK where self-referencing belongs_to associations double up their foreign keys
|
732
819
|
if need_fk # Funky foreign key?
|
733
820
|
options[:foreign_key] = if assoc[:fk].is_a?(Array)
|
@@ -737,7 +824,7 @@ class Object
|
|
737
824
|
assoc[:fk].to_sym
|
738
825
|
end
|
739
826
|
end
|
740
|
-
options[:inverse_of] = inverse_assoc_name.to_sym if inverse_assoc_name && (need_class_name || need_fk || need_inverse_of)
|
827
|
+
options[:inverse_of] = inverse_assoc_name.tr('.', '_').to_sym if inverse_assoc_name && (need_class_name || need_fk || need_inverse_of)
|
741
828
|
|
742
829
|
# Prepare a list of entries for "has_many :through"
|
743
830
|
if macro == :has_many
|
@@ -748,19 +835,20 @@ class Object
|
|
748
835
|
end
|
749
836
|
end
|
750
837
|
# And finally create a has_one, has_many, or belongs_to for this association
|
751
|
-
assoc_name = assoc_name.to_sym
|
838
|
+
assoc_name = assoc_name.tr('.', '_').to_sym
|
752
839
|
code << " #{macro} #{assoc_name.inspect}#{options.map { |k, v| ", #{k}: #{v.inspect}" }.join}\n"
|
753
840
|
self.send(macro, assoc_name, **options)
|
754
841
|
end
|
755
842
|
|
756
|
-
def build_controller(class_name, plural_class_name, model, relations)
|
843
|
+
def build_controller(namespace, class_name, plural_class_name, model, relations)
|
757
844
|
table_name = ActiveSupport::Inflector.underscore(plural_class_name)
|
758
845
|
singular_table_name = ActiveSupport::Inflector.singularize(table_name)
|
759
846
|
pk = model._brick_primary_key(relations.fetch(table_name, nil))
|
760
847
|
|
761
|
-
|
848
|
+
namespace_name = "#{namespace.name}::" if namespace
|
849
|
+
code = +"class #{namespace_name}#{class_name} < ApplicationController\n"
|
762
850
|
built_controller = Class.new(ActionController::Base) do |new_controller_class|
|
763
|
-
Object.const_set(class_name.to_sym, new_controller_class)
|
851
|
+
(namespace || Object).const_set(class_name.to_sym, new_controller_class)
|
764
852
|
|
765
853
|
code << " def index\n"
|
766
854
|
code << " @#{table_name} = #{model.name}#{pk&.present? ? ".order(#{pk.inspect})" : '.all'}\n"
|
@@ -787,9 +875,10 @@ class Object
|
|
787
875
|
# %%% Add custom HM count columns
|
788
876
|
# %%% What happens when the PK is composite?
|
789
877
|
counts = hm_counts.each_with_object([]) { |v, s| s << "_br_#{v.first}._ct_ AS _br_#{v.first}_ct" }
|
790
|
-
# *selects,
|
791
878
|
instance_variable_set("@#{table_name}".to_sym, ar_relation.dup._select!(*selects, *counts))
|
792
|
-
|
879
|
+
if namespace && (idx = lookup_context.prefixes.index(table_name))
|
880
|
+
lookup_context.prefixes[idx] = "#{namespace.name.underscore}/#{lookup_context.prefixes[idx]}"
|
881
|
+
end
|
793
882
|
@_brick_bt_descrip = bt_descrip
|
794
883
|
@_brick_hm_counts = hm_counts
|
795
884
|
@_brick_join_array = join_array
|
@@ -814,9 +903,9 @@ class Object
|
|
814
903
|
code << " # (Define :new, :create)\n"
|
815
904
|
|
816
905
|
if model.primary_key
|
817
|
-
if (schema = ::Brick.config.schema_to_analyse) && ::Brick.db_schemas&.include?(schema)
|
818
|
-
|
819
|
-
end
|
906
|
+
# if (schema = ::Brick.config.schema_behavior[:multitenant]&.fetch(:schema_to_analyse, nil)) && ::Brick.db_schemas&.include?(schema)
|
907
|
+
# ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?;", schema)
|
908
|
+
# end
|
820
909
|
|
821
910
|
is_need_params = true
|
822
911
|
# code << " # (Define :edit, and :destroy)\n"
|
@@ -827,7 +916,6 @@ class Object
|
|
827
916
|
code << " end\n"
|
828
917
|
self.define_method :update do
|
829
918
|
::Brick.set_db_schema(params)
|
830
|
-
|
831
919
|
if request.format == :csv # Importing CSV?
|
832
920
|
require 'csv'
|
833
921
|
# See if internally it's likely a TSV file (tab-separated)
|
@@ -843,7 +931,9 @@ class Object
|
|
843
931
|
# return
|
844
932
|
end
|
845
933
|
|
846
|
-
|
934
|
+
id = params[:id]&.split(/[\/,_]/)
|
935
|
+
id = id.first if id.is_a?(Array) && id.length == 1
|
936
|
+
instance_variable_set("@#{singular_table_name}".to_sym, (obj = model.find(id)))
|
847
937
|
obj = obj.first if obj.is_a?(Array)
|
848
938
|
obj.send(:update, send(params_name = params_name.to_sym))
|
849
939
|
end
|
@@ -851,7 +941,7 @@ class Object
|
|
851
941
|
|
852
942
|
if is_need_params
|
853
943
|
code << "private\n"
|
854
|
-
code << " def
|
944
|
+
code << " def #{params_name}\n"
|
855
945
|
code << " params.require(:#{singular_table_name}).permit(#{model.columns_hash.keys.map { |c| c.to_sym.inspect }.join(', ')})\n"
|
856
946
|
code << " end\n"
|
857
947
|
self.define_method(params_name) do
|
@@ -861,7 +951,7 @@ class Object
|
|
861
951
|
# Get column names for params from relations[model.table_name][:cols].keys
|
862
952
|
end
|
863
953
|
end
|
864
|
-
code << "end # #{class_name}\n\n"
|
954
|
+
code << "end # #{namespace_name}#{class_name}\n\n"
|
865
955
|
end # class definition
|
866
956
|
[built_controller, code]
|
867
957
|
end
|
@@ -871,7 +961,8 @@ class Object
|
|
871
961
|
plural = ActiveSupport::Inflector.pluralize(hm_assoc[:alternate_name])
|
872
962
|
[hm_assoc[:alternate_name] == name.underscore ? "#{hm_assoc[:assoc_name].singularize}_#{plural}" : plural, true]
|
873
963
|
else
|
874
|
-
|
964
|
+
assoc_name = hm_assoc[:inverse_table].pluralize
|
965
|
+
[assoc_name, assoc_name.include?('.')]
|
875
966
|
end
|
876
967
|
end
|
877
968
|
end
|
@@ -889,18 +980,29 @@ module ActiveRecord::ConnectionHandling
|
|
889
980
|
conn
|
890
981
|
end
|
891
982
|
|
983
|
+
# This is done separately so that during testing it can be called right after a migration
|
984
|
+
# in order to make sure everything is good.
|
892
985
|
def _brick_reflect_tables
|
986
|
+
initializer_loaded = false
|
893
987
|
if (relations = ::Brick.relations).empty?
|
894
|
-
|
895
|
-
|
988
|
+
# If there's schema things configured then we only expect our initializer to be named exactly this
|
989
|
+
if File.exist?(brick_initializer = Rails.root.join('config/initializers/brick.rb'))
|
990
|
+
initializer_loaded = load brick_initializer
|
991
|
+
end
|
992
|
+
# Only for Postgres? (Doesn't work in sqlite3)
|
993
|
+
# puts ActiveRecord::Base.execute_sql("SELECT current_setting('SEARCH_PATH')").to_a.inspect
|
896
994
|
|
897
|
-
|
898
|
-
|
995
|
+
schema_sql = 'SELECT NULL AS table_schema;'
|
996
|
+
case ActiveRecord::Base.connection.adapter_name
|
899
997
|
when 'PostgreSQL'
|
900
|
-
|
998
|
+
if (is_multitenant = (multitenancy = ::Brick.config.schema_behavior[:multitenant]) &&
|
999
|
+
(sta = multitenancy[:schema_to_analyse]) != 'public')
|
1000
|
+
::Brick.default_schema = schema = sta
|
1001
|
+
ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?", schema)
|
1002
|
+
end
|
901
1003
|
schema_sql = 'SELECT DISTINCT table_schema FROM INFORMATION_SCHEMA.tables;'
|
902
1004
|
when 'Mysql2'
|
903
|
-
schema = ActiveRecord::Base.connection.current_database
|
1005
|
+
::Brick.default_schema = schema = ActiveRecord::Base.connection.current_database
|
904
1006
|
when 'SQLite'
|
905
1007
|
sql = "SELECT m.name AS relation_name, UPPER(m.type) AS table_type,
|
906
1008
|
p.name AS column_name, p.type AS data_type,
|
@@ -913,8 +1015,29 @@ module ActiveRecord::ConnectionHandling
|
|
913
1015
|
puts "Unfamiliar with connection adapter #{ActiveRecord::Base.connection.adapter_name}"
|
914
1016
|
end
|
915
1017
|
|
916
|
-
|
917
|
-
|
1018
|
+
unless (db_schemas = ActiveRecord::Base.execute_sql(schema_sql)).is_a?(Array)
|
1019
|
+
db_schemas = db_schemas.to_a
|
1020
|
+
end
|
1021
|
+
unless db_schemas.empty?
|
1022
|
+
::Brick.db_schemas = db_schemas.each_with_object({}) do |row, s|
|
1023
|
+
row = row.is_a?(String) ? row : row['table_schema']
|
1024
|
+
# Remove any system schemas
|
1025
|
+
s[row] = nil unless ['information_schema', 'pg_catalog'].include?(row)
|
1026
|
+
end
|
1027
|
+
end
|
1028
|
+
|
1029
|
+
if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
|
1030
|
+
if (possible_schema = ::Brick.config.schema_behavior&.[](:multitenant)&.[](:schema_to_analyse))
|
1031
|
+
if ::Brick.db_schemas.key?(possible_schema)
|
1032
|
+
::Brick.default_schema = schema = possible_schema
|
1033
|
+
ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?", schema)
|
1034
|
+
else
|
1035
|
+
puts "*** In the brick.rb initializer the line \"::Brick.schema_behavior = ...\" refers to a schema called \"#{possible_schema}\". This schema does not exist. ***"
|
1036
|
+
end
|
1037
|
+
end
|
1038
|
+
end
|
1039
|
+
|
1040
|
+
sql ||= "SELECT t.table_schema AS schema, t.table_name AS relation_name, t.table_type,
|
918
1041
|
c.column_name, c.data_type,
|
919
1042
|
COALESCE(c.character_maximum_length, c.numeric_precision) AS max_length,
|
920
1043
|
tc.constraint_type AS const, kcu.constraint_name AS \"key\",
|
@@ -932,18 +1055,22 @@ module ActiveRecord::ConnectionHandling
|
|
932
1055
|
ON kcu.CONSTRAINT_SCHEMA = tc.CONSTRAINT_SCHEMA
|
933
1056
|
AND kcu.TABLE_NAME = tc.TABLE_NAME
|
934
1057
|
AND kcu.CONSTRAINT_NAME = tc.constraint_name
|
935
|
-
WHERE t.table_schema
|
1058
|
+
WHERE t.table_schema NOT IN ('information_schema', 'pg_catalog')#{"
|
1059
|
+
AND t.table_schema = COALESCE(current_setting('SEARCH_PATH'), 'public')" if schema }
|
936
1060
|
-- AND t.table_type IN ('VIEW') -- 'BASE TABLE', 'FOREIGN TABLE'
|
937
1061
|
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"
|
939
|
-
])
|
940
|
-
|
1062
|
+
ORDER BY 1, t.table_type DESC, c.ordinal_position"
|
941
1063
|
measures = []
|
942
1064
|
case ActiveRecord::Base.connection.adapter_name
|
943
1065
|
when 'PostgreSQL', 'SQLite' # These bring back a hash for each row because the query uses column aliases
|
1066
|
+
# schema ||= 'public' if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
|
944
1067
|
ActiveRecord::Base.execute_sql(sql).each do |r|
|
945
|
-
|
946
|
-
|
1068
|
+
relation_name = if ![schema, 'public'].include?(r['schema']) && !::Brick.config.schema_behavior[:multitenant]
|
1069
|
+
"#{schema_name = r['schema']}.#{r['relation_name']}"
|
1070
|
+
else
|
1071
|
+
r['relation_name']
|
1072
|
+
end
|
1073
|
+
relation = relations[relation_name]
|
947
1074
|
relation[:isView] = true if r['table_type'] == 'VIEW'
|
948
1075
|
col_name = r['column_name']
|
949
1076
|
key = case r['const']
|
@@ -961,7 +1088,6 @@ module ActiveRecord::ConnectionHandling
|
|
961
1088
|
end
|
962
1089
|
else # MySQL2 acts a little differently, bringing back an array for each row
|
963
1090
|
ActiveRecord::Base.execute_sql(sql).each do |r|
|
964
|
-
# next if internal_views.include?(r['relation_name']) # Skip internal views such as v_all_assessments
|
965
1091
|
relation = relations[(relation_name = r[0])] # here relation represents a table or view from the database
|
966
1092
|
relation[:isView] = true if r[1] == 'VIEW' # table_type
|
967
1093
|
col_name = r[2]
|
@@ -1000,11 +1126,11 @@ module ActiveRecord::ConnectionHandling
|
|
1000
1126
|
# end
|
1001
1127
|
# end
|
1002
1128
|
# end
|
1003
|
-
|
1129
|
+
# schema = ::Brick.default_schema # Reset back for this next round of fun
|
1004
1130
|
case ActiveRecord::Base.connection.adapter_name
|
1005
1131
|
when 'PostgreSQL', 'Mysql2'
|
1006
|
-
sql =
|
1007
|
-
|
1132
|
+
sql = "SELECT kcu1.CONSTRAINT_SCHEMA, kcu1.TABLE_NAME, kcu1.COLUMN_NAME,
|
1133
|
+
kcu2.CONSTRAINT_SCHEMA AS primary_schema, kcu2.TABLE_NAME AS primary_table, kcu1.CONSTRAINT_NAME AS CONSTRAINT_SCHEMA_FK
|
1008
1134
|
FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS AS rc
|
1009
1135
|
INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS kcu1
|
1010
1136
|
ON kcu1.CONSTRAINT_CATALOG = rc.CONSTRAINT_CATALOG
|
@@ -1014,10 +1140,9 @@ module ActiveRecord::ConnectionHandling
|
|
1014
1140
|
ON kcu2.CONSTRAINT_CATALOG = rc.UNIQUE_CONSTRAINT_CATALOG
|
1015
1141
|
AND kcu2.CONSTRAINT_SCHEMA = rc.UNIQUE_CONSTRAINT_SCHEMA
|
1016
1142
|
AND kcu2.CONSTRAINT_NAME = rc.UNIQUE_CONSTRAINT_NAME
|
1017
|
-
AND kcu2.ORDINAL_POSITION = kcu1.ORDINAL_POSITION
|
1018
|
-
WHERE kcu1.CONSTRAINT_SCHEMA =
|
1143
|
+
AND kcu2.ORDINAL_POSITION = kcu1.ORDINAL_POSITION#{"
|
1144
|
+
WHERE kcu1.CONSTRAINT_SCHEMA = COALESCE(current_setting('SEARCH_PATH'), 'public')" if schema }"
|
1019
1145
|
# AND kcu2.TABLE_NAME = ?;", Apartment::Tenant.current, table_name
|
1020
|
-
])
|
1021
1146
|
when 'SQLite'
|
1022
1147
|
sql = "SELECT m.name, fkl.\"from\", fkl.\"table\", m.name || '_' || fkl.\"from\" AS constraint_name
|
1023
1148
|
FROM sqlite_master m
|
@@ -1026,12 +1151,12 @@ module ActiveRecord::ConnectionHandling
|
|
1026
1151
|
else
|
1027
1152
|
end
|
1028
1153
|
if sql
|
1029
|
-
::Brick.
|
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']
|
1154
|
+
# ::Brick.default_schema ||= schema ||= 'public' if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
|
1033
1155
|
ActiveRecord::Base.execute_sql(sql).each do |fk|
|
1034
1156
|
fk = fk.values unless fk.is_a?(Array)
|
1157
|
+
# Multitenancy makes things a little more general overall
|
1158
|
+
fk[0] = nil if fk[0] == 'public' || (is_multitenant && fk[0] == schema)
|
1159
|
+
fk[3] = nil if fk[3] == 'public' || (is_multitenant && fk[3] == schema)
|
1035
1160
|
::Brick._add_bt_and_hm(fk, relations)
|
1036
1161
|
end
|
1037
1162
|
end
|
@@ -1044,11 +1169,7 @@ module ActiveRecord::ConnectionHandling
|
|
1044
1169
|
views.keys.each { |k| puts ActiveSupport::Inflector.singularize(k).camelize }
|
1045
1170
|
end
|
1046
1171
|
|
1047
|
-
|
1048
|
-
if File.exist?(brick_initialiser = Rails.root.join('config/initializers/brick.rb'))
|
1049
|
-
load brick_initialiser
|
1050
|
-
::Brick.load_additional_references
|
1051
|
-
end
|
1172
|
+
::Brick.load_additional_references if initializer_loaded
|
1052
1173
|
end
|
1053
1174
|
end
|
1054
1175
|
|
@@ -1075,35 +1196,53 @@ module Brick
|
|
1075
1196
|
# rubocop:enable Style/CommentedKeyword
|
1076
1197
|
|
1077
1198
|
class << self
|
1078
|
-
def _add_bt_and_hm(fk, relations, is_polymorphic = false)
|
1079
|
-
bt_assoc_name = fk[
|
1080
|
-
|
1081
|
-
|
1082
|
-
|
1199
|
+
def _add_bt_and_hm(fk, relations, is_polymorphic = false, is_optional = false)
|
1200
|
+
bt_assoc_name = fk[2]
|
1201
|
+
unless is_polymorphic
|
1202
|
+
bt_assoc_name = if bt_assoc_name.underscore.end_with?('_id')
|
1203
|
+
bt_assoc_name[0..-4]
|
1204
|
+
elsif bt_assoc_name.downcase.end_with?('id') && bt_assoc_name.exclude?('_')
|
1205
|
+
bt_assoc_name[0..-3] # Make the bold assumption that we can just peel off any final ID part
|
1206
|
+
else
|
1207
|
+
"#{bt_assoc_name}_bt"
|
1208
|
+
end
|
1209
|
+
end
|
1210
|
+
# %%% Temporary schema patch
|
1211
|
+
for_tbl = fk[1]
|
1212
|
+
fk[1] = "#{fk[0]}.#{fk[1]}" if fk[0] # && fk[0] != ::Brick.default_schema
|
1213
|
+
bts = (relation = relations.fetch(fk[1], nil))&.fetch(:fks) { relation[:fks] = {} }
|
1083
1214
|
# %%% Do we miss out on has_many :through or even HM based on constantizing this model early?
|
1084
1215
|
# 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[
|
1216
|
+
primary_table = if (is_class = fk[4].is_a?(Hash) && fk[4].key?(:class))
|
1217
|
+
pri_tbl = (primary_class = fk[4][:class].constantize).table_name
|
1218
|
+
else
|
1219
|
+
is_schema = fk[3] != ::Brick.default_schema && (::Brick.config.schema_behavior[:multitenant] || fk[3] != 'public')
|
1220
|
+
pri_tbl = fk[4]
|
1221
|
+
fk[3] && is_schema ? "#{fk[3]}.#{pri_tbl}" : pri_tbl
|
1222
|
+
end
|
1086
1223
|
hms = (relation = relations.fetch(primary_table, nil))&.fetch(:fks) { relation[:fks] = {} } unless is_class
|
1087
1224
|
|
1088
|
-
unless (cnstr_name = fk[
|
1225
|
+
unless (cnstr_name = fk[5])
|
1089
1226
|
# For any appended references (those that come from config), arrive upon a definitely unique constraint name
|
1090
|
-
|
1227
|
+
pri_tbl = is_class ? fk[4][:class].underscore : pri_tbl
|
1228
|
+
pri_tbl = "#{bt_assoc_name}_#{pri_tbl}" if pri_tbl.singularize != bt_assoc_name
|
1229
|
+
cnstr_base = cnstr_name = "(brick) #{for_tbl}_#{pri_tbl}"
|
1091
1230
|
cnstr_added_num = 1
|
1092
1231
|
cnstr_name = "#{cnstr_base}_#{cnstr_added_num += 1}" while bts&.key?(cnstr_name) || hms&.key?(cnstr_name)
|
1093
1232
|
missing = []
|
1094
|
-
missing << fk[
|
1233
|
+
missing << fk[1] unless relations.key?(fk[1])
|
1095
1234
|
missing << primary_table unless is_class || relations.key?(primary_table)
|
1096
1235
|
unless missing.empty?
|
1097
1236
|
tables = relations.reject { |_k, v| v.fetch(:isView, nil) }.keys.sort
|
1098
1237
|
puts "Brick: Additional reference #{fk.inspect} refers to non-existent #{'table'.pluralize(missing.length)} #{missing.join(' and ')}. (Available tables include #{tables.join(', ')}.)"
|
1099
1238
|
return
|
1100
1239
|
end
|
1101
|
-
unless (cols = relations[fk[
|
1240
|
+
unless (cols = relations[fk[1]][:cols]).key?(fk[2]) || (is_polymorphic && cols.key?("#{fk[2]}_id") && cols.key?("#{fk[2]}_type"))
|
1102
1241
|
columns = cols.map { |k, v| "#{k} (#{v.first.split(' ').first})" }
|
1103
|
-
puts "Brick: Additional reference #{fk.inspect} refers to non-existent column #{fk[
|
1242
|
+
puts "Brick: Additional reference #{fk.inspect} refers to non-existent column #{fk[2]}. (Columns present in #{fk[1]} are #{columns.join(', ')}.)"
|
1104
1243
|
return
|
1105
1244
|
end
|
1106
|
-
if (redundant = bts.find { |_k, v| v[:inverse]&.fetch(:inverse_table, nil) == fk[
|
1245
|
+
if (redundant = bts.find { |_k, v| v[:inverse]&.fetch(:inverse_table, nil) == fk[1] && v[:fk] == fk[2] && v[:inverse_table] == primary_table })
|
1107
1246
|
if is_class && !redundant.last.key?(:class)
|
1108
1247
|
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
1248
|
else
|
@@ -1115,18 +1254,19 @@ module Brick
|
|
1115
1254
|
if (assoc_bt = bts[cnstr_name])
|
1116
1255
|
if is_polymorphic
|
1117
1256
|
# Assuming same fk (don't yet support composite keys for polymorphics)
|
1118
|
-
assoc_bt[:inverse_table] << fk[
|
1257
|
+
assoc_bt[:inverse_table] << fk[4]
|
1119
1258
|
else # Expect we could have a composite key going
|
1120
1259
|
if assoc_bt[:fk].is_a?(String)
|
1121
|
-
assoc_bt[:fk] = [assoc_bt[:fk], fk[
|
1122
|
-
elsif assoc_bt[:fk].exclude?(fk[
|
1123
|
-
assoc_bt[:fk] << fk[
|
1260
|
+
assoc_bt[:fk] = [assoc_bt[:fk], fk[2]] unless fk[2] == assoc_bt[:fk]
|
1261
|
+
elsif assoc_bt[:fk].exclude?(fk[2])
|
1262
|
+
assoc_bt[:fk] << fk[2]
|
1124
1263
|
end
|
1125
|
-
assoc_bt[:assoc_name] = "#{assoc_bt[:assoc_name]}_#{fk[
|
1264
|
+
assoc_bt[:assoc_name] = "#{assoc_bt[:assoc_name]}_#{fk[2]}"
|
1126
1265
|
end
|
1127
1266
|
else
|
1128
1267
|
inverse_table = [primary_table] if is_polymorphic
|
1129
|
-
assoc_bt = bts[cnstr_name] = { is_bt: true, fk: fk[
|
1268
|
+
assoc_bt = bts[cnstr_name] = { is_bt: true, fk: fk[2], assoc_name: bt_assoc_name, inverse_table: inverse_table || primary_table }
|
1269
|
+
assoc_bt[:optional] = true if is_optional
|
1130
1270
|
assoc_bt[:polymorphic] = true if is_polymorphic
|
1131
1271
|
end
|
1132
1272
|
if is_class
|
@@ -1136,21 +1276,21 @@ module Brick
|
|
1136
1276
|
# assoc_bt[:inverse_of] = primary_class.reflect_on_all_associations.find { |a| a.foreign_key == bt[1] }
|
1137
1277
|
end
|
1138
1278
|
|
1139
|
-
return if is_class || ::Brick.config.exclude_hms&.any? { |exclusion| fk[
|
1279
|
+
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
1280
|
|
1141
1281
|
if (assoc_hm = hms.fetch((hm_cnstr_name = "hm_#{cnstr_name}"), nil))
|
1142
1282
|
if assoc_hm[:fk].is_a?(String)
|
1143
|
-
assoc_hm[:fk] = [assoc_hm[:fk], fk[
|
1144
|
-
elsif assoc_hm[:fk].exclude?(fk[
|
1145
|
-
assoc_hm[:fk] << fk[
|
1283
|
+
assoc_hm[:fk] = [assoc_hm[:fk], fk[2]] unless fk[2] == assoc_hm[:fk]
|
1284
|
+
elsif assoc_hm[:fk].exclude?(fk[2])
|
1285
|
+
assoc_hm[:fk] << fk[2]
|
1146
1286
|
end
|
1147
1287
|
assoc_hm[:alternate_name] = "#{assoc_hm[:alternate_name]}_#{bt_assoc_name}" unless assoc_hm[:alternate_name] == bt_assoc_name
|
1148
1288
|
assoc_hm[:inverse] = assoc_bt
|
1149
1289
|
else
|
1150
|
-
assoc_hm = hms[hm_cnstr_name] = { is_bt: false, fk: fk[
|
1290
|
+
assoc_hm = hms[hm_cnstr_name] = { is_bt: false, fk: fk[2], assoc_name: for_tbl.pluralize, alternate_name: bt_assoc_name, inverse_table: fk[1], inverse: assoc_bt }
|
1151
1291
|
assoc_hm[:polymorphic] = true if is_polymorphic
|
1152
1292
|
hm_counts = relation.fetch(:hm_counts) { relation[:hm_counts] = {} }
|
1153
|
-
hm_counts[fk[
|
1293
|
+
hm_counts[fk[1]] = hm_counts.fetch(fk[1]) { 0 } + 1
|
1154
1294
|
end
|
1155
1295
|
assoc_bt[:inverse] = assoc_hm
|
1156
1296
|
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
|
-
|
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,
|
77
|
+
[[fk_name, pk.length == 1 ? pk.first : pk.inspect]]
|
76
78
|
end
|
77
|
-
keys << [hm_assoc.inverse_of.foreign_type,
|
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
|
-
|
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;
|
@@ -219,7 +222,7 @@ end %>"
|
|
219
222
|
if ['index', 'show', 'update'].include?(args.first)
|
220
223
|
poly_cols = []
|
221
224
|
css << "<% bts = { #{
|
222
|
-
bts.each_with_object([]) do |v, s|
|
225
|
+
bt_items = bts.each_with_object([]) do |v, s|
|
223
226
|
foreign_models = if v.last[2] # Polymorphic?
|
224
227
|
poly_cols << @_brick_model.reflect_on_association(v[1].first).foreign_type
|
225
228
|
v.last[1].each_with_object([]) { |x, s| s << "[#{x.name}, #{x.primary_key.inspect}]" }.join(', ')
|
@@ -227,7 +230,10 @@ end %>"
|
|
227
230
|
"[#{v.last[1].name}, #{v.last[1].primary_key.inspect}]"
|
228
231
|
end
|
229
232
|
s << "#{v.first.inspect} => [#{v.last.first.inspect}, [#{foreign_models}], #{v.last[2].inspect}]"
|
230
|
-
end
|
233
|
+
end
|
234
|
+
# # %%% Need to fix poly going to an STI class
|
235
|
+
# binding.pry unless poly_cols.empty?
|
236
|
+
bt_items.join(', ')
|
231
237
|
} }
|
232
238
|
poly_cols = #{poly_cols.inspect} %>"
|
233
239
|
end
|
@@ -261,7 +267,8 @@ if (schemaSelect) {
|
|
261
267
|
|
262
268
|
var tblSelect = document.getElementById(\"tbl\");
|
263
269
|
if (tblSelect) {
|
264
|
-
tblSelect.value = changeout(location.href);
|
270
|
+
tblSelect.value = changeout(location.href)[0];
|
271
|
+
if (tblSelect.selectedIndex < 0) tblSelect.value = changeout(location.href)[1];
|
265
272
|
tblSelect.addEventListener(\"change\", function () {
|
266
273
|
var lhr = changeout(location.href, null, this.value);
|
267
274
|
if (brickSchema)
|
@@ -276,7 +283,8 @@ function changeout(href, param, value) {
|
|
276
283
|
hrefParts = hrefParts[0].split(\"://\");
|
277
284
|
var pathParts = hrefParts[hrefParts.length - 1].split(\"/\");
|
278
285
|
if (value === undefined)
|
279
|
-
|
286
|
+
// A couple possibilities if it's namespaced, starting with two parts in the path -- and then try just one
|
287
|
+
return [pathParts.slice(1, 3).join('/'), pathParts.slice(1, 2)];
|
280
288
|
else
|
281
289
|
return hrefParts[0] + \"://\" + pathParts[0] + \"/\" + value;
|
282
290
|
}
|
@@ -313,7 +321,7 @@ function changeout(href, param, value) {
|
|
313
321
|
btnImport.style.display = droppedTSV.length > 0 ? \"block\" : \"none\";
|
314
322
|
});
|
315
323
|
btnImport.addEventListener(\"click\", function () {
|
316
|
-
fetch(changeout(<%= #{
|
324
|
+
fetch(changeout(<%= #{path_obj_name}_path(-1, format: :csv).inspect.html_safe %>, \"_brick_schema\", brickSchema), {
|
317
325
|
method: 'PATCH',
|
318
326
|
headers: { 'Content-Type': 'text/tab-separated-values' },
|
319
327
|
body: droppedTSV
|
@@ -384,17 +392,32 @@ function changeout(href, param, value) {
|
|
384
392
|
<script async defer src=\"https://apis.google.com/js/api.js\" onload=\"gapiLoaded()\"></script>
|
385
393
|
"
|
386
394
|
end
|
395
|
+
# %%% Instead of our current "for Janet Leverling (Employee)" kind of link we previously had this code that did a "where x = 123" thing:
|
396
|
+
# (where <%= @_brick_params.each_with_object([]) { |v, s| s << \"#\{v.first\} = #\{v.last.inspect\}\" }.join(', ') %>)
|
387
397
|
"#{css}
|
388
398
|
<p style=\"color: green\"><%= notice %></p>#{"
|
389
|
-
<select id=\"schema\">#{schema_options}</select>" if ::Brick.db_schemas.length > 1}
|
399
|
+
<select id=\"schema\">#{schema_options}</select>" if ::Brick.config.schema_behavior[:multitenant] && ::Brick.db_schemas.length > 1}
|
390
400
|
<select id=\"tbl\">#{table_options}</select>
|
391
|
-
<h1>#{model_name.pluralize}</h1>#{template_link}
|
392
|
-
|
393
|
-
<% if @_brick_params&.present?
|
401
|
+
<h1>#{model_plural = model_name.pluralize}</h1>#{template_link}
|
402
|
+
|
403
|
+
<% if @_brick_params&.present? %>
|
404
|
+
<% if @_brick_params.length == 1 # %%% Does not yet work with composite keys
|
405
|
+
k, id = @_brick_params.first
|
406
|
+
id = id.first if id.is_a?(Array) && id.length == 1
|
407
|
+
origin = (key_parts = k.split('.')).length == 1 ? #{model_name} : #{model_name}.reflect_on_association(key_parts.first).klass
|
408
|
+
# binding.pry
|
409
|
+
if (destination_fk = Brick.relations[origin.table_name][:fks].values.find { |fk| puts fk.inspect; fk[:fk] == key_parts.last }) &&
|
410
|
+
(obj = (destination = origin.reflect_on_association(destination_fk[:assoc_name])&.klass)&.find(id)) %>
|
411
|
+
<h3>for <%= link_to \"#{"#\{obj.brick_descrip\} (#\{destination.name\})\""}, send(\"#\{destination.name.underscore.tr('/', '_')\}_path\".to_sym, id) %></h3><%
|
412
|
+
end
|
413
|
+
end %>
|
414
|
+
(<%= link_to 'See all #{model_plural.split('::').last}', #{path_obj_name.pluralize}_path %>)
|
415
|
+
<% end %>
|
394
416
|
<table id=\"#{table_name}\">
|
395
417
|
<thead><tr>#{'<th></th>' if pk.present?}
|
396
418
|
<% @#{table_name}.columns.map(&:name).each do |col| %>
|
397
|
-
<% next if #{(pk || []).inspect}.include?(col)
|
419
|
+
<% next if (#{(pk || []).inspect}.include?(col) && #{model_name}.column_for_attribute(col).type == :integer && !bts.key?(col)) ||
|
420
|
+
::Brick.config.metadata_columns.include?(col) || poly_cols.include?(col) %>
|
398
421
|
<th>
|
399
422
|
<% if (bt = bts[col]) %>
|
400
423
|
BT <%
|
@@ -407,15 +430,16 @@ function changeout(href, param, value) {
|
|
407
430
|
</th>
|
408
431
|
<% end %>
|
409
432
|
<%# 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}
|
433
|
+
#{hms_headers.map { |h| "<th>#{h[1]} <%= link_to('#{h[2]}', #{h.first.klass.name.underscore.tr('/', '_').pluralize}_path) %></th>\n" }.join}
|
411
434
|
</tr></thead>
|
412
435
|
|
413
436
|
<tbody>
|
414
437
|
<% @#{table_name}.each do |#{obj_name}| %>
|
415
438
|
<tr>#{"
|
416
|
-
<td><%= link_to '⇛', #{
|
439
|
+
<td><%= link_to '⇛', #{path_obj_name}_path(#{obj_pk}), { class: 'big-arrow' } %></td>" if obj_pk}
|
417
440
|
<% #{obj_name}.attributes.each do |k, val| %>
|
418
|
-
<% next if #{(obj_pk || []).inspect}.include?(k)
|
441
|
+
<% next if (#{(obj_pk || []).inspect}.include?(k) && #{model_name}.column_for_attribute(k).type == :integer && !bts.key?(k)) ||
|
442
|
+
::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
443
|
<td>
|
420
444
|
<% if (bt = bts[k]) %>
|
421
445
|
<%# binding.pry # Postgres column names are limited to 63 characters %>
|
@@ -430,7 +454,7 @@ function changeout(href, param, value) {
|
|
430
454
|
#{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
455
|
)
|
432
456
|
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 %>
|
457
|
+
<%= bt_id ? link_to(bt_txt, send(\"#\{bt_class.base_class.name.underscore.tr('/', '_')\}_path\".to_sym, bt_id)) : bt_txt %>
|
434
458
|
<%#= 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
459
|
<% end %>
|
436
460
|
<% else %>
|
@@ -440,19 +464,19 @@ function changeout(href, param, value) {
|
|
440
464
|
<% end %>
|
441
465
|
#{hms_columns.each_with_object(+'') { |hm_col, s| s << "<td>#{hm_col}</td>" }}
|
442
466
|
</tr>
|
443
|
-
</tbody>
|
444
467
|
<% end %>
|
468
|
+
</tbody>
|
445
469
|
</table>
|
446
470
|
|
447
|
-
#{"<hr><%= link_to \"New #{obj_name}\", new_#{
|
471
|
+
#{"<hr><%= link_to \"New #{obj_name}\", new_#{path_obj_name}_path %>" unless @_brick_model.is_view?}
|
448
472
|
#{script}"
|
449
473
|
when 'show', 'update'
|
450
474
|
"#{css}
|
451
475
|
<p style=\"color: green\"><%= notice %></p>#{"
|
452
|
-
<select id=\"schema\">#{schema_options}</select>" if ::Brick.db_schemas.length > 1}
|
476
|
+
<select id=\"schema\">#{schema_options}</select>" if ::Brick.config.schema_behavior[:multitenant] && ::Brick.db_schemas.length > 1}
|
453
477
|
<select id=\"tbl\">#{table_options}</select>
|
454
478
|
<h1>#{model_name}: <%= (obj = @#{obj_name})&.brick_descrip || controller_name %></h1>
|
455
|
-
<%= link_to '(See all #{obj_name.pluralize})', #{
|
479
|
+
<%= link_to '(See all #{obj_name.pluralize})', #{path_obj_name.pluralize}_path %>
|
456
480
|
<% if obj %>
|
457
481
|
<%= # path_options = [obj.#{pk}]
|
458
482
|
# path_options << { '_brick_schema': } if
|
@@ -462,7 +486,8 @@ function changeout(href, param, value) {
|
|
462
486
|
<% has_fields = false
|
463
487
|
@#{obj_name}.attributes.each do |k, val| %>
|
464
488
|
<tr>
|
465
|
-
<% next if #{(pk || []).inspect}.include?(k)
|
489
|
+
<% next if (#{(pk || []).inspect}.include?(k) && !bts.key?(k)) ||
|
490
|
+
::Brick.config.metadata_columns.include?(k) %>
|
466
491
|
<th class=\"show-field\">
|
467
492
|
<% has_fields = true
|
468
493
|
if (bt = bts[k])
|
@@ -504,7 +529,7 @@ function changeout(href, param, value) {
|
|
504
529
|
html_options = { prompt: \"Select #\{bt_name\}\" }
|
505
530
|
html_options[:class] = 'dimmed' unless val %>
|
506
531
|
<%= 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 %>
|
532
|
+
<%= 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
533
|
<% else case #{model_name}.column_for_attribute(k).type
|
509
534
|
when :string, :text %>
|
510
535
|
<% if is_bcrypt?(val) # || .readonly? %>
|
@@ -546,7 +571,7 @@ function changeout(href, param, value) {
|
|
546
571
|
<tr><td>(none)</td></tr>
|
547
572
|
<% else %>
|
548
573
|
<% 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>
|
574
|
+
<tr><td><%= link_to(#{hm_singular_name}.brick_descrip, #{hm.first.klass.name.underscore.tr('/', '_')}_path([#{obj_pk}])) %></td></tr>
|
550
575
|
<% end %>
|
551
576
|
<% end %>
|
552
577
|
</table>"
|
data/lib/brick/version_number.rb
CHANGED
data/lib/brick.rb
CHANGED
@@ -90,7 +90,7 @@ 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
96
|
schema = params['_brick_schema'] || 'public'
|
@@ -99,10 +99,13 @@ module Brick
|
|
99
99
|
|
100
100
|
# All tables and views (what Postgres calls "relations" including column and foreign key info)
|
101
101
|
def relations
|
102
|
-
connections = Brick.instance_variable_get(:@relations) ||
|
103
|
-
Brick.instance_variable_set(:@relations, (connections = {}))
|
104
102
|
# Key our list of relations for this connection off of the connection pool's object_id
|
105
|
-
(
|
103
|
+
(@relations ||= {})[ActiveRecord::Base.connection_pool.object_id] ||= Hash.new { |h, k| h[k] = Hash.new { |h, k| h[k] = {} } }
|
104
|
+
end
|
105
|
+
|
106
|
+
# If multitenancy is enabled, a list of non-tenanted "global" models
|
107
|
+
def non_tenanted_models
|
108
|
+
@pending_models ||= {}
|
106
109
|
end
|
107
110
|
|
108
111
|
def get_bts_and_hms(model)
|
@@ -287,8 +290,16 @@ module Brick
|
|
287
290
|
# Database schema to use when analysing existing data, such as deriving a list of polymorphic classes
|
288
291
|
# for polymorphics in which it wasn't originally specified.
|
289
292
|
# @api public
|
290
|
-
def
|
291
|
-
Brick.config.
|
293
|
+
def schema_behavior=(behavior)
|
294
|
+
Brick.config.schema_behavior = (behavior.is_a?(Symbol) ? { behavior => nil } : behavior)
|
295
|
+
end
|
296
|
+
# For any Brits out there
|
297
|
+
def schema_behaviour=(behavior)
|
298
|
+
Brick.schema_behavior = behavior
|
299
|
+
end
|
300
|
+
|
301
|
+
def sti_type_column=(type_col)
|
302
|
+
Brick.config.sti_type_column = (type_col.is_a?(String) ? { type_col => nil } : type_col)
|
292
303
|
end
|
293
304
|
|
294
305
|
def default_route_fallback=(resource_name)
|
@@ -303,18 +314,23 @@ module Brick
|
|
303
314
|
|
304
315
|
relations = ::Brick.relations
|
305
316
|
if (ars = ::Brick.config.additional_references) || ::Brick.config.polymorphics
|
306
|
-
|
317
|
+
if ars
|
318
|
+
ars.each do |ar|
|
319
|
+
fk = ar.length < 5 ? [nil, +ar[0], ar[1], nil, +ar[2]] : [ar[0], +ar[1], ar[2], ar[3], +ar[4], ar[5]]
|
320
|
+
::Brick._add_bt_and_hm(fk, relations, false, true)
|
321
|
+
end
|
322
|
+
end
|
307
323
|
if (polys = ::Brick.config.polymorphics)
|
308
|
-
if (schema = ::Brick.config.schema_to_analyse) && ::Brick.db_schemas&.include?(schema)
|
324
|
+
if (schema = ::Brick.config.schema_behavior[:multitenant]&.fetch(:schema_to_analyse, nil)) && ::Brick.db_schemas&.include?(schema)
|
309
325
|
ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?;", schema)
|
310
326
|
end
|
311
327
|
missing_stis = {}
|
312
328
|
polys.each do |k, v|
|
313
329
|
table_name, poly = k.split('.')
|
314
|
-
v ||= ActiveRecord::Base.execute_sql("SELECT DISTINCT #{poly}_type AS typ FROM #{table_name}").
|
330
|
+
v ||= ActiveRecord::Base.execute_sql("SELECT DISTINCT #{poly}_type AS typ FROM #{table_name}").each_with_object([]) { |result, s| s << result['typ'] if result['typ'] }
|
315
331
|
v.each do |type|
|
316
332
|
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)
|
333
|
+
::Brick._add_bt_and_hm([nil, table_name, poly, nil, primary_table, "(brick) #{table_name}_#{poly}"], relations, true, true)
|
318
334
|
else
|
319
335
|
missing_stis[primary_table] = type unless ::Brick.existing_stis.key?(type)
|
320
336
|
end
|
@@ -390,11 +406,20 @@ In config/initializers/brick.rb appropriate entries would look something like:
|
|
390
406
|
end
|
391
407
|
# %%% TODO: If no auto-controllers then enumerate the controllers folder in order to build matching routes
|
392
408
|
# If auto-controllers and auto-models are both enabled then this makes sense:
|
393
|
-
::Brick.relations.each do |
|
394
|
-
|
409
|
+
::Brick.relations.each do |rel_name, v|
|
410
|
+
rel_name = rel_name.split('.').map(&:underscore)
|
411
|
+
schema_names = rel_name[0..-2]
|
412
|
+
k = rel_name.last
|
413
|
+
unless existing_controllers.key?(controller_name = k.pluralize)
|
395
414
|
options = {}
|
396
415
|
options[:only] = [:index, :show] if v.key?(:isView)
|
397
|
-
|
416
|
+
if schema_names.present? # && !Object.const_defined('Apartment')
|
417
|
+
send(:namespace, schema_names.first) do
|
418
|
+
send(:resources, controller_name.to_sym, **options)
|
419
|
+
end
|
420
|
+
else
|
421
|
+
send(:resources, controller_name.to_sym, **options)
|
422
|
+
end
|
398
423
|
end
|
399
424
|
end
|
400
425
|
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.
|
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.
|
4
|
+
version: 1.0.32
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Lorin Thwaits
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-06-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|