brick 1.0.245 → 1.0.246

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: 74ab9c3403cc8ec25b0a2a2d24cc8c9cf871aa5de82daf92055542c70d4977df
4
- data.tar.gz: 6e119a05135fb27a1d89b67cf8762a2d2d394876dfd79795481eb5f416476f06
3
+ metadata.gz: 3410ae5fca9b12aa31007df9d3fac95a39f09eee58f4ffc2089bada97bde7793
4
+ data.tar.gz: 0da56c9311d8dd0ce2fd3a05039a6f85ca9910c64b303c25fc26f8d65fab512b
5
5
  SHA512:
6
- metadata.gz: 220af7d1a7be7d224d0b25c8b9e3e635384067c408cd80bf012b289b83957c1bcda11a43eb152208b76bf80ec2d3cfa4c25b91ecfd9db4fa8710b217602ec766
7
- data.tar.gz: 01c63fd12b462aa64ac72812f30541036c78e5934b4b5215ee1215394105d521be44771adcd9a5685cba2d784546f216b07016784c820e4cd510a4e367b9d873
6
+ metadata.gz: 7a2e821dfee25a5df265caf5700e25003cbc7cab88e439102fc949f5dfbba2f9f7b73803c98068d373ffbf12cab27dac3859c76a7772ea19bbaf4ffdbfbddbe6
7
+ data.tar.gz: 701d6d9e7d374cbe850806ce732f5b765c1ea3d051c111f316bcd29fdf45622d8999c04fa89ca058f13052670f56c77350fde24e00139ef40dc892b0c97ca896
@@ -162,7 +162,7 @@ module ActiveRecord
162
162
  def _brick_deserialized_id(obj, id_col)
163
163
  id = []
164
164
  self._pk_as_array.each_with_index do |pk_part, idx|
165
- id << if obj.respond_to?(id_sym = id_col[idx].to_sym)
165
+ id << if (id_sym = id_col[idx]&.to_sym) && obj.respond_to?(id_sym)
166
166
  self._brick_deserialized_value(obj, [[pk_part, id_col[idx]]])&.first
167
167
  else
168
168
  id_col[idx]
@@ -926,11 +926,13 @@ module ActiveRecord
926
926
  poly_ft = [hm.source_reflection.inverse_of.foreign_type, hmt_assoc.source_reflection.class_name]
927
927
  end
928
928
  # link_back << hm.source_reflection.inverse_of.name
929
- while hmt_assoc.options[:through] && (hmt_assoc = klass.reflect_on_association(hmt_assoc.options[:through]))
929
+ while hmt_assoc.options[:through] && (hmt_assoc = klass.reflect_on_association(xy = hmt_assoc.options[:through]))
930
+ if hmt_assoc.macro == :has_and_belongs_to_many
931
+ through_sources.unshift(hmt_assoc)
932
+ link_back << hmt_assoc.source_reflection.klass.reflect_on_all_associations.find { |a| a.macro == :has_and_belongs_to_many && a.join_table == hmt_assoc.join_table }.name
933
+ end
930
934
  through_sources.unshift(hmt_assoc)
931
935
  end
932
- # Turn the last member of link_back into a foreign key
933
- link_back << hmt_assoc.source_reflection.foreign_key
934
936
  # If it's a HMT based on a HM -> HM, must JOIN the last table into the mix at the end
935
937
  this_hm = hm
936
938
  while !(src_ref = this_hm.source_reflection).belongs_to? && (thr = src_ref.options[:through])
@@ -940,39 +942,48 @@ module ActiveRecord
940
942
  from_clause = +"#{_br_quoted_name(through_sources.first.table_name)} br_t0"
941
943
  # ActiveStorage will not get the correct count unless we do some extra filtering later
942
944
  tbl_nm = 'br_t0' if Object.const_defined?('ActiveStorage') && through_sources.first.klass <= ::ActiveStorage::Attachment
943
- fk_col = through_sources.shift.foreign_key
944
-
945
+ # Turn the last member of link_back into a foreign key
946
+ link_back << ((tsf = through_sources.shift).macro == :has_and_belongs_to_many ?
947
+ tsf.klass.primary_key : hmt_assoc.source_reflection.foreign_key)
948
+ fk_col = tsf.macro == :has_and_belongs_to_many ?
949
+ tsf.klass.primary_key : tsf.foreign_key
945
950
  idx = 0
946
951
  bail_out = nil
947
952
  the_chain = through_sources.map do |a|
948
- from_clause << "\n LEFT OUTER JOIN #{a.table_name} br_t#{idx += 1} "
949
- from_clause << if (src_ref = a.source_reflection).macro == :belongs_to
950
- link_back << (nm = hmt_assoc.source_reflection.inverse_of&.name)
951
- # puts "BT #{a.table_name}"
952
- "ON br_t#{idx}.#{a.active_record.primary_key} = br_t#{idx - 1}.#{a.foreign_key}"
953
- elsif src_ref.options[:as]
954
- "ON br_t#{idx}.#{src_ref.type} = '#{src_ref.active_record.name}'" + # "polymorphable_type"
955
- " AND br_t#{idx}.#{src_ref.foreign_key} = br_t#{idx - 1}.id"
956
- elsif src_ref.options[:source_type]
957
- if a == hm.source_reflection
958
- print "Skipping #{hm.name} --HMT-> #{hm.source_reflection.name} as it uses source_type in a way which is not yet supported"
959
- nix << k
960
- bail_out = true
961
- break
962
- # "ON br_t#{idx}.#{a.foreign_type} = '#{src_ref.options[:source_type]}' AND " \
963
- # "br_t#{idx}.#{a.foreign_key} = br_t#{idx - 1}.#{a.active_record.primary_key}"
964
- else # Works for HMT through a polymorphic HO
965
- link_back << hmt_assoc.source_reflection.inverse_of&.name # Some polymorphic "_able" thing
966
- "ON br_t#{idx - 1}.#{a.foreign_type} = '#{src_ref.options[:source_type]}' AND " \
967
- "br_t#{idx - 1}.#{a.foreign_key} = br_t#{idx}.#{a.active_record.primary_key}"
953
+ puts "#{a.name} - #{a.macro}"
954
+ if a.macro == :has_and_belongs_to_many
955
+ from_clause << "\n LEFT OUTER JOIN #{a.join_table} br_t#{idx += 1} ON br_t#{idx}.#{a.association_foreign_key} = br_t#{idx - 1}.#{a.active_record.primary_key}"
956
+ from_clause << "\n LEFT OUTER JOIN #{a.active_record.table_name} br_t#{idx += 1} ON br_t#{idx}.#{a.active_record.primary_key} = br_t#{idx - 1}.#{a.foreign_key}"
957
+ else
958
+ from_clause << "\n LEFT OUTER JOIN #{a.table_name} br_t#{idx += 1} "
959
+ from_clause << if (src_ref = a.source_reflection).macro == :belongs_to
960
+ link_back << (nm = hmt_assoc.source_reflection.inverse_of&.name)
961
+ # puts "BT #{a.table_name}"
962
+ "ON br_t#{idx}.#{a.active_record.primary_key} = br_t#{idx - 1}.#{a.foreign_key}"
963
+ elsif src_ref.options[:as]
964
+ "ON br_t#{idx}.#{src_ref.type} = '#{src_ref.active_record.name}'" + # "polymorphable_type"
965
+ " AND br_t#{idx}.#{src_ref.foreign_key} = br_t#{idx - 1}.id"
966
+ elsif src_ref.options[:source_type]
967
+ if a == hm.source_reflection
968
+ print "Skipping #{hm.name} --HMT-> #{hm.source_reflection.name} as it uses source_type in a way which is not yet supported"
969
+ nix << k
970
+ bail_out = true
971
+ break
972
+ # "ON br_t#{idx}.#{a.foreign_type} = '#{src_ref.options[:source_type]}' AND " \
973
+ # "br_t#{idx}.#{a.foreign_key} = br_t#{idx - 1}.#{a.active_record.primary_key}"
974
+ else # Works for HMT through a polymorphic HO
975
+ link_back << hmt_assoc.source_reflection.inverse_of&.name # Some polymorphic "_able" thing
976
+ "ON br_t#{idx - 1}.#{a.foreign_type} = '#{src_ref.options[:source_type]}' AND " \
977
+ "br_t#{idx - 1}.#{a.foreign_key} = br_t#{idx}.#{a.active_record.primary_key}"
978
+ end
979
+ else # Standard has_many or has_one
980
+ # puts "HM #{a.table_name}"
981
+ nm = hmt_assoc.source_reflection.inverse_of&.name
982
+ # binding.pry unless nm
983
+ link_back << nm # if nm
984
+ "ON br_t#{idx}.#{a.foreign_key} = br_t#{idx - 1}.#{a.active_record.primary_key}"
968
985
  end
969
- else # Standard has_many or has_one
970
- # puts "HM #{a.table_name}"
971
- nm = hmt_assoc.source_reflection.inverse_of&.name
972
- # binding.pry unless nm
973
- link_back << nm # if nm
974
- "ON br_t#{idx}.#{a.foreign_key} = br_t#{idx - 1}.#{a.active_record.primary_key}"
975
- end
986
+ end
976
987
  link_back.unshift(a.source_reflection.name)
977
988
  [a.table_name, a.foreign_key, a.source_reflection.macro]
978
989
  end
@@ -980,7 +991,9 @@ module ActiveRecord
980
991
 
981
992
  # puts "LINK BACK! #{k} : #{hm.table_name} #{link_back.map(&:to_s).join('.')}"
982
993
  # count_column is determined from the originating HMT member
983
- if (src_ref = hm.source_reflection).nil?
994
+ if hmt_assoc.macro == :has_and_belongs_to_many
995
+ "br_t#{idx}.#{src_ref.active_record.primary_key}"
996
+ elsif (src_ref = hm.source_reflection).nil?
984
997
  puts "*** Warning: Could not determine destination model for this HMT association in model #{klass.name}:\n has_many :#{hm.name}, through: :#{hm.options[:through]}"
985
998
  puts
986
999
  nix << k
@@ -2162,7 +2175,9 @@ class Object
2162
2175
  # (More information on https://docs.avohq.io/3.0/controllers.html)
2163
2176
  controller_base = Avo::ResourcesController
2164
2177
  end
2165
- if !model&.table_exists? && (tn = model&.table_name)
2178
+ if model && !(model < ActiveRecord::Base)
2179
+ raise "Your project defines class \"#{model.name}\" which is not an ActiveRecord model. If some other model class for this resource exists that can be used instead, you can configure a Brick table_name_prefix in order to have a specific table name refer to that class."
2180
+ elsif !model&.table_exists? && (tn = model&.table_name)
2166
2181
  msg = +"Can't find table \"#{tn}\" for model #{model.name}."
2167
2182
  puts
2168
2183
  # Potential bad inflection?
@@ -2706,7 +2721,6 @@ class Object
2706
2721
  # ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?;", schema)
2707
2722
  # end
2708
2723
 
2709
- is_need_params = true
2710
2724
  code << " def edit\n"
2711
2725
  code << " #{find_by_name}\n"
2712
2726
  code << " end\n"
@@ -2799,7 +2813,7 @@ class Object
2799
2813
  end
2800
2814
  end
2801
2815
 
2802
- code << "private\n" if pk.present? || is_need_params
2816
+ code << "private\n"
2803
2817
 
2804
2818
  if pk.present?
2805
2819
  code << " def #{find_obj}
@@ -2829,29 +2843,23 @@ class Object
2829
2843
  private find_obj
2830
2844
  end
2831
2845
 
2832
- if is_need_params
2833
- code << " def #{params_name}\n"
2834
- require_txt = model.base_class.name.underscore.tr('/', '_')
2835
- is_for_expects = ::ActiveSupport.version >= ::Gem::Version.new('8.0a')
2836
- permits_txt = model._brick_find_permits(model, permits = model._brick_all_fields(true), is_for_expects)
2837
- if is_for_expects
2838
- code << " params.expect(#{require_txt
2839
- }: #{permits_txt})\n"
2840
- code << " end\n"
2841
- self.define_method(params_name) do
2842
- params.expect({ model.base_class.name.underscore.tr('/', '_').to_sym => permits })
2843
- end
2844
- else
2845
- code << " params.require(:#{require_txt
2846
- }).permit(#{permits_txt.map(&:inspect).join(', ')})\n"
2847
- code << " end\n"
2848
- self.define_method(params_name) do
2849
- params.require(model.base_class.name.underscore.tr('/', '_').to_sym).permit(permits)
2850
- end
2846
+ code << " def #{params_name}\n"
2847
+ require_txt = model.base_class.name.underscore.tr('/', '_')
2848
+ is_for_expects = ::ActiveSupport.version >= ::Gem::Version.new('8.0a')
2849
+ permits_txt = model._brick_find_permits(model, permits = model._brick_all_fields(true), is_for_expects)
2850
+ if is_for_expects
2851
+ code << " params.expect(#{require_txt}: #{permits_txt})\n"
2852
+ self.define_method(params_name) do
2853
+ params.expect({ model.base_class.name.underscore.tr('/', '_').to_sym => permits })
2854
+ end
2855
+ else
2856
+ code << " params.require(:#{require_txt}).permit(#{permits_txt.map(&:inspect).join(', ')})\n"
2857
+ self.define_method(params_name) do
2858
+ params.require(model.base_class.name.underscore.tr('/', '_').to_sym).permit(permits)
2851
2859
  end
2852
- private params_name
2853
- # Get column names for params from relations[model.table_name][:cols].keys
2854
2860
  end
2861
+ code << " end\n"
2862
+ private params_name
2855
2863
  end # unless is_openapi
2856
2864
  code << "end # #{class_name}\n"
2857
2865
  end # class definition
@@ -3210,7 +3218,6 @@ module Brick
3210
3218
  end
3211
3219
  end
3212
3220
  abstract_activerecord_bases = ::Brick.eager_load_classes(true)
3213
- rails_root = ::Rails.root.to_s
3214
3221
  models = ::Brick.relations.each_with_object({}) do |rel, s|
3215
3222
  next if rel.first.is_a?(Symbol)
3216
3223
 
@@ -826,20 +826,24 @@ window.addEventListener(\"popstate\", linkSchemas);
826
826
  ['Crosstab', is_crosstab]].each do |table_option, show_it|
827
827
  table_options << "<option value=\"#{prefix}brick_#{table_option.downcase}\">(#{table_option})</option>".html_safe if show_it
828
828
  end
829
- css = +"<style>#{::Brick::Rails::BRICK_CSS}</style>
830
- <script<%=
829
+ css = +"
830
+ <%
831
831
  if @_request.respond_to?(:content_security_policy) && (csp = @_request.content_security_policy)&.directives&.present?
832
832
  @_request.env['_is_brick'] = true
833
833
  if @_request.respond_to?(:content_security_policy_nonce_directives)
834
834
  @_request.content_security_policy_nonce_directives = %w[ script-src ]
835
835
  @_request.env['_brick_nonce'] = \" nonce=\\\"#\{@_request.content_security_policy_nonce}\\\"\".html_safe
836
+ %><meta name=\"csp-nonce\" content=\"<%= @_request.content_security_policy_nonce %>\"><%
836
837
  end
837
838
  if !@_request.respond_to?(:_brick_content_security_policy)
838
839
  if csp.instance_variables.exclude?(:@_brick_style_shas)
839
840
  csp.instance_variable_set(:@_brick_style_shas, [
840
841
  \"'sha256-#\{Base64.encode64(Digest.const_get(:SHA256).digest(::Brick::Rails::BRICK_CSS)).chomp}'\",
841
842
  \"'sha256-#\{Base64.encode64(Digest.const_get(:SHA256).digest(::Brick::Rails::IN_APP_STYLE)).chomp}'\",
842
- \"'sha256-y+oXtN5Bag5VRQgH6D87Eo4UdOZOJiqg31ZNfDibDwM='\" # SHA for the text_field used in brick_field ('min-width: 154px;field-sizing: content;')
843
+ \"'sha256-y+oXtN5Bag5VRQgH6D87Eo4UdOZOJiqg31ZNfDibDwM='\", # SHA for the text_field used in brick_field ('min-width: 154px;field-sizing: content;')
844
+ \"'sha256-aOKu38ec/bSbd1BWfOZWow6kP78MsLOsTmNyCpYqsXM='\", # SHA for Mermaid ('font-family: Arial')
845
+ \"'sha256-P8hbc8HpnNaBWbGVqO8n4uIP3lIM4rGog3PnEYWwrzQ='\" # SHA for flatpickr calendar ('.flatpickr-calendar {background: #A0FFA0;}')
846
+ # \"'sha256-oU3/kVZLL/AmlO0Oi4iZplNUwV9XJW7qt6J9Mm2ASNs='\" # SHA for Mermaid (SVG things)
843
847
  ])
844
848
  end
845
849
 
@@ -850,18 +854,18 @@ window.addEventListener(\"popstate\", linkSchemas);
850
854
 
851
855
  csp = ::ActionDispatch::ContentSecurityPolicy.new
852
856
  csp.directives.merge! ({
853
- 'style-src': [\"'self'\", 'https://cdn.jsdelivr.net', \"'unsafe-hashes'\"] +
857
+ 'style-src': [\"'self'\", 'https://cdn.jsdelivr.net', \"'unsafe-hashes'\", \"'nonce-#\{content_security_policy_nonce}'\"] +
854
858
  _brick_content_security_policy.instance_variable_get(:@_brick_style_shas),
855
- 'script-src': [\"'self'\", 'https://cdn.jsdelivr.net', \"'nonce-#\{content_security_policy_nonce}'\"],
859
+ 'script-src': [\"'self'\", 'https://cdn.jsdelivr.net', 'https://apis.google.com', 'https://accounts.google.com', \"'nonce-#\{content_security_policy_nonce}'\"],
856
860
  'connect-src': [\"'self'\", 'https://cdn.jsdelivr.net']
857
861
  })
858
862
  csp
859
863
  end
860
864
  end
861
865
  end
862
- end
863
-
864
- @_request.env['_brick_nonce'] %>>
866
+ end %>
867
+ <style>#{::Brick::Rails::BRICK_CSS}</style>
868
+ <script<%= @_request.env['_brick_nonce'] %>>
865
869
  if (window.history.state && window.history.state.turbo)
866
870
  window.addEventListener(\"popstate\", function () { location.reload(true); });
867
871
  </script>
@@ -881,7 +885,11 @@ callbacks = {} %>"
881
885
  poly_cols << @_brick_model.brick_foreign_type(v[1].first)
882
886
  v.last[1].each_with_object([]) { |x, s| s << "[#{x.name}, #{x.primary_key.inspect}]" }.join(', ')
883
887
  else
884
- "[#{v.last[1].name}, #{v.last[1].primary_key.inspect}]"
888
+ # If the BT association specifies a column for the primary_key, use that ...
889
+ fm_pk = @_brick_model.reflect_on_association(v.last.first)&.options&.[](:primary_key)&.to_s ||
890
+ # ... otherwise, use the model's primary_key.
891
+ (v.last[1].primary_key.present? && v.last[1].primary_key)
892
+ "[#{v.last[1].name}, #{fm_pk.inspect}]"
885
893
  end
886
894
  s << "#{v.first.inspect} => [#{v.last.first.inspect}, [#{foreign_models}], #{v.last[2].inspect}]"
887
895
  end
@@ -973,19 +981,6 @@ if (window.brickFontFamily) {
973
981
  }
974
982
  var sheetUrl;
975
983
  var spreadsheetId;
976
- var sheetsLink = document.getElementById(\"sheetsLink\");
977
- function gapiLoaded() {
978
- // Have a click on the sheets link to bring up the sign-in window. (Must happen from some kind of user click.)
979
- sheetsLink.addEventListener(\"click\", async function (evt) {
980
- evt.preventDefault();
981
- var client = google.accounts.oauth2.initTokenClient({
982
- client_id: \"487319557829-fgj4u660igrpptdji7ev0r5hb6kh05dh.apps.googleusercontent.com\",
983
- scope: \"https://www.googleapis.com/auth/spreadsheets https://www.googleapis.com/auth/drive.file\",
984
- callback: updateSignInStatus
985
- });
986
- client.requestAccessToken();
987
- });
988
- }
989
984
 
990
985
  async function updateSignInStatus(token) {
991
986
  await new Promise(function (resolve) {
@@ -1044,7 +1039,24 @@ if (window.brickFontFamily) {
1044
1039
  }
1045
1040
  </script>
1046
1041
  <script src=\"https://apis.google.com/js/api.js\"></script>
1047
- <script async defer src=\"https://accounts.google.com/gsi/client\" onload=\"gapiLoaded()\"></script>
1042
+ <script async defer src=\"https://accounts.google.com/gsi/client\" id=\"gapiScript\"<%= @_request.env['_brick_nonce'] %>></script>
1043
+ <script<%= @_request.env['_brick_nonce'] %>>
1044
+ document.getElementById(\"gapiScript\").addEventListener(\"onload\",
1045
+ function () { // gapiLoaded
1046
+ var sheetsLink = document.getElementById(\"sheetsLink\");
1047
+ // Have a click on the sheets link to bring up the sign-in window. (Must happen from some kind of user click.)
1048
+ sheetsLink.addEventListener(\"click\", async function (evt) {
1049
+ evt.preventDefault();
1050
+ var client = google.accounts.oauth2.initTokenClient({
1051
+ client_id: \"487319557829-fgj4u660igrpptdji7ev0r5hb6kh05dh.apps.googleusercontent.com\",
1052
+ scope: \"https://www.googleapis.com/auth/spreadsheets https://www.googleapis.com/auth/drive.file\",
1053
+ callback: updateSignInStatus
1054
+ });
1055
+ client.requestAccessToken();
1056
+ });
1057
+ }
1058
+ );
1059
+ </script>
1048
1060
  "
1049
1061
  end # DutyFree data export and import
1050
1062
  # %%% Instead of our current "for Janet Leverling (Employee)" kind of link we previously had this code that did a "where x = 123" thing:
@@ -1319,7 +1331,7 @@ end
1319
1331
  kls = Object.const_get((rel = ::Brick.relations.fetch(r[0], nil))&.fetch(:class_name, nil))
1320
1332
  rescue
1321
1333
  end
1322
- if kls.is_a?(Class) && (path_helper = respond_to?(bi_path = \"#\{kls._brick_index}_path\".to_sym) ? bi_path : nil)
1334
+ if kls.is_a?(Class) && kls.respond_to?(:_brick_index) && (path_helper = respond_to?(bi_path = \"#\{kls._brick_index}_path\".to_sym) ? bi_path : nil)
1323
1335
  link_to(r[0], send(path_helper))
1324
1336
  else
1325
1337
  r[0]
@@ -1603,7 +1615,7 @@ flatpickr(\".timepicker\", {enableTime: true, noCalendar: true});
1603
1615
  # Uncaught TypeError: Failed to resolve module specifier \"immutable-json-patch\". Relative references must start with either \"/\", \"./\", or \"../\".
1604
1616
  if @_json_fields_present %>
1605
1617
  <link rel=\"stylesheet\" type=\"text/css\" href=\"https://cdn.jsdelivr.net/npm/vanilla-jsoneditor@0.19.0/themes/jse-theme-default.min.css\">
1606
- <script type=\"module\">
1618
+ <script type=\"module\"<%= @_request.env['_brick_nonce'] %>>
1607
1619
  import { JSONEditor } from \"https://cdn.jsdelivr.net/npm/vanilla-jsoneditor@0.19.0/index.min.js\";
1608
1620
  document.querySelectorAll(\"input.jsonpicker\").forEach(function (inp) {
1609
1621
  var jsonDiv;
@@ -112,7 +112,7 @@ module Brick::Rails::FormTags
112
112
  next unless klass.table_exists?
113
113
 
114
114
  rid = pk.map { |pk_part| obj.send(pk_part.to_sym) }
115
- out << "<tr x-id=\"#{rid.join('/')}\">\n"
115
+ out << "<tr x-id=\"#{slashed_rid = rid.join('/')}\">\n"
116
116
  out << "<td class=\"col-sticky alternating-gray\">#{link_to('⇛', send("#{klass._brick_index(:singular)}_path".to_sym, rid),
117
117
  { class: 'big-arrow' })}</td>\n" if pk.present?
118
118
  ac = obj.instance_variable_get(:@association_cache) || obj.instance_variable_set(:@association_cache, {})
@@ -134,8 +134,14 @@ module Brick::Rails::FormTags
134
134
  if bt[2] && obj.respond_to?(poly_id_col = "#{bt.first}_id") # Polymorphic?
135
135
  if (poly_id = obj.send(poly_id_col))
136
136
  bt_class = obj.send(klass.brick_foreign_type(bt.first))
137
- base_class_underscored = (::Brick.existing_stis[bt_class] || bt_class).constantize.base_class._brick_index(:singular)
138
- out << link_to("#{bt_class} ##{poly_id}", send("#{base_class_underscored}_path".to_sym, poly_id))
137
+ root_class_name = (::Brick.existing_stis[bt_class] || bt_class)
138
+ begin
139
+ base_class_underscored = root_class_name.constantize.base_class._brick_index(:singular)
140
+ out << link_to("#{bt_class} ##{poly_id}", send("#{base_class_underscored}_path".to_sym, poly_id))
141
+ rescue NameError => ee
142
+ puts "WARNING: While searching for a polymorphically associated #{bt.first} for the #{klass.name} with ID #{slashed_rid}, was unable to locate a model called \"#{root_class_name}\". This model might have been deleted, thereby orphaning rows in the #{klass.table_name} table."
143
+ out << "<span class=\"orphan\">&lt;&lt; Orphaned \"#{root_class_name}\" with ID: #{slashed_rid} >></span>"
144
+ end
139
145
  end
140
146
  else # BT or HOT
141
147
  bt_class = bt[1].first.first
@@ -151,11 +157,17 @@ module Brick::Rails::FormTags
151
157
  end
152
158
  br_descrip_args = [obj]
153
159
  br_descrip_args += [bt_class._brick_deserialized_value(obj, descrips[0..-2]), bt_id_col] if descrips
154
- bt_txt = bt_class.brick_descrip(*br_descrip_args)
155
- bt_txt = ::Brick::Rails.display_binary(bt_txt).html_safe if bt_txt&.encoding&.name == 'ASCII-8BIT'
156
- bt_txt ||= "<span class=\"orphan\">&lt;&lt; Orphaned ID: #{val} >></span>" if val
157
- bt_id = bt_class._brick_deserialized_id(obj, bt_id_col)
158
- out << (bt_id&.first ? link_to(bt_txt, send("#{bt_class.base_class._brick_index(:singular)}_path".to_sym, bt_id)) : bt_txt || '')
160
+ out << if bt_id_col.blank? && bt[1].first[1] # Is this a BT which references a table with no primary key?
161
+ # Although we have no way to directly reference this record via a show action, we can at least create a link like this:
162
+ # http://localhost:3000/brick/local_authority_postcode?__value=PA2Y+5LQ
163
+ link_to(val, send("#{bt_class.base_class._brick_index(:singular)}_path".to_sym, '__the_id__')).gsub('__the_id__', "?__#{bt[1].first[1]}=#{CGI.escape(val)}")
164
+ else # Normal BT or HOT
165
+ bt_txt = bt_class.brick_descrip(*br_descrip_args)
166
+ bt_txt = ::Brick::Rails.display_binary(bt_txt).html_safe if bt_txt&.encoding&.name == 'ASCII-8BIT'
167
+ bt_txt ||= "<span class=\"orphan\">&lt;&lt; Orphaned ID: #{val} >></span>" if val
168
+ bt_id = bt_class._brick_deserialized_id(obj, bt_id_col)
169
+ (bt_id&.first ? link_to(bt_txt, send("#{bt_class.base_class._brick_index(:singular)}_path".to_sym, bt_id)) : bt_txt || '')
170
+ end
159
171
  end
160
172
  elsif (hms_col = hms_cols[col_name])
161
173
  if hms_col.length == 1
@@ -165,15 +177,14 @@ module Brick::Rails::FormTags
165
177
  if col[2] == 'HO'
166
178
  descrips = bt_descrip[col_name.to_sym][hm_klass]
167
179
  if (descrip_parts = hm_klass._brick_deserialized_value(obj, descrips[0..-2]))&.first
180
+ ho_id = hm_klass._brick_deserialized_id(obj, ho_id_col = descrips.last)
168
181
  ho_txt = if hm_klass.name == 'ActiveStorage::Attachment'
169
182
  begin
170
183
  ::Brick::Rails.display_binary(obj.send(col[3])&.blob&.download)&.html_safe
171
184
  rescue
185
+ # Attachment is missing for some reason ... default to showing just a description.
172
186
  end
173
- else
174
- hm_klass.brick_descrip(obj, descrip_parts, ho_id_col = descrips.last)
175
- end
176
- ho_id = hm_klass._brick_deserialized_id(obj, ho_id_col)
187
+ end || hm_klass.brick_descrip(obj, descrip_parts, ho_id_col)
177
188
  out << link_to(ho_txt, send("#{hm_klass.base_class._brick_index(:singular)}_path".to_sym, ho_id))
178
189
  end
179
190
  elsif obj.respond_to?(ct_col = hms_col[1].to_sym) && (ct = obj.send(ct_col)&.to_i)&.positive?
data/lib/brick/rails.rb CHANGED
@@ -179,7 +179,7 @@ erDiagram
179
179
  'timestamp without time zone' => 'timestamp',
180
180
  'timestamp with time zone' => 'timestamp',
181
181
  'time without time zone' => 'time',
182
- 'time with time zone' => 'time' }[dt] || dt&.tr(' (', '_').tr(')', '') || 'int'
182
+ 'time with time zone' => 'time' }[dt] || dt&.tr(' (', '_')&.tr(')', '') || 'int'
183
183
  end
184
184
  callbacks.merge({#{model_short_name.inspect} => ::#{model.name}}).each do |cb_k, cb_class|
185
185
  cb_relation = ::Brick.relations[cb_class.table_name]
@@ -425,8 +425,7 @@ ORDER BY 1, 2, c.internal_column_id, acc.position"
425
425
  end
426
426
  )&.present?
427
427
  if tnp.last&.start_with?('::') # TNP that points to an exact class?
428
- # Had considered: [tnp.last[2..-1]]
429
- [singular]
428
+ tnp.last[2..-1].split('::')
430
429
  elsif tnp.last
431
430
  v[:auto_prefixed_schema], v[:auto_prefixed_class] = tnp
432
431
  # v[:resource] = rel_name.last[tnp.first.length..-1]
@@ -5,7 +5,7 @@ module Brick
5
5
  module VERSION
6
6
  MAJOR = 1
7
7
  MINOR = 0
8
- TINY = 245
8
+ TINY = 246
9
9
 
10
10
  # PRE is nil unless it's a pre-release (beta, RC, etc.)
11
11
  PRE = nil
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: brick
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.245
4
+ version: 1.0.246
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lorin Thwaits
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2026-06-28 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: activerecord
@@ -246,7 +245,6 @@ homepage: https://github.com/lorint/brick
246
245
  licenses:
247
246
  - MIT
248
247
  metadata: {}
249
- post_install_message:
250
248
  rdoc_options: []
251
249
  require_paths:
252
250
  - lib
@@ -261,8 +259,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
261
259
  - !ruby/object:Gem::Version
262
260
  version: 1.3.6
263
261
  requirements: []
264
- rubygems_version: 3.4.19
265
- signing_key:
262
+ rubygems_version: 4.0.18
266
263
  specification_version: 4
267
264
  summary: Create a Rails app from data alone
268
265
  test_files: []