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