brick 1.0.26 → 1.0.29

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: 922a55ec392e2ea9cef11c7297e4c2e4883e918130a23e49102b5a7e2442140e
4
- data.tar.gz: 7cda213ec2e7cdccae096ef4ad8091a53a91359a1e6fabeb753cc2b579ac5c16
3
+ metadata.gz: 1a312c2fd5baf0793b4bb4143167b4399c70a514991206a611d525d0784de95b
4
+ data.tar.gz: bb1af47d2d073dab2c046ff9757fb5db9447f02dd0688330ad7ac8ad678d6751
5
5
  SHA512:
6
- metadata.gz: 7b817257f9c2aedd47bc2471b12e13edc8b370b71920258755f0f9a7594060e065d022e8d4b2b360cdc41f2a60def02cfc406889d526d80088670bd64acd58be
7
- data.tar.gz: 1ea923301103dcc53853940770861ab5c2635f1280026e0740287274c0e9bb7d566ddcf96db0d3858467b788bc9ee4b73e7bcfc61145be1bbb43775cfb859b80
6
+ metadata.gz: dbfabaf60608b2094e9903cb538bab00706f434f94289b9647320bb128adf0b34317a6404ace34897cf4b2f91700ebd7c028a12b4072ba1ebf9de3380f75add3
7
+ data.tar.gz: 6e5444ac763c71de71fc1aca64cdb41db6276f1df57f5b556b03bad0b743373f2720865ba7c6ce36f9887d77ffee3b091f32d233121742c4786ca21e8d512705
data/lib/brick/config.rb CHANGED
@@ -130,6 +130,14 @@ module Brick
130
130
  @mutex.synchronize { @schema_to_analyse = schema }
131
131
  end
132
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
+
133
141
  def skip_database_views
134
142
  @mutex.synchronize { @skip_database_views }
135
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
@@ -62,6 +67,18 @@ module ActiveRecord
62
67
  false
63
68
  end
64
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
+ if relation && pk.present?
76
+ @_brick_primary_key ||= pk.any? { |pk_part| !relation[:cols].key?(pk_part) } ? [] : pk
77
+ else # No definitive key yet, so return what we can without setting the instance variable
78
+ pk
79
+ end
80
+ end
81
+
65
82
  # Used to show a little prettier name for an object
66
83
  def self.brick_get_dsl
67
84
  # If there's no DSL yet specified, just try to find the first usable column on this model
@@ -290,7 +307,7 @@ module ActiveRecord
290
307
  # %%% Skip the metadata columns
291
308
  if selects&.empty? # Default to all columns
292
309
  columns.each do |col|
293
- selects << "#{table.name}.#{col.name}"
310
+ selects << "\"#{table.name}\".\"#{col.name}\""
294
311
  end
295
312
  end
296
313
 
@@ -344,23 +361,19 @@ module ActiveRecord
344
361
  next if chains[k1].nil?
345
362
 
346
363
  tbl_name = field_tbl_names[v.first][k1] ||= shift_or_first(chains[k1])
347
- # if (col_name = v1[1].last&.last) # col_name is weak when there are multiple, using sel_col.last instead
348
364
  field_tbl_name = nil
349
- v1.map { |x|
350
- [translations[x[0..-2].map(&:to_s).join('.')], x.last]
351
- }.each_with_index do |sel_col, idx|
352
- field_tbl_name ||= field_tbl_names[v.first][sel_col.first] ||= shift_or_first(chains[sel_col.first])
365
+ v1.map { |x| [translations[x[0..-2].map(&:to_s).join('.')], x.last] }.each_with_index do |sel_col, idx|
366
+ field_tbl_name = field_tbl_names[v.first][sel_col.first] ||= shift_or_first(chains[sel_col.first])
353
367
 
354
- selects << "#{"#{field_tbl_name}.#{sel_col.last}"} AS \"#{(col_alias = "_brfk_#{v.first}__#{sel_col.last}")}\""
368
+ selects << "#{"\"#{field_tbl_name}\".\"#{sel_col.last}\""} AS \"#{(col_alias = "_brfk_#{v.first}__#{sel_col.last}")}\""
355
369
  v1[idx] << col_alias
356
370
  end
357
- # end
358
371
 
359
372
  unless id_for_tables.key?(v.first)
360
373
  # Accommodate composite primary key by allowing id_col to come in as an array
361
374
  ((id_col = k1.primary_key).is_a?(Array) ? id_col : [id_col]).each do |id_part|
362
375
  id_for_tables[v.first] << if id_part
363
- selects << "#{"#{tbl_name}.#{id_part}"} AS \"#{(id_alias = "_brfk_#{v.first}__#{id_part}")}\""
376
+ selects << "#{"\"#{tbl_name}\".\"#{id_part}\""} AS \"#{(id_alias = "_brfk_#{v.first}__#{id_part}")}\""
364
377
  id_alias
365
378
  end
366
379
  end
@@ -385,7 +398,8 @@ module ActiveRecord
385
398
  else
386
399
  fk_col = hm.foreign_key
387
400
  poly_type = hm.inverse_of.foreign_type if hm.options.key?(:as)
388
- hm.klass.primary_key || '*'
401
+ pk = hm.klass.primary_key
402
+ (pk.is_a?(Array) ? pk.first : pk) || '*'
389
403
  end
390
404
  tbl_alias = "_br_#{hm.name}"
391
405
  pri_tbl = hm.active_record
@@ -523,7 +537,7 @@ class Object
523
537
  singular_table_name = ActiveSupport::Inflector.underscore(model_name)
524
538
 
525
539
  # Adjust for STI if we know of a base model for the requested model name
526
- table_name = if (base_model = ::Brick.sti_models[model_name]&.fetch(:base, ::Brick.existing_stis[model_name]&.constantize))
540
+ table_name = if (base_model = ::Brick.sti_models[model_name]&.fetch(:base, nil) || ::Brick.existing_stis[model_name]&.constantize)
527
541
  base_model.table_name
528
542
  else
529
543
  ActiveSupport::Inflector.pluralize(singular_table_name)
@@ -564,7 +578,7 @@ class Object
564
578
  return
565
579
  end
566
580
 
567
- if (base_model = ::Brick.sti_models[model_name]&.fetch(:base, ::Brick.existing_stis[model_name]&.constantize))
581
+ if (base_model = ::Brick.sti_models[model_name]&.fetch(:base, nil) || ::Brick.existing_stis[model_name]&.constantize)
568
582
  is_sti = true
569
583
  else
570
584
  base_model = ::Brick.config.models_inherit_from || ActiveRecord::Base
@@ -584,16 +598,14 @@ class Object
584
598
  code << " def self.is_view?; true; end\n"
585
599
  end
586
600
 
587
- # Missing a primary key column? (Usually "id")
588
- ar_pks = primary_key.is_a?(String) ? [primary_key] : primary_key || []
589
601
  db_pks = relation[:cols]&.map(&:first)
590
- has_pk = ar_pks.length.positive? && (db_pks & ar_pks).sort == ar_pks.sort
602
+ has_pk = _brick_primary_key(relation).length.positive? && (db_pks & _brick_primary_key).sort == _brick_primary_key.sort
591
603
  our_pks = relation[:pkey].values.first
592
604
  # No primary key, but is there anything UNIQUE?
593
605
  # (Sort so that if there are multiple UNIQUE constraints we'll pick one that uses the least number of columns.)
594
606
  our_pks = relation[:ukeys].values.sort { |a, b| a.length <=> b.length }.first unless our_pks&.present?
595
607
  if has_pk
596
- code << " # Primary key: #{ar_pks.join(', ')}\n" unless ar_pks == ['id']
608
+ code << " # Primary key: #{_brick_primary_key.join(', ')}\n" unless _brick_primary_key == ['id']
597
609
  elsif our_pks&.present?
598
610
  if our_pks.length > 1 && respond_to?(:'primary_keys=') # Using the composite_primary_keys gem?
599
611
  new_model_class.primary_keys = our_pks
@@ -602,6 +614,7 @@ class Object
602
614
  new_model_class.primary_key = (pk_sym = our_pks.first.to_sym)
603
615
  code << " self.primary_key = #{pk_sym.inspect}\n"
604
616
  end
617
+ _brick_primary_key(relation) # Set the newly-found PK in the instance variable
605
618
  else
606
619
  code << " # Could not identify any column(s) to use as a primary key\n" unless is_view
607
620
  end
@@ -613,10 +626,13 @@ class Object
613
626
  # The key in each hash entry (fk.first) is the constraint name
614
627
  inverse_assoc_name = (assoc = fk.last)[:inverse]&.fetch(:assoc_name, nil)
615
628
  if (invs = assoc[:inverse_table]).is_a?(Array)
616
- invs.each { |inv| build_bt_or_hm(relations, model_name, relation, hmts, assoc, inverse_assoc_name, inv, code) }
617
- else
618
- build_bt_or_hm(relations, model_name, relation, hmts, assoc, inverse_assoc_name, invs, code)
629
+ if assoc[:is_bt]
630
+ invs = invs.first # Just do the first one of what would be multiple identical polymorphic belongs_to
631
+ else
632
+ invs.each { |inv| build_bt_or_hm(relations, model_name, relation, hmts, assoc, inverse_assoc_name, inv, code) }
633
+ end
619
634
  end
635
+ build_bt_or_hm(relations, model_name, relation, hmts, assoc, inverse_assoc_name, invs, code) unless invs.is_a?(Array)
620
636
  hmts
621
637
  end
622
638
  hmts.each do |hmt_fk, fks|
@@ -643,7 +659,7 @@ class Object
643
659
  # # Not NULLables
644
660
  # # %%% For the minute we've had to pull this out because it's been troublesome implementing the NotNull validator
645
661
  # relation[:cols].each do |col, datatype|
646
- # if (datatype[3] && ar_pks.exclude?(col) && ::Brick.config.metadata_columns.exclude?(col)) ||
662
+ # if (datatype[3] && _brick_primary_key.exclude?(col) && ::Brick.config.metadata_columns.exclude?(col)) ||
647
663
  # ::Brick.config.not_nullables.include?("#{matching}.#{col}")
648
664
  # code << " validates :#{col}, not_null: true\n"
649
665
  # self.send(:validates, col.to_sym, { not_null: true })
@@ -740,13 +756,14 @@ class Object
740
756
  def build_controller(class_name, plural_class_name, model, relations)
741
757
  table_name = ActiveSupport::Inflector.underscore(plural_class_name)
742
758
  singular_table_name = ActiveSupport::Inflector.singularize(table_name)
759
+ pk = model._brick_primary_key(relations.fetch(table_name, nil))
743
760
 
744
761
  code = +"class #{class_name} < ApplicationController\n"
745
762
  built_controller = Class.new(ActionController::Base) do |new_controller_class|
746
763
  Object.const_set(class_name.to_sym, new_controller_class)
747
764
 
748
765
  code << " def index\n"
749
- code << " @#{table_name} = #{model.name}#{model.primary_key ? ".order(#{model.primary_key.inspect})" : '.all'}\n"
766
+ code << " @#{table_name} = #{model.name}#{pk&.present? ? ".order(#{pk.inspect})" : '.all'}\n"
750
767
  code << " @#{table_name}.brick_select(params)\n"
751
768
  code << " end\n"
752
769
  self.protect_from_forgery unless: -> { self.request.format.js? }
@@ -764,7 +781,8 @@ class Object
764
781
  return
765
782
  end
766
783
 
767
- ar_relation = model.primary_key ? model.order("#{model.table_name}.#{model.primary_key}") : model.all
784
+ order = pk.each_with_object([]) { |pk_part, s| s << "#{model.table_name}.#{pk_part}" }
785
+ ar_relation = order.present? ? model.order("#{order.join(', ')}") : model.all
768
786
  @_brick_params = ar_relation.brick_select(params, (selects = []), (bt_descrip = {}), (hm_counts = {}), (join_array = ::Brick::JoinArray.new))
769
787
  # %%% Add custom HM count columns
770
788
  # %%% What happens when the PK is composite?
@@ -779,11 +797,15 @@ class Object
779
797
 
780
798
  if model.primary_key
781
799
  code << " def show\n"
782
- code << (find_by_id = " @#{singular_table_name} = #{model.name}.find(params[:id].split(','))\n")
800
+ code << (find_by_id = " id = params[:id]&.split(/[\\/,_]/)
801
+ id = id.first if id.is_a?(Array) && id.length == 1
802
+ @#{singular_table_name} = #{model.name}.find(id)\n")
783
803
  code << " end\n"
784
804
  self.define_method :show do
785
805
  ::Brick.set_db_schema(params)
786
- instance_variable_set("@#{singular_table_name}".to_sym, model.find(params[:id].split(',')))
806
+ id = params[:id]&.split(/[\/,_]/)
807
+ id = id.first if id.is_a?(Array) && id.length == 1
808
+ instance_variable_set("@#{singular_table_name}".to_sym, model.find(id))
787
809
  end
788
810
  end
789
811
 
@@ -792,6 +814,10 @@ class Object
792
814
  code << " # (Define :new, :create)\n"
793
815
 
794
816
  if model.primary_key
817
+ if (schema = ::Brick.config.schema_to_analyse) && ::Brick.db_schemas&.include?(schema)
818
+ ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?;", schema)
819
+ end
820
+
795
821
  is_need_params = true
796
822
  # code << " # (Define :edit, and :destroy)\n"
797
823
  code << " def update\n"
@@ -866,12 +892,12 @@ module ActiveRecord::ConnectionHandling
866
892
  def _brick_reflect_tables
867
893
  if (relations = ::Brick.relations).empty?
868
894
  # Only for Postgres? (Doesn't work in sqlite3)
869
- # puts ActiveRecord::Base.connection.execute("SELECT current_setting('SEARCH_PATH')").to_a.inspect
895
+ # puts ActiveRecord::Base.execute_sql("SELECT current_setting('SEARCH_PATH')").to_a.inspect
870
896
 
871
897
  schema_sql = 'SELECT NULL AS table_schema;'
872
898
  case ActiveRecord::Base.connection.adapter_name
873
899
  when 'PostgreSQL'
874
- schema = 'public'
900
+ schema = 'public' # Too early at this point to be able to pick up: Brick.config.schema_to_analyse
875
901
  schema_sql = 'SELECT DISTINCT table_schema FROM INFORMATION_SCHEMA.tables;'
876
902
  when 'Mysql2'
877
903
  schema = ActiveRecord::Base.connection.current_database
@@ -915,7 +941,7 @@ module ActiveRecord::ConnectionHandling
915
941
  measures = []
916
942
  case ActiveRecord::Base.connection.adapter_name
917
943
  when 'PostgreSQL', 'SQLite' # These bring back a hash for each row because the query uses column aliases
918
- ActiveRecord::Base.connection.execute(sql).each do |r|
944
+ ActiveRecord::Base.execute_sql(sql).each do |r|
919
945
  # next if internal_views.include?(r['relation_name']) # Skip internal views such as v_all_assessments
920
946
  relation = relations[(relation_name = r['relation_name'])]
921
947
  relation[:isView] = true if r['table_type'] == 'VIEW'
@@ -934,7 +960,7 @@ module ActiveRecord::ConnectionHandling
934
960
  # puts "KEY! #{r['relation_name']}.#{col_name} #{r['key']} #{r['const']}" if r['key']
935
961
  end
936
962
  else # MySQL2 acts a little differently, bringing back an array for each row
937
- ActiveRecord::Base.connection.execute(sql).each do |r|
963
+ ActiveRecord::Base.execute_sql(sql).each do |r|
938
964
  # next if internal_views.include?(r['relation_name']) # Skip internal views such as v_all_assessments
939
965
  relation = relations[(relation_name = r[0])] # here relation represents a table or view from the database
940
966
  relation[:isView] = true if r[1] == 'VIEW' # table_type
@@ -1000,11 +1026,11 @@ module ActiveRecord::ConnectionHandling
1000
1026
  else
1001
1027
  end
1002
1028
  if sql
1003
- ::Brick.db_schemas = ActiveRecord::Base.connection.execute(schema_sql)
1029
+ ::Brick.db_schemas = ActiveRecord::Base.execute_sql(schema_sql)
1004
1030
  ::Brick.db_schemas = ::Brick.db_schemas.to_a unless ::Brick.db_schemas.is_a?(Array)
1005
1031
  ::Brick.db_schemas.map! { |row| row['table_schema'] } unless ::Brick.db_schemas.empty? || ::Brick.db_schemas.first.is_a?(String)
1006
1032
  ::Brick.db_schemas -= ['information_schema', 'pg_catalog']
1007
- ActiveRecord::Base.connection.execute(sql).each do |fk|
1033
+ ActiveRecord::Base.execute_sql(sql).each do |fk|
1008
1034
  fk = fk.values unless fk.is_a?(Array)
1009
1035
  ::Brick._add_bt_and_hm(fk, relations)
1010
1036
  end
@@ -1113,10 +1139,10 @@ module Brick
1113
1139
  return if is_class || ::Brick.config.exclude_hms&.any? { |exclusion| fk[0] == exclusion[0] && fk[1] == exclusion[1] && primary_table == exclusion[2] }
1114
1140
 
1115
1141
  if (assoc_hm = hms.fetch((hm_cnstr_name = "hm_#{cnstr_name}"), nil))
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]
1142
+ if assoc_hm[:fk].is_a?(String)
1143
+ assoc_hm[:fk] = [assoc_hm[:fk], fk[1]] unless fk[1] == assoc_hm[:fk]
1144
+ elsif assoc_hm[:fk].exclude?(fk[1])
1145
+ assoc_hm[:fk] << fk[1]
1120
1146
  end
1121
1147
  assoc_hm[:alternate_name] = "#{assoc_hm[:alternate_name]}_#{bt_assoc_name}" unless assoc_hm[:alternate_name] == bt_assoc_name
1122
1148
  assoc_hm[:inverse] = assoc_bt
@@ -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.fetch(model_name, nil))
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
@@ -206,7 +207,13 @@ input[type=submit] {
206
207
  val.is_a?(String) && val.length == 60 && val.start_with?('$2a$')
207
208
  end
208
209
  def hide_bcrypt(val)
209
- is_bcrypt?(val) ? '(hidden)' : val
210
+ if is_bcrypt?(val)
211
+ '(hidden)'
212
+ elsif val.is_a?(String) && val.encoding.name != 'UTF-8'
213
+ val[0..1000].force_encoding('UTF-8')
214
+ else
215
+ val
216
+ end
210
217
  end %>"
211
218
 
212
219
  if ['index', 'show', 'update'].include?(args.first)
@@ -283,7 +290,7 @@ function changeout(href, param, value) {
283
290
  inline = case args.first
284
291
  when 'index'
285
292
  obj_pk = if pk&.is_a?(Array) # Composite primary key?
286
- "[#{pk.map { |pk_part| "#{obj_name}.#{pk_part}" }.join(', ')}]"
293
+ "[#{pk.map { |pk_part| "#{obj_name}.#{pk_part}" }.join(', ')}]" unless pk.empty?
287
294
  elsif pk
288
295
  "#{obj_name}.#{pk}"
289
296
  end
@@ -385,9 +392,9 @@ function changeout(href, param, value) {
385
392
 
386
393
  <% if @_brick_params&.present? %><h3>where <%= @_brick_params.each_with_object([]) { |v, s| s << \"#\{v.first\} = #\{v.last.inspect\}\" }.join(', ') %></h3><% end %>
387
394
  <table id=\"#{table_name}\">
388
- <thead><tr>#{'<th></th>' if pk}
395
+ <thead><tr>#{'<th></th>' if pk.present?}
389
396
  <% @#{table_name}.columns.map(&:name).each do |col| %>
390
- <% next if col == '#{pk}' || ::Brick.config.metadata_columns.include?(col) || poly_cols.include?(col) %>
397
+ <% next if #{(pk || []).inspect}.include?(col) || ::Brick.config.metadata_columns.include?(col) || poly_cols.include?(col) %>
391
398
  <th>
392
399
  <% if (bt = bts[col]) %>
393
400
  BT <%
@@ -408,7 +415,7 @@ function changeout(href, param, value) {
408
415
  <tr>#{"
409
416
  <td><%= link_to '⇛', #{obj_name}_path(#{obj_pk}), { class: 'big-arrow' } %></td>" if obj_pk}
410
417
  <% #{obj_name}.attributes.each do |k, val| %>
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'))) %>
418
+ <% 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'))) %>
412
419
  <td>
413
420
  <% if (bt = bts[k]) %>
414
421
  <%# binding.pry # Postgres column names are limited to 63 characters %>
@@ -444,7 +451,7 @@ function changeout(href, param, value) {
444
451
  <p style=\"color: green\"><%= notice %></p>#{"
445
452
  <select id=\"schema\">#{schema_options}</select>" if ::Brick.db_schemas.length > 1}
446
453
  <select id=\"tbl\">#{table_options}</select>
447
- <h1>#{model_name}: <%= (obj = @#{obj_name}&.first)&.brick_descrip || controller_name %></h1>
454
+ <h1>#{model_name}: <%= (obj = @#{obj_name})&.brick_descrip || controller_name %></h1>
448
455
  <%= link_to '(See all #{obj_name.pluralize})', #{table_name}_path %>
449
456
  <% if obj %>
450
457
  <%= # path_options = [obj.#{pk}]
@@ -453,10 +460,9 @@ function changeout(href, param, value) {
453
460
  form_for(obj.becomes(#{model_name})) do |f| %>
454
461
  <table>
455
462
  <% has_fields = false
456
- @#{obj_name}.first.attributes.each do |k, val| %>
463
+ @#{obj_name}.attributes.each do |k, val| %>
457
464
  <tr>
458
- <%# %%% Accommodate composite keys %>
459
- <% next if k == '#{pk}' || ::Brick.config.metadata_columns.include?(k) %>
465
+ <% next if #{(pk || []).inspect}.include?(k) || ::Brick.config.metadata_columns.include?(k) %>
460
466
  <th class=\"show-field\">
461
467
  <% has_fields = true
462
468
  if (bt = bts[k])
@@ -464,21 +470,31 @@ function changeout(href, param, value) {
464
470
  bt_name = bt[1].map { |x| x.first.name }.join('/')
465
471
  # %%% Only do this if the user has permissions to edit this bt field
466
472
  if bt[2] # Polymorphic?
467
- poly_class_name = @#{obj_name}.first.send(\"#\{bt.first\}_type\")
468
- bt_pair = bt[1].find { |pair| pair.first.name == poly_class_name }
473
+ poly_class_name = orig_poly_name = @#{obj_name}.send(\"#\{bt.first\}_type\")
474
+ bt_pair = nil
475
+ loop do
476
+ bt_pair = bt[1].find { |pair| pair.first.name == poly_class_name }
477
+ # Acxommodate any valid STI by going up the chain of inheritance
478
+ break unless bt_pair.nil? && poly_class_name = ::Brick.existing_stis[poly_class_name]
479
+ end
480
+ puts \"*** Might be missing an STI class called #\{orig_poly_name\} whose base class should have this:
481
+ *** has_many :#{table_name}, as: :#\{bt.first\}
482
+ *** Can probably auto-configure everything using these lines in an initialiser:
483
+ *** Brick.sti_namespace_prefixes = { '::#\{orig_poly_name\}' => 'SomeParentModel' }
484
+ *** Brick.polymorphics = { '#{table_name}.#\{bt.first\}' => ['SomeParentModel'] }\" if bt_pair.nil?
469
485
  # descrips = @_brick_bt_descrip[bt.first][bt_class]
470
- poly_id = @#{obj_name}.first.send(\"#\{bt.first\}_id\")
486
+ poly_id = @#{obj_name}.send(\"#\{bt.first\}_id\")
471
487
  # bt_class.order(obj_pk = bt_class.primary_key).each { |obj| option_detail << [obj.brick_descrip(nil, obj_pk), obj.send(obj_pk)] }
472
488
  else # No polymorphism, so just get the first one
473
489
  bt_pair = bt[1].first
474
490
  end
475
- bt_class = bt_pair.first
491
+ bt_class = bt_pair&.first
476
492
  if bt.length < 4
477
493
  bt << (option_detail = [[\"(No #\{bt_name\} chosen)\", '^^^brick_NULL^^^']])
478
494
  # %%% Accommodate composite keys for obj.pk at the end here
479
- bt_class.order(obj_pk = bt_class.primary_key).each { |obj| option_detail << [obj.brick_descrip(nil, obj_pk), obj.send(obj_pk)] }
495
+ bt_class&.order(obj_pk = bt_class.primary_key)&.each { |obj| option_detail << [obj.brick_descrip(nil, obj_pk), obj.send(obj_pk)] }
480
496
  end %>
481
- BT <%= bt_class.bt_link(bt.first) %>
497
+ BT <%= bt_class&.bt_link(bt.first) || orig_poly_name %>
482
498
  <% else %>
483
499
  <%= k %>
484
500
  <% end %>
@@ -488,7 +504,7 @@ function changeout(href, param, value) {
488
504
  html_options = { prompt: \"Select #\{bt_name\}\" }
489
505
  html_options[:class] = 'dimmed' unless val %>
490
506
  <%= 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 %>
507
+ <%= bt_obj = bt_class&.find_by(bt_pair[1] => val); link_to('⇛', send(\"#\{bt_class.base_class.name.underscore\}_path\".to_sym, bt_obj.send(bt_class.primary_key.to_sym)), { class: 'show-arrow' }) if bt_obj %>
492
508
  <% else case #{model_name}.column_for_attribute(k).type
493
509
  when :string, :text %>
494
510
  <% if is_bcrypt?(val) # || .readonly? %>
@@ -520,16 +536,17 @@ function changeout(href, param, value) {
520
536
 
521
537
  #{hms_headers.each_with_object(+'') do |hm, s|
522
538
  if (pk = hm.first.klass.primary_key)
523
- s << "<table id=\"#{hm_name = hm.first.name.to_s}\">
539
+ hm_singular_name = (hm_name = hm.first.name.to_s).singularize.underscore
540
+ obj_pk = (pk.is_a?(Array) ? pk : [pk]).each_with_object([]) { |pk_part, s| s << "#{hm_singular_name}.#{pk_part}" }.join(', ')
541
+ s << "<table id=\"#{hm_name}\">
524
542
  <tr><th>#{hm[3]}</th></tr>
525
- <% collection = @#{obj_name}.first.#{hm_name}
543
+ <% collection = @#{obj_name}.#{hm_name}
526
544
  collection = collection.is_a?(ActiveRecord::Associations::CollectionProxy) ? collection.order(#{pk.inspect}) : [collection]
527
545
  if collection.empty? %>
528
546
  <tr><td>(none)</td></tr>
529
547
  <% else %>
530
- <% collection.uniq.each do |#{hm_singular_name = hm_name.singularize.underscore}| %>
531
- <%# %%% accommodate composite primary key %>
532
- <tr><td><%= link_to(#{hm_singular_name}.brick_descrip, #{hm.first.klass.name.underscore}_path(#{hm_singular_name}.#{pk})) %></td></tr>
548
+ <% collection.uniq.each do |#{hm_singular_name}| %>
549
+ <tr><td><%= link_to(#{hm_singular_name}.brick_descrip, #{hm.first.klass.name.underscore}_path([#{obj_pk}])) %></td></tr>
533
550
  <% end %>
534
551
  <% end %>
535
552
  </table>"
@@ -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 = 26
8
+ TINY = 29
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
@@ -291,6 +291,10 @@ module Brick
291
291
  Brick.config.schema_to_analyse = schema
292
292
  end
293
293
 
294
+ def default_route_fallback=(resource_name)
295
+ Brick.config.default_route_fallback = resource_name
296
+ end
297
+
294
298
  # Load additional references (virtual foreign keys)
295
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
296
300
  # %%% Maybe look for differences the second time 'round and just add new stuff instead of entirely deferring
@@ -321,9 +325,9 @@ module Brick
321
325
  You might be missing an STI namespace prefix entry for these tables: #{missing_stis.keys.join(', ')}.
322
326
  In config/initializers/brick.rb appropriate entries would look something like:
323
327
  Brick.sti_namespace_prefixes = {"
324
- puts missing_stis.map { |_k, missing_sti| "\n '::#{missing_sti}' => 'YourParentModel'" }.join(',')
328
+ puts missing_stis.map { |_k, missing_sti| "\n '::#{missing_sti}' => 'SomeParentModel'" }.join(',')
325
329
  puts " }
326
- (Just trade out YourParentModel with some more appropriate one.)"
330
+ (Just trade out SomeParentModel with some more appropriate one.)"
327
331
  end
328
332
  end
329
333
  @_additional_references_loaded = true
@@ -381,6 +385,9 @@ In config/initializers/brick.rb appropriate entries would look something like:
381
385
  def finalize!
382
386
  existing_controllers = routes.each_with_object({}) { |r, s| c = r.defaults[:controller]; s[c] = nil if c }
383
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
384
391
  # %%% TODO: If no auto-controllers then enumerate the controllers folder in order to build matching routes
385
392
  # If auto-controllers and auto-models are both enabled then this makes sense:
386
393
  ::Brick.relations.each do |k, v|
@@ -444,7 +451,7 @@ ActiveSupport.on_load(:active_record) do
444
451
  relation = clone # spawn
445
452
  relation.select_values = column_names
446
453
  result = if klass.connection.class.name.end_with?('::PostgreSQLAdapter')
447
- rslt = klass.connection.execute(relation.arel.to_sql)
454
+ rslt = klass.execute_sql(relation.arel.to_sql)
448
455
  rslt.type_map =
449
456
  @type_map ||= proc do
450
457
  # This aliasing avoids the warning:
@@ -113,24 +113,24 @@ module Brick
113
113
  # # #{resembles_fks.join(', ')}"
114
114
  end
115
115
 
116
- poly = case (possible_polymorphics = possible_polymorphics.values.flatten).length
116
+ poly = case (possible_polymorphics = possible_polymorphics.values.flatten.map { |poss_poly| "#{poss_poly} => nil"}).length
117
117
  when 0
118
118
  " like this:
119
- # Brick.polymorphics = [
120
- # 'comments.commentable',
121
- # 'images.imageable'
122
- # ]"
119
+ # Brick.polymorphics = {
120
+ # 'comments.commentable' => nil,
121
+ # 'images.imageable' => nil
122
+ # }"
123
123
  when 1
124
124
  ".
125
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}]"
126
+ # Brick.polymorphics = { #{possible_additional_references.first} }"
127
127
 
128
128
  else
129
129
  ".
130
130
  # # Here are possible polymorphic associations that have been auto-identified for the #{ActiveRecord::Base.connection.current_database} database:
131
- # Brick.polymorphics = [
131
+ # Brick.polymorphics = {
132
132
  # #{possible_polymorphics.join(",\n# ")}
133
- # ]"
133
+ # }"
134
134
  end
135
135
 
136
136
  create_file(filename, "# frozen_string_literal: true
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.26
4
+ version: 1.0.29
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-22 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