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 +4 -4
- data/lib/brick/extensions.rb +128 -97
- data/lib/brick/rails/engine.rb +43 -38
- data/lib/brick/rails/form_builder.rb +18 -16
- data/lib/brick/rails/form_tags.rb +78 -36
- data/lib/brick/rails.rb +2 -2
- data/lib/brick/reflect_tables.rb +12 -7
- data/lib/brick/version_number.rb +1 -1
- data/lib/brick.rb +1 -1
- data/lib/generators/brick/airtable_api_caller.rb +21 -7
- data/lib/generators/brick/airtable_migrations_generator.rb +3 -2
- data/lib/generators/brick/airtable_seeds_generator.rb +1 -1
- data/lib/generators/brick/install_generator.rb +1 -1
- data/lib/generators/brick/migrations_builder.rb +25 -12
- data/lib/generators/brick/seeds_builder.rb +75 -33
- metadata +3 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: baf36636026c4ecad5ff76ccdeea80604c6c9128f7065d7b546ab41cc4fccf1c
|
|
4
|
+
data.tar.gz: ec8c081354229eb5b4cdcd3112b24313468aaf731f856168cace28d3882e1f51
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3742d4d628c4f0ff18008f5dd965b3e49c545d24141cea917a0ba141cff7f3243b6f159e732f2e786b87f274e80237bcea2af74f17b9494345cf898e54e72928
|
|
7
|
+
data.tar.gz: 3aa5e54bc3b4af270e5b2342430e294920b151139af66ec5771b15a575377f7275f9c10e593d7c1d314e67d145fd9ffb0ae834b4f68a5d4c1afff8b22ecc404c
|
data/lib/brick/extensions.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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 || [])
|
|
223
|
-
|
|
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 =
|
|
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
|
-
|
|
697
|
-
|
|
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
|
-
|
|
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
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
"br_t#{idx
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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"
|
|
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
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
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
|
|
data/lib/brick/rails/engine.rb
CHANGED
|
@@ -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 = +"
|
|
830
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
<%=
|
|
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 %> <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\"
|
|
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
|
-
|
|
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;
|