brick 1.0.245 → 1.0.247

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: baf36636026c4ecad5ff76ccdeea80604c6c9128f7065d7b546ab41cc4fccf1c
4
+ data.tar.gz: ec8c081354229eb5b4cdcd3112b24313468aaf731f856168cace28d3882e1f51
5
5
  SHA512:
6
- metadata.gz: 220af7d1a7be7d224d0b25c8b9e3e635384067c408cd80bf012b289b83957c1bcda11a43eb152208b76bf80ec2d3cfa4c25b91ecfd9db4fa8710b217602ec766
7
- data.tar.gz: 01c63fd12b462aa64ac72812f30541036c78e5934b4b5215ee1215394105d521be44771adcd9a5685cba2d784546f216b07016784c820e4cd510a4e367b9d873
6
+ metadata.gz: 3742d4d628c4f0ff18008f5dd965b3e49c545d24141cea917a0ba141cff7f3243b6f159e732f2e786b87f274e80237bcea2af74f17b9494345cf898e54e72928
7
+ data.tar.gz: 3aa5e54bc3b4af270e5b2342430e294920b151139af66ec5771b15a575377f7275f9c10e593d7c1d314e67d145fd9ffb0ae834b4f68a5d4c1afff8b22ecc404c
@@ -105,13 +105,41 @@ module ActiveRecord
105
105
  col_names = columns_hash.keys
106
106
  # If it's a composite primary key then allow all the values through
107
107
  # TODO: Should disallow any autoincrement / SERIAL columns
108
- if skip_id && (pk_as_array = _pk_as_array).length == 1
109
- col_names -= _pk_as_array
108
+ if skip_id && (pk_as_array = _pk_as_array).length == 1 &&
109
+ [:string, :text].exclude?(columns_hash[pk_as_array.first]&.type)
110
+ col_names -= pk_as_array
110
111
  end
111
112
  hoa, hma, rtans = _activestorage_actiontext_fields
112
113
  col_names.map(&:to_sym) + hoa + hma.map { |as| { as => [] } } + rtans.values
113
114
  end
114
115
 
116
+ def _brick_set_wheres(params, join_array = nil)
117
+ is_distinct = nil
118
+ wheres = {}
119
+ params.each do |k, v|
120
+ k = k.to_s # Rails < 4.2 comes in as a symbol
121
+ next unless k.start_with?('__')
122
+
123
+ k = k[2..-1] # Take off leading "__"
124
+ if (where_col = (ks = k.split('.')).last)[-1] == '!'
125
+ where_col = where_col[0..-2]
126
+ end
127
+ case ks.length
128
+ when 1
129
+ next unless self.column_names.any?(where_col) || self._brick_get_fks.include?(where_col)
130
+ when 2
131
+ assoc_name = ks.first.to_sym
132
+ # Make sure it's a good association name and that the model has that column name
133
+ next unless self.reflect_on_association(assoc_name)&.klass&.column_names&.any?(where_col)
134
+
135
+ join_array&.[]=(assoc_name, nil) # Store this relation name in our special collection for .joins()
136
+ is_distinct = true
137
+ end
138
+ wheres[k] = v.is_a?(String) ? v.split(',') : v
139
+ end
140
+ [wheres, is_distinct]
141
+ end
142
+
115
143
  # Return three lists of fields for this model --
116
144
  # has_one_attached, has_many_attached, and has_rich_text
117
145
  def _activestorage_actiontext_fields
@@ -162,7 +190,7 @@ module ActiveRecord
162
190
  def _brick_deserialized_id(obj, id_col)
163
191
  id = []
164
192
  self._pk_as_array.each_with_index do |pk_part, idx|
165
- id << if obj.respond_to?(id_sym = id_col[idx].to_sym)
193
+ id << if (id_sym = id_col[idx]&.to_sym) && obj.respond_to?(id_sym)
166
194
  self._brick_deserialized_value(obj, [[pk_part, id_col[idx]]])&.first
167
195
  else
168
196
  id_col[idx]
@@ -219,10 +247,17 @@ module ActiveRecord
219
247
  else
220
248
  # If there's no DSL yet specified, just try to find the first usable column on this model
221
249
  dsl = if table_exists?
222
- skip_columns = _brick_get_fks + (::Brick.config.metadata_columns || []) + [primary_key]
223
- if (descrip_col = columns.find { |c| c.type == :string && skip_columns.exclude?(c.name) })
250
+ skip_columns = _brick_get_fks + (::Brick.config.metadata_columns || [])
251
+ column_list = columns.dup
252
+ pk_idx = column_list.index { |c| c.name == primary_key }
253
+ if pk_idx
254
+ pk_column = column_list.delete_at(pk_idx)
255
+ # if the PK column is a string, put it at the end so it will get chosen as a last resort
256
+ column_list << pk_column if [:string, :text].include?(columns_hash[primary_key]&.type)
257
+ end
258
+ if (descrip_col = column_list.find { |c| c.type == :string && skip_columns.exclude?(c.name) })
224
259
  "[#{descrip_col.name}]"
225
- elsif (descrip_col = columns.find { |c| [:boolean, :binary, :xml].exclude?(c.type) && skip_columns.exclude?(c.name) })
260
+ elsif (descrip_col = column_list.find { |c| [:boolean, :binary, :xml].exclude?(c.type) && skip_columns.exclude?(c.name) })
226
261
  "[#{descrip_col.name}]"
227
262
  else
228
263
  "#{name} ##{_pk_as_array.map { |pk_part| "[#{pk_part}]" }.join(', ')}"
@@ -689,32 +724,10 @@ module ActiveRecord
689
724
  # model early in case the user wants to do an ORDER BY based on any of that.
690
725
  model._brick_calculate_bts_hms(translations, join_array) if is_add_bts || is_add_hms
691
726
 
692
- is_distinct = nil
693
- wheres = {}
694
727
  params = params.to_unsafe_h unless params.is_a?(Hash)
695
728
  params.merge!(args[1]) if args[1]
696
- params.each do |k, v|
697
- k = k.to_s # Rails < 4.2 comes in as a symbol
698
- next unless k.start_with?('__')
699
-
700
- k = k[2..-1] # Take off leading "__"
701
- if (where_col = (ks = k.split('.')).last)[-1] == '!'
702
- where_col = where_col[0..-2]
703
- end
704
- case ks.length
705
- when 1
706
- next unless klass.column_names.any?(where_col) || klass._brick_get_fks.include?(where_col)
707
- when 2
708
- assoc_name = ks.first.to_sym
709
- # Make sure it's a good association name and that the model has that column name
710
- next unless klass.reflect_on_association(assoc_name)&.klass&.column_names&.any?(where_col)
711
-
712
- join_array[assoc_name] = nil # Store this relation name in our special collection for .joins()
713
- is_distinct = true
714
- distinct!
715
- end
716
- wheres[k] = v.is_a?(String) ? v.split(',') : v
717
- end
729
+ wheres, is_distinct = klass._brick_set_wheres(params, join_array)
730
+ distinct! if is_distinct
718
731
 
719
732
  # %%% Skip the metadata columns
720
733
  if selects.empty? # Default to all columns
@@ -926,11 +939,13 @@ module ActiveRecord
926
939
  poly_ft = [hm.source_reflection.inverse_of.foreign_type, hmt_assoc.source_reflection.class_name]
927
940
  end
928
941
  # link_back << hm.source_reflection.inverse_of.name
929
- while hmt_assoc.options[:through] && (hmt_assoc = klass.reflect_on_association(hmt_assoc.options[:through]))
942
+ while hmt_assoc.options[:through] && (hmt_assoc = klass.reflect_on_association(xy = hmt_assoc.options[:through]))
943
+ if hmt_assoc.macro == :has_and_belongs_to_many
944
+ through_sources.unshift(hmt_assoc)
945
+ 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
946
+ end
930
947
  through_sources.unshift(hmt_assoc)
931
948
  end
932
- # Turn the last member of link_back into a foreign key
933
- link_back << hmt_assoc.source_reflection.foreign_key
934
949
  # If it's a HMT based on a HM -> HM, must JOIN the last table into the mix at the end
935
950
  this_hm = hm
936
951
  while !(src_ref = this_hm.source_reflection).belongs_to? && (thr = src_ref.options[:through])
@@ -940,39 +955,48 @@ module ActiveRecord
940
955
  from_clause = +"#{_br_quoted_name(through_sources.first.table_name)} br_t0"
941
956
  # ActiveStorage will not get the correct count unless we do some extra filtering later
942
957
  tbl_nm = 'br_t0' if Object.const_defined?('ActiveStorage') && through_sources.first.klass <= ::ActiveStorage::Attachment
943
- fk_col = through_sources.shift.foreign_key
944
-
958
+ # Turn the last member of link_back into a foreign key
959
+ link_back << ((tsf = through_sources.shift).macro == :has_and_belongs_to_many ?
960
+ tsf.klass.primary_key : hmt_assoc.source_reflection.foreign_key)
961
+ fk_col = tsf.macro == :has_and_belongs_to_many ?
962
+ tsf.klass.primary_key : tsf.foreign_key
945
963
  idx = 0
946
964
  bail_out = nil
947
965
  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}"
966
+ puts "#{a.name} - #{a.macro}"
967
+ if a.macro == :has_and_belongs_to_many
968
+ 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}"
969
+ 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}"
970
+ else
971
+ from_clause << "\n LEFT OUTER JOIN #{a.table_name} br_t#{idx += 1} "
972
+ from_clause << if (src_ref = a.source_reflection).macro == :belongs_to
973
+ link_back << (nm = hmt_assoc.source_reflection.inverse_of&.name)
974
+ # puts "BT #{a.table_name}"
975
+ "ON br_t#{idx}.#{a.active_record.primary_key} = br_t#{idx - 1}.#{a.foreign_key}"
976
+ elsif src_ref.options[:as]
977
+ "ON br_t#{idx}.#{src_ref.type} = '#{src_ref.active_record.name}'" + # "polymorphable_type"
978
+ " AND br_t#{idx}.#{src_ref.foreign_key} = br_t#{idx - 1}.id"
979
+ elsif src_ref.options[:source_type]
980
+ if a == hm.source_reflection
981
+ print "Skipping #{hm.name} --HMT-> #{hm.source_reflection.name} as it uses source_type in a way which is not yet supported"
982
+ nix << k
983
+ bail_out = true
984
+ break
985
+ # "ON br_t#{idx}.#{a.foreign_type} = '#{src_ref.options[:source_type]}' AND " \
986
+ # "br_t#{idx}.#{a.foreign_key} = br_t#{idx - 1}.#{a.active_record.primary_key}"
987
+ else # Works for HMT through a polymorphic HO
988
+ link_back << hmt_assoc.source_reflection.inverse_of&.name # Some polymorphic "_able" thing
989
+ "ON br_t#{idx - 1}.#{a.foreign_type} = '#{src_ref.options[:source_type]}' AND " \
990
+ "br_t#{idx - 1}.#{a.foreign_key} = br_t#{idx}.#{a.active_record.primary_key}"
991
+ end
992
+ else # Standard has_many or has_one
993
+ # puts "HM #{a.table_name}"
994
+ nm = hmt_assoc.source_reflection.inverse_of&.name
995
+ # binding.pry unless nm
996
+ link_back << nm # if nm
997
+ "ON br_t#{idx}.#{a.foreign_key} = br_t#{idx - 1}.#{a.active_record.primary_key}"
968
998
  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
999
+ end
976
1000
  link_back.unshift(a.source_reflection.name)
977
1001
  [a.table_name, a.foreign_key, a.source_reflection.macro]
978
1002
  end
@@ -980,7 +1004,9 @@ module ActiveRecord
980
1004
 
981
1005
  # puts "LINK BACK! #{k} : #{hm.table_name} #{link_back.map(&:to_s).join('.')}"
982
1006
  # count_column is determined from the originating HMT member
983
- if (src_ref = hm.source_reflection).nil?
1007
+ if hmt_assoc.macro == :has_and_belongs_to_many
1008
+ "br_t#{idx}.#{src_ref.active_record.primary_key}"
1009
+ elsif (src_ref = hm.source_reflection).nil?
984
1010
  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
1011
  puts
986
1012
  nix << k
@@ -1743,7 +1769,7 @@ class Object
1743
1769
  end
1744
1770
  full_name = if relation || schema_name.blank?
1745
1771
  if singular_table_name != table_name.singularize && # %%% Try this with http://localhost:3000/brick/spree/property_translations
1746
- (schema_module = ::Brick.config.table_name_prefixes.find { |k, v| table_name.start_with?(k) }&.last&.constantize)
1772
+ (schema_module = ::Brick.config.table_name_prefixes.find { |k, v| table_name.length > k&.length && table_name.start_with?(k) }&.last&.constantize)
1747
1773
  "#{schema_module&.name}::#{inheritable_name || model_name}"
1748
1774
  else
1749
1775
  inheritable_name || model_name
@@ -1762,7 +1788,7 @@ class Object
1762
1788
  ::Brick.config.exclude_tables.include?(matching)
1763
1789
 
1764
1790
  # Are they trying to use a pluralised class name such as "Employees" instead of "Employee"?
1765
- if table_name == singular_table_name && !ActiveSupport::Inflector.inflections.uncountable.include?(table_name)
1791
+ if table_name == singular_table_name && model_name.pluralize == model_name && !ActiveSupport::Inflector.inflections.uncountable.include?(table_name)
1766
1792
  # unless ::Brick.config.sti_namespace_prefixes&.key?("::#{singular_table_name.camelize}::")
1767
1793
  # puts "Warning: Class name for a model that references table \"#{matching
1768
1794
  # }\" should be \"#{ActiveSupport::Inflector.singularize(inheritable_name || model_name)}\"."
@@ -2162,7 +2188,9 @@ class Object
2162
2188
  # (More information on https://docs.avohq.io/3.0/controllers.html)
2163
2189
  controller_base = Avo::ResourcesController
2164
2190
  end
2165
- if !model&.table_exists? && (tn = model&.table_name)
2191
+ if model && !(model < ActiveRecord::Base)
2192
+ 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."
2193
+ elsif !model&.table_exists? && (tn = model&.table_name)
2166
2194
  msg = +"Can't find table \"#{tn}\" for model #{model.name}."
2167
2195
  puts
2168
2196
  # Potential bad inflection?
@@ -2460,10 +2488,19 @@ class Object
2460
2488
 
2461
2489
  real_model = model.find_real_model(params)
2462
2490
 
2491
+ # %%% Allow params to define which columns to use for order_by
2492
+ # Overriding the default by providing a querystring param?
2493
+ order_by = params['_brick_order']&.split(',')&.map(&:to_sym) || Object.send(:default_ordering, table_name, pk)
2494
+
2463
2495
  if request.format == :csv # Asking for a template?
2464
2496
  require 'csv'
2465
2497
  exported_csv = CSV.generate(force_quotes: false) do |csv_out|
2466
- real_model.df_export(true, real_model.brick_import_template, false, wheres, order_by).each do |row|
2498
+ export_args = [true, real_model.brick_import_template, false]
2499
+ if real_model.method(:df_export).parameters.length > 3
2500
+ wheres, _is_distinct = real_model._brick_set_wheres(params)
2501
+ export_args += [wheres, order_by]
2502
+ end
2503
+ real_model.df_export(*export_args).each do |row|
2467
2504
  row.each do |d|
2468
2505
  row.each_with_index do |d, idx|
2469
2506
  # "false" disallows HTML encoding the descriptions of binary content
@@ -2484,11 +2521,6 @@ class Object
2484
2521
  end
2485
2522
 
2486
2523
  # Normal (not swagger or CSV) request
2487
-
2488
- # %%% Allow params to define which columns to use for order_by
2489
- # Overriding the default by providing a querystring param?
2490
- order_by = params['_brick_order']&.split(',')&.map(&:to_sym) || Object.send(:default_ordering, table_name, pk)
2491
-
2492
2524
  ar_relation = ActiveRecord.version < Gem::Version.new('4') ? real_model.preload : real_model.all
2493
2525
  params['_brick_is_api'] = true if (is_api = request.format == :js || current_api_root)
2494
2526
  @_brick_params = ar_relation._brick_querying((selects ||= []), params: params, order_by: order_by,
@@ -2681,7 +2713,14 @@ class Object
2681
2713
  render json: { result: es_result }
2682
2714
  else
2683
2715
  real_model, real_singular_table_name = model.real_singular(params)
2684
- created_obj = model.send(:new, send(params_name_sym))
2716
+ # If there are any foreign keys that are strings and we've received a blank value, set that to nil
2717
+ internal_params = (ac_params = send(params_name_sym))&.send(:parameters)
2718
+ real_model.reflect_on_all_associations.each do |a|
2719
+ if a.belongs_to? && internal_params.key?(fk_name = a.foreign_key.to_s) && [:string, :text].include?((fk_col = real_model.columns_hash[fk_name])&.type)
2720
+ internal_params[fk_name] = nil if internal_params[fk_name].blank?
2721
+ end
2722
+ end
2723
+ created_obj = model.send(:new, ac_params)
2685
2724
  if created_obj.respond_to?(inh_col = model.inheritance_column) && created_obj.send(inh_col) == ''
2686
2725
  created_obj.send("#{inh_col}=", model.name)
2687
2726
  end
@@ -2706,7 +2745,6 @@ class Object
2706
2745
  # ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?;", schema)
2707
2746
  # end
2708
2747
 
2709
- is_need_params = true
2710
2748
  code << " def edit\n"
2711
2749
  code << " #{find_by_name}\n"
2712
2750
  code << " end\n"
@@ -2799,7 +2837,7 @@ class Object
2799
2837
  end
2800
2838
  end
2801
2839
 
2802
- code << "private\n" if pk.present? || is_need_params
2840
+ code << "private\n"
2803
2841
 
2804
2842
  if pk.present?
2805
2843
  code << " def #{find_obj}
@@ -2829,29 +2867,23 @@ class Object
2829
2867
  private find_obj
2830
2868
  end
2831
2869
 
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
2870
+ code << " def #{params_name}\n"
2871
+ require_txt = model.base_class.name.underscore.tr('/', '_')
2872
+ is_for_expects = ::ActiveSupport.version >= ::Gem::Version.new('8.0a')
2873
+ permits = model._brick_find_permits(model, model._brick_all_fields(true), is_for_expects)
2874
+ if is_for_expects
2875
+ code << " params.expect(#{require_txt}: #{permits})\n"
2876
+ self.define_method(params_name) do
2877
+ params.expect({ model.base_class.name.underscore.tr('/', '_').to_sym => permits })
2878
+ end
2879
+ else
2880
+ code << " params.require(:#{require_txt}).permit(#{permits.map(&:inspect).join(', ')})\n"
2881
+ self.define_method(params_name) do
2882
+ params.require(model.base_class.name.underscore.tr('/', '_').to_sym).permit(permits)
2851
2883
  end
2852
- private params_name
2853
- # Get column names for params from relations[model.table_name][:cols].keys
2854
2884
  end
2885
+ code << " end\n"
2886
+ private params_name
2855
2887
  end # unless is_openapi
2856
2888
  code << "end # #{class_name}\n"
2857
2889
  end # class definition
@@ -3210,7 +3242,6 @@ module Brick
3210
3242
  end
3211
3243
  end
3212
3244
  abstract_activerecord_bases = ::Brick.eager_load_classes(true)
3213
- rails_root = ::Rails.root.to_s
3214
3245
  models = ::Brick.relations.each_with_object({}) do |rel, s|
3215
3246
  next if rel.first.is_a?(Symbol)
3216
3247
 
@@ -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
@@ -942,7 +950,10 @@ if (window.brickFontFamily) {
942
950
  when 'index'
943
951
  if Object.const_defined?('DutyFree')
944
952
  template_link = "
945
- <%= link_to 'CSV', #{@_brick_model._brick_index}_path(format: :csv) %> &nbsp; <a href=\"#\" id=\"sheetsLink\">Sheets</a>
953
+ <%= csv_path = #{@_brick_model._brick_index}_path(format: :csv)
954
+ where_params = params.to_unsafe_h.each_with_object([]) { |v, s| s << \"#\{v.first}=#\{v[1]}\" if v.first.start_with?('__') }.join('&')
955
+ csv_path << \"?#\{where_params}\" unless where_params.blank?
956
+ link_to 'CSV', csv_path %> &nbsp; <a href=\"#\" id=\"sheetsLink\">Sheets</a>
946
957
  <div id=\"dropper\" contenteditable=\"true\"></div>
947
958
  <input type=\"button\" id=\"btnImport\" value=\"Import\">
948
959
 
@@ -973,19 +984,6 @@ if (window.brickFontFamily) {
973
984
  }
974
985
  var sheetUrl;
975
986
  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
987
 
990
988
  async function updateSignInStatus(token) {
991
989
  await new Promise(function (resolve) {
@@ -1044,7 +1042,24 @@ if (window.brickFontFamily) {
1044
1042
  }
1045
1043
  </script>
1046
1044
  <script src=\"https://apis.google.com/js/api.js\"></script>
1047
- <script async defer src=\"https://accounts.google.com/gsi/client\" onload=\"gapiLoaded()\"></script>
1045
+ <script async defer src=\"https://accounts.google.com/gsi/client\" id=\"gapiScript\"<%= @_request.env['_brick_nonce'] %>></script>
1046
+ <script<%= @_request.env['_brick_nonce'] %>>
1047
+ document.getElementById(\"gapiScript\").addEventListener(\"onload\",
1048
+ function () { // gapiLoaded
1049
+ var sheetsLink = document.getElementById(\"sheetsLink\");
1050
+ // Have a click on the sheets link to bring up the sign-in window. (Must happen from some kind of user click.)
1051
+ sheetsLink.addEventListener(\"click\", async function (evt) {
1052
+ evt.preventDefault();
1053
+ var client = google.accounts.oauth2.initTokenClient({
1054
+ client_id: \"487319557829-fgj4u660igrpptdji7ev0r5hb6kh05dh.apps.googleusercontent.com\",
1055
+ scope: \"https://www.googleapis.com/auth/spreadsheets https://www.googleapis.com/auth/drive.file\",
1056
+ callback: updateSignInStatus
1057
+ });
1058
+ client.requestAccessToken();
1059
+ });
1060
+ }
1061
+ );
1062
+ </script>
1048
1063
  "
1049
1064
  end # DutyFree data export and import
1050
1065
  # %%% 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 +1334,7 @@ end
1319
1334
  kls = Object.const_get((rel = ::Brick.relations.fetch(r[0], nil))&.fetch(:class_name, nil))
1320
1335
  rescue
1321
1336
  end
1322
- if kls.is_a?(Class) && (path_helper = respond_to?(bi_path = \"#\{kls._brick_index}_path\".to_sym) ? bi_path : nil)
1337
+ 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
1338
  link_to(r[0], send(path_helper))
1324
1339
  else
1325
1340
  r[0]
@@ -1454,21 +1469,11 @@ if (description = rel&.fetch(:description, nil)) %>
1454
1469
  end
1455
1470
  %><%= link_to \"(See all #\{model_name.pluralize})\", see_all_path, { class: '__brick' } %>
1456
1471
  #{::Brick::Rails.erd_markup(@_brick_model, prefix) if @_brick_model}
1457
- <% if obj
1458
- # path_options = [obj.#{pk}]
1459
- # path_options << { '_brick_schema': } if
1460
- options = {}
1461
- options[:url] = if obj.new_record?
1462
- link_to_brick(obj.class, path_only: true) # Properly supports STI, but only works for :new
1463
- else
1464
- path_helper = obj.new_record? ? #{model_name}._brick_index : #{model_name}._brick_index(:singular)
1465
- options[:url] = send(\"#\{path_helper}_path\".to_sym, obj) if ::Brick.config.path_prefix || (path_helper != obj.class.table_name)
1466
- end
1467
- %>
1472
+ <% if obj %>
1468
1473
  <br><br>
1469
1474
 
1470
1475
  <%= # Write out the mega-form
1471
- brick_form_for(obj, options, #{model_name}, bts, #{pk.inspect}) %>
1476
+ brick_form_with(model: obj, bts: bts, pk: #{pk.inspect}) %>
1472
1477
 
1473
1478
  #{unless args.first == 'new'
1474
1479
  # Was: confirm_are_you_sure = ActionView.version < ::Gem::Version.new('7.0') ? "data: { confirm: \"Delete #\{model_name} -- Are you sure?\" }" : "form: { data: { turbo_confirm: \"Delete #\{model_name} -- Are you sure?\" } }"
@@ -1603,7 +1608,7 @@ flatpickr(\".timepicker\", {enableTime: true, noCalendar: true});
1603
1608
  # Uncaught TypeError: Failed to resolve module specifier \"immutable-json-patch\". Relative references must start with either \"/\", \"./\", or \"../\".
1604
1609
  if @_json_fields_present %>
1605
1610
  <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\">
1611
+ <script type=\"module\"<%= @_request.env['_brick_nonce'] %>>
1607
1612
  import { JSONEditor } from \"https://cdn.jsdelivr.net/npm/vanilla-jsoneditor@0.19.0/index.min.js\";
1608
1613
  document.querySelectorAll(\"input.jsonpicker\").forEach(function (inp) {
1609
1614
  var jsonDiv;