brick 1.0.25 → 1.0.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b0dfa64d7a8a148b4c2ad990f5af1604d5855b80c6e95f39cd371a43a591b7f1
4
- data.tar.gz: bc7644a08678136d91696a68e0ad670f4f04d41772f36ba1bd806ebe45f23b04
3
+ metadata.gz: ab7e6a8198638a0e6d3cde289db17d107e699fad2b4b5a200703cb1982d19569
4
+ data.tar.gz: 708360bb9463e45c8f7fece60dc762dc65b2b3893d8044ffce9ee87e33f63b80
5
5
  SHA512:
6
- metadata.gz: a39a8adc0c72288db5bd08e2483cefb701db9c2122fe1b4012c19bef07c7d048fed7f6e051473a9b4b6c80e321e4a781a16543d40f3e569f23c31f420a5117b6
7
- data.tar.gz: 3ddcc914e3143a02a4582d6f525417c3012ae4eafc9ae889d5935223f6173afb4fb5ff3a40dc6c9caf95dfbf7a3c5be8e7bc4890efb385867ef5fa6f3ccbfb9e
6
+ metadata.gz: 92229661eff9aac30ec7fad96603bc649a122131956e8632562e22d11c8bae67db0272ec5453ab2cc233bd11f7ab72222c3520f2acceecb83e35d4c54cbd8f4d
7
+ data.tar.gz: ddc169b2effaf58e60861d0153f191f6f1e2f11d459363a000da2c1f44c1583c8cd037f78269c238a84de58e04d7633c7a7ecbf046c08ef48fdaf5fb01de76a3
data/lib/brick/config.rb CHANGED
@@ -122,6 +122,22 @@ module Brick
122
122
  @mutex.synchronize { @sti_namespace_prefixes = prefixes }
123
123
  end
124
124
 
125
+ def schema_to_analyse
126
+ @mutex.synchronize { @schema_to_analyse }
127
+ end
128
+
129
+ def schema_to_analyse=(schema)
130
+ @mutex.synchronize { @schema_to_analyse = schema }
131
+ end
132
+
133
+ def default_route_fallback
134
+ @mutex.synchronize { @default_route_fallback }
135
+ end
136
+
137
+ def default_route_fallback=(resource_name)
138
+ @mutex.synchronize { @default_route_fallback = resource_name }
139
+ end
140
+
125
141
  def skip_database_views
126
142
  @mutex.synchronize { @skip_database_views }
127
143
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # Some future enhancement ideas:
4
+
3
5
  # Have markers on HM relationships to indicate "load this one every time" or "lazy load it" or "don't bother"
4
6
  # Others on BT to indicate "this is a lookup"
5
7
 
@@ -18,7 +20,7 @@
18
20
  # Sensitive stuff -- make a lock icon thing so people don't accidentally edit stuff
19
21
 
20
22
  # Static text that can go on pages - headings and footers and whatever
21
- # Eventually some indication about if it should be a paginated table / unpaginated / a list of just some fields / etc
23
+ # Eventually some indication about if it should be a paginated table / unpaginated / a list of just some fields / columns shown in a different sequence / etc
22
24
 
23
25
  # Grid where each cell is one field and then when you mouse over then it shows a popup other table of detail inside
24
26
 
@@ -32,6 +34,9 @@
32
34
 
33
35
  # Currently quadrupling up routes
34
36
 
37
+ # Modal pop-up things for editing large text / date ranges / hierarchies of data
38
+
39
+ # For recognised self-references, have the show page display all related objects up to the parent (or the start of a circular reference)
35
40
 
36
41
  # ==========================================================
37
42
  # Dynamically create model or controller classes when needed
@@ -52,14 +57,6 @@ module Arel
52
57
  end
53
58
  end
54
59
 
55
- # module ActiveModel
56
- # class NotNullValidator < EachValidator
57
- # def validate_each(record, attribute, value)
58
- # record.errors[attribute] << "must not be null" if value.nil?
59
- # end
60
- # end
61
- # end
62
-
63
60
  module ActiveRecord
64
61
  class Base
65
62
  def self._assoc_names
@@ -70,6 +67,14 @@ module ActiveRecord
70
67
  false
71
68
  end
72
69
 
70
+ def self._brick_primary_key(relation = nil)
71
+ return instance_variable_get(:@_brick_primary_key) if instance_variable_defined?(:@_brick_primary_key)
72
+
73
+ pk = primary_key.is_a?(String) ? [primary_key] : primary_key || []
74
+ # Just return [] if we're missing any part of the primary key. (PK is usually just "id")
75
+ @_brick_primary_key ||= pk.any? { |pk_part| !relation[:cols].key?(pk_part) } ? [] : pk
76
+ end
77
+
73
78
  # Used to show a little prettier name for an object
74
79
  def self.brick_get_dsl
75
80
  # If there's no DSL yet specified, just try to find the first usable column on this model
@@ -82,7 +87,6 @@ module ActiveRecord
82
87
  dsl
83
88
  end
84
89
 
85
- # Pass in true for build_array, or just pass in a JoinArray
86
90
  def self.brick_parse_dsl(build_array = nil, prefix = [], translations = {}, is_polymorphic = false)
87
91
  build_array = ::Brick::JoinArray.new.tap { |ary| ary.replace([build_array]) } if build_array.is_a?(::Brick::JoinHash)
88
92
  build_array = ::Brick::JoinArray.new unless build_array.nil? || build_array.is_a?(Array)
@@ -308,6 +312,8 @@ module ActiveRecord
308
312
  if is_add_bts || is_add_hms
309
313
  bts, hms, associatives = ::Brick.get_bts_and_hms(klass)
310
314
  bts.each do |_k, bt|
315
+ next if bt[2] # Polymorphic?
316
+
311
317
  # join_array will receive this relation name when calling #brick_parse_dsl
312
318
  bt_descrip[bt.first] = if bt[1].is_a?(Array)
313
319
  bt[1].each_with_object({}) { |bt_class, s| s[bt_class] = bt_class.brick_parse_dsl(join_array, bt.first, translations, true) }
@@ -351,27 +357,24 @@ module ActiveRecord
351
357
  next if chains[k1].nil?
352
358
 
353
359
  tbl_name = field_tbl_names[v.first][k1] ||= shift_or_first(chains[k1])
354
- # if (col_name = v1[1].last&.last) # col_name is weak when there are multiple, using sel_col.last instead
355
360
  field_tbl_name = nil
356
- v1.map { |x|
357
- [translations[x[0..-2].map(&:to_s).join('.')], x.last]
358
- }.each_with_index do |sel_col, idx|
359
- field_tbl_name ||= field_tbl_names[v.first][sel_col.first] ||= shift_or_first(chains[sel_col.first])
361
+ v1.map { |x| [translations[x[0..-2].map(&:to_s).join('.')], x.last] }.each_with_index do |sel_col, idx|
362
+ field_tbl_name = field_tbl_names[v.first][sel_col.first] ||= shift_or_first(chains[sel_col.first])
360
363
 
361
364
  selects << "#{"#{field_tbl_name}.#{sel_col.last}"} AS \"#{(col_alias = "_brfk_#{v.first}__#{sel_col.last}")}\""
362
365
  v1[idx] << col_alias
363
366
  end
364
- # end
365
367
 
366
- if (id_col = k1.primary_key) && !id_for_tables.key?(v.first) # was tbl_name
368
+ unless id_for_tables.key?(v.first)
367
369
  # Accommodate composite primary key by allowing id_col to come in as an array
368
- (id_col.is_a?(Array) ? id_col : [id_col]).each do |id_part|
369
- selects << "#{"#{tbl_name}.#{id_part}"} AS \"#{(id_alias = "_brfk_#{v.first}__#{id_part}")}\""
370
- id_for_tables[v.first] << id_alias
370
+ ((id_col = k1.primary_key).is_a?(Array) ? id_col : [id_col]).each do |id_part|
371
+ id_for_tables[v.first] << if id_part
372
+ selects << "#{"#{tbl_name}.#{id_part}"} AS \"#{(id_alias = "_brfk_#{v.first}__#{id_part}")}\""
373
+ id_alias
374
+ end
371
375
  end
372
- v1 << id_for_tables[v.first]
376
+ v1 << id_for_tables[v.first].compact
373
377
  end
374
-
375
378
  end
376
379
  end
377
380
  join_array.each do |assoc_name|
@@ -391,7 +394,8 @@ module ActiveRecord
391
394
  else
392
395
  fk_col = hm.foreign_key
393
396
  poly_type = hm.inverse_of.foreign_type if hm.options.key?(:as)
394
- hm.klass.primary_key || '*'
397
+ pk = hm.klass.primary_key
398
+ (pk.is_a?(Array) ? pk.first : pk) || '*'
395
399
  end
396
400
  tbl_alias = "_br_#{hm.name}"
397
401
  pri_tbl = hm.active_record
@@ -454,8 +458,7 @@ JOIN (SELECT #{selects.join(', ')}, COUNT(#{count_column}) AS _ct_ FROM #{associ
454
458
  this_module.const_get(class_name)
455
459
  else
456
460
  # Build STI subclass and place it into the namespace module
457
- # %%% Does this ever get used???
458
- puts [this_module.const_set(class_name, klass = Class.new(self)).name, class_name].inspect
461
+ this_module.const_set(class_name, klass = Class.new(self))
459
462
  klass
460
463
  end
461
464
  end
@@ -530,7 +533,7 @@ class Object
530
533
  singular_table_name = ActiveSupport::Inflector.underscore(model_name)
531
534
 
532
535
  # Adjust for STI if we know of a base model for the requested model name
533
- table_name = if (base_model = ::Brick.sti_models[model_name]&.fetch(:base, nil))
536
+ table_name = if (base_model = ::Brick.sti_models[model_name]&.fetch(:base, nil) || ::Brick.existing_stis[model_name]&.constantize)
534
537
  base_model.table_name
535
538
  else
536
539
  ActiveSupport::Inflector.pluralize(singular_table_name)
@@ -571,7 +574,7 @@ class Object
571
574
  return
572
575
  end
573
576
 
574
- if (base_model = ::Brick.sti_models[model_name]&.fetch(:base, nil))
577
+ if (base_model = ::Brick.sti_models[model_name]&.fetch(:base, nil) || ::Brick.existing_stis[model_name]&.constantize)
575
578
  is_sti = true
576
579
  else
577
580
  base_model = ::Brick.config.models_inherit_from || ActiveRecord::Base
@@ -591,16 +594,14 @@ class Object
591
594
  code << " def self.is_view?; true; end\n"
592
595
  end
593
596
 
594
- # Missing a primary key column? (Usually "id")
595
- ar_pks = primary_key.is_a?(String) ? [primary_key] : primary_key || []
596
597
  db_pks = relation[:cols]&.map(&:first)
597
- has_pk = ar_pks.length.positive? && (db_pks & ar_pks).sort == ar_pks.sort
598
+ has_pk = _brick_primary_key(relation).length.positive? && (db_pks & _brick_primary_key).sort == _brick_primary_key.sort
598
599
  our_pks = relation[:pkey].values.first
599
600
  # No primary key, but is there anything UNIQUE?
600
601
  # (Sort so that if there are multiple UNIQUE constraints we'll pick one that uses the least number of columns.)
601
602
  our_pks = relation[:ukeys].values.sort { |a, b| a.length <=> b.length }.first unless our_pks&.present?
602
603
  if has_pk
603
- code << " # Primary key: #{ar_pks.join(', ')}\n" unless ar_pks == ['id']
604
+ code << " # Primary key: #{_brick_primary_key.join(', ')}\n" unless _brick_primary_key == ['id']
604
605
  elsif our_pks&.present?
605
606
  if our_pks.length > 1 && respond_to?(:'primary_keys=') # Using the composite_primary_keys gem?
606
607
  new_model_class.primary_keys = our_pks
@@ -620,10 +621,13 @@ class Object
620
621
  # The key in each hash entry (fk.first) is the constraint name
621
622
  inverse_assoc_name = (assoc = fk.last)[:inverse]&.fetch(:assoc_name, nil)
622
623
  if (invs = assoc[:inverse_table]).is_a?(Array)
623
- invs.each { |inv| build_bt_or_hm(relations, model_name, relation, hmts, assoc, inverse_assoc_name, inv, code) }
624
- else
625
- build_bt_or_hm(relations, model_name, relation, hmts, assoc, inverse_assoc_name, invs, code)
624
+ if assoc[:is_bt]
625
+ invs = invs.first # Just do the first one of what would be multiple identical polymorphic belongs_to
626
+ else
627
+ invs.each { |inv| build_bt_or_hm(relations, model_name, relation, hmts, assoc, inverse_assoc_name, inv, code) }
628
+ end
626
629
  end
630
+ build_bt_or_hm(relations, model_name, relation, hmts, assoc, inverse_assoc_name, invs, code) unless invs.is_a?(Array)
627
631
  hmts
628
632
  end
629
633
  hmts.each do |hmt_fk, fks|
@@ -650,7 +654,7 @@ class Object
650
654
  # # Not NULLables
651
655
  # # %%% For the minute we've had to pull this out because it's been troublesome implementing the NotNull validator
652
656
  # relation[:cols].each do |col, datatype|
653
- # if (datatype[3] && ar_pks.exclude?(col) && ::Brick.config.metadata_columns.exclude?(col)) ||
657
+ # if (datatype[3] && _brick_primary_key.exclude?(col) && ::Brick.config.metadata_columns.exclude?(col)) ||
654
658
  # ::Brick.config.not_nullables.include?("#{matching}.#{col}")
655
659
  # code << " validates :#{col}, not_null: true\n"
656
660
  # self.send(:validates, col.to_sym, { not_null: true })
@@ -698,9 +702,8 @@ class Object
698
702
  # need_class_name = ActiveSupport::Inflector.singularize(assoc_name) == ActiveSupport::Inflector.singularize(table_name.underscore)
699
703
  # Are there multiple foreign keys out to the same table?
700
704
  assoc_name, need_class_name = _brick_get_hm_assoc_name(relation, assoc)
701
- # binding.pry if assoc.key?(:polymorphic)
702
705
  if assoc.key?(:polymorphic)
703
- options[:as] = assoc[:fk].to_sym if assoc.key?(:polymorphic)
706
+ options[:as] = assoc[:fk].to_sym
704
707
  else
705
708
  need_fk = "#{ActiveSupport::Inflector.singularize(assoc[:inverse][:inverse_table])}_id" != assoc[:fk]
706
709
  end
@@ -748,13 +751,14 @@ class Object
748
751
  def build_controller(class_name, plural_class_name, model, relations)
749
752
  table_name = ActiveSupport::Inflector.underscore(plural_class_name)
750
753
  singular_table_name = ActiveSupport::Inflector.singularize(table_name)
754
+ pk = model._brick_primary_key(relations[table_name])
751
755
 
752
756
  code = +"class #{class_name} < ApplicationController\n"
753
757
  built_controller = Class.new(ActionController::Base) do |new_controller_class|
754
758
  Object.const_set(class_name.to_sym, new_controller_class)
755
759
 
756
760
  code << " def index\n"
757
- code << " @#{table_name} = #{model.name}#{model.primary_key ? ".order(#{model.primary_key.inspect})" : '.all'}\n"
761
+ code << " @#{table_name} = #{model.name}#{pk&.present? ? ".order(#{pk.inspect})" : '.all'}\n"
758
762
  code << " @#{table_name}.brick_select(params)\n"
759
763
  code << " end\n"
760
764
  self.protect_from_forgery unless: -> { self.request.format.js? }
@@ -772,7 +776,8 @@ class Object
772
776
  return
773
777
  end
774
778
 
775
- ar_relation = model.primary_key ? model.order("#{model.table_name}.#{model.primary_key}") : model.all
779
+ order = pk.each_with_object([]) { |pk_part, s| s << "#{model.table_name}.#{pk_part}" }
780
+ ar_relation = order.present? ? model.order("#{order.join(', ')}") : model.all
776
781
  @_brick_params = ar_relation.brick_select(params, (selects = []), (bt_descrip = {}), (hm_counts = {}), (join_array = ::Brick::JoinArray.new))
777
782
  # %%% Add custom HM count columns
778
783
  # %%% What happens when the PK is composite?
@@ -787,11 +792,15 @@ class Object
787
792
 
788
793
  if model.primary_key
789
794
  code << " def show\n"
790
- code << (find_by_id = " @#{singular_table_name} = #{model.name}.find(params[:id].split(','))\n")
795
+ code << (find_by_id = " id = params[:id]&.split(/[\\/,_]/)
796
+ id = id.first if id.is_a?(Array) && id.length == 1
797
+ @#{singular_table_name} = #{model.name}.find(id)\n")
791
798
  code << " end\n"
792
799
  self.define_method :show do
793
800
  ::Brick.set_db_schema(params)
794
- instance_variable_set("@#{singular_table_name}".to_sym, model.find(params[:id].split(',')))
801
+ id = params[:id]&.split(/[\/,_]/)
802
+ id = id.first if id.is_a?(Array) && id.length == 1
803
+ instance_variable_set("@#{singular_table_name}".to_sym, model.find(id))
795
804
  end
796
805
  end
797
806
 
@@ -800,6 +809,10 @@ class Object
800
809
  code << " # (Define :new, :create)\n"
801
810
 
802
811
  if model.primary_key
812
+ if (schema = ::Brick.config.schema_to_analyse) && ::Brick.db_schemas&.include?(schema)
813
+ ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?;", schema)
814
+ end
815
+
803
816
  is_need_params = true
804
817
  # code << " # (Define :edit, and :destroy)\n"
805
818
  code << " def update\n"
@@ -874,12 +887,12 @@ module ActiveRecord::ConnectionHandling
874
887
  def _brick_reflect_tables
875
888
  if (relations = ::Brick.relations).empty?
876
889
  # Only for Postgres? (Doesn't work in sqlite3)
877
- # puts ActiveRecord::Base.connection.execute("SELECT current_setting('SEARCH_PATH')").to_a.inspect
890
+ # puts ActiveRecord::Base.execute_sql("SELECT current_setting('SEARCH_PATH')").to_a.inspect
878
891
 
879
892
  schema_sql = 'SELECT NULL AS table_schema;'
880
893
  case ActiveRecord::Base.connection.adapter_name
881
894
  when 'PostgreSQL'
882
- schema = 'public'
895
+ schema = 'public' # Too early at this point to be able to pick up: Brick.config.schema_to_analyse
883
896
  schema_sql = 'SELECT DISTINCT table_schema FROM INFORMATION_SCHEMA.tables;'
884
897
  when 'Mysql2'
885
898
  schema = ActiveRecord::Base.connection.current_database
@@ -923,7 +936,7 @@ module ActiveRecord::ConnectionHandling
923
936
  measures = []
924
937
  case ActiveRecord::Base.connection.adapter_name
925
938
  when 'PostgreSQL', 'SQLite' # These bring back a hash for each row because the query uses column aliases
926
- ActiveRecord::Base.connection.execute(sql).each do |r|
939
+ ActiveRecord::Base.execute_sql(sql).each do |r|
927
940
  # next if internal_views.include?(r['relation_name']) # Skip internal views such as v_all_assessments
928
941
  relation = relations[(relation_name = r['relation_name'])]
929
942
  relation[:isView] = true if r['table_type'] == 'VIEW'
@@ -942,7 +955,7 @@ module ActiveRecord::ConnectionHandling
942
955
  # puts "KEY! #{r['relation_name']}.#{col_name} #{r['key']} #{r['const']}" if r['key']
943
956
  end
944
957
  else # MySQL2 acts a little differently, bringing back an array for each row
945
- ActiveRecord::Base.connection.execute(sql).each do |r|
958
+ ActiveRecord::Base.execute_sql(sql).each do |r|
946
959
  # next if internal_views.include?(r['relation_name']) # Skip internal views such as v_all_assessments
947
960
  relation = relations[(relation_name = r[0])] # here relation represents a table or view from the database
948
961
  relation[:isView] = true if r[1] == 'VIEW' # table_type
@@ -1008,11 +1021,11 @@ module ActiveRecord::ConnectionHandling
1008
1021
  else
1009
1022
  end
1010
1023
  if sql
1011
- ::Brick.db_schemas = ActiveRecord::Base.connection.execute(schema_sql)
1024
+ ::Brick.db_schemas = ActiveRecord::Base.execute_sql(schema_sql)
1012
1025
  ::Brick.db_schemas = ::Brick.db_schemas.to_a unless ::Brick.db_schemas.is_a?(Array)
1013
1026
  ::Brick.db_schemas.map! { |row| row['table_schema'] } unless ::Brick.db_schemas.empty? || ::Brick.db_schemas.first.is_a?(String)
1014
1027
  ::Brick.db_schemas -= ['information_schema', 'pg_catalog']
1015
- ActiveRecord::Base.connection.execute(sql).each do |fk|
1028
+ ActiveRecord::Base.execute_sql(sql).each do |fk|
1016
1029
  fk = fk.values unless fk.is_a?(Array)
1017
1030
  ::Brick._add_bt_and_hm(fk, relations)
1018
1031
  end
@@ -1064,12 +1077,8 @@ module Brick
1064
1077
  bts = (relation = relations.fetch(fk[0], nil))&.fetch(:fks) { relation[:fks] = {} }
1065
1078
  # %%% Do we miss out on has_many :through or even HM based on constantizing this model early?
1066
1079
  # Maybe it's already gotten this info because we got as far as to say there was a unique class
1067
- # if is_polymorphic
1068
- # primary_table = fk[]
1069
- # else
1070
- primary_table = (is_class = fk[2].is_a?(Hash) && fk[2].key?(:class)) ? (primary_class = fk[2][:class].constantize).table_name : fk[2]
1071
- hms = (relation = relations.fetch(primary_table, nil))&.fetch(:fks) { relation[:fks] = {} } unless is_class
1072
- # end
1080
+ primary_table = (is_class = fk[2].is_a?(Hash) && fk[2].key?(:class)) ? (primary_class = fk[2][:class].constantize).table_name : fk[2]
1081
+ hms = (relation = relations.fetch(primary_table, nil))&.fetch(:fks) { relation[:fks] = {} } unless is_class
1073
1082
 
1074
1083
  unless (cnstr_name = fk[3])
1075
1084
  # For any appended references (those that come from config), arrive upon a definitely unique constraint name
@@ -1102,8 +1111,12 @@ module Brick
1102
1111
  if is_polymorphic
1103
1112
  # Assuming same fk (don't yet support composite keys for polymorphics)
1104
1113
  assoc_bt[:inverse_table] << fk[2]
1105
- else # Expect we've got a composite key going
1106
- assoc_bt[:fk] = assoc_bt[:fk].is_a?(String) ? [assoc_bt[:fk], fk[1]] : assoc_bt[:fk].concat(fk[1])
1114
+ else # Expect we could have a composite key going
1115
+ if assoc_bt[:fk].is_a?(String)
1116
+ assoc_bt[:fk] = [assoc_bt[:fk], fk[1]] unless fk[1] == assoc_bt[:fk]
1117
+ elsif assoc_bt[:fk].exclude?(fk[1])
1118
+ assoc_bt[:fk] << fk[1]
1119
+ end
1107
1120
  assoc_bt[:assoc_name] = "#{assoc_bt[:assoc_name]}_#{fk[1]}"
1108
1121
  end
1109
1122
  else
@@ -1121,7 +1134,11 @@ module Brick
1121
1134
  return if is_class || ::Brick.config.exclude_hms&.any? { |exclusion| fk[0] == exclusion[0] && fk[1] == exclusion[1] && primary_table == exclusion[2] }
1122
1135
 
1123
1136
  if (assoc_hm = hms.fetch((hm_cnstr_name = "hm_#{cnstr_name}"), nil))
1124
- assoc_hm[:fk] = assoc_hm[:fk].is_a?(String) ? [assoc_hm[:fk], fk[1]] : assoc_hm[:fk].concat(fk[1])
1137
+ if assoc_hm[:fk].is_a?(String)
1138
+ assoc_hm[:fk] = [assoc_hm[:fk], fk[1]] unless fk[1] == assoc_hm[:fk]
1139
+ elsif assoc_hm[:fk].exclude?(fk[1])
1140
+ assoc_hm[:fk] << fk[1]
1141
+ end
1125
1142
  assoc_hm[:alternate_name] = "#{assoc_hm[:alternate_name]}_#{bt_assoc_name}" unless assoc_hm[:alternate_name] == bt_assoc_name
1126
1143
  assoc_hm[:inverse] = assoc_bt
1127
1144
  else
@@ -71,7 +71,8 @@ module Brick
71
71
  keys = if fk_name.is_a?(Array) && pk.is_a?(Array) # Composite keys?
72
72
  fk_name.zip(pk.map { |pk_part| "#{obj_name}.#{pk_part}" })
73
73
  else
74
- [[fk_name, "#{obj_name}.#{pk}"]]
74
+ pk = pk.each_with_object([]) { |pk_part, s| s << "#{obj_name}.#{pk_part}" }
75
+ [[fk_name, "#{pk.length == 1 ? pk.first : pk.inspect}"]]
75
76
  end
76
77
  keys << [hm_assoc.inverse_of.foreign_type, "#{hm_assoc.active_record.name}"] if hm_assoc.options.key?(:as)
77
78
  keys.map { |x| "#{x.first}: #{x.last}"}.join(', ')
@@ -82,7 +83,7 @@ module Brick
82
83
  return _brick_find_template(*args, **options) unless @_brick_model
83
84
 
84
85
  model_name = @_brick_model.name
85
- pk = @_brick_model.primary_key
86
+ pk = @_brick_model._brick_primary_key(::Brick.relations[model_name])
86
87
  obj_name = model_name.underscore
87
88
  table_name = model_name.pluralize.underscore
88
89
  template_link = nil
@@ -112,7 +113,7 @@ module Brick
112
113
  "<%= obj = #{obj_name}.#{hm.first}; link_to(obj.brick_descrip, obj) if obj %>\n"
113
114
  end
114
115
  elsif args.first == 'show'
115
- hm_stuff << "<%= link_to '#{assoc_name}', #{hm_assoc.klass.name.underscore.pluralize}_path({ #{path_keys(hm_assoc, hm_fk_name, "@#{obj_name}&.first&", pk)} }) %>\n"
116
+ hm_stuff << "<%= link_to '#{assoc_name}', #{hm_assoc.klass.name.underscore.pluralize}_path({ #{path_keys(hm_assoc, hm_fk_name, "@#{obj_name}", pk)} }) %>\n"
116
117
  end
117
118
  s << hm_stuff
118
119
  end
@@ -210,16 +211,19 @@ def hide_bcrypt(val)
210
211
  end %>"
211
212
 
212
213
  if ['index', 'show', 'update'].include?(args.first)
214
+ poly_cols = []
213
215
  css << "<% bts = { #{
214
216
  bts.each_with_object([]) do |v, s|
215
- foreign_models = if v.last[1].is_a?(Array)
217
+ foreign_models = if v.last[2] # Polymorphic?
218
+ poly_cols << @_brick_model.reflect_on_association(v[1].first).foreign_type
216
219
  v.last[1].each_with_object([]) { |x, s| s << "[#{x.name}, #{x.primary_key.inspect}]" }.join(', ')
217
220
  else
218
221
  "[#{v.last[1].name}, #{v.last[1].primary_key.inspect}]"
219
222
  end
220
- s << "#{v.first.inspect} => [#{v.last.first.inspect}, [#{foreign_models}]]"
223
+ s << "#{v.first.inspect} => [#{v.last.first.inspect}, [#{foreign_models}], #{v.last[2].inspect}]"
221
224
  end.join(', ')
222
- } } %>"
225
+ } }
226
+ poly_cols = #{poly_cols.inspect} %>"
223
227
  end
224
228
 
225
229
  # %%% When doing schema select, if there's an ID then remove it, or if we're on a new page go to index
@@ -280,7 +284,7 @@ function changeout(href, param, value) {
280
284
  inline = case args.first
281
285
  when 'index'
282
286
  obj_pk = if pk&.is_a?(Array) # Composite primary key?
283
- "[#{pk.map { |pk_part| "#{obj_name}.#{pk_part}" }.join(', ')}]"
287
+ "[#{pk.map { |pk_part| "#{obj_name}.#{pk_part}" }.join(', ')}]" unless pk.empty?
284
288
  elsif pk
285
289
  "#{obj_name}.#{pk}"
286
290
  end
@@ -382,9 +386,9 @@ function changeout(href, param, value) {
382
386
 
383
387
  <% if @_brick_params&.present? %><h3>where <%= @_brick_params.each_with_object([]) { |v, s| s << \"#\{v.first\} = #\{v.last.inspect\}\" }.join(', ') %></h3><% end %>
384
388
  <table id=\"#{table_name}\">
385
- <thead><tr>#{'<th></th>' if pk}
389
+ <thead><tr>#{'<th></th>' if pk.present?}
386
390
  <% @#{table_name}.columns.map(&:name).each do |col| %>
387
- <% next if col == '#{pk}' || ::Brick.config.metadata_columns.include?(col) %>
391
+ <% next if #{pk.inspect}.include?(col) || ::Brick.config.metadata_columns.include?(col) || poly_cols.include?(col) %>
388
392
  <th>
389
393
  <% if (bt = bts[col]) %>
390
394
  BT <%
@@ -405,22 +409,22 @@ function changeout(href, param, value) {
405
409
  <tr>#{"
406
410
  <td><%= link_to '⇛', #{obj_name}_path(#{obj_pk}), { class: 'big-arrow' } %></td>" if obj_pk}
407
411
  <% #{obj_name}.attributes.each do |k, val| %>
408
- <% next if k == '#{pk}' || ::Brick.config.metadata_columns.include?(k) || k.start_with?('_brfk_') || (k.start_with?('_br_') && (k.length == 63 || k.end_with?('_ct'))) %>
412
+ <% next if #{obj_pk.inspect}.include?(k) || ::Brick.config.metadata_columns.include?(k) || poly_cols.include?(k) || k.start_with?('_brfk_') || (k.start_with?('_br_') && (k.length == 63 || k.end_with?('_ct'))) %>
409
413
  <td>
410
414
  <% if (bt = bts[k]) %>
411
415
  <%# binding.pry # Postgres column names are limited to 63 characters %>
412
- <% if (pairs = bt[1].length > 1)
416
+ <% if bt[2] # Polymorphic?
413
417
  bt_class = #{obj_name}.send(\"#\{bt.first\}_type\")
414
- # descrips = @_brick_bt_descrip[bt.first][bt_class]
418
+ base_class = (::Brick.existing_stis[bt_class] || bt_class).constantize.base_class.name.underscore
415
419
  poly_id = #{obj_name}.send(\"#\{bt.first\}_id\")
416
420
  %><%= link_to(\"#\{bt_class\} ##\{poly_id\}\",
417
- send(\"#\{bt_class.underscore\}_path\".to_sym, poly_id)) if poly_id %><%
418
- else # We should do something other than [0..-2] for when there is no primary key (or maybe have an empty final array there in that case?)
419
- bt_txt = (bt_class = bt[1].first.first).brick_descrip(
420
- #{obj_name}, (descrips = @_brick_bt_descrip[bt.first][bt_class])[0..-2].map { |z| #{obj_name}.send(z.last[0..62]) }, (bt_id_col = descrips.last)
421
- )
422
- bt_id = #{obj_name}.send(*bt_id_col) if bt_id_col&.present? %>
423
- <%= bt_id ? link_to(bt_txt, send(\"#\{bt_class.name.underscore\}_path\".to_sym, bt_id)) : bt_txt %>
421
+ send(\"#\{base_class\}_path\".to_sym, poly_id)) if poly_id %><%
422
+ else
423
+ bt_txt = (bt_class = bt[1].first.first).brick_descrip(
424
+ #{obj_name}, (descrips = @_brick_bt_descrip[bt.first][bt_class])[0..-2].map { |z| #{obj_name}.send(z.last[0..62]) }, (bt_id_col = descrips.last)
425
+ )
426
+ bt_id = #{obj_name}.send(*bt_id_col) if bt_id_col&.present? %>
427
+ <%= bt_id ? link_to(bt_txt, send(\"#\{bt_class.base_class.name.underscore\}_path\".to_sym, bt_id)) : bt_txt %>
424
428
  <%#= Previously was: bt_obj = bt[1].first.first.find_by(bt[2] => val); link_to(bt_obj.brick_descrip, send(\"#\{bt[1].first.first.name.underscore\}_path\".to_sym, bt_obj.send(bt[1].first.first.primary_key.to_sym))) if bt_obj %>
425
429
  <% end %>
426
430
  <% else %>
@@ -441,7 +445,7 @@ function changeout(href, param, value) {
441
445
  <p style=\"color: green\"><%= notice %></p>#{"
442
446
  <select id=\"schema\">#{schema_options}</select>" if ::Brick.db_schemas.length > 1}
443
447
  <select id=\"tbl\">#{table_options}</select>
444
- <h1>#{model_name}: <%= (obj = @#{obj_name}&.first)&.brick_descrip || controller_name %></h1>
448
+ <h1>#{model_name}: <%= (obj = @#{obj_name})&.brick_descrip || controller_name %></h1>
445
449
  <%= link_to '(See all #{obj_name.pluralize})', #{table_name}_path %>
446
450
  <% if obj %>
447
451
  <%= # path_options = [obj.#{pk}]
@@ -450,32 +454,42 @@ function changeout(href, param, value) {
450
454
  form_for(obj.becomes(#{model_name})) do |f| %>
451
455
  <table>
452
456
  <% has_fields = false
453
- @#{obj_name}.first.attributes.each do |k, val| %>
457
+ @#{obj_name}.attributes.each do |k, val| %>
454
458
  <tr>
455
459
  <%# %%% Accommodate composite keys %>
456
- <% next if k == '#{pk}' || ::Brick.config.metadata_columns.include?(k) %>
460
+ <% next if #{pk}.include?(k) || ::Brick.config.metadata_columns.include?(k) %>
457
461
  <th class=\"show-field\">
458
462
  <% has_fields = true
459
463
  if (bt = bts[k])
460
464
  # Add a final member in this array with descriptive options to be used in <select> drop-downs
461
465
  bt_name = bt[1].map { |x| x.first.name }.join('/')
462
466
  # %%% Only do this if the user has permissions to edit this bt field
463
- if (pairs = bt[1]).length > 1
464
- poly_class_name = @#{obj_name}.first.send(\"#\{bt.first\}_type\")
465
- bt_pair = pairs.find { |pair| pair.first.name == poly_class_name }
467
+ if bt[2] # Polymorphic?
468
+ poly_class_name = orig_poly_name = @#{obj_name}.send(\"#\{bt.first\}_type\")
469
+ bt_pair = nil
470
+ loop do
471
+ bt_pair = bt[1].find { |pair| pair.first.name == poly_class_name }
472
+ # Acxommodate any valid STI by going up the chain of inheritance
473
+ break unless bt_pair.nil? && poly_class_name = ::Brick.existing_stis[poly_class_name]
474
+ end
475
+ puts \"*** Might be missing an STI class called #\{orig_poly_name\} whose base class should have this:
476
+ *** has_many :#{table_name}, as: :#\{bt.first\}
477
+ *** Can probably auto-configure everything using these lines in an initialiser:
478
+ *** Brick.sti_namespace_prefixes = { '::#\{orig_poly_name\}' => 'SomeParentModel' }
479
+ *** Brick.polymorphics = { '#{table_name}.#\{bt.first\}' => ['SomeParentModel'] }\" if bt_pair.nil?
466
480
  # descrips = @_brick_bt_descrip[bt.first][bt_class]
467
- poly_id = @#{obj_name}.first.send(\"#\{bt.first\}_id\")
481
+ poly_id = @#{obj_name}.send(\"#\{bt.first\}_id\")
468
482
  # bt_class.order(obj_pk = bt_class.primary_key).each { |obj| option_detail << [obj.brick_descrip(nil, obj_pk), obj.send(obj_pk)] }
469
483
  else # No polymorphism, so just get the first one
470
484
  bt_pair = bt[1].first
471
485
  end
472
- bt_class = bt_pair.first
473
- if bt.length < 3
486
+ bt_class = bt_pair&.first
487
+ if bt.length < 4
474
488
  bt << (option_detail = [[\"(No #\{bt_name\} chosen)\", '^^^brick_NULL^^^']])
475
489
  # %%% Accommodate composite keys for obj.pk at the end here
476
- bt_class.order(obj_pk = bt_class.primary_key).each { |obj| option_detail << [obj.brick_descrip(nil, obj_pk), obj.send(obj_pk)] }
490
+ bt_class&.order(obj_pk = bt_class.primary_key)&.each { |obj| option_detail << [obj.brick_descrip(nil, obj_pk), obj.send(obj_pk)] }
477
491
  end %>
478
- BT <%= bt_class.bt_link(bt.first) %>
492
+ BT <%= bt_class&.bt_link(bt.first) || orig_poly_name %>
479
493
  <% else %>
480
494
  <%= k %>
481
495
  <% end %>
@@ -484,8 +498,8 @@ function changeout(href, param, value) {
484
498
  <% if bt
485
499
  html_options = { prompt: \"Select #\{bt_name\}\" }
486
500
  html_options[:class] = 'dimmed' unless val %>
487
- <%= f.select k.to_sym, bt[2], { value: val || '^^^brick_NULL^^^' }, html_options %>
488
- <%= bt_obj = bt_class.find_by(bt_pair[1] => val); link_to('⇛', send(\"#\{bt_class.name.underscore\}_path\".to_sym, bt_obj.send(bt_class.primary_key.to_sym)), { class: 'show-arrow' }) if bt_obj %>
501
+ <%= f.select k.to_sym, bt[3], { value: val || '^^^brick_NULL^^^' }, html_options %>
502
+ <%= bt_obj = bt_class&.find_by(bt_pair[1] => val); link_to('⇛', send(\"#\{bt_class.base_class.name.underscore\}_path\".to_sym, bt_obj.send(bt_class.primary_key.to_sym)), { class: 'show-arrow' }) if bt_obj %>
489
503
  <% else case #{model_name}.column_for_attribute(k).type
490
504
  when :string, :text %>
491
505
  <% if is_bcrypt?(val) # || .readonly? %>
@@ -517,16 +531,17 @@ function changeout(href, param, value) {
517
531
 
518
532
  #{hms_headers.each_with_object(+'') do |hm, s|
519
533
  if (pk = hm.first.klass.primary_key)
520
- s << "<table id=\"#{hm_name = hm.first.name.to_s}\">
534
+ hm_singular_name = (hm_name = hm.first.name.to_s).singularize.underscore
535
+ obj_pk = (pk.is_a?(Array) ? pk : [pk]).each_with_object([]) { |pk_part, s| s << "#{hm_singular_name}.#{pk_part}" }.join(', ')
536
+ s << "<table id=\"#{hm_name}\">
521
537
  <tr><th>#{hm[3]}</th></tr>
522
- <% collection = @#{obj_name}.first.#{hm_name}
538
+ <% collection = @#{obj_name}.#{hm_name}
523
539
  collection = collection.is_a?(ActiveRecord::Associations::CollectionProxy) ? collection.order(#{pk.inspect}) : [collection]
524
540
  if collection.empty? %>
525
541
  <tr><td>(none)</td></tr>
526
542
  <% else %>
527
- <% collection.uniq.each do |#{hm_singular_name = hm_name.singularize.underscore}| %>
528
- <%# %%% accommodate composite primary key %>
529
- <tr><td><%= link_to(#{hm_singular_name}.brick_descrip, #{hm.first.klass.name.underscore}_path(#{hm_singular_name}.#{pk})) %></td></tr>
543
+ <% collection.uniq.each do |#{hm_singular_name}| %>
544
+ <tr><td><%= link_to(#{hm_singular_name}.brick_descrip, #{hm.first.klass.name.underscore}_path([#{obj_pk}])) %></td></tr>
530
545
  <% end %>
531
546
  <% end %>
532
547
  </table>"
@@ -538,7 +553,6 @@ function changeout(href, param, value) {
538
553
  #{script}"
539
554
 
540
555
  end
541
- puts inline
542
556
  # As if it were an inline template (see #determine_template in actionview-5.2.6.2/lib/action_view/renderer/template_renderer.rb)
543
557
  keys = options.has_key?(:locals) ? options[:locals].keys : []
544
558
  handler = ActionView::Template.handler_for_extension(options[:type] || 'erb')
@@ -213,7 +213,7 @@ module Brick
213
213
  puts "X3"
214
214
  super(key, ::Brick::JoinArray.new.replace([current, value]))
215
215
  when ::Brick::JoinArray # Concatenate new stuff onto any existing JoinArray
216
- current.set_matching(value, nil)
216
+ current.set_matching(value, nil) if value
217
217
  when ::Brick::JoinHash # Graduate an existing hash into being in an array if things are dissimilar
218
218
  super(key, ::Brick::JoinArray.new.replace([current, value]))
219
219
  value
@@ -5,7 +5,7 @@ module Brick
5
5
  module VERSION
6
6
  MAJOR = 1
7
7
  MINOR = 0
8
- TINY = 25
8
+ TINY = 28
9
9
 
10
10
  # PRE is nil unless it's a pre-release (beta, RC, etc.)
11
11
  PRE = nil
data/lib/brick.rb CHANGED
@@ -85,12 +85,16 @@ module Brick
85
85
  @sti_models ||= {}
86
86
  end
87
87
 
88
+ def self.existing_stis
89
+ @existing_stis ||= Brick.config.sti_namespace_prefixes.each_with_object({}) { |snp, s| s[snp.first[2..-1]] = snp.last unless snp.first.end_with?('::') }
90
+ end
91
+
88
92
  class << self
89
93
  attr_accessor :db_schemas
90
94
 
91
95
  def set_db_schema(params)
92
96
  schema = params['_brick_schema'] || 'public'
93
- ActiveRecord::Base.connection.execute("SET SEARCH_PATH='#{schema}';") if schema && ::Brick.db_schemas&.include?(schema)
97
+ ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?;", schema) if schema && ::Brick.db_schemas&.include?(schema)
94
98
  end
95
99
 
96
100
  # All tables and views (what Postgres calls "relations" including column and foreign key info)
@@ -103,14 +107,14 @@ module Brick
103
107
 
104
108
  def get_bts_and_hms(model)
105
109
  bts, hms = model.reflect_on_all_associations.each_with_object([{}, {}]) do |a, s|
106
- next if (!const_defined?(a.name.to_s.singularize.camelize) && ::Brick.config.exclude_tables.include?(a.plural_name))
110
+ next if !const_defined?(a.name.to_s.singularize.camelize) && ::Brick.config.exclude_tables.include?(a.plural_name)
107
111
 
108
112
  case a.macro
109
113
  when :belongs_to
110
114
  s.first[a.foreign_key] = if a.polymorphic?
111
115
  primary_tables = relations[model.table_name][:fks].find { |_k, fk| fk[:assoc_name] == a.name.to_s }&.last&.fetch(:inverse_table, [])
112
116
  models = primary_tables&.map { |table| table.singularize.camelize.constantize }
113
- [a.name, models]
117
+ [a.name, models, true]
114
118
  else
115
119
  [a.name, a.klass]
116
120
  end
@@ -264,6 +268,7 @@ module Brick
264
268
 
265
269
  # Polymorphic associations
266
270
  def polymorphics=(polys)
271
+ polys = polys.each_with_object({}) { |poly, s| s[poly] = nil } if polys.is_a?(Array)
267
272
  Brick.config.polymorphics = polys || {}
268
273
  end
269
274
 
@@ -279,6 +284,17 @@ module Brick
279
284
  Brick.config.sti_namespace_prefixes = snp
280
285
  end
281
286
 
287
+ # Database schema to use when analysing existing data, such as deriving a list of polymorphic classes
288
+ # for polymorphics in which it wasn't originally specified.
289
+ # @api public
290
+ def schema_to_analyse=(schema)
291
+ Brick.config.schema_to_analyse = schema
292
+ end
293
+
294
+ def default_route_fallback=(resource_name)
295
+ Brick.config.default_route_fallback = resource_name
296
+ end
297
+
282
298
  # Load additional references (virtual foreign keys)
283
299
  # This is attempted early if a brick initialiser file is found, and then again as a failsafe at the end of our engine's initialisation
284
300
  # %%% Maybe look for differences the second time 'round and just add new stuff instead of entirely deferring
@@ -289,13 +305,30 @@ module Brick
289
305
  if (ars = ::Brick.config.additional_references) || ::Brick.config.polymorphics
290
306
  ars.each { |fk| ::Brick._add_bt_and_hm(fk[0..2], relations) } if ars
291
307
  if (polys = ::Brick.config.polymorphics)
308
+ if (schema = ::Brick.config.schema_to_analyse) && ::Brick.db_schemas&.include?(schema)
309
+ ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?;", schema)
310
+ end
311
+ missing_stis = {}
292
312
  polys.each do |k, v|
293
313
  table_name, poly = k.split('.')
294
314
  v ||= ActiveRecord::Base.execute_sql("SELECT DISTINCT #{poly}_type AS typ FROM #{table_name}").map { |result| result['typ'] }
295
315
  v.each do |type|
296
- ::Brick._add_bt_and_hm([table_name, poly, type.underscore.pluralize, "(brick) #{table_name}_#{poly}"], relations, true)
316
+ if relations.key?(primary_table = type.underscore.pluralize)
317
+ ::Brick._add_bt_and_hm([table_name, poly, primary_table, "(brick) #{table_name}_#{poly}"], relations, true)
318
+ else
319
+ missing_stis[primary_table] = type unless ::Brick.existing_stis.key?(type)
320
+ end
297
321
  end
298
322
  end
323
+ unless missing_stis.empty?
324
+ print "
325
+ You might be missing an STI namespace prefix entry for these tables: #{missing_stis.keys.join(', ')}.
326
+ In config/initializers/brick.rb appropriate entries would look something like:
327
+ Brick.sti_namespace_prefixes = {"
328
+ puts missing_stis.map { |_k, missing_sti| "\n '::#{missing_sti}' => 'SomeParentModel'" }.join(',')
329
+ puts " }
330
+ (Just trade out SomeParentModel with some more appropriate one.)"
331
+ end
299
332
  end
300
333
  @_additional_references_loaded = true
301
334
  end
@@ -352,6 +385,9 @@ module Brick
352
385
  def finalize!
353
386
  existing_controllers = routes.each_with_object({}) { |r, s| c = r.defaults[:controller]; s[c] = nil if c }
354
387
  ::Rails.application.routes.append do
388
+ unless ::Brick.config.default_route_fallback.blank? || ::Rails.application.routes.named_routes.send(:routes)[:root]
389
+ send(:root, "#{::Brick.config.default_route_fallback}#index")
390
+ end
355
391
  # %%% TODO: If no auto-controllers then enumerate the controllers folder in order to build matching routes
356
392
  # If auto-controllers and auto-models are both enabled then this makes sense:
357
393
  ::Brick.relations.each do |k, v|
@@ -415,7 +451,7 @@ ActiveSupport.on_load(:active_record) do
415
451
  relation = clone # spawn
416
452
  relation.select_values = column_names
417
453
  result = if klass.connection.class.name.end_with?('::PostgreSQLAdapter')
418
- rslt = klass.connection.execute(relation.arel.to_sql)
454
+ rslt = klass.execute_sql(relation.arel.to_sql)
419
455
  rslt.type_map =
420
456
  @type_map ||= proc do
421
457
  # This aliasing avoids the warning:
@@ -19,11 +19,31 @@ module Brick
19
19
 
20
20
  def create_initializer_file
21
21
  unless File.exist?(filename = 'config/initializers/brick.rb')
22
- # See if we can make suggestions for additional_references
23
- resembles_fks = []
24
- possible_additional_references = (relations = ::Brick.relations).each_with_object([]) do |v, s|
25
- v.last[:cols].each do |col, _type|
22
+ # See if we can make suggestions for additional_references and polymorphic associations
23
+ resembles_fks = Hash.new { |h, k| h[k] = [] }
24
+ possible_polymorphics = {}
25
+ possible_additional_references = (relations = ::Brick.relations).each_with_object(Hash.new { |h, k| h[k] = [] }) do |v, s|
26
+ model_filename = "app/models/#{ActiveSupport::Inflector.singularize(v.first)}.rb"
27
+ v.last[:cols].each do |col, type|
26
28
  col_down = col.downcase
29
+
30
+ if (is_possible_poly = ['character varying', 'text'].include?(type.first))
31
+ if col_down.end_with?('_type') &&
32
+ poly_type_cut_length = -6
33
+ col_down = col_down[0..-6]
34
+ elsif col_down.end_with?('type')
35
+ poly_type_cut_length = -5
36
+ col_down = col_down[0..-5]
37
+ else
38
+ is_possible_poly = false
39
+ end
40
+ is_possible_poly = false if col_down.length < 6 # Was it simply called "type" or something else really short?
41
+ if is_possible_poly && !File.exist?(model_filename) # Make sure a model file isn't present
42
+ possible_polymorphics["#{v.first}.#{col_down}"] = "'#{v.first}.#{col[0..poly_type_cut_length]}'"
43
+ next
44
+ end
45
+ end
46
+
27
47
  is_possible = true
28
48
  if col_down.end_with?('_id')
29
49
  col_down = col_down[0..-4]
@@ -40,30 +60,48 @@ module Brick
40
60
  if col_down.start_with?('fk_')
41
61
  is_possible = true
42
62
  col_down = col_down[3..-1]
63
+ elsif col_down.start_with?('fk')
64
+ is_possible = true
65
+ col_down = col_down[2..-1]
43
66
  end
44
67
  # This possible key not really a primary key and not yet used as a foreign key?
45
68
  if is_possible && !(relation = relations.fetch(v.first, {}))[:pkey].first&.last&.include?(col) &&
46
69
  !relations.fetch(v.first, {})[:fks]&.any? { |_k, v| v[:is_bt] && v[:fk] == col }
47
- if (relations.fetch(f_table = col_down, nil) ||
48
- relations.fetch(f_table = ActiveSupport::Inflector.pluralize(col_down), nil)) &&
49
- # Looks pretty promising ... just make sure a model file isn't present
50
- !File.exist?("app/models/#{ActiveSupport::Inflector.singularize(v.first)}.rb")
51
- s << "['#{v.first}', '#{col}', '#{f_table}']"
52
- else
53
- resembles_fks << "#{v.first}.#{col}"
70
+ # Starting to look promising ... make sure a model file isn't present
71
+ if !File.exist?(model_filename)
72
+ if (relations.fetch(f_table = col_down, nil) ||
73
+ relations.fetch(f_table = ActiveSupport::Inflector.pluralize(col_down), nil)) &&
74
+ s["#{v.first}.#{col_down}"] << "['#{v.first}', '#{col}', '#{f_table}']"
75
+ else
76
+ resembles_fks["#{v.first}.#{col_down}"] << "#{v.first}.#{col}"
77
+ end
54
78
  end
55
79
  end
56
80
  end
57
- s
58
81
  end
59
82
 
60
- bar = case possible_additional_references.length
83
+ possible_polymorphics.each_key do |k|
84
+ # Also matching one of the FK suggestions means it could be polymorphic,
85
+ # so delete any suggestions for a FK of the same name and only recommend
86
+ # the polymorphic association.
87
+ if resembles_fks.key?(k)
88
+ resembles_fks.delete(k)
89
+ elsif possible_additional_references.key?(k)
90
+ possible_additional_references.delete(k)
91
+ else
92
+ # While this one has a type, it's missing a corresponding ID column so it isn't polymorphic
93
+ possible_polymorphics.delete(k)
94
+ end
95
+ end
96
+ resembles_fks = resembles_fks.values.flatten
97
+
98
+ bar = case (possible_additional_references = possible_additional_references.values.flatten).length
61
99
  when 0
62
100
  +"# Brick.additional_references = [['orders', 'customer_id', 'customer'],
63
101
  # ['customer', 'region_id', 'regions']]"
64
102
  when 1
65
103
  +"# # Here is a possible additional reference that has been auto-identified for the #{ActiveRecord::Base.connection.current_database} database:
66
- # Brick.additional_references = [[#{possible_additional_references.first}]"
104
+ # Brick.additional_references = [#{possible_additional_references.first}]"
67
105
  else
68
106
  +"# # Here are possible additional references that have been auto-identified for the #{ActiveRecord::Base.connection.current_database} database:
69
107
  # Brick.additional_references = [
@@ -75,6 +113,26 @@ module Brick
75
113
  # # #{resembles_fks.join(', ')}"
76
114
  end
77
115
 
116
+ poly = case (possible_polymorphics = possible_polymorphics.values.flatten.map { |poss_poly| "#{poss_poly} => nil"}).length
117
+ when 0
118
+ " like this:
119
+ # Brick.polymorphics = {
120
+ # 'comments.commentable' => nil,
121
+ # 'images.imageable' => nil
122
+ # }"
123
+ when 1
124
+ ".
125
+ # # Here is a possible polymorphic association that has been auto-identified for the #{ActiveRecord::Base.connection.current_database} database:
126
+ # Brick.polymorphics = { #{possible_additional_references.first} }"
127
+
128
+ else
129
+ ".
130
+ # # Here are possible polymorphic associations that have been auto-identified for the #{ActiveRecord::Base.connection.current_database} database:
131
+ # Brick.polymorphics = {
132
+ # #{possible_polymorphics.join(",\n# ")}
133
+ # }"
134
+ end
135
+
78
136
  create_file(filename, "# frozen_string_literal: true
79
137
 
80
138
  # # Settings for the Brick gem
@@ -119,7 +177,7 @@ module Brick
119
177
  # # Skip showing counts for these specific has_many associations when building auto-generated #index views.
120
178
  # # When there are related tables with a significant number of records, this can lessen the load on the database
121
179
  # # considerably, sometimes fixing what might appear to be an index page that just \"hangs\" for no apparent reason.
122
- Brick.skip_index_hms = ['User.litany_of_woes']
180
+ # Brick.skip_index_hms = ['User.litany_of_woes']
123
181
 
124
182
  # # By default primary tables involved in a foreign key relationship will indicate a \"has_many\" relationship pointing
125
183
  # # back to the foreign table. In order to represent a \"has_one\" association instead, an override can be provided
@@ -157,9 +215,11 @@ Brick.skip_index_hms = ['User.litany_of_woes']
157
215
  # Brick.sti_namespace_prefixes = { '::Animals::' => 'Animal',
158
216
  # '::Snake' => 'Reptile' }
159
217
 
160
- # # Polymorphic associations must be explicitly specified, which is as easy as providing a model name and polymorphic
161
- # # association name like this:
162
- # Brick.polymorphics = ['Comment.commentable', 'Image.imageable']
218
+ # # Database schema to use when analysing existing data, such as deriving a list of polymorphic classes in the case that
219
+ # # it wasn't originally specified.
220
+ # Brick.schema_to_analyse = 'engineering'
221
+
222
+ # # Polymorphic associations are set up by providing a model name and polymorphic association name#{poly}
163
223
 
164
224
  # # If a default route is not supplied, Brick attempts to find the most \"central\" table and wires up the default
165
225
  # # route to go to the :index action for what would be a controller for that table. You can specify any controller
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.25
4
+ version: 1.0.28
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lorin Thwaits
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-05-21 00:00:00.000000000 Z
11
+ date: 2022-05-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord