brick 1.0.25 → 1.0.26

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: 922a55ec392e2ea9cef11c7297e4c2e4883e918130a23e49102b5a7e2442140e
4
+ data.tar.gz: 7cda213ec2e7cdccae096ef4ad8091a53a91359a1e6fabeb753cc2b579ac5c16
5
5
  SHA512:
6
- metadata.gz: a39a8adc0c72288db5bd08e2483cefb701db9c2122fe1b4012c19bef07c7d048fed7f6e051473a9b4b6c80e321e4a781a16543d40f3e569f23c31f420a5117b6
7
- data.tar.gz: 3ddcc914e3143a02a4582d6f525417c3012ae4eafc9ae889d5935223f6173afb4fb5ff3a40dc6c9caf95dfbf7a3c5be8e7bc4890efb385867ef5fa6f3ccbfb9e
6
+ metadata.gz: 7b817257f9c2aedd47bc2471b12e13edc8b370b71920258755f0f9a7594060e065d022e8d4b2b360cdc41f2a60def02cfc406889d526d80088670bd64acd58be
7
+ data.tar.gz: 1ea923301103dcc53853940770861ab5c2635f1280026e0740287274c0e9bb7d566ddcf96db0d3858467b788bc9ee4b73e7bcfc61145be1bbb43775cfb859b80
data/lib/brick/config.rb CHANGED
@@ -122,6 +122,14 @@ 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
+
125
133
  def skip_database_views
126
134
  @mutex.synchronize { @skip_database_views }
127
135
  end
@@ -52,14 +52,6 @@ module Arel
52
52
  end
53
53
  end
54
54
 
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
55
  module ActiveRecord
64
56
  class Base
65
57
  def self._assoc_names
@@ -82,7 +74,6 @@ module ActiveRecord
82
74
  dsl
83
75
  end
84
76
 
85
- # Pass in true for build_array, or just pass in a JoinArray
86
77
  def self.brick_parse_dsl(build_array = nil, prefix = [], translations = {}, is_polymorphic = false)
87
78
  build_array = ::Brick::JoinArray.new.tap { |ary| ary.replace([build_array]) } if build_array.is_a?(::Brick::JoinHash)
88
79
  build_array = ::Brick::JoinArray.new unless build_array.nil? || build_array.is_a?(Array)
@@ -308,6 +299,8 @@ module ActiveRecord
308
299
  if is_add_bts || is_add_hms
309
300
  bts, hms, associatives = ::Brick.get_bts_and_hms(klass)
310
301
  bts.each do |_k, bt|
302
+ next if bt[2] # Polymorphic?
303
+
311
304
  # join_array will receive this relation name when calling #brick_parse_dsl
312
305
  bt_descrip[bt.first] = if bt[1].is_a?(Array)
313
306
  bt[1].each_with_object({}) { |bt_class, s| s[bt_class] = bt_class.brick_parse_dsl(join_array, bt.first, translations, true) }
@@ -363,15 +356,16 @@ module ActiveRecord
363
356
  end
364
357
  # end
365
358
 
366
- if (id_col = k1.primary_key) && !id_for_tables.key?(v.first) # was tbl_name
359
+ unless id_for_tables.key?(v.first)
367
360
  # 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
361
+ ((id_col = k1.primary_key).is_a?(Array) ? id_col : [id_col]).each do |id_part|
362
+ id_for_tables[v.first] << if id_part
363
+ selects << "#{"#{tbl_name}.#{id_part}"} AS \"#{(id_alias = "_brfk_#{v.first}__#{id_part}")}\""
364
+ id_alias
365
+ end
371
366
  end
372
- v1 << id_for_tables[v.first]
367
+ v1 << id_for_tables[v.first].compact
373
368
  end
374
-
375
369
  end
376
370
  end
377
371
  join_array.each do |assoc_name|
@@ -454,8 +448,7 @@ JOIN (SELECT #{selects.join(', ')}, COUNT(#{count_column}) AS _ct_ FROM #{associ
454
448
  this_module.const_get(class_name)
455
449
  else
456
450
  # 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
451
+ this_module.const_set(class_name, klass = Class.new(self))
459
452
  klass
460
453
  end
461
454
  end
@@ -530,7 +523,7 @@ class Object
530
523
  singular_table_name = ActiveSupport::Inflector.underscore(model_name)
531
524
 
532
525
  # 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))
526
+ table_name = if (base_model = ::Brick.sti_models[model_name]&.fetch(:base, ::Brick.existing_stis[model_name]&.constantize))
534
527
  base_model.table_name
535
528
  else
536
529
  ActiveSupport::Inflector.pluralize(singular_table_name)
@@ -571,7 +564,7 @@ class Object
571
564
  return
572
565
  end
573
566
 
574
- if (base_model = ::Brick.sti_models[model_name]&.fetch(:base, nil))
567
+ if (base_model = ::Brick.sti_models[model_name]&.fetch(:base, ::Brick.existing_stis[model_name]&.constantize))
575
568
  is_sti = true
576
569
  else
577
570
  base_model = ::Brick.config.models_inherit_from || ActiveRecord::Base
@@ -698,9 +691,8 @@ class Object
698
691
  # need_class_name = ActiveSupport::Inflector.singularize(assoc_name) == ActiveSupport::Inflector.singularize(table_name.underscore)
699
692
  # Are there multiple foreign keys out to the same table?
700
693
  assoc_name, need_class_name = _brick_get_hm_assoc_name(relation, assoc)
701
- # binding.pry if assoc.key?(:polymorphic)
702
694
  if assoc.key?(:polymorphic)
703
- options[:as] = assoc[:fk].to_sym if assoc.key?(:polymorphic)
695
+ options[:as] = assoc[:fk].to_sym
704
696
  else
705
697
  need_fk = "#{ActiveSupport::Inflector.singularize(assoc[:inverse][:inverse_table])}_id" != assoc[:fk]
706
698
  end
@@ -1064,12 +1056,8 @@ module Brick
1064
1056
  bts = (relation = relations.fetch(fk[0], nil))&.fetch(:fks) { relation[:fks] = {} }
1065
1057
  # %%% Do we miss out on has_many :through or even HM based on constantizing this model early?
1066
1058
  # 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
1059
+ primary_table = (is_class = fk[2].is_a?(Hash) && fk[2].key?(:class)) ? (primary_class = fk[2][:class].constantize).table_name : fk[2]
1060
+ hms = (relation = relations.fetch(primary_table, nil))&.fetch(:fks) { relation[:fks] = {} } unless is_class
1073
1061
 
1074
1062
  unless (cnstr_name = fk[3])
1075
1063
  # For any appended references (those that come from config), arrive upon a definitely unique constraint name
@@ -1102,8 +1090,12 @@ module Brick
1102
1090
  if is_polymorphic
1103
1091
  # Assuming same fk (don't yet support composite keys for polymorphics)
1104
1092
  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])
1093
+ else # Expect we could have a composite key going
1094
+ if assoc_bt[:fk].is_a?(String)
1095
+ assoc_bt[:fk] = [assoc_bt[:fk], fk[1]] unless fk[1] == assoc_bt[:fk]
1096
+ elsif assoc_bt[:fk].exclude?(fk[1])
1097
+ assoc_bt[:fk] << fk[1]
1098
+ end
1107
1099
  assoc_bt[:assoc_name] = "#{assoc_bt[:assoc_name]}_#{fk[1]}"
1108
1100
  end
1109
1101
  else
@@ -1121,7 +1113,11 @@ module Brick
1121
1113
  return if is_class || ::Brick.config.exclude_hms&.any? { |exclusion| fk[0] == exclusion[0] && fk[1] == exclusion[1] && primary_table == exclusion[2] }
1122
1114
 
1123
1115
  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])
1116
+ if assoc_bt[:fk].is_a?(String)
1117
+ assoc_bt[:fk] = [assoc_bt[:fk], fk[1]] unless fk[1] == assoc_bt[:fk]
1118
+ elsif assoc_bt[:fk].exclude?(fk[1])
1119
+ assoc_bt[:fk] << fk[1]
1120
+ end
1125
1121
  assoc_hm[:alternate_name] = "#{assoc_hm[:alternate_name]}_#{bt_assoc_name}" unless assoc_hm[:alternate_name] == bt_assoc_name
1126
1122
  assoc_hm[:inverse] = assoc_bt
1127
1123
  else
@@ -210,16 +210,19 @@ def hide_bcrypt(val)
210
210
  end %>"
211
211
 
212
212
  if ['index', 'show', 'update'].include?(args.first)
213
+ poly_cols = []
213
214
  css << "<% bts = { #{
214
215
  bts.each_with_object([]) do |v, s|
215
- foreign_models = if v.last[1].is_a?(Array)
216
+ foreign_models = if v.last[2] # Polymorphic?
217
+ poly_cols << @_brick_model.reflect_on_association(v[1].first).foreign_type
216
218
  v.last[1].each_with_object([]) { |x, s| s << "[#{x.name}, #{x.primary_key.inspect}]" }.join(', ')
217
219
  else
218
220
  "[#{v.last[1].name}, #{v.last[1].primary_key.inspect}]"
219
221
  end
220
- s << "#{v.first.inspect} => [#{v.last.first.inspect}, [#{foreign_models}]]"
222
+ s << "#{v.first.inspect} => [#{v.last.first.inspect}, [#{foreign_models}], #{v.last[2].inspect}]"
221
223
  end.join(', ')
222
- } } %>"
224
+ } }
225
+ poly_cols = #{poly_cols.inspect} %>"
223
226
  end
224
227
 
225
228
  # %%% When doing schema select, if there's an ID then remove it, or if we're on a new page go to index
@@ -384,7 +387,7 @@ function changeout(href, param, value) {
384
387
  <table id=\"#{table_name}\">
385
388
  <thead><tr>#{'<th></th>' if pk}
386
389
  <% @#{table_name}.columns.map(&:name).each do |col| %>
387
- <% next if col == '#{pk}' || ::Brick.config.metadata_columns.include?(col) %>
390
+ <% next if col == '#{pk}' || ::Brick.config.metadata_columns.include?(col) || poly_cols.include?(col) %>
388
391
  <th>
389
392
  <% if (bt = bts[col]) %>
390
393
  BT <%
@@ -405,22 +408,22 @@ function changeout(href, param, value) {
405
408
  <tr>#{"
406
409
  <td><%= link_to '⇛', #{obj_name}_path(#{obj_pk}), { class: 'big-arrow' } %></td>" if obj_pk}
407
410
  <% #{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'))) %>
411
+ <% next if k == '#{pk}' || ::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
412
  <td>
410
413
  <% if (bt = bts[k]) %>
411
414
  <%# binding.pry # Postgres column names are limited to 63 characters %>
412
- <% if (pairs = bt[1].length > 1)
415
+ <% if bt[2] # Polymorphic?
413
416
  bt_class = #{obj_name}.send(\"#\{bt.first\}_type\")
414
- # descrips = @_brick_bt_descrip[bt.first][bt_class]
417
+ base_class = (::Brick.existing_stis[bt_class] || bt_class).constantize.base_class.name.underscore
415
418
  poly_id = #{obj_name}.send(\"#\{bt.first\}_id\")
416
419
  %><%= 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 %>
420
+ send(\"#\{base_class\}_path\".to_sym, poly_id)) if poly_id %><%
421
+ else
422
+ bt_txt = (bt_class = bt[1].first.first).brick_descrip(
423
+ #{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)
424
+ )
425
+ bt_id = #{obj_name}.send(*bt_id_col) if bt_id_col&.present? %>
426
+ <%= bt_id ? link_to(bt_txt, send(\"#\{bt_class.base_class.name.underscore\}_path\".to_sym, bt_id)) : bt_txt %>
424
427
  <%#= 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
428
  <% end %>
426
429
  <% else %>
@@ -460,9 +463,9 @@ function changeout(href, param, value) {
460
463
  # Add a final member in this array with descriptive options to be used in <select> drop-downs
461
464
  bt_name = bt[1].map { |x| x.first.name }.join('/')
462
465
  # %%% Only do this if the user has permissions to edit this bt field
463
- if (pairs = bt[1]).length > 1
466
+ if bt[2] # Polymorphic?
464
467
  poly_class_name = @#{obj_name}.first.send(\"#\{bt.first\}_type\")
465
- bt_pair = pairs.find { |pair| pair.first.name == poly_class_name }
468
+ bt_pair = bt[1].find { |pair| pair.first.name == poly_class_name }
466
469
  # descrips = @_brick_bt_descrip[bt.first][bt_class]
467
470
  poly_id = @#{obj_name}.first.send(\"#\{bt.first\}_id\")
468
471
  # bt_class.order(obj_pk = bt_class.primary_key).each { |obj| option_detail << [obj.brick_descrip(nil, obj_pk), obj.send(obj_pk)] }
@@ -470,7 +473,7 @@ function changeout(href, param, value) {
470
473
  bt_pair = bt[1].first
471
474
  end
472
475
  bt_class = bt_pair.first
473
- if bt.length < 3
476
+ if bt.length < 4
474
477
  bt << (option_detail = [[\"(No #\{bt_name\} chosen)\", '^^^brick_NULL^^^']])
475
478
  # %%% Accommodate composite keys for obj.pk at the end here
476
479
  bt_class.order(obj_pk = bt_class.primary_key).each { |obj| option_detail << [obj.brick_descrip(nil, obj_pk), obj.send(obj_pk)] }
@@ -484,8 +487,8 @@ function changeout(href, param, value) {
484
487
  <% if bt
485
488
  html_options = { prompt: \"Select #\{bt_name\}\" }
486
489
  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 %>
490
+ <%= f.select k.to_sym, bt[3], { value: val || '^^^brick_NULL^^^' }, html_options %>
491
+ <%= 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
492
  <% else case #{model_name}.column_for_attribute(k).type
490
493
  when :string, :text %>
491
494
  <% if is_bcrypt?(val) # || .readonly? %>
@@ -538,7 +541,6 @@ function changeout(href, param, value) {
538
541
  #{script}"
539
542
 
540
543
  end
541
- puts inline
542
544
  # As if it were an inline template (see #determine_template in actionview-5.2.6.2/lib/action_view/renderer/template_renderer.rb)
543
545
  keys = options.has_key?(:locals) ? options[:locals].keys : []
544
546
  handler = ActionView::Template.handler_for_extension(options[:type] || 'erb')
@@ -5,7 +5,7 @@ module Brick
5
5
  module VERSION
6
6
  MAJOR = 1
7
7
  MINOR = 0
8
- TINY = 25
8
+ TINY = 26
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,13 @@ 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
+
282
294
  # Load additional references (virtual foreign keys)
283
295
  # 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
296
  # %%% Maybe look for differences the second time 'round and just add new stuff instead of entirely deferring
@@ -289,13 +301,30 @@ module Brick
289
301
  if (ars = ::Brick.config.additional_references) || ::Brick.config.polymorphics
290
302
  ars.each { |fk| ::Brick._add_bt_and_hm(fk[0..2], relations) } if ars
291
303
  if (polys = ::Brick.config.polymorphics)
304
+ if (schema = ::Brick.config.schema_to_analyse) && ::Brick.db_schemas&.include?(schema)
305
+ ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?;", schema)
306
+ end
307
+ missing_stis = {}
292
308
  polys.each do |k, v|
293
309
  table_name, poly = k.split('.')
294
310
  v ||= ActiveRecord::Base.execute_sql("SELECT DISTINCT #{poly}_type AS typ FROM #{table_name}").map { |result| result['typ'] }
295
311
  v.each do |type|
296
- ::Brick._add_bt_and_hm([table_name, poly, type.underscore.pluralize, "(brick) #{table_name}_#{poly}"], relations, true)
312
+ if relations.key?(primary_table = type.underscore.pluralize)
313
+ ::Brick._add_bt_and_hm([table_name, poly, primary_table, "(brick) #{table_name}_#{poly}"], relations, true)
314
+ else
315
+ missing_stis[primary_table] = type unless ::Brick.existing_stis.key?(type)
316
+ end
297
317
  end
298
318
  end
319
+ unless missing_stis.empty?
320
+ print "
321
+ You might be missing an STI namespace prefix entry for these tables: #{missing_stis.keys.join(', ')}.
322
+ In config/initializers/brick.rb appropriate entries would look something like:
323
+ Brick.sti_namespace_prefixes = {"
324
+ puts missing_stis.map { |_k, missing_sti| "\n '::#{missing_sti}' => 'YourParentModel'" }.join(',')
325
+ puts " }
326
+ (Just trade out YourParentModel with some more appropriate one.)"
327
+ end
299
328
  end
300
329
  @_additional_references_loaded = true
301
330
  end
@@ -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).length
117
+ when 0
118
+ " like this:
119
+ # Brick.polymorphics = [
120
+ # 'comments.commentable',
121
+ # 'images.imageable'
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.26
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-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord