brick 1.0.125 → 1.0.126

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dc3caefac264427d0a4a7efa11160fc3b7c1cc6c82afa01bf7340941dbab18e1
4
- data.tar.gz: 7d9aebd13fe256d43ee3ec24f94065a64ab4955e3ee379023352013db59fe3ba
3
+ metadata.gz: b59690045d31108dece3222f655503a771ab860697993459a6beb92aa0ecdb07
4
+ data.tar.gz: 4c83d54ddc01e03330ac82f5949cece3f21631624adb704b923bd338ac84481c
5
5
  SHA512:
6
- metadata.gz: c02e8bdceca8f726d24a8f7eddb6057427d2e31c3db3934bd72da0393509aaf2d5e7246ba65e8bdd773fc3d995c538567137dec729eb2d062ba0f7e006ace7c6
7
- data.tar.gz: c6061b00b761638ebd0f9750423643615c08dffb9c62bec423d1bf5c047dcfa82c9b2bccaea1491d9f7a4c23932b9299be1254e708e26ca624165a5695557c63
6
+ metadata.gz: c12e4073de8230683825617fccabeeda71746dcf3c892a60557990777543c15a3e9a18a8c7bdcf3b1c236bf422ec74d10b98bf1341b5d472e611799c8e74de31
7
+ data.tar.gz: 6e9fdb9e41ff12a1b5092226133cb7c0d8444bf330e2557da156bc7f21d4377678061bd65c5b36ad3b9d1f9bf88203594c34cf8bcaabad7b93c2609b2798e474
@@ -19,19 +19,17 @@ unless ActiveSupport.respond_to?(:version)
19
19
  end
20
20
  end
21
21
  end
22
- if Object.const_defined?('ActionPack')
23
- unless ActionPack.respond_to?(:version)
24
- module ActionPack
25
- def self.version
26
- ::Gem::Version.new(ActionPack::VERSION::STRING)
27
- end
22
+ if Object.const_defined?('ActionPack') && !ActionPack.respond_to?(:version)
23
+ module ActionPack
24
+ def self.version
25
+ ::Gem::Version.new(ActionPack::VERSION::STRING)
28
26
  end
29
27
  end
30
- if Object.const_defined?('ActionView') && !ActionView.respond_to?(:version)
31
- module ActionView
32
- def self.version
33
- ActionPack.version
34
- end
28
+ end
29
+ if Object.const_defined?('ActionView') && !ActionView.respond_to?(:version)
30
+ module ActionView
31
+ def self.version
32
+ ActionPack.version
35
33
  end
36
34
  end
37
35
  end
@@ -231,6 +231,9 @@ module ActiveRecord
231
231
  if this_obj.is_a?(ActiveRecord::Base) && (obj_descrip = this_obj.class.brick_descrip(this_obj))
232
232
  this_obj = obj_descrip
233
233
  end
234
+ if this_obj.is_a?(ActiveStorage::Filename) && this_obj.instance_variable_get(:@filename).nil?
235
+ this_obj.instance_variable_set(:@filename, '')
236
+ end
234
237
  this_obj&.to_s || ''
235
238
  end
236
239
  is_brackets_have_content = true unless datum.blank?
@@ -914,8 +917,23 @@ end
914
917
 
915
918
  if Object.const_defined?('ActionView')
916
919
  require 'brick/frameworks/rails/form_tags'
917
- module ActionView::Helpers::FormTagHelper
918
- include ::Brick::Rails::FormTags
920
+ require 'brick/frameworks/rails/form_builder'
921
+ module ActionView::Helpers
922
+ module FormTagHelper
923
+ include ::Brick::Rails::FormTags
924
+ end
925
+ FormBuilder.class_exec do
926
+ include ::Brick::Rails::FormBuilder
927
+ end
928
+ end
929
+
930
+ # FormBuilder#field_id isn't available in Rails < 7.0. This is a rudimentary version with no `index`.
931
+ unless ActionView::Helpers::FormBuilder.methods.include?(:field_id)
932
+ ActionView::Helpers::FormBuilder.class_exec do
933
+ def field_id(method)
934
+ [object_name, method.to_s].join('_')
935
+ end
936
+ end
919
937
  end
920
938
  end
921
939
 
@@ -1817,7 +1835,13 @@ class Object
1817
1835
  code << " end\n"
1818
1836
  self.define_method :new do
1819
1837
  _schema, @_is_show_schema_list = ::Brick.set_db_schema(params)
1820
- instance_variable_set("@#{singular_table_name}".to_sym, model.new)
1838
+ if (new_obj = model.new).respond_to?(:serializable_hash)
1839
+ # Convert any Filename objects with nil into an empty string so that #encode can be called on them
1840
+ new_obj.serializable_hash.each do |k, v|
1841
+ new_obj.send("#{k}=", ActiveStorage::Filename.new('')) if v.is_a?(ActiveStorage::Filename) && !v.instance_variable_get(:@filename)
1842
+ end
1843
+ end
1844
+ instance_variable_set("@#{singular_table_name}".to_sym, new_obj)
1821
1845
  end
1822
1846
 
1823
1847
  params_name_sym = (params_name = "#{singular_table_name}_params").to_sym
@@ -2113,7 +2137,6 @@ end.class_exec do
2113
2137
  load apartment_initializer
2114
2138
  @_apartment_loaded = true
2115
2139
  end
2116
- apartment_excluded = Apartment.excluded_models
2117
2140
  end
2118
2141
  # Only for Postgres (Doesn't work in sqlite3 or MySQL)
2119
2142
  # puts ActiveRecord::Base.execute_sql("SELECT current_setting('SEARCH_PATH')").to_a.inspect
@@ -2203,7 +2226,7 @@ end.class_exec do
2203
2226
  # If Apartment gem lists the table as being associated with a non-tenanted model then use whatever it thinks
2204
2227
  # is the default schema, usually 'public'.
2205
2228
  schema_name = if ::Brick.config.schema_behavior[:multitenant]
2206
- ::Brick.apartment_default_tenant if apartment_excluded&.include?(r['relation_name'].singularize.camelize)
2229
+ ::Brick.apartment_default_tenant if ::Brick.is_apartment_excluded_table(r['relation_name'])
2207
2230
  elsif ![schema, 'public'].include?(r['schema'])
2208
2231
  r['schema']
2209
2232
  end
@@ -2353,7 +2376,7 @@ ORDER BY 1, 2, c.internal_column_id, acc.position"
2353
2376
  fk_references&.each do |fk|
2354
2377
  fk = fk.values unless fk.is_a?(Array)
2355
2378
  # Multitenancy makes things a little more general overall, except for non-tenanted tables
2356
- if apartment_excluded&.include?(::Brick.namify(fk[1]).singularize.camelize)
2379
+ if ::Brick.is_apartment_excluded_table(::Brick.namify(fk[1]))
2357
2380
  fk[0] = ::Brick.apartment_default_tenant
2358
2381
  elsif (is_postgres && (fk[0] == 'public' || (multitenancy && fk[0] == schema))) ||
2359
2382
  (::Brick.is_oracle && fk[0] == schema) ||
@@ -2361,7 +2384,7 @@ ORDER BY 1, 2, c.internal_column_id, acc.position"
2361
2384
  (!is_postgres && !::Brick.is_oracle && !is_mssql && ['mysql', 'performance_schema', 'sys'].exclude?(fk[0]))
2362
2385
  fk[0] = nil
2363
2386
  end
2364
- if apartment_excluded&.include?(fk[4].singularize.camelize)
2387
+ if ::Brick.is_apartment_excluded_table(fk[4])
2365
2388
  fk[3] = ::Brick.apartment_default_tenant
2366
2389
  elsif (is_postgres && (fk[3] == 'public' || (multitenancy && fk[3] == schema))) ||
2367
2390
  (::Brick.is_oracle && fk[3] == schema) ||
@@ -2537,8 +2560,7 @@ module Brick
2537
2560
  # %%% Temporary schema patch
2538
2561
  for_tbl = fk[1]
2539
2562
  fk_namified = ::Brick.namify(fk[1])
2540
- apartment = Object.const_defined?('Apartment') && Apartment
2541
- fk[0] = ::Brick.apartment_default_tenant if apartment && apartment.excluded_models.include?(fk_namified.singularize.camelize)
2563
+ fk[0] = ::Brick.apartment_default_tenant if ::Brick.is_apartment_excluded_table(fk_namified)
2542
2564
  fk[1] = "#{fk[0]}.#{fk[1]}" if fk[0] # && fk[0] != ::Brick.default_schema
2543
2565
  bts = (relation = relations.fetch(fk[1], nil))&.fetch(:fks) { relation[:fks] = {} }
2544
2566
 
@@ -2553,7 +2575,7 @@ module Brick
2553
2575
  is_schema = if ::Brick.config.schema_behavior[:multitenant]
2554
2576
  # If Apartment gem lists the primary table as being associated with a non-tenanted model
2555
2577
  # then use 'public' schema for the primary table
2556
- if apartment && apartment&.excluded_models.include?(fk[4].singularize.camelize)
2578
+ if ::Brick.is_apartment_excluded_table(fk[4])
2557
2579
  fk[3] = ::Brick.apartment_default_tenant
2558
2580
  true
2559
2581
  end
@@ -2630,7 +2652,7 @@ module Brick
2630
2652
  end
2631
2653
  assoc_hm[:alternate_name] = "#{assoc_hm[:alternate_name]}_#{bt_assoc_name}" unless assoc_hm[:alternate_name] == bt_assoc_name
2632
2654
  else
2633
- inv_tbl = if ::Brick.config.schema_behavior[:multitenant] && apartment && fk[0] == ::Brick.apartment_default_tenant
2655
+ inv_tbl = if ::Brick.config.schema_behavior[:multitenant] && Object.const_defined?('Apartment') && fk[0] == ::Brick.apartment_default_tenant
2634
2656
  for_tbl
2635
2657
  else
2636
2658
  fk[1]
@@ -2800,5 +2822,13 @@ module Brick
2800
2822
  def _class_pk(dotted_name, multitenant)
2801
2823
  Object.const_get((multitenant ? [dotted_name.split('.').last] : dotted_name.split('.')).map { |nm| "::#{nm.singularize.camelize}" }.join).primary_key
2802
2824
  end
2825
+
2826
+ def is_apartment_excluded_table(tbl)
2827
+ if Object.const_defined?('Apartment')
2828
+ tbl_klass = (tnp = ::Brick.config.table_name_prefixes&.find { |k, _v| tbl.start_with?(k) }) ? +"#{tnp.last}::" : +''
2829
+ tbl_klass << tbl[tnp&.first&.length || 0..-1].singularize.camelize
2830
+ Apartment.excluded_models&.include?(tbl_klass)
2831
+ end
2832
+ end
2803
2833
  end
2804
2834
  end
@@ -2,36 +2,103 @@
2
2
 
3
3
  module Brick
4
4
  module Rails
5
- def self.display_binary(val)
6
- @image_signatures ||= { (+"\xFF\xD8\xFF\xEE").force_encoding('ASCII-8BIT') => 'jpeg',
7
- (+"\xFF\xD8\xFF\xE0\x00\x10\x4A\x46\x49\x46\x00\x01").force_encoding('ASCII-8BIT') => 'jpeg',
8
- (+"\x89PNG\r\n\x1A\n").force_encoding('ASCII-8BIT') => 'png',
9
- '<svg' => 'svg+xml', # %%% Not yet very good detection for SVG
10
- (+'BM').force_encoding('ASCII-8BIT') => 'bmp',
11
- (+'GIF87a').force_encoding('ASCII-8BIT') => 'gif',
12
- (+'GIF89a').force_encoding('ASCII-8BIT') => 'gif' }
13
-
14
- if val[0..1] == "\x15\x1C" # One of those goofy Microsoft OLE containers?
15
- package_header_length = val[2..3].bytes.reverse.inject(0) {|m, b| (m << 8) + b }
16
- # This will often be just FF FF FF FF
17
- # object_size = val[16..19].bytes.reverse.inject(0) {|m, b| (m << 8) + b }
18
- friendly_and_class_names = val[20...package_header_length].split("\0")
19
- object_type_name_length = val[package_header_length + 8..package_header_length+11].bytes.reverse.inject(0) {|m, b| (m << 8) + b }
20
- friendly_and_class_names << val[package_header_length + 12...package_header_length + 12 + object_type_name_length].strip
21
- # friendly_and_class_names will now be something like: ['Bitmap Image', 'Paint.Picture', 'PBrush']
22
- real_object_size = val[package_header_length + 20 + object_type_name_length..package_header_length + 23 + object_type_name_length].bytes.reverse.inject(0) {|m, b| (m << 8) + b }
23
- object_start = package_header_length + 24 + object_type_name_length
24
- val = val[object_start...object_start + real_object_size]
5
+ class << self
6
+ def display_value(col_type, val)
7
+ is_mssql_geography = nil
8
+ # Some binary thing that really looks like a Microsoft-encoded WGS84 point? (With the first two bytes, E6 10, indicating an EPSG code of 4326)
9
+ if col_type == :binary && val && val.length < 31 && (val.length - 6) % 8 == 0 && val[0..5].bytes == [230, 16, 0, 0, 1, 12]
10
+ col_type = 'geography'
11
+ is_mssql_geography = true
12
+ end
13
+ case col_type
14
+ when 'geometry', 'geography'
15
+ if Object.const_defined?('RGeo')
16
+ @is_mysql = ['Mysql2', 'Trilogy'].include?(ActiveRecord::Base.connection.adapter_name) if @is_mysql.nil?
17
+ @is_mssql = ActiveRecord::Base.connection.adapter_name == 'SQLServer' if @is_mssql.nil?
18
+ val_err = nil
19
+
20
+ if @is_mysql || (is_mssql_geography ||=
21
+ (@is_mssql ||
22
+ (val && val.length < 31 && (val.length - 6) % 8 == 0 && val[0..5].bytes == [230, 16, 0, 0, 1, 12])
23
+ )
24
+ )
25
+ # MySQL's \"Internal Geometry Format\" and MSSQL's Geography are like WKB, but with an initial 4 bytes that indicates the SRID.
26
+ if (srid = val&.[](0..3)&.unpack('I'))
27
+ val = val.dup.force_encoding('BINARY')[4..-1].bytes
28
+
29
+ # MSSQL spatial bitwise flags, often 0C for a point:
30
+ # xxxx xxx1 = HasZValues
31
+ # xxxx xx1x = HasMValues
32
+ # xxxx x1xx = IsValid
33
+ # xxxx 1xxx = IsSinglePoint
34
+ # xxx1 xxxx = IsSingleLineSegment
35
+ # xx1x xxxx = IsWholeGlobe
36
+ # Convert Microsoft's unique geography binary to standard WKB
37
+ # (MSSQL point usually has two doubles, lng / lat, and can also have Z)
38
+ if is_mssql_geography
39
+ if val[0] == 1 && (val[1] & 8 > 0) && # Single point?
40
+ (val.length - 2) % 8 == 0 && val.length < 27 # And containing up to three 8-byte values?
41
+ val = [0, 0, 0, 0, 1] + val[2..-1].reverse
42
+ else
43
+ val_err = '(Microsoft internal SQL geography type)'
44
+ end
45
+ end
46
+ end
47
+ end
48
+ unless val_err || val.nil?
49
+ if (geometry = RGeo::WKRep::WKBParser.new.parse(val.pack('c*'))).is_a?(RGeo::Cartesian::PointImpl) &&
50
+ !(geometry.y == 0.0 && geometry.x == 0.0)
51
+ # Create a POINT link to this style of Google maps URL: https://www.google.com/maps/place/38.7071296+-121.2810649/@38.7071296,-121.2810649,12z
52
+ geometry = "<a href=\"https://www.google.com/maps/place/#{geometry.y}+#{geometry.x}/@#{geometry.y},#{geometry.x},12z\" target=\"blank\">#{geometry.to_s}</a>"
53
+ end
54
+ val = geometry
55
+ end
56
+ val_err || val
57
+ else
58
+ '(Add RGeo gem to parse geometry detail)'
59
+ end
60
+ when :binary
61
+ ::Brick::Rails.display_binary(val) if val
62
+ else
63
+ if col_type
64
+ ::Brick::Rails::FormBuilder.hide_bcrypt(val, col_type == :xml)
65
+ else
66
+ '?'
67
+ end
68
+ end
25
69
  end
26
70
 
27
- if (signature = @image_signatures.find { |k, _v| val[0...k.length] == k })
28
- if val.length < 500_000
29
- "<img src=\"data:image/#{signature.last};base64,#{Base64.encode64(val)}\">"
71
+ def display_binary(val)
72
+ @image_signatures ||= { (+"\xFF\xD8\xFF\xEE").force_encoding('ASCII-8BIT') => 'jpeg',
73
+ (+"\xFF\xD8\xFF\xE0\x00\x10\x4A\x46\x49\x46\x00\x01").force_encoding('ASCII-8BIT') => 'jpeg',
74
+ (+"\x89PNG\r\n\x1A\n").force_encoding('ASCII-8BIT') => 'png',
75
+ '<svg' => 'svg+xml', # %%% Not yet very good detection for SVG
76
+ (+'BM').force_encoding('ASCII-8BIT') => 'bmp',
77
+ (+'GIF87a').force_encoding('ASCII-8BIT') => 'gif',
78
+ (+'GIF89a').force_encoding('ASCII-8BIT') => 'gif' }
79
+
80
+ if val[0..1] == "\x15\x1C" # One of those goofy Microsoft OLE containers?
81
+ package_header_length = val[2..3].bytes.reverse.inject(0) {|m, b| (m << 8) + b }
82
+ # This will often be just FF FF FF FF
83
+ # object_size = val[16..19].bytes.reverse.inject(0) {|m, b| (m << 8) + b }
84
+ friendly_and_class_names = val[20...package_header_length].split("\0")
85
+ object_type_name_length = val[package_header_length + 8..package_header_length+11].bytes.reverse.inject(0) {|m, b| (m << 8) + b }
86
+ friendly_and_class_names << val[package_header_length + 12...package_header_length + 12 + object_type_name_length].strip
87
+ # friendly_and_class_names will now be something like: ['Bitmap Image', 'Paint.Picture', 'PBrush']
88
+ real_object_size = val[package_header_length + 20 + object_type_name_length..package_header_length + 23 + object_type_name_length].bytes.reverse.inject(0) {|m, b| (m << 8) + b }
89
+ object_start = package_header_length + 24 + object_type_name_length
90
+ val = val[object_start...object_start + real_object_size]
91
+ end
92
+
93
+ if (signature = @image_signatures.find { |k, _v| val[0...k.length] == k })
94
+ if val.length < 500_000
95
+ "<img src=\"data:image/#{signature.last};base64,#{Base64.encode64(val)}\">"
96
+ else
97
+ "&lt;&nbsp;#{signature.last} image, #{val.length} bytes&nbsp;>"
98
+ end
30
99
  else
31
- "&lt;&nbsp;#{signature.last} image, #{val.length} bytes&nbsp;>"
100
+ "&lt;&nbsp;Binary, #{val.length} bytes&nbsp;>"
32
101
  end
33
- else
34
- "&lt;&nbsp;Binary, #{val.length} bytes&nbsp;>"
35
102
  end
36
103
  end
37
104
 
@@ -878,138 +945,7 @@ input+svg.revert {
878
945
  window.addEventListener(\"popstate\", function () { location.reload(true); });
879
946
  </script>
880
947
 
881
- <% is_includes_dates = nil
882
- is_includes_json = nil
883
- is_includes_text = nil
884
- def is_bcrypt?(val)
885
- val.is_a?(String) && val.length == 60 && val.start_with?('$2a$')
886
- end
887
- def hide_bcrypt(val, is_xml = nil, max_len = 200)
888
- if is_bcrypt?(val)
889
- '(hidden)'
890
- else
891
- if val.is_a?(String)
892
- val = val.dup.force_encoding('UTF-8').strip
893
- return CGI.escapeHTML(val) if is_xml
894
-
895
- if val.length > max_len
896
- if val[0] == '<' # Seems to be HTML?
897
- cur_len = 0
898
- cur_idx = 0
899
- # Find which HTML tags we might be inside so we can apply ending tags to balance
900
- element_name = nil
901
- in_closing = nil
902
- elements = []
903
- val.each_char do |ch|
904
- case ch
905
- when '<'
906
- element_name = +''
907
- when '/' # First character of tag is '/'?
908
- in_closing = true if element_name == ''
909
- when '>'
910
- if element_name
911
- if in_closing
912
- if (idx = elements.index { |tag| tag.downcase == element_name.downcase })
913
- elements.delete_at(idx)
914
- end
915
- elsif (tag_name = element_name.split.first).present?
916
- elements.unshift(tag_name)
917
- end
918
- element_name = nil
919
- in_closing = nil
920
- end
921
- else
922
- element_name << ch if element_name
923
- end
924
- cur_idx += 1
925
- # Unless it's inside wickets then this is real text content, and see if we're at the limit
926
- break if element_name.nil? && ((cur_len += 1) > max_len)
927
- end
928
- val = val[0..cur_idx]
929
- # Somehow still in the middle of an opening tag right at the end? (Should never happen)
930
- if !in_closing && (tag_name = element_name&.split&.first)&.present?
931
- elements.unshift(tag_name)
932
- val << '>'
933
- end
934
- elements.each do |closing_tag|
935
- val << \"</#\{closing_tag}>\"
936
- end
937
- else # Not HTML, just cut it at the length
938
- val = val[0...max_len]
939
- end
940
- val = \"#\{val}...\"
941
- end
942
- val
943
- else
944
- val.to_s
945
- end
946
- end
947
- end
948
- def display_value(col_type, val)
949
- is_mssql_geography = nil
950
- # Some binary thing that really looks like a Microsoft-encoded WGS84 point? (With the first two bytes, E6 10, indicating an EPSG code of 4326)
951
- if col_type == :binary && val && val.length < 31 && (val.length - 6) % 8 == 0 && val[0..5].bytes == [230, 16, 0, 0, 1, 12]
952
- col_type = 'geography'
953
- is_mssql_geography = true
954
- end
955
- case col_type
956
- when 'geometry', 'geography'
957
- if Object.const_defined?('RGeo')
958
- @is_mysql = ['Mysql2', 'Trilogy'].include?(ActiveRecord::Base.connection.adapter_name) if @is_mysql.nil?
959
- @is_mssql = ActiveRecord::Base.connection.adapter_name == 'SQLServer' if @is_mssql.nil?
960
- val_err = nil
961
-
962
- if @is_mysql || (is_mssql_geography ||=
963
- (@is_mssql ||
964
- (val && val.length < 31 && (val.length - 6) % 8 == 0 && val[0..5].bytes == [230, 16, 0, 0, 1, 12])
965
- )
966
- )
967
- # MySQL's \"Internal Geometry Format\" and MSSQL's Geography are like WKB, but with an initial 4 bytes that indicates the SRID.
968
- if (srid = val&.[](0..3)&.unpack('I'))
969
- val = val.dup.force_encoding('BINARY')[4..-1].bytes
970
-
971
- # MSSQL spatial bitwise flags, often 0C for a point:
972
- # xxxx xxx1 = HasZValues
973
- # xxxx xx1x = HasMValues
974
- # xxxx x1xx = IsValid
975
- # xxxx 1xxx = IsSinglePoint
976
- # xxx1 xxxx = IsSingleLineSegment
977
- # xx1x xxxx = IsWholeGlobe
978
- # Convert Microsoft's unique geography binary to standard WKB
979
- # (MSSQL point usually has two doubles, lng / lat, and can also have Z)
980
- if is_mssql_geography
981
- if val[0] == 1 && (val[1] & 8 > 0) && # Single point?
982
- (val.length - 2) % 8 == 0 && val.length < 27 # And containing up to three 8-byte values?
983
- val = [0, 0, 0, 0, 1] + val[2..-1].reverse
984
- else
985
- val_err = '(Microsoft internal SQL geography type)'
986
- end
987
- end
988
- end
989
- end
990
- unless val_err || val.nil?
991
- if (geometry = RGeo::WKRep::WKBParser.new.parse(val.pack('c*'))).is_a?(RGeo::Cartesian::PointImpl) &&
992
- !(geometry.y == 0.0 && geometry.x == 0.0)
993
- # Create a POINT link to this style of Google maps URL: https://www.google.com/maps/place/38.7071296+-121.2810649/@38.7071296,-121.2810649,12z
994
- geometry = \"<a href=\\\"https://www.google.com/maps/place/#\{geometry.y}+#\{geometry.x}/@#\{geometry.y},#\{geometry.x},12z\\\" target=\\\"blank\\\">#\{geometry.to_s}</a>\"
995
- end
996
- val = geometry
997
- end
998
- val_err || val
999
- else
1000
- '(Add RGeo gem to parse geometry detail)'
1001
- end
1002
- when :binary
1003
- ::Brick::Rails.display_binary(val) if val
1004
- else
1005
- if col_type
1006
- hide_bcrypt(val, col_type == :xml)
1007
- else
1008
- '?'
1009
- end
1010
- end
1011
- end
1012
-
948
+ <%
1013
949
  # Accommodate composite primary keys that include strings with forward-slash characters
1014
950
  def slashify(*vals)
1015
951
  vals.map { |val_part| val_part.is_a?(String) ? val_part.gsub('/', '^^sl^^') : val_part }
@@ -1569,10 +1505,10 @@ end
1569
1505
  <title><%=
1570
1506
  base_model = (model = (obj = @#{obj_name})&.class).base_class
1571
1507
  see_all_path = send(\"#\{base_model._brick_index}_path\")
1572
- if obj.respond_to?(:#{inh_col = @_brick_model.inheritance_column}) &&
1573
- (model_name = @#{obj_name}.#{inh_col}) != base_model.name
1508
+ #{(inh_col = @_brick_model.inheritance_column).present? &&
1509
+ " if obj.respond_to?(:#{inh_col}) && (model_name = @#{obj_name}.#{inh_col}) != base_model.name
1574
1510
  see_all_path << \"?#{inh_col}=#\{model_name}\"
1575
- end
1511
+ end"}
1576
1512
  page_title = (\"#\{model_name ||= model.name}: #\{obj&.brick_descrip || controller_name}\")
1577
1513
  %></title>
1578
1514
  </head>
@@ -1674,103 +1610,7 @@ end
1674
1610
  <% end %>
1675
1611
  </th>
1676
1612
  <td>
1677
- <table><tr><td>
1678
- <% dt_pickers = { datetime: 'datetimepicker', timestamp: 'datetimepicker', time: 'timepicker', date: 'datepicker' }
1679
- html_options = {}
1680
- html_options[:class] = 'dimmed' unless val
1681
- is_revert = true
1682
- if bt
1683
- html_options[:prompt] = \"Select #\{bt_name\}\" %>
1684
- <%= f.select k.to_sym, bt[3], { value: val || '^^^brick_NULL^^^' }, html_options %>
1685
- <%= if (bt_obj = bt_class&.find_by(bt_pair[1] => val))
1686
- link_to('⇛', send(\"#\{bt_class.base_class._brick_index(:singular)\}_path\".to_sym, bt_obj.send(bt_class.primary_key.to_sym)), { class: 'show-arrow' })
1687
- elsif val
1688
- \"<span class=\\\"orphan\\\">Orphaned ID: #\{val}</span>\".html_safe
1689
- end %>
1690
- <% elsif @_brick_monetized_attributes&.include?(k)
1691
- %><%= f.text_field(k.to_sym, html_options.merge({ value: Money.new(val.to_i).format })) %><%
1692
- else
1693
- col_type = if model.json_column?(col)
1694
- :json
1695
- elsif col&.sql_type == 'geography'
1696
- col.sql_type
1697
- else
1698
- col&.type
1699
- end
1700
- case (col_type ||= col&.sql_type)
1701
- when :string, :text
1702
- if is_bcrypt?(val) # || .readonly?
1703
- is_revert = false %>
1704
- <%= hide_bcrypt(val, nil, 1000) %>
1705
- <% elsif col_type == :string
1706
- if model.respond_to?(:enumerized_attributes) && (attr = model.enumerized_attributes[k])&.options.present?
1707
- enum_html_options = attr.kind_of?(Enumerize::Multiple) ? html_options.merge({ multiple: true, size: (opts = attr.options)&.length + 1 }) : html_options %>
1708
- <%= f.select(k.to_sym, [[\"(No #\{k} chosen)\", '^^^brick_NULL^^^']] + opts, { value: val || '^^^brick_NULL^^^' }, enum_html_options) %><%
1709
- else %>
1710
- <%= f.text_field(k.to_sym, html_options) %><%
1711
- end
1712
- else
1713
- is_includes_text = true %>
1714
- <%= f.hidden_field(k.to_sym, html_options) %>
1715
- <trix-editor input=\"<%= f.field_id(k) %>\"></trix-editor>
1716
- <% end %>
1717
- <% when :boolean %>
1718
- <%= f.check_box k.to_sym %>
1719
- <% when :integer, :decimal, :float %>
1720
- <%= digit_pattern = col_type == :integer ? '\\d*' : '\\d*(?:\\.\\d*|)'
1721
- # Used to do this for float / decimal: f.number_field k.to_sym
1722
- f.text_field k.to_sym, { pattern: digit_pattern, class: 'check-validity' } %>
1723
- <% when *dt_pickers.keys
1724
- is_includes_dates = true %>
1725
- <%= f.text_field k.to_sym, { class: dt_pickers[col_type] } %>
1726
- <% when :uuid
1727
- is_revert = false %>
1728
- <%=
1729
- # Postgres naturally uses the +uuid_generate_v4()+ function from the uuid-ossp extension
1730
- # If it's not yet enabled then: create extension \"uuid-ossp\";
1731
- # ActiveUUID gem created a new :uuid type
1732
- val %>
1733
- <% when :ltree %>
1734
- <%=
1735
- # In Postgres labels of data stored in a hierarchical tree-like structure
1736
- # If it's not yet enabled then: create extension ltree;
1737
- val %>
1738
- <% when :binary %>
1739
- <%= is_revert = false
1740
- if val
1741
- # %%% This same kind of geography check is done two other times above ... would be great to DRY it up.
1742
- if val.length < 31 && (val.length - 6) % 8 == 0 && val[0..5].bytes == [230, 16, 0, 0, 1, 12]
1743
- display_value('geography', val)
1744
- else
1745
- ::Brick::Rails.display_binary(val)
1746
- end.html_safe
1747
- end %>
1748
- <% when :primary_key
1749
- is_revert = false %>
1750
- <% when :json
1751
- is_includes_json = true
1752
- if val.is_a?(String)
1753
- val_str = val
1754
- else
1755
- eheij = ActiveSupport::JSON::Encoding.escape_html_entities_in_json
1756
- ActiveSupport::JSON::Encoding.escape_html_entities_in_json = false if eheij
1757
- val_str = val.to_json
1758
- ActiveSupport::JSON::Encoding.escape_html_entities_in_json = eheij
1759
- end %>
1760
- <%= # Because there are so danged many quotes in JSON, escape them specially by converting to backticks.
1761
- # (and previous to this, escape backticks with our own goofy code of ^^br_btick__ )
1762
- json_field = f.hidden_field k.to_sym, { class: 'jsonpicker', value: val_str.gsub('`', '^^br_btick__').tr('\"', '`').html_safe } %>
1763
- <div id=\"_br_json_<%= f.field_id(k) %>\"></div>
1764
- <% else %>
1765
- <%= is_revert = false
1766
- display_value(col_type, val).html_safe %>
1767
- <% end
1768
- end
1769
- if is_revert
1770
- %></td>
1771
- <td><svg class=\"revert\" width=\"1.5em\" viewBox=\"0 0 512 512\"><use xlink:href=\"#revertPath\" /></svg>
1772
- <% end %>
1773
- </td></tr></table>
1613
+ <%= f.brick_field(k, html_options = {}, val, col, bt, bt_class, bt_name, bt_pair) %>
1774
1614
  </td>
1775
1615
  </tr>
1776
1616
  <% end
@@ -1799,11 +1639,12 @@ end
1799
1639
  # with a .where(). And the polymorphic class name it points to is the base class name of
1800
1640
  # the STI model instead of its subclass.
1801
1641
  poly_type = #{poly_type.inspect}
1802
- if poly_type && @#{obj_name}.respond_to?(:#{@_brick_model.inheritance_column}) &&
1642
+ #{ (inh_col = @_brick_model.inheritance_column).present? &&
1643
+ " if poly_type && @#{obj_name}.respond_to?(:#{inh_col}) &&
1803
1644
  (base_type = collection.where_values_hash[poly_type])
1804
- collection = collection.rewhere(poly_type => [base_type, @#{obj_name}.#{@_brick_model.inheritance_column}])
1805
- end"
1806
- end
1645
+ collection = collection.rewhere(poly_type => [base_type, @#{obj_name}.#{inh_col}])
1646
+ end"}"
1647
+ end
1807
1648
  s << "<table id=\"#{hm_name}\" class=\"shadow\">
1808
1649
  <tr><th>#{hm[1]}#{' poly' if hm[0].options[:as]} #{hm[3]}</th></tr>
1809
1650
  <% collection = @#{obj_name}.#{hm_name}
@@ -1844,7 +1685,7 @@ end}
1844
1685
  end
1845
1686
  unless is_crosstab
1846
1687
  inline << "
1847
- <% if is_includes_dates %>
1688
+ <% if @_date_fields_present %>
1848
1689
  <link rel=\"stylesheet\" type=\"text/css\" href=\"https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css\">
1849
1690
  <style>
1850
1691
  .flatpickr-calendar {
@@ -1859,18 +1700,18 @@ flatpickr(\".timepicker\", {enableTime: true, noCalendar: true});
1859
1700
  </script>
1860
1701
  <% end %>
1861
1702
 
1862
- <% if false # is_includes_dropdowns %>
1703
+ <% if false # @_dropdown_fields_present %>
1863
1704
  <script src=\"https://cdnjs.cloudflare.com/ajax/libs/slim-select/1.27.1/slimselect.min.js\"></script>
1864
1705
  <link rel=\"stylesheet\" type=\"text/css\" href=\"https://cdnjs.cloudflare.com/ajax/libs/slim-select/1.27.1/slimselect.min.css\">
1865
1706
  <% end %>
1866
1707
 
1867
- <% if is_includes_text %>
1708
+ <% if @_text_fields_present %>
1868
1709
  <script src=\"https://cdn.jsdelivr.net/npm/trix@2.0/dist/trix.umd.min.js\"></script>
1869
1710
  <link rel=\"stylesheet\" type=\"text/css\" href=\"https://cdn.jsdelivr.net/npm/trix@2.0/dist/trix.min.css\">
1870
1711
  <% end %>
1871
1712
 
1872
1713
  <% # Started with v0.14.4 of vanilla-jsoneditor
1873
- if is_includes_json %>
1714
+ if @_json_fields_present %>
1874
1715
  <link rel=\"stylesheet\" type=\"text/css\" href=\"https://cdn.jsdelivr.net/npm/vanilla-jsoneditor/themes/jse-theme-default.min.css\">
1875
1716
  <script type=\"module\">
1876
1717
  import { JSONEditor } from \"https://cdn.jsdelivr.net/npm/vanilla-jsoneditor/index.min.js\";
@@ -0,0 +1,185 @@
1
+ module Brick::Rails::FormBuilder
2
+ DT_PICKERS = { datetime: 'datetimepicker', timestamp: 'datetimepicker', time: 'timepicker', date: 'datepicker' }
3
+
4
+ # When this field is one of the appropriate types, will set one of these instance variables accordingly:
5
+ # @_text_fields_present - To include trix editor
6
+ # @_date_fields_present - To include flatpickr date / time editor
7
+ # @_json_fields_present - To include JSONEditor
8
+ def brick_field(method, html_options = {}, val = nil, col = nil, bt = nil, bt_class = nil, bt_name = nil, bt_pair = nil)
9
+ model = self.object.class
10
+ col ||= model.columns_hash[method]
11
+ out = +'<table><tr><td>'
12
+ html_options[:class] = 'dimmed' unless val
13
+ is_revert = true
14
+ template = instance_variable_get(:@template)
15
+ if bt
16
+ bt_class ||= bt[1].first.first
17
+ bt_name ||= bt[1].map { |x| x.first.name }.join('/')
18
+ bt_pair ||= bt[1].first
19
+
20
+ html_options[:prompt] = "Select #{bt_name}"
21
+ out << self.select(method.to_sym, bt[3], { value: val || '^^^brick_NULL^^^' }, html_options)
22
+ out << if (bt_obj = bt_class&.find_by(bt_pair[1] => val))
23
+ bt_path = template.send(
24
+ "#{bt_class.base_class._brick_index(:singular)}_path".to_sym,
25
+ bt_obj.send(bt_class.primary_key.to_sym)
26
+ )
27
+ template.link_to('⇛', bt_path, { class: 'show-arrow' })
28
+ elsif val
29
+ "<span class=\"orphan\">Orphaned ID: #{val}</span>".html_safe
30
+ end
31
+ elsif @_brick_monetized_attributes&.include?(method)
32
+ out << self.text_field(method.to_sym, html_options.merge({ value: Money.new(val.to_i).format }))
33
+ else
34
+ col_type = if model.json_column?(col) || val.is_a?(Array)
35
+ :json
36
+ elsif col&.sql_type == 'geography'
37
+ col.sql_type
38
+ else
39
+ col&.type
40
+ end
41
+ case (col_type ||= col&.sql_type)
42
+ when :string, :text
43
+ if ::Brick::Rails::FormBuilder.is_bcrypt?(val) # || .readonly?
44
+ is_revert = false
45
+ out << ::Brick::Rails::FormBuilder.hide_bcrypt(val, nil, 1000)
46
+ elsif col_type == :string
47
+ if model.respond_to?(:enumerized_attributes) && (opts = (attr = model.enumerized_attributes[method])&.options).present?
48
+ enum_html_options = attr.kind_of?(Enumerize::Multiple) ? html_options.merge({ multiple: true, size: opts.length + 1 }) : html_options
49
+ out << self.select(method.to_sym, [["(No #{method} chosen)", '^^^brick_NULL^^^']] + opts, { value: val || '^^^brick_NULL^^^' }, enum_html_options)
50
+ else
51
+ out << self.text_field(method.to_sym, html_options)
52
+ end
53
+ else
54
+ template.instance_variable_set(:@_text_fields_present, true)
55
+ out << self.hidden_field(method.to_sym, html_options)
56
+ out << "<trix-editor input=\"#{self.field_id(method)}\"></trix-editor>"
57
+ end
58
+ when :boolean
59
+ out << self.check_box(method.to_sym)
60
+ when :integer, :decimal, :float
61
+ digit_pattern = col_type == :integer ? '\d*' : '\d*(?:\.\d*|)'
62
+ # Used to do this for float / decimal: self.number_field method.to_sym
63
+ out << self.text_field(method.to_sym, { pattern: digit_pattern, class: 'check-validity' })
64
+ when *DT_PICKERS.keys
65
+ template.instance_variable_set(:@_date_fields_present, true)
66
+ out << self.text_field(method.to_sym, { class: DT_PICKERS[col_type] })
67
+ when :uuid
68
+ is_revert = false
69
+ # Postgres naturally uses the +uuid_generate_v4()+ function from the uuid-ossp extension
70
+ # If it's not yet enabled then: create extension \"uuid-ossp\";
71
+ # ActiveUUID gem created a new :uuid type
72
+ out << val
73
+ when :ltree
74
+ # In Postgres labels of data stored in a hierarchical tree-like structure
75
+ # If it's not yet enabled then: create extension ltree;
76
+ out << val
77
+ when :binary
78
+ is_revert = false
79
+ if val
80
+ # %%% This same kind of geography check is done two other times in engine.rb ... would be great to DRY it up.
81
+ out << if val.length < 31 && (val.length - 6) % 8 == 0 && val[0..5].bytes == [230, 16, 0, 0, 1, 12]
82
+ ::Brick::Rails.display_value('geography', val)
83
+ else
84
+ ::Brick::Rails.display_binary(val)
85
+ end
86
+ end
87
+ when :primary_key
88
+ is_revert = false
89
+ when :json
90
+ template.instance_variable_set(:@_json_fields_present, true)
91
+ if val.is_a?(String)
92
+ val_str = val
93
+ else
94
+ eheij = ActiveSupport::JSON::Encoding.escape_html_entities_in_json
95
+ ActiveSupport::JSON::Encoding.escape_html_entities_in_json = false if eheij
96
+ val_str = val.to_json
97
+ ActiveSupport::JSON::Encoding.escape_html_entities_in_json = eheij
98
+ end
99
+ # Because there are so danged many quotes in JSON, escape them specially by converting to backticks.
100
+ # (and previous to this, escape backticks with our own goofy code of ^^br_btick__ )
101
+ out << (json_field = self.hidden_field(method.to_sym, { class: 'jsonpicker', value: val_str.gsub('`', '^^br_btick__').tr('\"', '`').html_safe }))
102
+ out << "<div id=\"_br_json_#{self.field_id(method)}\"></div>"
103
+ else
104
+ is_revert = false
105
+ out << ::Brick::Rails.display_value(col_type, val).html_safe
106
+ end
107
+ end
108
+ if is_revert
109
+ out << "</td>
110
+ "
111
+ out << '<td><svg class="revert" width="1.5em" viewBox="0 0 512 512"><use xlink:href="#revertPath" /></svg>'
112
+ end
113
+ out << "</td></tr></table>
114
+ "
115
+ out.html_safe
116
+ end # brick_field
117
+
118
+ # --- CLASS METHODS ---
119
+
120
+ def self.is_bcrypt?(val)
121
+ val.is_a?(String) && val.length == 60 && val.start_with?('$2a$')
122
+ end
123
+
124
+ def self.hide_bcrypt(val, is_xml = nil, max_len = 200)
125
+ if ::Brick::Rails::FormBuilder.is_bcrypt?(val)
126
+ '(hidden)'
127
+ else
128
+ if val.is_a?(String)
129
+ val = val.dup.force_encoding('UTF-8').strip
130
+ return CGI.escapeHTML(val) if is_xml
131
+
132
+ if val.length > max_len
133
+ if val[0] == '<' # Seems to be HTML?
134
+ cur_len = 0
135
+ cur_idx = 0
136
+ # Find which HTML tags we might be inside so we can apply ending tags to balance
137
+ element_name = nil
138
+ in_closing = nil
139
+ elements = []
140
+ val.each_char do |ch|
141
+ case ch
142
+ when '<'
143
+ element_name = +''
144
+ when '/' # First character of tag is '/'?
145
+ in_closing = true if element_name == ''
146
+ when '>'
147
+ if element_name
148
+ if in_closing
149
+ if (idx = elements.index { |tag| tag.downcase == element_name.downcase })
150
+ elements.delete_at(idx)
151
+ end
152
+ elsif (tag_name = element_name.split.first).present?
153
+ elements.unshift(tag_name)
154
+ end
155
+ element_name = nil
156
+ in_closing = nil
157
+ end
158
+ else
159
+ element_name << ch if element_name
160
+ end
161
+ cur_idx += 1
162
+ # Unless it's inside wickets then this is real text content, and see if we're at the limit
163
+ break if element_name.nil? && ((cur_len += 1) > max_len)
164
+ end
165
+ val = val[0..cur_idx]
166
+ # Somehow still in the middle of an opening tag right at the end? (Should never happen)
167
+ if !in_closing && (tag_name = element_name&.split&.first)&.present?
168
+ elements.unshift(tag_name)
169
+ val << '>'
170
+ end
171
+ elements.each do |closing_tag|
172
+ val << "</#{closing_tag}>"
173
+ end
174
+ else # Not HTML, just cut it at the length
175
+ val = val[0...max_len]
176
+ end
177
+ val = "#{val}..."
178
+ end
179
+ val
180
+ else
181
+ val.to_s
182
+ end
183
+ end
184
+ end
185
+ end
@@ -133,7 +133,7 @@ module Brick::Rails::FormTags
133
133
  val ? Money.new(val.to_i).format : ''
134
134
  else
135
135
  col_type = col&.sql_type == 'geography' ? col.sql_type : col&.type
136
- display_value(col_type || col&.sql_type, val).to_s
136
+ ::Brick::Rails.display_value(col_type || col&.sql_type, val).to_s
137
137
  end
138
138
  elsif cust_col
139
139
  data = cust_col.first.map { |cc_part| obj.send(cc_part.last) }
@@ -5,7 +5,7 @@ module Brick
5
5
  module VERSION
6
6
  MAJOR = 1
7
7
  MINOR = 0
8
- TINY = 125
8
+ TINY = 126
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,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: brick
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.125
4
+ version: 1.0.126
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lorin Thwaits
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-04-02 00:00:00.000000000 Z
11
+ date: 2023-04-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -244,6 +244,7 @@ files:
244
244
  - lib/brick/frameworks/rails/controller.rb
245
245
  - lib/brick/frameworks/rails/crosstab.brk
246
246
  - lib/brick/frameworks/rails/engine.rb
247
+ - lib/brick/frameworks/rails/form_builder.rb
247
248
  - lib/brick/frameworks/rails/form_tags.rb
248
249
  - lib/brick/frameworks/rspec.rb
249
250
  - lib/brick/join_array.rb
@@ -270,7 +271,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
270
271
  requirements:
271
272
  - - ">="
272
273
  - !ruby/object:Gem::Version
273
- version: 2.3.5
274
+ version: 2.3.8
274
275
  required_rubygems_version: !ruby/object:Gem::Requirement
275
276
  requirements:
276
277
  - - ">="