brick 1.0.244 → 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: 4de3ef8371e163df0913a1fe70d55666010abefcdcc77d26813a27ea3b72967f
4
- data.tar.gz: 73dffd4bb0f5c5e9f57d1e19550cc212a67b4cc9636dd6eed53bee458cd155ee
3
+ metadata.gz: 3410ae5fca9b12aa31007df9d3fac95a39f09eee58f4ffc2089bada97bde7793
4
+ data.tar.gz: 0da56c9311d8dd0ce2fd3a05039a6f85ca9910c64b303c25fc26f8d65fab512b
5
5
  SHA512:
6
- metadata.gz: 948aaae186933b70b69b8df65ac2081168edd4d3badc9cf7bcdfb2d2a49243be376bb70c3c15aef7e00fdbf42f5be5c51185a91e01e4728332e0e0a476382403
7
- data.tar.gz: 500f395af03a898ed11ee8e3caad5fa0571568b19f1183a4f7b90793a8324e5d87870220b8a60b7e91d61e73ad7855d450a46711e8404c519bded7599b761866
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
 
@@ -324,7 +324,7 @@ function linkSchemas() {
324
324
  end
325
325
  end
326
326
  end
327
- "<script>
327
+ "<script<%= @_request.env['_brick_nonce'] %>>
328
328
  #{JS_CHANGEOUT}
329
329
  document.addEventListener(\"turbo:render\", linkSchemas);
330
330
  window.addEventListener(\"popstate\", linkSchemas);
@@ -826,56 +826,51 @@ 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
+ if @_request.respond_to?(:content_security_policy) && (csp = @_request.content_security_policy)&.directives&.present?
832
+ @_request.env['_is_brick'] = true
833
+ if @_request.respond_to?(:content_security_policy_nonce_directives)
834
+ @_request.content_security_policy_nonce_directives = %w[ script-src ]
835
+ @_request.env['_brick_nonce'] = \" nonce=\\\"#\{@_request.content_security_policy_nonce}\\\"\".html_safe
836
+ %><meta name=\"csp-nonce\" content=\"<%= @_request.content_security_policy_nonce %>\"><%
837
+ end
838
+ if !@_request.respond_to?(:_brick_content_security_policy)
839
+ if csp.instance_variables.exclude?(:@_brick_style_shas)
840
+ csp.instance_variable_set(:@_brick_style_shas, [
841
+ \"'sha256-#\{Base64.encode64(Digest.const_get(:SHA256).digest(::Brick::Rails::BRICK_CSS)).chomp}'\",
842
+ \"'sha256-#\{Base64.encode64(Digest.const_get(:SHA256).digest(::Brick::Rails::IN_APP_STYLE)).chomp}'\",
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)
847
+ ])
848
+ end
849
+
850
+ ::ActionDispatch::ContentSecurityPolicy::Request.module_exec do
851
+ alias :_brick_content_security_policy :content_security_policy
852
+ def content_security_policy
853
+ return _brick_content_security_policy if env['_is_brick'].blank?
854
+
855
+ csp = ::ActionDispatch::ContentSecurityPolicy.new
856
+ csp.directives.merge! ({
857
+ 'style-src': [\"'self'\", 'https://cdn.jsdelivr.net', \"'unsafe-hashes'\", \"'nonce-#\{content_security_policy_nonce}'\"] +
858
+ _brick_content_security_policy.instance_variable_get(:@_brick_style_shas),
859
+ 'script-src': [\"'self'\", 'https://cdn.jsdelivr.net', 'https://apis.google.com', 'https://accounts.google.com', \"'nonce-#\{content_security_policy_nonce}'\"],
860
+ 'connect-src': [\"'self'\", 'https://cdn.jsdelivr.net']
861
+ })
862
+ csp
863
+ end
864
+ end
865
+ end
866
+ end %>
867
+ <style>#{::Brick::Rails::BRICK_CSS}</style>
868
+ <script<%= @_request.env['_brick_nonce'] %>>
831
869
  if (window.history.state && window.history.state.turbo)
832
870
  window.addEventListener(\"popstate\", function () { location.reload(true); });
833
871
  </script>
834
872
 
835
873
  <%
836
- if @_request.respond_to?(:content_security_policy)
837
- @_request.env['_is_brick'] = true
838
- if (csp = @_request.content_security_policy).instance_variables.exclude?(:@_brick_style_shas)
839
- csp.instance_variable_set(:@_brick_style_shas, [
840
- \"'sha256-#\{Base64.encode64(Digest.const_get(:SHA256).digest(::Brick::Rails::BRICK_CSS)).chomp}'\",
841
- \"'sha256-#\{Base64.encode64(Digest.const_get(:SHA256).digest(::Brick::Rails::IN_APP_STYLE)).chomp}'\"
842
- ])
843
- end
844
- if !@_request.respond_to?(:_brick_content_security_policy)
845
- ::ActionDispatch::ContentSecurityPolicy::Request.module_exec do
846
- alias :_brick_content_security_policy :content_security_policy
847
- def content_security_policy
848
- # Add appropriate hashes for inline styles to the content-security-policy if needed
849
- if (cspd = (csp = _brick_content_security_policy).directives.fetch('style-src', nil)) && env['_is_brick']
850
- cspd.select! { |val| val == \"'self'\" }
851
- if params['action'] == 'show'
852
- cspd << \"'unsafe-inline'\"
853
- else
854
- csp.instance_variable_get(:@_brick_style_shas).each { |s| cspd << s }
855
- cspd << \"'unsafe-hashes'\"
856
- end
857
- cspd << 'https://cdn.jsdelivr.net'
858
- end
859
- if (cspsd = csp.directives.fetch('script-src', nil))
860
- cspsd.select! { |val| val == \"'self'\" }
861
- cspsd << \"'unsafe-inline'\"
862
- cspsd << 'https://cdn.jsdelivr.net'
863
- end
864
- if (cspcd = csp.directives.fetch('connect-src', nil))
865
- cspcd.select! { |val| val == \"'self'\" }
866
- cspcd << 'https://cdn.jsdelivr.net'
867
- end
868
- csp
869
- end
870
-
871
- alias :_brick_content_security_policy_nonce :content_security_policy_nonce
872
- def content_security_policy_nonce
873
- _brick_content_security_policy_nonce unless env['_is_brick']
874
- end
875
- end
876
- end
877
- end
878
-
879
874
  # Accommodate composite primary keys that include strings with forward-slash characters
880
875
  def slashify(*vals)
881
876
  vals.map { |val_part| val_part.is_a?(String) ? val_part.gsub('/', '^^sl^^') : val_part }
@@ -890,7 +885,11 @@ callbacks = {} %>"
890
885
  poly_cols << @_brick_model.brick_foreign_type(v[1].first)
891
886
  v.last[1].each_with_object([]) { |x, s| s << "[#{x.name}, #{x.primary_key.inspect}]" }.join(', ')
892
887
  else
893
- "[#{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}]"
894
893
  end
895
894
  s << "#{v.first.inspect} => [#{v.last.first.inspect}, [#{foreign_models}], #{v.last[2].inspect}]"
896
895
  end
@@ -902,7 +901,7 @@ callbacks = {} %>"
902
901
  end
903
902
 
904
903
  # %%% When doing schema select, if we're on a new page go to index
905
- script = "<script>
904
+ script = "<script<%= @_request.env['_brick_nonce'] %>>
906
905
  // Add \"Are you sure?\" behaviour to any data-confirm buttons out there
907
906
  document.querySelectorAll(\"input[type=submit][data-confirm]\").forEach(function (btn) {
908
907
  btn.addEventListener(\"click\", function (evt) {
@@ -919,7 +918,8 @@ document.querySelectorAll(\"input[type=submit][data-confirm]\").forEach(function
919
918
  "\nbrickTestSchema = \"#{::Brick.test_schema}\";" if ::Brick.test_schema
920
919
  }
921
920
  function doFetch(method, payload, success) {
922
- payload.authenticity_token = <%= (session[:_csrf_token] || form_authenticity_token).inspect.html_safe %>;
921
+ payload.authenticity_token = <%=
922
+ (!session.respond_to?(:enabled?) || session.enabled?) ? ((session[:_csrf_token] || form_authenticity_token).inspect.html_safe) : 'null' %>;
923
923
  var action = payload._brick_action || location.href;
924
924
  delete payload._brick_action;
925
925
  if (!success) {
@@ -954,7 +954,7 @@ if (window.brickFontFamily) {
954
954
  <div id=\"dropper\" contenteditable=\"true\"></div>
955
955
  <input type=\"button\" id=\"btnImport\" value=\"Import\">
956
956
 
957
- <script>
957
+ <script<%= @_request.env['_brick_nonce'] %>>
958
958
  var dropperDiv = document.getElementById(\"dropper\");
959
959
  var btnImport = document.getElementById(\"btnImport\");
960
960
  var droppedTSV;
@@ -981,19 +981,6 @@ if (window.brickFontFamily) {
981
981
  }
982
982
  var sheetUrl;
983
983
  var spreadsheetId;
984
- var sheetsLink = document.getElementById(\"sheetsLink\");
985
- function gapiLoaded() {
986
- // Have a click on the sheets link to bring up the sign-in window. (Must happen from some kind of user click.)
987
- sheetsLink.addEventListener(\"click\", async function (evt) {
988
- evt.preventDefault();
989
- var client = google.accounts.oauth2.initTokenClient({
990
- client_id: \"487319557829-fgj4u660igrpptdji7ev0r5hb6kh05dh.apps.googleusercontent.com\",
991
- scope: \"https://www.googleapis.com/auth/spreadsheets https://www.googleapis.com/auth/drive.file\",
992
- callback: updateSignInStatus
993
- });
994
- client.requestAccessToken();
995
- });
996
- }
997
984
 
998
985
  async function updateSignInStatus(token) {
999
986
  await new Promise(function (resolve) {
@@ -1052,7 +1039,24 @@ if (window.brickFontFamily) {
1052
1039
  }
1053
1040
  </script>
1054
1041
  <script src=\"https://apis.google.com/js/api.js\"></script>
1055
- <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>
1056
1060
  "
1057
1061
  end # DutyFree data export and import
1058
1062
  # %%% Instead of our current "for Janet Leverling (Employee)" kind of link we previously had this code that did a "where x = 123" thing:
@@ -1130,7 +1134,7 @@ end
1130
1134
  <div class=\"colExclusion\"><%= excl %></div>
1131
1135
  <% end %>
1132
1136
  </div>
1133
- <script>
1137
+ <script<%= @_request.env['_brick_nonce'] %>>
1134
1138
  [... document.getElementsByClassName(\"colExclusion\")].forEach(function (excl) {
1135
1139
  excl.addEventListener(\"click\", function () {
1136
1140
  doFetch(\"POST\", {_brick_unexclude: this.innerHTML});
@@ -1141,7 +1145,7 @@ end
1141
1145
  # SEARCH BOX
1142
1146
  if @_brick_es && @_brick_es&.index('r') # Must have at least Elasticsearch Read access %>
1143
1147
  <input type=\"text\" id=\"esSearch\" class=\"dimmed\">
1144
- <script>
1148
+ <script<%= @_request.env['_brick_nonce'] %>>
1145
1149
  var esSearch = document.getElementById(\"esSearch\");
1146
1150
  var usedTerms = {};
1147
1151
  var isEsFiltered = false;
@@ -1327,7 +1331,7 @@ end
1327
1331
  kls = Object.const_get((rel = ::Brick.relations.fetch(r[0], nil))&.fetch(:class_name, nil))
1328
1332
  rescue
1329
1333
  end
1330
- 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)
1331
1335
  link_to(r[0], send(path_helper))
1332
1336
  else
1333
1337
  r[0]
@@ -1504,7 +1508,7 @@ end
1504
1508
  end
1505
1509
  s << "<table id=\"#{hm_name}\" class=\"shadow\">
1506
1510
  <tr><th>#{hm[1]}#{' poly' if hm[0].options[:as]} #{hm[3]}
1507
- <% if predicates && respond_to?(:new_#{partial_new_path_name = hm.first.klass._brick_index(:singular)}_path) %>
1511
+ <% if defined?(predicates) && predicates && respond_to?(:new_#{partial_new_path_name = hm.first.klass._brick_index(:singular)}_path) %>
1508
1512
  <span class = \"add-hm-related\"><%=
1509
1513
  pk_val = (obj_pk = model.primary_key).is_a?(String) ? obj.send(obj_pk) : obj_pk.map { |pk_part| obj.send(pk_part) }
1510
1514
  pk_val_arr = [pk_val] unless pk_val.is_a?(Array)
@@ -1590,7 +1594,7 @@ end}
1590
1594
  }
1591
1595
  </style>
1592
1596
  <script src=\"https://cdn.jsdelivr.net/npm/flatpickr\"></script>
1593
- <script>
1597
+ <script<%= @_request.env['_brick_nonce'] %>>
1594
1598
  flatpickr(\".datepicker\");
1595
1599
  flatpickr(\".datetimepicker\", {enableTime: true});
1596
1600
  flatpickr(\".timepicker\", {enableTime: true, noCalendar: true});
@@ -1611,7 +1615,7 @@ flatpickr(\".timepicker\", {enableTime: true, noCalendar: true});
1611
1615
  # Uncaught TypeError: Failed to resolve module specifier \"immutable-json-patch\". Relative references must start with either \"/\", \"./\", or \"../\".
1612
1616
  if @_json_fields_present %>
1613
1617
  <link rel=\"stylesheet\" type=\"text/css\" href=\"https://cdn.jsdelivr.net/npm/vanilla-jsoneditor@0.19.0/themes/jse-theme-default.min.css\">
1614
- <script type=\"module\">
1618
+ <script type=\"module\"<%= @_request.env['_brick_nonce'] %>>
1615
1619
  import { JSONEditor } from \"https://cdn.jsdelivr.net/npm/vanilla-jsoneditor@0.19.0/index.min.js\";
1616
1620
  document.querySelectorAll(\"input.jsonpicker\").forEach(function (inp) {
1617
1621
  var jsonDiv;
@@ -1639,7 +1643,7 @@ flatpickr(\".timepicker\", {enableTime: true, noCalendar: true});
1639
1643
 
1640
1644
  <% if true # @_brick_erd
1641
1645
  %>
1642
- <script>
1646
+ <script<%= @_request.env['_brick_nonce'] %>>
1643
1647
  var imgErd = document.getElementById(\"imgErd\");
1644
1648
  var mermaidErd = document.getElementById(\"mermaidErd\");
1645
1649
  var mermaidCode;
@@ -1708,7 +1712,7 @@ flatpickr(\".timepicker\", {enableTime: true, noCalendar: true});
1708
1712
  "
1709
1713
  end
1710
1714
  if representation == :grid
1711
- inline << "<script>
1715
+ inline << "<script<%= @_request.env['_brick_nonce'] %>>
1712
1716
  <% # Make column headers sort when clicked
1713
1717
  # %%% Create a smart javascript routine which can do this client-side %>
1714
1718
  [... document.getElementsByTagName(\"TH\")].forEach(function (th) {
@@ -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?
@@ -402,7 +413,7 @@ module Brick::Rails::FormTags
402
413
  end
403
414
  out << " </tbody>
404
415
  </table>
405
- <script>
416
+ <script<%= @_request.env['_brick_nonce'] %>>
406
417
  var constellation = document.getElementById(\"#{table_name}\");
407
418
  var nextSib,
408
419
  _this;
@@ -868,7 +879,7 @@ function onImagesLoaded(event) {
868
879
  </select></td>
869
880
  <td class=\"paddingTopZero\"><input id=\"txtColName\"></td>
870
881
  </tr></table>
871
- <script>
882
+ <script<%= @_request.env['_brick_nonce'] %>>
872
883
  var btnAddCol = document.getElementById(\"btnAddCol\");
873
884
  btnAddCol.addEventListener(\"click\", function () {
874
885
  var txtColName = document.getElementById(\"txtColName\");
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 = 244
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.244
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-23 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: []