brick 1.0.125 → 1.0.126

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: 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
  - - ">="