brick 1.0.76 → 1.0.78
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 +9 -0
- data/lib/brick/extensions.rb +244 -111
- data/lib/brick/frameworks/rails/engine.rb +63 -42
- data/lib/brick/version_number.rb +1 -1
- data/lib/brick.rb +44 -16
- data/lib/generators/brick/install_generator.rb +4 -0
- 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: 52f0716e20661f909922bdcddf3401b79b87c33c7b118d1581dfdf9861dd702c
|
4
|
+
data.tar.gz: 45323c512a8c7490b0ac715595ead6e6299d06a442b9c723318b90bcdce6106d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2ef6ac2089e1150bc9c74c6625fc3e7627f5ee06a42d1a2d5fe43790d99156abacc27ba2e042044ee42171f352718d99ae8b468ec9dca4350cd6631bead51ce7
|
7
|
+
data.tar.gz: 8c4da365eb99677930a9f35f0cd5981b91adc790a13ecf55b5369da03fdf1592cccd01f911457cf204aa8769767d5a8537d856ff7080dec75543524b3e680904
|
data/lib/brick/config.rb
CHANGED
@@ -20,6 +20,15 @@ module Brick
|
|
20
20
|
@serializer = Brick::Serializers::YAML
|
21
21
|
end
|
22
22
|
|
23
|
+
# Any path prefixing to apply to all auto-generated Brick routes
|
24
|
+
def path_prefix
|
25
|
+
@mutex.synchronize { @path_prefix }
|
26
|
+
end
|
27
|
+
|
28
|
+
def path_prefix=(path)
|
29
|
+
@mutex.synchronize { @path_prefix = path }
|
30
|
+
end
|
31
|
+
|
23
32
|
# Indicates whether Brick models are on or off. Default: true.
|
24
33
|
def enable_models
|
25
34
|
@mutex.synchronize { !!@enable_models }
|
data/lib/brick/extensions.rb
CHANGED
@@ -256,12 +256,13 @@ module ActiveRecord
|
|
256
256
|
table_name == assoc_name ? link : "#{assoc_name}-#{link}".html_safe
|
257
257
|
end
|
258
258
|
|
259
|
-
def self._brick_index
|
260
|
-
tbl_parts = table_name.split('.')
|
261
|
-
tbl_parts.shift if ::Brick.apartment_multitenant && tbl_parts.first == Apartment.default_schema
|
262
|
-
|
263
|
-
|
264
|
-
|
259
|
+
def self._brick_index(mode = nil)
|
260
|
+
tbl_parts = ((mode == :singular) ? table_name.singularize : table_name).split('.')
|
261
|
+
tbl_parts.shift if ::Brick.apartment_multitenant && tbl_parts.length > 1 && tbl_parts.first == Apartment.default_schema
|
262
|
+
tbl_parts.unshift(::Brick.config.path_prefix) if ::Brick.config.path_prefix
|
263
|
+
index = tbl_parts.map(&:underscore).join('_')
|
264
|
+
# Rails applies an _index suffix to that route when the resource name is singular
|
265
|
+
index << '_index' if mode != :singular && index == index.singularize
|
265
266
|
index
|
266
267
|
end
|
267
268
|
|
@@ -554,7 +555,10 @@ module ActiveRecord
|
|
554
555
|
tbl_name = "\"#{tbl_name}\"" if ::Brick.is_oracle && rel_dupe._arel_applied_aliases.include?(tbl_name)
|
555
556
|
field_tbl_name = nil
|
556
557
|
v1.map { |x| [translations[x[0..-2].map(&:to_s).join('.')], x.last] }.each_with_index do |sel_col, idx|
|
557
|
-
#
|
558
|
+
# unless chains[sel_col.first]
|
559
|
+
# puts 'You might have some bogus DSL in your brick.rb file'
|
560
|
+
# next
|
561
|
+
# end
|
558
562
|
field_tbl_name = (field_tbl_names[v.first][sel_col.first] ||= shift_or_first(chains[sel_col.first])).split('.').last
|
559
563
|
# If it's Oracle, quote any AREL aliases that had been applied
|
560
564
|
field_tbl_name = "\"#{field_tbl_name}\"" if ::Brick.is_oracle && rel_dupe._arel_applied_aliases.include?(field_tbl_name)
|
@@ -605,15 +609,42 @@ module ActiveRecord
|
|
605
609
|
end
|
606
610
|
end
|
607
611
|
# Add derived table JOIN for the has_many counts
|
612
|
+
nix = []
|
608
613
|
klass._br_hm_counts.each do |k, hm|
|
609
|
-
associative = nil
|
610
614
|
count_column = if hm.options[:through]
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
615
|
+
# Build the chain of JOINs going to the final destination HMT table
|
616
|
+
# (Usually just one JOIN, but could be many.)
|
617
|
+
x = [hmt_assoc = hm]
|
618
|
+
x.unshift(hmt_assoc) while hmt_assoc.options[:through] && (hmt_assoc = klass.reflect_on_association(hmt_assoc.options[:through]))
|
619
|
+
from_clause = +"#{x.first.klass.table_name} br_t0"
|
620
|
+
fk_col = x.shift.foreign_key
|
621
|
+
link_back = [klass.primary_key] # %%% Inverse path back to the original object -- used to build out a link with a filter
|
622
|
+
idx = 0
|
623
|
+
bail_out = nil
|
624
|
+
x[0..-2].map do |a|
|
625
|
+
from_clause << "\n LEFT OUTER JOIN #{a.klass.table_name} br_t#{idx += 1} "
|
626
|
+
from_clause << if (src_ref = a.source_reflection).macro == :belongs_to
|
627
|
+
"ON br_t#{idx}.id = br_t#{idx - 1}.#{a.foreign_key}"
|
628
|
+
elsif src_ref.options[:as]
|
629
|
+
"ON br_t#{idx}.#{src_ref.type} = '#{src_ref.active_record.name}'" + # "polymorphable_type"
|
630
|
+
" AND br_t#{idx}.#{src_ref.foreign_key} = br_t#{idx - 1}.id"
|
631
|
+
elsif src_ref.options[:source_type]
|
632
|
+
print "Skipping #{hm.name} --HMT-> #{hm.source_reflection.name} as it uses source_type which is not supported"
|
633
|
+
nix << k
|
634
|
+
bail_out = true
|
635
|
+
break
|
636
|
+
else # Standard has_many
|
637
|
+
"ON br_t#{idx}.#{a.foreign_key} = br_t#{idx - 1}.id"
|
638
|
+
end
|
639
|
+
link_back.unshift(a.source_reflection.name)
|
640
|
+
[a.klass.table_name, a.foreign_key, a.source_reflection.macro]
|
641
|
+
end
|
642
|
+
next if bail_out
|
643
|
+
# count_column is determined from the last HMT member
|
644
|
+
if (src_ref = x.last.source_reflection).macro == :belongs_to # Traditional HMT using an associative table
|
645
|
+
"br_t#{idx}.#{x.last.foreign_key}"
|
646
|
+
else # A HMT that goes HM -> HM, something like Categories -> Products -> LineItems
|
647
|
+
"br_t#{idx}.#{src_ref.active_record.primary_key}"
|
617
648
|
end
|
618
649
|
else
|
619
650
|
fk_col = hm.foreign_key
|
@@ -651,20 +682,38 @@ module ActiveRecord
|
|
651
682
|
selects << poly_type
|
652
683
|
on_clause << "#{tbl_alias}.#{poly_type} = '#{name}'"
|
653
684
|
end
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
|
658
|
-
|
659
|
-
|
660
|
-
|
685
|
+
unless from_clause
|
686
|
+
hm_table_name = if is_mysql
|
687
|
+
"`#{hm.klass.table_name}`"
|
688
|
+
elsif is_postgres || is_mssql
|
689
|
+
"\"#{(hm.klass.table_name).gsub('.', '"."')}\""
|
690
|
+
else
|
691
|
+
hm.klass.table_name
|
692
|
+
end
|
693
|
+
end
|
661
694
|
group_bys = ::Brick.is_oracle || is_mssql ? selects : (1..selects.length).to_a
|
662
695
|
join_clause = "LEFT OUTER
|
663
|
-
JOIN (SELECT #{selects.join(', ')}, COUNT(#{'DISTINCT ' if hm.options[:through]}#{count_column
|
664
|
-
}) AS c_t_ FROM #{hm_table_name} GROUP BY #{group_bys.join(', ')}) #{tbl_alias}"
|
696
|
+
JOIN (SELECT #{selects.map { |s| "#{'br_t0.' if from_clause}#{s}" }.join(', ')}, COUNT(#{'DISTINCT ' if hm.options[:through]}#{count_column
|
697
|
+
}) AS c_t_ FROM #{from_clause || hm_table_name} GROUP BY #{group_bys.join(', ')}) #{tbl_alias}"
|
665
698
|
joins!("#{join_clause} ON #{on_clause.join(' AND ')}")
|
666
699
|
end
|
667
|
-
|
700
|
+
while (n = nix.pop)
|
701
|
+
klass._br_hm_counts.delete(n)
|
702
|
+
end
|
703
|
+
|
704
|
+
unless wheres.empty?
|
705
|
+
# Rewrite the wheres to reference table and correlation names built out by AREL
|
706
|
+
wheres2 = wheres.each_with_object({}) do |v, s|
|
707
|
+
if (v_parts = v.first.split('.')).length == 1
|
708
|
+
s[v.first] = v.last
|
709
|
+
else
|
710
|
+
k1 = klass.reflect_on_association(v_parts.first)&.klass
|
711
|
+
tbl_name = (field_tbl_names[v_parts.first][k1] ||= shift_or_first(chains[k1])).split('.').last
|
712
|
+
s["#{tbl_name}.#{v_parts.last}"] = v.last
|
713
|
+
end
|
714
|
+
end
|
715
|
+
where!(wheres2)
|
716
|
+
end
|
668
717
|
# Must parse the order_by and see if there are any symbols which refer to BT associations
|
669
718
|
# or custom columns as they must be expanded to find the corresponding b_r_model__column
|
670
719
|
# or br_cc_column naming for each.
|
@@ -783,50 +832,76 @@ end
|
|
783
832
|
Module.class_exec do
|
784
833
|
alias _brick_const_missing const_missing
|
785
834
|
def const_missing(*args)
|
786
|
-
|
835
|
+
requested = args.first.to_s
|
836
|
+
is_controller = requested.end_with?('Controller')
|
837
|
+
# self.name is nil when a model name is requested in an .erb file
|
838
|
+
if self.name && ::Brick.config.path_prefix
|
839
|
+
camelize_prefix = ::Brick.config.path_prefix.camelize
|
840
|
+
# Asking for the prefix module?
|
841
|
+
if self == Object && requested == camelize_prefix
|
842
|
+
Object.const_set(args.first, (built_module = Module.new))
|
843
|
+
puts "module #{camelize_prefix}; end\n"
|
844
|
+
return built_module
|
845
|
+
end
|
846
|
+
split_self_name.shift if (split_self_name = self.name.split('::')).first.blank?
|
847
|
+
if split_self_name.first == camelize_prefix
|
848
|
+
split_self_name.shift # Remove the identified path prefix from the split name
|
849
|
+
if is_controller
|
850
|
+
brick_root = split_self_name.empty? ? self : camelize_prefix.constantize
|
851
|
+
end
|
852
|
+
end
|
853
|
+
end
|
854
|
+
base_module = if self < ActiveRecord::Migration || !self.name
|
855
|
+
brick_root || Object
|
856
|
+
elsif (split_self_name || self.name.split('::')).length > 1 # Classic mode
|
857
|
+
begin
|
858
|
+
return self._brick_const_missing(*args)
|
859
|
+
|
860
|
+
rescue NameError # %%% Avoid the error "____ cannot be autoloaded from an anonymous class or module"
|
861
|
+
return self.const_get(args.first) if self.const_defined?(args.first)
|
862
|
+
|
863
|
+
# unless self == (prnt = (respond_to?(:parent) ? parent : module_parent))
|
864
|
+
unless self == Object
|
865
|
+
begin
|
866
|
+
return Object._brick_const_missing(*args)
|
867
|
+
|
868
|
+
rescue NameError
|
869
|
+
return Object.const_get(args.first) if Object.const_defined?(args.first)
|
870
|
+
|
871
|
+
end
|
872
|
+
end
|
873
|
+
end
|
874
|
+
Object
|
875
|
+
else
|
876
|
+
self
|
877
|
+
end
|
878
|
+
# puts "#{self.name} - #{args.first}"
|
879
|
+
desired_classname = (self == Object || !name) ? requested : "#{name}::#{requested}"
|
787
880
|
if ((is_defined = self.const_defined?(args.first)) && (possible = self.const_get(args.first)) && possible.name == desired_classname) ||
|
788
881
|
# Try to require the respective Ruby file
|
789
882
|
((filename = ActiveSupport::Dependencies.search_for_file(desired_classname.underscore) ||
|
790
|
-
(self != Object && ActiveSupport::Dependencies.search_for_file((desired_classname =
|
883
|
+
(self != Object && ActiveSupport::Dependencies.search_for_file((desired_classname = requested).underscore))
|
791
884
|
) && (require_dependency(filename) || true) &&
|
792
885
|
((possible = self.const_get(args.first)) && possible.name == desired_classname)
|
793
886
|
) ||
|
794
887
|
# If any class has turned up so far (and we're not in the middle of eager loading)
|
795
888
|
# then return what we've found.
|
796
|
-
(is_defined && !::Brick.is_eager_loading)
|
797
|
-
|
798
|
-
|
799
|
-
|
800
|
-
|
801
|
-
base_module = (self < ActiveRecord::Migration || !self.name) ? Object : self
|
802
|
-
# See if a file is there in the same way that ActiveSupport::Dependencies#load_missing_constant
|
803
|
-
# checks for it in ~/.rvm/gems/ruby-2.7.5/gems/activesupport-5.2.6.2/lib/active_support/dependencies.rb
|
804
|
-
# that is, checking #qualified_name_for with: from_mod, const_name
|
805
|
-
# If we want to support namespacing in the future, might have to utilise something like this:
|
806
|
-
# path_suffix = ActiveSupport::Dependencies.qualified_name_for(Object, args.first).underscore
|
807
|
-
# return self._brick_const_missing(*args) if ActiveSupport::Dependencies.search_for_file(path_suffix)
|
808
|
-
# If the file really exists, go and snag it:
|
809
|
-
if ActiveSupport::Dependencies.search_for_file(class_name.underscore)
|
810
|
-
return base_module._brick_const_missing(*args)
|
811
|
-
# elsif ActiveSupport::Dependencies.search_for_file(filepath) # Last-ditch effort to pick this thing up before we fill in the gaps on our own
|
812
|
-
# my_const = parent.const_missing(class_name) # ends up having: MyModule::MyClass
|
813
|
-
# return my_const
|
814
|
-
else
|
815
|
-
filepath = base_module.name&.split('::')&.[](0..-2) unless base_module == Object
|
816
|
-
filepath = ((filepath || []) + [class_name]).join('/').underscore + '.rb'
|
817
|
-
if ActiveSupport::Dependencies.search_for_file(filepath) # Last-ditch effort to pick this thing up before we fill in the gaps on our own
|
818
|
-
return base_module._brick_const_missing(*args)
|
889
|
+
(is_defined && !::Brick.is_eager_loading) # Used to also have: && possible != self
|
890
|
+
if (!brick_root && (filename || possible.instance_of?(Class))) ||
|
891
|
+
(possible.instance_of?(Module) && possible.module_parent == self) ||
|
892
|
+
(possible.instance_of?(Class) && possible == self) # Are we simply searching for ourselves?
|
893
|
+
return possible
|
819
894
|
end
|
820
895
|
end
|
821
|
-
|
896
|
+
class_name = ::Brick.namify(requested)
|
822
897
|
relations = ::Brick.relations
|
823
|
-
|
824
|
-
|
898
|
+
result = if ::Brick.enable_controllers? &&
|
899
|
+
is_controller && (plural_class_name = class_name[0..-11]).length.positive?
|
825
900
|
# Otherwise now it's up to us to fill in the gaps
|
901
|
+
full_class_name = +''
|
902
|
+
full_class_name << "::#{(split_self_name&.first && split_self_name.join('::')) || self.name}" unless self == Object
|
826
903
|
# (Go over to underscores for a moment so that if we have something come in like VABCsController then the model name ends up as
|
827
904
|
# Vabc instead of VABC)
|
828
|
-
full_class_name = +''
|
829
|
-
full_class_name << "::#{self.name}" unless self == Object
|
830
905
|
singular_class_name = ::Brick.namify(plural_class_name, :underscore).singularize.camelize
|
831
906
|
full_class_name << "::#{singular_class_name}"
|
832
907
|
if plural_class_name == 'BrickSwagger' ||
|
@@ -870,9 +945,21 @@ Module.class_exec do
|
|
870
945
|
base_module._brick_const_missing(*args)
|
871
946
|
# elsif base_module != Object
|
872
947
|
# module_parent.const_missing(*args)
|
873
|
-
else
|
874
|
-
|
875
|
-
|
948
|
+
elsif Rails.respond_to?(:autoloaders) && # After finding nothing else, if Zeitwerk is enabled ...
|
949
|
+
(Rails::Autoloaders.respond_to?(:zeitwerk_enabled?) ? Rails::Autoloaders.zeitwerk_enabled? : true)
|
950
|
+
self._brick_const_missing(*args) # ... rely solely on Zeitwerk.
|
951
|
+
else # Classic mode
|
952
|
+
unless (found = base_module._brick_const_missing(*args))
|
953
|
+
puts "MISSING! #{base_module.name} #{args.inspect} #{table_name}"
|
954
|
+
end
|
955
|
+
found
|
956
|
+
end
|
957
|
+
end
|
958
|
+
|
959
|
+
# Support Rails < 6.0 which adds #parent instead of #module_parent
|
960
|
+
unless respond_to?(:module_parent)
|
961
|
+
def module_parent # Weirdly for Grape::API does NOT come in with the proper class, but some anonymous Class thing
|
962
|
+
parent
|
876
963
|
end
|
877
964
|
end
|
878
965
|
end
|
@@ -929,7 +1016,7 @@ class Object
|
|
929
1016
|
schema_name
|
930
1017
|
else
|
931
1018
|
matching = "#{schema_name}.#{matching}"
|
932
|
-
(Brick.db_schemas[schema_name] ||= self.const_get(schema_name.camelize)
|
1019
|
+
(::Brick.db_schemas[schema_name] || {})[:class] ||= self.const_get(schema_name.camelize.to_sym)
|
933
1020
|
end
|
934
1021
|
"#{schema_module&.name}::#{inheritable_name || model_name}"
|
935
1022
|
end
|
@@ -939,10 +1026,10 @@ class Object
|
|
939
1026
|
|
940
1027
|
# Are they trying to use a pluralised class name such as "Employees" instead of "Employee"?
|
941
1028
|
if table_name == singular_table_name && !ActiveSupport::Inflector.inflections.uncountable.include?(table_name)
|
942
|
-
unless ::Brick.config.sti_namespace_prefixes&.key?("::#{singular_table_name.camelize}::")
|
943
|
-
|
944
|
-
|
945
|
-
end
|
1029
|
+
# unless ::Brick.config.sti_namespace_prefixes&.key?("::#{singular_table_name.camelize}::")
|
1030
|
+
# puts "Warning: Class name for a model that references table \"#{matching
|
1031
|
+
# }\" should be \"#{ActiveSupport::Inflector.singularize(inheritable_name || model_name)}\"."
|
1032
|
+
# end
|
946
1033
|
return
|
947
1034
|
end
|
948
1035
|
|
@@ -1197,8 +1284,7 @@ class Object
|
|
1197
1284
|
is_postgres = ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
|
1198
1285
|
is_mysql = ActiveRecord::Base.connection.adapter_name == 'Mysql2'
|
1199
1286
|
|
1200
|
-
|
1201
|
-
code = +"class #{namespace_name}#{class_name} < ApplicationController\n"
|
1287
|
+
code = +"class #{class_name} < ApplicationController\n"
|
1202
1288
|
built_controller = Class.new(ActionController::Base) do |new_controller_class|
|
1203
1289
|
(namespace || Object).const_set(class_name.to_sym, new_controller_class)
|
1204
1290
|
|
@@ -1341,13 +1427,6 @@ class Object
|
|
1341
1427
|
code << " end\n"
|
1342
1428
|
self.define_method :show do
|
1343
1429
|
::Brick.set_db_schema(params)
|
1344
|
-
id = if model.columns_hash[pk.first]&.type == :string
|
1345
|
-
is_pk_string = true
|
1346
|
-
params[:id]
|
1347
|
-
else
|
1348
|
-
params[:id]&.split(/[\/,_]/)
|
1349
|
-
end
|
1350
|
-
id = id.first if id.is_a?(Array) && id.length == 1
|
1351
1430
|
instance_variable_set("@#{singular_table_name}".to_sym, find_obj)
|
1352
1431
|
end
|
1353
1432
|
end
|
@@ -1388,7 +1467,7 @@ class Object
|
|
1388
1467
|
end
|
1389
1468
|
|
1390
1469
|
if pk.present?
|
1391
|
-
# if (schema = ::Brick.config.schema_behavior[:multitenant]&.fetch(:schema_to_analyse, nil)) && ::Brick.db_schemas&.
|
1470
|
+
# if (schema = ::Brick.config.schema_behavior[:multitenant]&.fetch(:schema_to_analyse, nil)) && ::Brick.db_schemas&.key?(schema)
|
1392
1471
|
# ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?;", schema)
|
1393
1472
|
# end
|
1394
1473
|
|
@@ -1442,7 +1521,14 @@ class Object
|
|
1442
1521
|
@#{singular_table_name} = #{model.name}.find(id.is_a?(Array) && id.length == 1 ? id.first : id)
|
1443
1522
|
end\n"
|
1444
1523
|
self.define_method :find_obj do
|
1445
|
-
id =
|
1524
|
+
id = if model.columns_hash[pk.first]&.type == :string
|
1525
|
+
is_pk_string = true
|
1526
|
+
params[:id].gsub('^^sl^^', '/')
|
1527
|
+
else
|
1528
|
+
params[:id]&.split(/[\/,_]/).map do |val_part|
|
1529
|
+
val_part.gsub('^^sl^^', '/')
|
1530
|
+
end
|
1531
|
+
end
|
1446
1532
|
model.find(id.is_a?(Array) && id.length == 1 ? id.first : id)
|
1447
1533
|
end
|
1448
1534
|
end
|
@@ -1465,33 +1551,55 @@ class Object
|
|
1465
1551
|
# Get column names for params from relations[model.table_name][:cols].keys
|
1466
1552
|
end
|
1467
1553
|
end # unless is_swagger
|
1468
|
-
code << "end # #{
|
1554
|
+
code << "end # #{class_name}\n"
|
1469
1555
|
end # class definition
|
1470
1556
|
[built_controller, code]
|
1471
1557
|
end
|
1472
1558
|
|
1473
1559
|
def _brick_get_hm_assoc_name(relation, hm_assoc, source = nil)
|
1474
|
-
if (relation[:hm_counts][hm_assoc[:inverse_table]]&.> 1) &&
|
1475
|
-
|
1476
|
-
|
1477
|
-
|
1478
|
-
|
1479
|
-
|
1480
|
-
|
1481
|
-
|
1482
|
-
|
1483
|
-
|
1484
|
-
|
1485
|
-
|
1486
|
-
|
1487
|
-
|
1488
|
-
|
1489
|
-
|
1490
|
-
|
1560
|
+
assoc_name, needs_class = if (relation[:hm_counts][hm_assoc[:inverse_table]]&.> 1) &&
|
1561
|
+
hm_assoc[:alternate_name] != (source || name.underscore)
|
1562
|
+
plural = "#{hm_assoc[:assoc_name]}_#{ActiveSupport::Inflector.pluralize(hm_assoc[:alternate_name])}"
|
1563
|
+
new_alt_name = (hm_assoc[:alternate_name] == name.underscore) ? "#{hm_assoc[:assoc_name].singularize}_#{plural}" : plural
|
1564
|
+
# uniq = 1
|
1565
|
+
# while same_name = relation[:fks].find { |x| x.last[:assoc_name] == hm_assoc[:assoc_name] && x.last != hm_assoc }
|
1566
|
+
# hm_assoc[:assoc_name] = "#{hm_assoc_name}_#{uniq += 1}"
|
1567
|
+
# end
|
1568
|
+
# puts new_alt_name
|
1569
|
+
# hm_assoc[:assoc_name] = new_alt_name
|
1570
|
+
[new_alt_name, true]
|
1571
|
+
else
|
1572
|
+
assoc_name = ::Brick.namify(hm_assoc[:inverse_table]).pluralize
|
1573
|
+
if (needs_class = assoc_name.include?('.')) # If there is a schema name present, use a downcased version for the :has_many
|
1574
|
+
assoc_parts = assoc_name.split('.')
|
1575
|
+
assoc_parts[0].downcase! if assoc_parts[0] =~ /^[A-Z0-9_]+$/
|
1576
|
+
assoc_name = assoc_parts.join('.')
|
1577
|
+
end
|
1578
|
+
# hm_assoc[:assoc_name] = assoc_name
|
1579
|
+
[assoc_name, needs_class]
|
1580
|
+
end
|
1581
|
+
# Already have the HM class around?
|
1582
|
+
begin
|
1583
|
+
if (hm_class = Object._brick_const_missing(hm_class_name = relation[:class_name].to_sym))
|
1584
|
+
existing_hm_assocs = hm_class.reflect_on_all_associations.select do |assoc|
|
1585
|
+
assoc.macro != :belongs_to && assoc.klass == self && assoc.foreign_key == hm_assoc[:fk]
|
1586
|
+
end
|
1587
|
+
# Missing a has_many in an existing class?
|
1588
|
+
if existing_hm_assocs.empty?
|
1589
|
+
options = { inverse_of: hm_assoc[:inverse][:assoc_name].to_sym }
|
1590
|
+
# Add class_name and foreign_key where necessary
|
1591
|
+
unless hm_assoc[:alternate_name] == (source || name.underscore)
|
1592
|
+
options[:class_name] = self.name
|
1593
|
+
options[:foreign_key] = hm_assoc[:fk].to_sym
|
1594
|
+
end
|
1595
|
+
hm_class.send(:has_many, assoc_name.to_sym, options)
|
1596
|
+
puts "# ** Adding a missing has_many to #{hm_class.name}:\nclass #{hm_class.name} < #{hm_class.superclass.name}"
|
1597
|
+
puts " has_many :#{assoc_name}, #{options.inspect}\nend\n"
|
1598
|
+
end
|
1491
1599
|
end
|
1492
|
-
|
1493
|
-
[assoc_name, needs_class]
|
1600
|
+
rescue NameError
|
1494
1601
|
end
|
1602
|
+
[assoc_name, needs_class]
|
1495
1603
|
end
|
1496
1604
|
end
|
1497
1605
|
end
|
@@ -1504,6 +1612,7 @@ module ActiveRecord::ConnectionHandling
|
|
1504
1612
|
alias _brick_establish_connection establish_connection
|
1505
1613
|
def establish_connection(*args)
|
1506
1614
|
conn = _brick_establish_connection(*args)
|
1615
|
+
begin
|
1507
1616
|
# Overwrite SQLite's #begin_db_transaction so it opens in IMMEDIATE mode instead of
|
1508
1617
|
# the default DEFERRED mode.
|
1509
1618
|
# https://discuss.rubyonrails.org/t/failed-write-transaction-upgrades-in-sqlite3/81480/2
|
@@ -1525,9 +1634,10 @@ module ActiveRecord::ConnectionHandling
|
|
1525
1634
|
end
|
1526
1635
|
end
|
1527
1636
|
end
|
1528
|
-
|
1637
|
+
# ::Brick.is_db_present = true
|
1529
1638
|
_brick_reflect_tables
|
1530
1639
|
rescue ActiveRecord::NoDatabaseError
|
1640
|
+
# ::Brick.is_db_present = false
|
1531
1641
|
end
|
1532
1642
|
conn
|
1533
1643
|
end
|
@@ -1535,7 +1645,10 @@ module ActiveRecord::ConnectionHandling
|
|
1535
1645
|
# This is done separately so that during testing it can be called right after a migration
|
1536
1646
|
# in order to make sure everything is good.
|
1537
1647
|
def _brick_reflect_tables
|
1648
|
+
# return if ActiveRecord::Base.connection.current_database == 'postgres'
|
1649
|
+
|
1538
1650
|
initializer_loaded = false
|
1651
|
+
orig_schema = nil
|
1539
1652
|
if (relations = ::Brick.relations).empty?
|
1540
1653
|
# If there's schema things configured then we only expect our initializer to be named exactly this
|
1541
1654
|
if File.exist?(brick_initializer = ::Rails.root.join('config/initializers/brick.rb'))
|
@@ -1543,8 +1656,8 @@ module ActiveRecord::ConnectionHandling
|
|
1543
1656
|
end
|
1544
1657
|
# Load the initializer for the Apartment gem a little early so that if .excluded_models and
|
1545
1658
|
# .default_schema are specified then we can work with non-tenanted models more appropriately
|
1546
|
-
apartment = Object.const_defined?('Apartment')
|
1547
|
-
|
1659
|
+
if (apartment = Object.const_defined?('Apartment')) &&
|
1660
|
+
File.exist?(apartment_initializer = ::Rails.root.join('config/initializers/apartment.rb'))
|
1548
1661
|
load apartment_initializer
|
1549
1662
|
apartment_excluded = Apartment.excluded_models
|
1550
1663
|
end
|
@@ -1556,23 +1669,27 @@ module ActiveRecord::ConnectionHandling
|
|
1556
1669
|
case ActiveRecord::Base.connection.adapter_name
|
1557
1670
|
when 'PostgreSQL', 'SQLServer'
|
1558
1671
|
is_postgres = !is_mssql
|
1559
|
-
db_schemas =
|
1672
|
+
db_schemas = if is_postgres
|
1673
|
+
ActiveRecord::Base.execute_sql('SELECT nspname AS table_schema, MAX(oid) AS dt FROM pg_namespace GROUP BY 1 ORDER BY 1;')
|
1674
|
+
else
|
1675
|
+
ActiveRecord::Base.execute_sql('SELECT DISTINCT table_schema, NULL AS dt FROM INFORMATION_SCHEMA.tables;')
|
1676
|
+
end
|
1560
1677
|
::Brick.db_schemas = db_schemas.each_with_object({}) do |row, s|
|
1561
1678
|
row = case row
|
1562
|
-
when String
|
1563
|
-
row
|
1564
1679
|
when Array
|
1565
|
-
row
|
1680
|
+
row
|
1566
1681
|
else
|
1567
|
-
row['table_schema']
|
1682
|
+
[row['table_schema'], row['dt']]
|
1568
1683
|
end
|
1569
1684
|
# Remove any system schemas
|
1570
|
-
s[row] =
|
1571
|
-
|
1685
|
+
s[row.first] = { dt: row.last } unless ['information_schema', 'pg_catalog', 'pg_toast', 'heroku_ext',
|
1686
|
+
'INFORMATION_SCHEMA', 'sys'].include?(row.first)
|
1572
1687
|
end
|
1573
1688
|
if (is_multitenant = (multitenancy = ::Brick.config.schema_behavior[:multitenant]) &&
|
1574
1689
|
(sta = multitenancy[:schema_to_analyse]) != 'public') &&
|
1575
|
-
::Brick.db_schemas.
|
1690
|
+
::Brick.db_schemas.key?(sta)
|
1691
|
+
# Take note of the current schema so we can go back to it at the end of all this
|
1692
|
+
orig_schema = ActiveRecord::Base.execute_sql('SELECT current_schemas(true)').first['current_schemas'][1..-2].split(',')
|
1576
1693
|
::Brick.default_schema = schema = sta
|
1577
1694
|
ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?", schema)
|
1578
1695
|
end
|
@@ -1582,7 +1699,7 @@ module ActiveRecord::ConnectionHandling
|
|
1582
1699
|
# ActiveRecord::Base.connection.current_database will be something like "XEPDB1"
|
1583
1700
|
::Brick.default_schema = schema = ActiveRecord::Base.connection.raw_connection.username
|
1584
1701
|
::Brick.db_schemas = {}
|
1585
|
-
ActiveRecord::Base.execute_sql("SELECT username FROM sys.all_users WHERE ORACLE_MAINTAINED != 'Y'").each { |s| ::Brick.db_schemas[s.first] =
|
1702
|
+
ActiveRecord::Base.execute_sql("SELECT username FROM sys.all_users WHERE ORACLE_MAINTAINED != 'Y'").each { |s| ::Brick.db_schemas[s.first] = {} }
|
1586
1703
|
when 'SQLite'
|
1587
1704
|
sql = "SELECT m.name AS relation_name, UPPER(m.type) AS table_type,
|
1588
1705
|
p.name AS column_name, p.type AS data_type,
|
@@ -1601,6 +1718,12 @@ module ActiveRecord::ConnectionHandling
|
|
1601
1718
|
if (possible_schema = ::Brick.config.schema_behavior&.[](:multitenant)&.[](:schema_to_analyse))
|
1602
1719
|
if ::Brick.db_schemas.key?(possible_schema)
|
1603
1720
|
::Brick.default_schema = schema = possible_schema
|
1721
|
+
orig_schema = ActiveRecord::Base.execute_sql('SELECT current_schemas(true)').first['current_schemas'][1..-2].split(',')
|
1722
|
+
ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?", schema)
|
1723
|
+
elsif Rails.env == 'test' # When testing, just find the most recently-created schema
|
1724
|
+
::Brick.default_schema = schema = ::Brick.db_schemas.to_a.sort { |a, b| b.last[:dt] <=> a.last[:dt] }.first.first
|
1725
|
+
puts "While running tests, had noticed in the brick.rb initializer that the line \"::Brick.schema_behavior = ...\" refers to a schema called \"#{possible_schema}\" which does not exist. Reading table structure from the most recently-created schema, #{schema}."
|
1726
|
+
orig_schema = ActiveRecord::Base.execute_sql('SELECT current_schemas(true)').first['current_schemas'][1..-2].split(',')
|
1604
1727
|
ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?", schema)
|
1605
1728
|
else
|
1606
1729
|
puts "*** In the brick.rb initializer the line \"::Brick.schema_behavior = ...\" refers to a schema called \"#{possible_schema}\". This schema does not exist. ***"
|
@@ -1614,7 +1737,7 @@ module ActiveRecord::ConnectionHandling
|
|
1614
1737
|
measures = []
|
1615
1738
|
::Brick.is_oracle = true if ActiveRecord::Base.connection.adapter_name == 'OracleEnhanced'
|
1616
1739
|
case ActiveRecord::Base.connection.adapter_name
|
1617
|
-
when 'PostgreSQL', 'SQLite' # These bring back a hash for each row because the query uses column aliases
|
1740
|
+
when 'PostgreSQL', 'SQLite', 'SQLServer' # These bring back a hash for each row because the query uses column aliases
|
1618
1741
|
# schema ||= 'public' if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
|
1619
1742
|
ActiveRecord::Base.retrieve_schema_and_tables(sql, is_postgres, is_mssql, schema).each do |r|
|
1620
1743
|
# If Apartment gem lists the table as being associated with a non-tenanted model then use whatever it thinks
|
@@ -1804,6 +1927,11 @@ ORDER BY 1, 2, c.internal_column_id, acc.position"
|
|
1804
1927
|
v[:class_name] = (schema_names + [rel_name.last.singularize]).map(&:camelize).join('::')
|
1805
1928
|
end
|
1806
1929
|
::Brick.load_additional_references if initializer_loaded
|
1930
|
+
|
1931
|
+
if orig_schema && (orig_schema = (orig_schema - ['pg_catalog', 'heroku_ext']).first)
|
1932
|
+
puts "Now switching back to \"#{orig_schema}\" schema."
|
1933
|
+
ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?", orig_schema)
|
1934
|
+
end
|
1807
1935
|
end
|
1808
1936
|
|
1809
1937
|
def retrieve_schema_and_tables(sql = nil, is_postgres = nil, is_mssql = nil, schema = nil)
|
@@ -1823,7 +1951,7 @@ ORDER BY 1, 2, c.internal_column_id, acc.position"
|
|
1823
1951
|
LEFT OUTER JOIN INFORMATION_SCHEMA.columns AS c ON t.table_schema = c.table_schema
|
1824
1952
|
AND t.table_name = c.table_name
|
1825
1953
|
LEFT OUTER JOIN
|
1826
|
-
(SELECT kcu1.constraint_schema, kcu1.table_name, kcu1.ordinal_position,
|
1954
|
+
(SELECT kcu1.constraint_schema, kcu1.table_name, kcu1.column_name, kcu1.ordinal_position,
|
1827
1955
|
tc.constraint_type, kcu1.constraint_name
|
1828
1956
|
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS kcu1
|
1829
1957
|
INNER JOIN INFORMATION_SCHEMA.table_constraints AS tc
|
@@ -1834,9 +1962,9 @@ ORDER BY 1, 2, c.internal_column_id, acc.position"
|
|
1834
1962
|
) AS kcu ON
|
1835
1963
|
-- kcu.CONSTRAINT_CATALOG = t.table_catalog AND
|
1836
1964
|
kcu.CONSTRAINT_SCHEMA = c.table_schema
|
1837
|
-
AND kcu.TABLE_NAME = c.table_name
|
1965
|
+
AND kcu.TABLE_NAME = c.table_name
|
1966
|
+
AND kcu.column_name = c.column_name#{"
|
1838
1967
|
-- AND kcu.position_in_unique_constraint IS NULL" unless is_mssql}
|
1839
|
-
AND kcu.ordinal_position = c.ordinal_position
|
1840
1968
|
WHERE t.table_schema #{is_postgres || is_mssql ?
|
1841
1969
|
"NOT IN ('information_schema', 'pg_catalog',
|
1842
1970
|
'INFORMATION_SCHEMA', 'sys')"
|
@@ -1845,7 +1973,7 @@ ORDER BY 1, 2, c.internal_column_id, acc.position"
|
|
1845
1973
|
AND t.table_schema = COALESCE(current_setting('SEARCH_PATH'), 'public')" if is_postgres && schema }
|
1846
1974
|
-- AND t.table_type IN ('VIEW') -- 'BASE TABLE', 'FOREIGN TABLE'
|
1847
1975
|
AND t.table_name NOT IN ('pg_stat_statements', ?, ?)
|
1848
|
-
ORDER BY 1, t.table_type DESC, 2,
|
1976
|
+
ORDER BY 1, t.table_type DESC, 2, kcu.ordinal_position"
|
1849
1977
|
ActiveRecord::Base.execute_sql(sql, *ar_tables)
|
1850
1978
|
end
|
1851
1979
|
|
@@ -2090,7 +2218,12 @@ module Brick
|
|
2090
2218
|
end
|
2091
2219
|
end
|
2092
2220
|
end
|
2093
|
-
::Brick.relations.keys.map
|
2221
|
+
::Brick.relations.keys.map do |v|
|
2222
|
+
tbl_parts = v.split('.')
|
2223
|
+
tbl_parts.shift if ::Brick.apartment_multitenant && tbl_parts.length > 1 && tbl_parts.first == Apartment.default_schema
|
2224
|
+
res = tbl_parts.join('.')
|
2225
|
+
[v, (model = models[res])&.last&.table_name, migrations&.fetch(res, nil), model&.first]
|
2226
|
+
end
|
2094
2227
|
end
|
2095
2228
|
|
2096
2229
|
def ensure_unique(name, *sources)
|
@@ -114,7 +114,7 @@ module Brick
|
|
114
114
|
if @_brick_model
|
115
115
|
pk = @_brick_model._brick_primary_key(::Brick.relations.fetch(@_brick_model&.table_name, nil))
|
116
116
|
obj_name = model_name.split('::').last.underscore
|
117
|
-
path_obj_name =
|
117
|
+
path_obj_name = @_brick_model._brick_index(:singular)
|
118
118
|
table_name = obj_name.pluralize
|
119
119
|
template_link = nil
|
120
120
|
bts, hms = ::Brick.get_bts_and_hms(@_brick_model) # This gets BT and HM and also has_many :through (HMT)
|
@@ -125,9 +125,11 @@ module Brick
|
|
125
125
|
"H#{hm_assoc.macro == :has_one ? 'O' : 'M'}#{'T' if hm_assoc.options[:through]}",
|
126
126
|
(assoc_name = hm.first)]
|
127
127
|
hm_fk_name = if (through = hm_assoc.options[:through])
|
128
|
-
next unless @_brick_model.
|
128
|
+
next unless @_brick_model._br_hm_counts.key?(hm_assoc.name) # Skip any weird HMTs that go through a HM with a source_type
|
129
|
+
|
130
|
+
next unless @_brick_model.instance_methods.include?(through) &&
|
131
|
+
(associative = @_brick_model._br_associatives.fetch(hm.first, nil))
|
129
132
|
|
130
|
-
associative = @_brick_model._br_associatives[hm.first]
|
131
133
|
tbl_nm = if hm_assoc.options[:source]
|
132
134
|
associative.klass.reflect_on_association(hm_assoc.options[:source]).inverse_of&.name
|
133
135
|
else
|
@@ -177,6 +179,7 @@ module Brick
|
|
177
179
|
# %%% If we are not auto-creating controllers (or routes) then omit by default, and if enabled anyway, such as in a development
|
178
180
|
# environment or whatever, then get either the controllers or routes list instead
|
179
181
|
apartment_default_schema = ::Brick.apartment_multitenant && Apartment.default_schema
|
182
|
+
prefix = "#{::Brick.config.path_prefix}/" if ::Brick.config.path_prefix
|
180
183
|
table_options = (::Brick.relations.keys - ::Brick.config.exclude_tables).each_with_object({}) do |tbl, s|
|
181
184
|
binding.pry if tbl.is_a?(Symbol)
|
182
185
|
if (tbl_parts = tbl.split('.')).first == apartment_default_schema
|
@@ -184,7 +187,7 @@ module Brick
|
|
184
187
|
end
|
185
188
|
s[tbl] = nil
|
186
189
|
end.keys.sort.each_with_object(+'') do |v, s|
|
187
|
-
s << "<option value=\"#{v.underscore.gsub('.', '/')}\">#{v}</option>"
|
190
|
+
s << "<option value=\"#{prefix}#{v.underscore.gsub('.', '/')}\">#{v}</option>"
|
188
191
|
end.html_safe
|
189
192
|
table_options << '<option value="brick_status">(Status)</option>'.html_safe if ::Brick.config.add_status
|
190
193
|
table_options << '<option value="brick_orphans">(Orphans)</option>'.html_safe if is_orphans
|
@@ -350,11 +353,8 @@ def hide_bcrypt(val, max_len = 200)
|
|
350
353
|
'(hidden)'
|
351
354
|
else
|
352
355
|
if val.is_a?(String)
|
353
|
-
if val.length > max_len
|
354
|
-
|
355
|
-
val << '...'
|
356
|
-
end
|
357
|
-
val.force_encoding('UTF-8') unless val.encoding.name == 'UTF-8'
|
356
|
+
val = \"#\{val[0...max_len]}...\" if val.length > max_len
|
357
|
+
val = val.dup.force_encoding('UTF-8') unless val.encoding.name == 'UTF-8'
|
358
358
|
end
|
359
359
|
val
|
360
360
|
end
|
@@ -369,7 +369,7 @@ def display_value(col_type, val)
|
|
369
369
|
if @is_mysql || @is_mssql
|
370
370
|
# MySQL's \"Internal Geometry Format\" and MSSQL's Geography are like WKB, but with an initial 4 bytes that indicates the SRID.
|
371
371
|
if (srid = val&.[](0..3)&.unpack('I'))
|
372
|
-
val = val.force_encoding('BINARY')[4..-1].bytes
|
372
|
+
val = val.dup.force_encoding('BINARY')[4..-1].bytes
|
373
373
|
|
374
374
|
# MSSQL spatial bitwise flags, often 0C for a point:
|
375
375
|
# xxxx xxx1 = HasZValues
|
@@ -405,6 +405,10 @@ def display_value(col_type, val)
|
|
405
405
|
end
|
406
406
|
end
|
407
407
|
end
|
408
|
+
# Accommodate composite primary keys that include strings with forward-slash characters
|
409
|
+
def slashify(*vals)
|
410
|
+
vals.map { |val_part| val_part.is_a?(String) ? val_part.gsub('/', '^^sl^^') : val_part }
|
411
|
+
end
|
408
412
|
callbacks = {} %>"
|
409
413
|
|
410
414
|
if ['index', 'show', 'new', 'update'].include?(args.first)
|
@@ -461,7 +465,7 @@ window.addEventListener(\"pageshow\", function() {
|
|
461
465
|
});
|
462
466
|
|
463
467
|
if (tblSelect) { // Always present
|
464
|
-
var i = schemaSelect ? 1 : 0,
|
468
|
+
var i = #{::Brick.config.path_prefix ? '0' : 'schemaSelect ? 1 : 0'},
|
465
469
|
changeoutList = changeout(location.href);
|
466
470
|
for (; i < changeoutList.length; ++i) {
|
467
471
|
tblSelect.value = changeoutList[i];
|
@@ -523,15 +527,19 @@ if (grid) {
|
|
523
527
|
function gridMove(evt) {
|
524
528
|
var lastHighCell = gridHighCell;
|
525
529
|
gridHighCell = document.elementFromPoint(evt.x, evt.y);
|
526
|
-
|
527
|
-
gridHighCell.
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
530
|
+
while (gridHighCell && gridHighCell.tagName !== \"TD\" && gridHighCell.tagName !== \"TH\")
|
531
|
+
gridHighCell = gridHighCell.parentElement;
|
532
|
+
if (gridHighCell) {
|
533
|
+
if (lastHighCell !== gridHighCell) {
|
534
|
+
gridHighCell.classList.add(\"highlight\");
|
535
|
+
if (lastHighCell) lastHighCell.classList.remove(\"highlight\");
|
536
|
+
}
|
537
|
+
var lastHighHeader = gridHighHeader;
|
538
|
+
gridHighHeader = headerCols[gridHighCell.cellIndex];
|
539
|
+
if (lastHighHeader !== gridHighHeader) {
|
540
|
+
if (gridHighHeader) gridHighHeader.classList.add(\"highlight\");
|
541
|
+
if (lastHighHeader) lastHighHeader.classList.remove(\"highlight\");
|
542
|
+
}
|
535
543
|
}
|
536
544
|
}
|
537
545
|
}
|
@@ -661,7 +669,7 @@ erDiagram
|
|
661
669
|
inline = case args.first
|
662
670
|
when 'index'
|
663
671
|
obj_pk = if pk&.is_a?(Array) # Composite primary key?
|
664
|
-
"
|
672
|
+
"#{pk.map { |pk_part| "#{obj_name}.#{pk_part}" }.join(', ')}" unless pk.empty?
|
665
673
|
elsif pk
|
666
674
|
"#{obj_name}.#{pk}"
|
667
675
|
end
|
@@ -775,7 +783,7 @@ erDiagram
|
|
775
783
|
origin = (key_parts = k.split('.')).length == 1 ? #{model_name} : #{model_name}.reflect_on_association(key_parts.first).klass
|
776
784
|
if (destination_fk = Brick.relations[origin.table_name][:fks].values.find { |fk| fk[:fk] == key_parts.last }) &&
|
777
785
|
(obj = (destination = origin.reflect_on_association(destination_fk[:assoc_name])&.klass)&.find(id)) %>
|
778
|
-
<h3>for <%= link_to \"#{"#\{obj.brick_descrip\} (#\{destination.name\})\""}, send(\"#\{destination._brick_index\}_path\".to_sym, id) %></h3><%
|
786
|
+
<h3>for <%= link_to \"#{"#\{obj.brick_descrip\} (#\{destination.name\})\""}, send(\"#\{destination._brick_index(:singular)\}_path\".to_sym, id) %></h3><%
|
779
787
|
end
|
780
788
|
end %>
|
781
789
|
(<%= link_to 'See all #{model_name.split('::').last.pluralize}', #{@_brick_model._brick_index}_path %>)
|
@@ -849,24 +857,25 @@ erDiagram
|
|
849
857
|
@#{table_name}.each do |#{obj_name}|
|
850
858
|
hms_cols = {#{hms_columns.join(', ')}} %>
|
851
859
|
<tr>#{"
|
852
|
-
<td><%= link_to '⇛', #{path_obj_name}_path(#{obj_pk}), { class: 'big-arrow' } %></td>" if obj_pk}
|
860
|
+
<td><%= link_to '⇛', #{path_obj_name}_path(slashify(#{obj_pk})), { class: 'big-arrow' } %></td>" if obj_pk}
|
853
861
|
<% @_brick_sequence.each do |col_name|
|
854
862
|
val = #{obj_name}.attributes[col_name] %>
|
855
863
|
<td<%= ' class=\"dimmed\"'.html_safe unless cols.key?(col_name) || (cust_col = cust_cols[col_name])%>><%
|
856
864
|
if (bt = bts[col_name])
|
857
865
|
if bt[2] # Polymorphic?
|
858
|
-
bt_class = #{obj_name}.send(\"#\{bt.first
|
859
|
-
|
860
|
-
poly_id = #{obj_name}.send(\"#\{bt.first
|
861
|
-
%><%= link_to(\"#\{bt_class
|
866
|
+
bt_class = #{obj_name}.send(\"#\{bt.first}_type\")
|
867
|
+
base_class_underscored = (::Brick.existing_stis[bt_class] || bt_class).constantize.base_class._brick_index(:singular)
|
868
|
+
poly_id = #{obj_name}.send(\"#\{bt.first}_id\")
|
869
|
+
%><%= link_to(\"#\{bt_class} ##\{poly_id}\", send(\"#\{base_class_underscored}_path\".to_sym, poly_id)) if poly_id %><%
|
862
870
|
else
|
871
|
+
# binding.pry if @_brick_bt_descrip[bt.first][bt[1].first.first].nil?
|
863
872
|
bt_txt = (bt_class = bt[1].first.first).brick_descrip(
|
864
873
|
# 0..62 because Postgres column names are limited to 63 characters
|
865
874
|
#{obj_name}, (descrips = @_brick_bt_descrip[bt.first][bt_class])[0..-2].map { |id| #{obj_name}.send(id.last[0..62]) }, (bt_id_col = descrips.last)
|
866
875
|
)
|
867
876
|
bt_txt ||= \"<span class=\\\"orphan\\\"><< Orphaned ID: #\{val} >></span>\".html_safe if val
|
868
877
|
bt_id = bt_id_col.map { |id_col| #{obj_name}.send(id_col.to_sym) } %>
|
869
|
-
<%= bt_id&.first ? link_to(bt_txt, send(\"#\{bt_class.base_class.
|
878
|
+
<%= bt_id&.first ? link_to(bt_txt, send(\"#\{bt_class.base_class._brick_index(:singular)}_path\".to_sym, bt_id)) : bt_txt %>
|
870
879
|
<% end
|
871
880
|
elsif (hms_col = hms_cols[col_name])
|
872
881
|
if hms_col.length == 1 %>
|
@@ -877,9 +886,9 @@ erDiagram
|
|
877
886
|
descrips = @_brick_bt_descrip[col_name.to_sym][klass]
|
878
887
|
ho_txt = klass.brick_descrip(#{obj_name}, descrips[0..-2].map { |id| #{obj_name}.send(id.last[0..62]) }, (ho_id_col = descrips.last))
|
879
888
|
ho_id = ho_id_col.map { |id_col| #{obj_name}.send(id_col.to_sym) }
|
880
|
-
ho_id&.first ? link_to(ho_txt, send(\"#\{klass.base_class.
|
889
|
+
ho_id&.first ? link_to(ho_txt, send(\"#\{klass.base_class._brick_index(:singular)}_path\".to_sym, ho_id)) : ho_txt
|
881
890
|
else
|
882
|
-
\"#\{hms_col[1] || 'View'
|
891
|
+
\"#\{hms_col[1] || 'View'} #\{hms_col.first}\"
|
883
892
|
end %>
|
884
893
|
<%= link_to txt, send(\"#\{klass._brick_index}_path\".to_sym, hms_col[2]) unless hms_col[1]&.zero? %>
|
885
894
|
<% end
|
@@ -927,7 +936,11 @@ erDiagram
|
|
927
936
|
@resources.each do |r|
|
928
937
|
%>
|
929
938
|
<tr>
|
930
|
-
<td><%=
|
939
|
+
<td><%= begin
|
940
|
+
kls = Object.const_get(::Brick.relations[r[0]].fetch(:class_name, nil))
|
941
|
+
rescue
|
942
|
+
end
|
943
|
+
kls ? link_to(r[0], send(\"#\{kls._brick_index}_path\".to_sym)) : r[0] %></td>
|
931
944
|
<td<%= if r[1]
|
932
945
|
' class=\"orphan\"' unless ::Brick.relations.key?(r[1])
|
933
946
|
else
|
@@ -984,12 +997,15 @@ if (description = (relation = Brick.relations[#{model_name}.table_name])&.fetch(
|
|
984
997
|
end
|
985
998
|
%><%= link_to '(See all #{obj_name.pluralize})', #{@_brick_model._brick_index}_path %>
|
986
999
|
#{erd_markup}
|
987
|
-
<% if obj
|
1000
|
+
<% if obj
|
1001
|
+
# path_options = [obj.#{pk}]
|
1002
|
+
# path_options << { '_brick_schema': } if
|
1003
|
+
# url = send(:#\{model_name._brick_index(:singular)}_path, obj.#{pk})
|
1004
|
+
options = {}
|
1005
|
+
options[:url] = send(\"#\{#{model_name}._brick_index(:singular)}_path\".to_sym, obj) if ::Brick.config.path_prefix
|
1006
|
+
%>
|
988
1007
|
<br><br>
|
989
|
-
<%= #
|
990
|
-
# path_options << { '_brick_schema': } if
|
991
|
-
# url = send(:#{model_name.underscore}_path, obj.#{pk})
|
992
|
-
form_for(obj.becomes(#{model_name})) do |f| %>
|
1008
|
+
<%= form_for(obj.becomes(#{model_name}), options) do |f| %>
|
993
1009
|
<table class=\"shadow\">
|
994
1010
|
<% has_fields = false
|
995
1011
|
@#{obj_name}.attributes.each do |k, val|
|
@@ -1008,7 +1024,7 @@ end
|
|
1008
1024
|
bt_pair = nil
|
1009
1025
|
loop do
|
1010
1026
|
bt_pair = bt[1].find { |pair| pair.first.name == poly_class_name }
|
1011
|
-
#
|
1027
|
+
# Accommodate any valid STI by going up the chain of inheritance
|
1012
1028
|
break unless bt_pair.nil? && poly_class_name = ::Brick.existing_stis[poly_class_name]
|
1013
1029
|
end
|
1014
1030
|
puts \"*** Might be missing an STI class called #\{orig_poly_name\} whose base class should have this:
|
@@ -1043,7 +1059,7 @@ end
|
|
1043
1059
|
html_options[:prompt] = \"Select #\{bt_name\}\" %>
|
1044
1060
|
<%= f.select k.to_sym, bt[3], { value: val || '^^^brick_NULL^^^' }, html_options %>
|
1045
1061
|
<%= if (bt_obj = bt_class&.find_by(bt_pair[1] => val))
|
1046
|
-
link_to('⇛', send(\"#\{bt_class.base_class.
|
1062
|
+
link_to('⇛', send(\"#\{bt_class.base_class._brick_index(:singular)\}_path\".to_sym, bt_obj.send(bt_class.primary_key.to_sym)), { class: 'show-arrow' })
|
1047
1063
|
elsif val
|
1048
1064
|
\"<span class=\\\"orphan\\\">Orphaned ID: #\{val}</span>\".html_safe
|
1049
1065
|
end %>
|
@@ -1101,9 +1117,10 @@ end
|
|
1101
1117
|
<tr><td colspan=\"2\">(No displayable fields)</td></tr>
|
1102
1118
|
<% end %>
|
1103
1119
|
</table>
|
1104
|
-
|
1120
|
+
<% end %>
|
1105
1121
|
|
1106
|
-
|
1122
|
+
#{unless args.first == 'new'
|
1123
|
+
hms_headers.each_with_object(+'') do |hm, s|
|
1107
1124
|
# %%% Would be able to remove this when multiple foreign keys to same destination becomes bulletproof
|
1108
1125
|
next if hm.first.options[:through] && !hm.first.through_reflection
|
1109
1126
|
|
@@ -1118,14 +1135,15 @@ end
|
|
1118
1135
|
<tr><td>(none)</td></tr>
|
1119
1136
|
<% else %>
|
1120
1137
|
<% collection.uniq.each do |#{hm_singular_name}| %>
|
1121
|
-
<tr><td><%= link_to(#{hm_singular_name}.brick_descrip, #{hm.first.klass.
|
1138
|
+
<tr><td><%= link_to(#{hm_singular_name}.brick_descrip, #{hm.first.klass._brick_index(:singular)}_path(slashify(#{obj_pk}))) %></td></tr>
|
1122
1139
|
<% end %>
|
1123
1140
|
<% end %>
|
1124
1141
|
</table>"
|
1125
1142
|
else
|
1126
1143
|
s
|
1127
1144
|
end
|
1128
|
-
end
|
1145
|
+
end
|
1146
|
+
end}
|
1129
1147
|
<% end %>
|
1130
1148
|
#{script}"
|
1131
1149
|
|
@@ -1259,6 +1277,9 @@ document.querySelectorAll(\"input, select\").forEach(function (inp) {
|
|
1259
1277
|
}
|
1260
1278
|
});
|
1261
1279
|
</script>"
|
1280
|
+
# puts "==============="
|
1281
|
+
# puts inline
|
1282
|
+
# puts "==============="
|
1262
1283
|
# As if it were an inline template (see #determine_template in actionview-5.2.6.2/lib/action_view/renderer/template_renderer.rb)
|
1263
1284
|
keys = options.has_key?(:locals) ? options[:locals].keys : []
|
1264
1285
|
handler = ActionView::Template.handler_for_extension(options[:type] || 'erb')
|
data/lib/brick/version_number.rb
CHANGED
data/lib/brick.rb
CHANGED
@@ -126,10 +126,15 @@ module Brick
|
|
126
126
|
attr_accessor :default_schema, :db_schemas, :routes_done, :is_oracle, :is_eager_loading
|
127
127
|
|
128
128
|
def set_db_schema(params = nil)
|
129
|
-
schema = (params ? params['_brick_schema'] : ::Brick.default_schema)
|
130
|
-
if schema && ::Brick.db_schemas&.
|
129
|
+
schema = (params ? params['_brick_schema'] : ::Brick.default_schema)
|
130
|
+
if schema && ::Brick.db_schemas&.key?(schema)
|
131
131
|
ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?;", schema)
|
132
132
|
schema
|
133
|
+
elsif ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
|
134
|
+
# Just return the current schema
|
135
|
+
orig_schema = ActiveRecord::Base.execute_sql('SELECT current_schemas(true)').first['current_schemas'][1..-2].split(',')
|
136
|
+
# ::Brick.apartment_multitenant && tbl_parts.first == Apartment.default_schema
|
137
|
+
(orig_schema - ['pg_catalog']).first
|
133
138
|
end
|
134
139
|
end
|
135
140
|
|
@@ -216,6 +221,12 @@ module Brick
|
|
216
221
|
true
|
217
222
|
end
|
218
223
|
|
224
|
+
# Any path prefixing to apply to all auto-generated Brick routes
|
225
|
+
# @api public
|
226
|
+
def path_prefix=(path)
|
227
|
+
Brick.config.path_prefix = path
|
228
|
+
end
|
229
|
+
|
219
230
|
# Switches Brick auto-models on or off, for all threads
|
220
231
|
# @api public
|
221
232
|
def enable_models=(value)
|
@@ -429,7 +440,7 @@ module Brick
|
|
429
440
|
end
|
430
441
|
end
|
431
442
|
if (polys = ::Brick.config.polymorphics)
|
432
|
-
if (schema = ::Brick.config.schema_behavior[:multitenant]&.fetch(:schema_to_analyse, nil)) && ::Brick.db_schemas&.
|
443
|
+
if (schema = ::Brick.config.schema_behavior[:multitenant]&.fetch(:schema_to_analyse, nil)) && ::Brick.db_schemas&.key?(schema)
|
433
444
|
ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?;", schema)
|
434
445
|
end
|
435
446
|
missing_stis = {}
|
@@ -519,9 +530,9 @@ In config/initializers/brick.rb appropriate entries would look something like:
|
|
519
530
|
abstract_ar_bases
|
520
531
|
end
|
521
532
|
|
522
|
-
def display_classes(rels, max_length)
|
533
|
+
def display_classes(prefix, rels, max_length)
|
523
534
|
rels.sort.each do |rel|
|
524
|
-
puts "#{rel.first}#{' ' * (max_length - rel.first.length)} /#{rel.last}"
|
535
|
+
puts "#{rel.first}#{' ' * (max_length - rel.first.length)} /#{prefix}#{rel.last}"
|
525
536
|
end
|
526
537
|
puts "\n"
|
527
538
|
end
|
@@ -538,23 +549,40 @@ In config/initializers/brick.rb appropriate entries would look something like:
|
|
538
549
|
view_class_length = 37 # Length of "Classes that can be built from views:"
|
539
550
|
existing_controllers = routes.each_with_object({}) { |r, s| c = r.defaults[:controller]; s[c] = nil if c }
|
540
551
|
::Rails.application.routes.append do
|
552
|
+
brick_routes_create = lambda do |schema_name, controller_name, v, options|
|
553
|
+
if schema_name # && !Object.const_defined('Apartment')
|
554
|
+
send(:namespace, schema_name) do
|
555
|
+
send(:resources, v[:resource].to_sym, **options)
|
556
|
+
end
|
557
|
+
else
|
558
|
+
send(:resources, v[:resource].to_sym, **options)
|
559
|
+
end
|
560
|
+
end
|
561
|
+
|
541
562
|
# %%% TODO: If no auto-controllers then enumerate the controllers folder in order to build matching routes
|
542
563
|
# If auto-controllers and auto-models are both enabled then this makes sense:
|
564
|
+
controller_prefix = (::Brick.config.path_prefix ? "#{::Brick.config.path_prefix}/" : '')
|
543
565
|
::Brick.relations.each do |k, v|
|
544
566
|
unless !(controller_name = v.fetch(:resource, nil)&.pluralize) || existing_controllers.key?(controller_name)
|
545
567
|
options = {}
|
546
568
|
options[:only] = [:index, :show] if v.key?(:isView)
|
569
|
+
# First do the API routes
|
547
570
|
full_resource = nil
|
548
|
-
if (schema_name = v.fetch(:schema, nil))
|
549
|
-
send(:namespace, schema_name) do
|
550
|
-
send(:resources, v[:resource].to_sym, **options)
|
551
|
-
end
|
571
|
+
if (schema_name = v.fetch(:schema, nil))
|
552
572
|
full_resource = "#{schema_name}/#{v[:resource]}"
|
553
|
-
send(:get, "#{::Brick.api_root}#{full_resource}", { to: "#{schema_name}/#{controller_name}#index" }) if Object.const_defined?('Rswag::Ui')
|
573
|
+
send(:get, "#{::Brick.api_root}#{full_resource}", { to: "#{controller_prefix}#{schema_name}/#{controller_name}#index" }) if Object.const_defined?('Rswag::Ui')
|
554
574
|
else
|
555
|
-
send(:resources, v[:resource].to_sym, **options)
|
556
575
|
# Normally goes to something like: /api/v1/employees
|
557
|
-
send(:get, "#{::Brick.api_root}#{v[:resource]}", { to: "#{controller_name}#index" }) if Object.const_defined?('Rswag::Ui')
|
576
|
+
send(:get, "#{::Brick.api_root}#{v[:resource]}", { to: "#{controller_prefix}#{controller_name}#index" }) if Object.const_defined?('Rswag::Ui')
|
577
|
+
end
|
578
|
+
# Now the normal routes
|
579
|
+
if ::Brick.config.path_prefix
|
580
|
+
# Was: send(:scope, path: ::Brick.config.path_prefix) do
|
581
|
+
send(:namespace, ::Brick.config.path_prefix) do
|
582
|
+
brick_routes_create.call(schema_name, controller_name, v, options)
|
583
|
+
end
|
584
|
+
else
|
585
|
+
brick_routes_create.call(schema_name, controller_name, v, options)
|
558
586
|
end
|
559
587
|
|
560
588
|
if (class_name = v.fetch(:class_name, nil))
|
@@ -572,19 +600,19 @@ In config/initializers/brick.rb appropriate entries would look something like:
|
|
572
600
|
if tables.present?
|
573
601
|
puts "Classes that can be built from tables:#{' ' * (table_class_length - 38)} Path:"
|
574
602
|
puts "======================================#{' ' * (table_class_length - 38)} ====="
|
575
|
-
::Brick.display_classes(tables, table_class_length)
|
603
|
+
::Brick.display_classes(controller_prefix, tables, table_class_length)
|
576
604
|
end
|
577
605
|
if views.present?
|
578
606
|
puts "Classes that can be built from views:#{' ' * (view_class_length - 37)} Path:"
|
579
607
|
puts "=====================================#{' ' * (view_class_length - 37)} ====="
|
580
|
-
::Brick.display_classes(views, view_class_length)
|
608
|
+
::Brick.display_classes(controller_prefix, views, view_class_length)
|
581
609
|
end
|
582
610
|
|
583
611
|
if ::Brick.config.add_status && instance_variable_get(:@set).named_routes.names.exclude?(:brick_status)
|
584
|
-
get(
|
612
|
+
get("/#{controller_prefix}brick_status", to: 'brick_gem#status', as: 'brick_status')
|
585
613
|
end
|
586
614
|
if ::Brick.config.add_orphans && instance_variable_get(:@set).named_routes.names.exclude?(:brick_orphans)
|
587
|
-
get(
|
615
|
+
get("/#{controller_prefix}brick_orphans", to: 'brick_gem#orphans', as: 'brick_orphans')
|
588
616
|
end
|
589
617
|
if Object.const_defined?('Rswag::Ui') && doc_endpoint = Rswag::Ui.config.config_object[:urls].last
|
590
618
|
# Serves JSON swagger info from a path such as '/api-docs/v1/swagger.json'
|
@@ -139,6 +139,10 @@ module Brick
|
|
139
139
|
# # Settings for the Brick gem
|
140
140
|
# # (By default this auto-creates models, controllers, views, and routes on-the-fly.)
|
141
141
|
|
142
|
+
# # Custom path prefix to apply to all auto-generated Brick routes. Also causes auto-generated controllers
|
143
|
+
# # to be created inside a module with the same name.
|
144
|
+
# ::Brick.path_prefix = 'admin'
|
145
|
+
|
142
146
|
# # Normally all are enabled in development mode, and for security reasons only models are enabled in production
|
143
147
|
# # and test. This allows you to either (a) turn off models entirely, or (b) enable controllers, views, and routes
|
144
148
|
# # in production.
|
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.78
|
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-10-
|
11
|
+
date: 2022-10-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|