brick 1.0.101 → 1.0.103

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: eba38d653370b0cc3e98f2adc82d8c475854440745bd35caab9a9a4253fc152c
4
- data.tar.gz: 18ca517b90266cf5db711040a7f40c8e848600135ef0a39ad91da9cd2a0b1c89
3
+ metadata.gz: 74743982216a9827513b4aaa68fe0887c9ba3e7b78c45ff52f0a2f2b99dc7b88
4
+ data.tar.gz: 3857a95e891e7561d285dd3ecbdeb6c4c16cc9a8205e7b733b55867ed7a69f64
5
5
  SHA512:
6
- metadata.gz: 859f48999fb0e8d1010a701e29c9f0275f716b20f54b4348576f4dbff7f68da1eb83709a51a1a7acd7debaf4dc1f82e863048ef16cda6b5f59862246e0c50787
7
- data.tar.gz: d93c3db79715f62705c0b8f2f5bd2640a2756e1fbcb6aa6a7cf017a18bc31fa5e98496ea41539611c149f84870f24596c105d146484519b7f040d2f36b25ce45
6
+ metadata.gz: 8b71aa5b1d4da8c4aa1ce88af6d0767c12bdf72b2457594575795d4b9b3f36d1bfb2adef82413a3bc80e041813e7bc6c05768016c5e4cfc197e3bbb730113934
7
+ data.tar.gz: 6399e040cb4b54eac4d7a09b907cfba11037a4b3391dd3a86b4845e79bd0c4d4e169586c095fdaaaa8506f346628072c9c4c9b6d2ed60c3306a58866e6f9d030
@@ -77,14 +77,13 @@ module ActiveRecord
77
77
  def self.brick_get_dsl
78
78
  # If there's no DSL yet specified, just try to find the first usable column on this model
79
79
  unless (dsl = ::Brick.config.model_descrips[name])
80
- descrip_col = (columns.map(&:name) - _brick_get_fks -
81
- (::Brick.config.metadata_columns || []) -
82
- [primary_key]).first
83
- dsl = ::Brick.config.model_descrips[name] = if descrip_col
84
- "[#{descrip_col}]"
85
- elsif (pk_parts = self.primary_key.is_a?(Array) ? self.primary_key : [self.primary_key])
86
- "#{name} ##{pk_parts.map { |pk_part| "[#{pk_part}]" }.join(', ')}"
87
- end
80
+ skip_columns = _brick_get_fks + (::Brick.config.metadata_columns || []) + [primary_key]
81
+ dsl = if (descrip_col = columns.find { |c| [:boolean, :binary, :xml].exclude?(c.type) && skip_columns.exclude?(c.name) })
82
+ "[#{descrip_col.name}]"
83
+ elsif (pk_parts = self.primary_key.is_a?(Array) ? self.primary_key : [self.primary_key])
84
+ "#{name} ##{pk_parts.map { |pk_part| "[#{pk_part}]" }.join(', ')}"
85
+ end
86
+ ::Brick.config.model_descrips[name] = dsl
88
87
  end
89
88
  dsl
90
89
  end
@@ -119,30 +118,32 @@ module ActiveRecord
119
118
  end
120
119
  s << part_sym
121
120
  end
122
- if (parts = prefix + first_parts + [parts[-1]]).length > 1 && klass
123
- unless is_polymorphic
124
- s = build_array
125
- parts[0..-3].each { |v| s = s[v.to_sym] }
126
- s[parts[-2]] = nil # unless parts[-2].empty? # Using []= will "hydrate" any missing part(s) in our whole series
127
- end
128
- translations[parts[0..-2].join('.')] = klass
129
- end
130
- if klass&.column_names.exclude?(parts.last) &&
131
- (klass = (orig_class = klass).reflect_on_association(possible_dsl = parts.pop.to_sym)&.klass)
132
- if prefix.empty? # Custom columns start with an empty prefix
133
- prefix << parts.shift until parts.empty?
121
+ if first_parts
122
+ if (parts = prefix + first_parts + [parts[-1]]).length > 1 && klass
123
+ unless is_polymorphic
124
+ s = build_array
125
+ parts[0..-3].each { |v| s = s[v.to_sym] }
126
+ s[parts[-2]] = nil # unless parts[-2].empty? # Using []= will "hydrate" any missing part(s) in our whole series
127
+ end
128
+ translations[parts[0..-2].join('.')] = klass
134
129
  end
135
- # Expand this entry which refers to an association name
136
- members2, dsl2a = klass.brick_parse_dsl(build_array, prefix + [possible_dsl], translations, is_polymorphic, nil, true)
137
- members += members2
138
- dsl2 << dsl2a
139
- dsl3 << dsl2a
140
- else
141
- dsl2 << "[#{bracket_name}]"
142
- if emit_dsl
143
- dsl3 << "[#{prefix[1..-1].map { |p| "#{p.to_s}." }.join if prefix.length > 1}#{bracket_name}]"
130
+ if klass&.column_names.exclude?(parts.last) &&
131
+ (klass = (orig_class = klass).reflect_on_association(possible_dsl = parts.pop.to_sym)&.klass)
132
+ if prefix.empty? # Custom columns start with an empty prefix
133
+ prefix << parts.shift until parts.empty?
134
+ end
135
+ # Expand this entry which refers to an association name
136
+ members2, dsl2a = klass.brick_parse_dsl(build_array, prefix + [possible_dsl], translations, is_polymorphic, nil, true)
137
+ members += members2
138
+ dsl2 << dsl2a
139
+ dsl3 << dsl2a
140
+ else
141
+ dsl2 << "[#{bracket_name}]"
142
+ if emit_dsl
143
+ dsl3 << "[#{prefix[1..-1].map { |p| "#{p.to_s}." }.join if prefix.length > 1}#{bracket_name}]"
144
+ end
145
+ members << parts
144
146
  end
145
- members << parts
146
147
  end
147
148
  bracket_name = nil
148
149
  else
@@ -409,7 +410,7 @@ module ActiveRecord
409
410
  model._brick_calculate_bts_hms(translations, join_array) if is_add_bts || is_add_hms
410
411
 
411
412
  is_postgres = ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
412
- is_mysql = ActiveRecord::Base.connection.adapter_name == 'Mysql2'
413
+ is_mysql = ['Mysql2', 'Trilogy'].include?(ActiveRecord::Base.connection.adapter_name)
413
414
  is_mssql = ActiveRecord::Base.connection.adapter_name == 'SQLServer'
414
415
  is_distinct = nil
415
416
  wheres = {}
@@ -557,7 +558,7 @@ module ActiveRecord
557
558
  brick_link = rel_dupe.brick_links[sel_col.first]
558
559
  field_tbl_name = brick_link&.split('.')&.last ||
559
560
  # ... so here's a best-effort guess for what the table name might be.
560
- rel_dupe.klass.reflect_on_association(sel_col.first).klass.table_name
561
+ rel_dupe.klass.reflect_on_association(sel_col.first)&.klass&.table_name
561
562
  # If it's Oracle, quote any AREL aliases that had been applied
562
563
  field_tbl_name = "\"#{field_tbl_name}\"" if ::Brick.is_oracle && rel_dupe.brick_links.values.include?(field_tbl_name)
563
564
 
@@ -803,6 +804,7 @@ JOIN (SELECT #{hm_selects.map { |s| "#{'br_t0.' if from_clause}#{s}" }.join(', '
803
804
  alias _brick_find_sti_class find_sti_class
804
805
  def find_sti_class(type_name)
805
806
  if ::Brick.sti_models.key?(type_name ||= name)
807
+ # Used to be: ::Brick.sti_models[type_name].fetch(:base, nil) || _brick_find_sti_class(type_name)
806
808
  _brick_find_sti_class(type_name)
807
809
  else
808
810
  # This auto-STI is more of a brute-force approach, building modules where needed
@@ -1407,7 +1409,7 @@ class Object
1407
1409
  singular_table_name = ActiveSupport::Inflector.singularize(ActiveSupport::Inflector.underscore(plural_class_name))
1408
1410
  pk = model&._brick_primary_key(relations.fetch(table_name, nil))
1409
1411
  is_postgres = ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
1410
- is_mysql = ActiveRecord::Base.connection.adapter_name == 'Mysql2'
1412
+ is_mysql = ['Mysql2', 'Trilogy'].include?(ActiveRecord::Base.connection.adapter_name)
1411
1413
 
1412
1414
  code = +"class #{class_name} < #{controller_base&.name || 'ApplicationController'}\n"
1413
1415
  built_controller = Class.new(controller_base || ActionController::Base) do |new_controller_class|
@@ -1622,6 +1624,18 @@ class Object
1622
1624
  end
1623
1625
 
1624
1626
  unless is_openapi || is_avo
1627
+ # Skip showing Bullet gem optimisation messages
1628
+ if Object.const_defined?('Bullet') && Bullet.respond_to?(:enable?)
1629
+ around_action :skip_bullet
1630
+ def skip_bullet
1631
+ bullet_enabled = Bullet.enable?
1632
+ Bullet.enable = false
1633
+ yield
1634
+ ensure
1635
+ Bullet.enable = bullet_enabled
1636
+ end
1637
+ end
1638
+
1625
1639
  _, order_by_txt = model._brick_calculate_ordering(default_ordering(table_name, pk)) if pk
1626
1640
  code << " def index\n"
1627
1641
  code << " @#{table_name.pluralize} = #{model.name}#{pk&.present? ? ".order(#{order_by_txt.join(', ')})" : '.all'}\n"
@@ -1935,7 +1949,7 @@ end.class_exec do
1935
1949
  puts "*** In the brick.rb initializer the line \"::Brick.schema_behavior = ...\" refers to schema(s) called #{possible_schemas.map { |s| "\"#{s}\"" }.join(', ')}. No mentioned schema exists. ***"
1936
1950
  end
1937
1951
  end
1938
- when 'Mysql2'
1952
+ when 'Mysql2', 'Trilogy'
1939
1953
  ::Brick.default_schema = schema = ActiveRecord::Base.connection.current_database
1940
1954
  when 'OracleEnhanced'
1941
1955
  # ActiveRecord::Base.connection.current_database will be something like "XEPDB1"
@@ -2070,7 +2084,7 @@ ORDER BY 1, 2, c.internal_column_id, acc.position"
2070
2084
  # end
2071
2085
  # schema = ::Brick.default_schema # Reset back for this next round of fun
2072
2086
  case ActiveRecord::Base.connection.adapter_name
2073
- when 'PostgreSQL', 'Mysql2', 'SQLServer'
2087
+ when 'PostgreSQL', 'Mysql2', 'Trilogy', 'SQLServer'
2074
2088
  sql = "SELECT kcu1.CONSTRAINT_SCHEMA, kcu1.TABLE_NAME, kcu1.COLUMN_NAME,
2075
2089
  kcu2.CONSTRAINT_SCHEMA AS primary_schema, kcu2.TABLE_NAME AS primary_table, kcu1.CONSTRAINT_NAME AS CONSTRAINT_SCHEMA_FK
2076
2090
  FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS AS rc
@@ -29,6 +29,54 @@ module Brick
29
29
  var finalParams = Object.keys(params).reduce(function (s, v) { if (params[v]) s.push(v + \"=\" + params[v]); return s; }, []).join(\"&\");
30
30
  return hrefParts[0] + (finalParams.length > 0 ? \"?\" + finalParams : \"\");
31
31
  }
32
+
33
+ // This PageTransitionEvent fires when the page first loads, as well as after any other history
34
+ // transition such as when using the browser's Back and Forward buttons.
35
+ window.addEventListener(\"pageshow\", linkSchemas);
36
+ var brickSchema;
37
+ function linkSchemas() {
38
+ var schemaSelect = document.getElementById(\"schema\");
39
+ var tblSelect = document.getElementById(\"tbl\");
40
+ if (tblSelect) { // Always present
41
+ // Used to be: var i = # {::Brick.config.path_prefix ? '0' : 'schemaSelect ? 1 : 0'},
42
+ var changeoutList = changeout(location.href);
43
+ for (var i = 0; i < changeoutList.length; ++i) {
44
+ tblSelect.value = changeoutList[i];
45
+ if (tblSelect.value !== \"\") break;
46
+ }
47
+
48
+ tblSelect.addEventListener(\"change\", function () {
49
+ var lhr = changeout(location.href, null, this.value);
50
+ if (brickSchema) lhr = changeout(lhr, \"_brick_schema\", schemaSelect.value);
51
+ location.href = lhr;
52
+ });
53
+ }
54
+
55
+ if (schemaSelect) { // First drop-down is only present if multitenant
56
+ if (brickSchema = changeout(location.href, \"_brick_schema\")) {
57
+ [... document.getElementsByTagName(\"A\")].forEach(function (a) { a.href = changeout(a.href, \"_brick_schema\", brickSchema); });
58
+ }
59
+ if (schemaSelect.options.length > 1) {
60
+ schemaSelect.value = brickSchema || \"public\";
61
+ schemaSelect.addEventListener(\"change\", function () {
62
+ // If there's an ID then remove it (trim after selected table)
63
+ location.href = changeout(location.href, \"_brick_schema\", this.value, tblSelect.value);
64
+ });
65
+ }
66
+ }
67
+ tblSelect.focus();
68
+
69
+ [... document.getElementsByTagName(\"FORM\")].forEach(function (form) {
70
+ if (brickSchema)
71
+ form.action = changeout(form.action, \"_brick_schema\", brickSchema);
72
+ form.addEventListener('submit', function (ev) {
73
+ [... ev.target.getElementsByTagName(\"SELECT\")].forEach(function (select) {
74
+ if (select.value === \"^^^brick_NULL^^^\") select.value = null;
75
+ });
76
+ return true;
77
+ });
78
+ });
79
+ };
32
80
  "
33
81
 
34
82
  # paths['app/models'] << 'lib/brick/frameworks/active_record/models'
@@ -168,17 +216,9 @@ module Brick
168
216
  end
169
217
  "<script>
170
218
  #{JS_CHANGEOUT}
171
- window.addEventListener(\"load\", linkSchemas);
172
219
  document.addEventListener(\"turbo:render\", linkSchemas);
173
220
  window.addEventListener(\"popstate\", linkSchemas);
174
221
  // [... document.getElementsByTagName('turbo-frame')].forEach(function (a) { a.addEventListener(\"turbo:frame-render\", linkSchemas); });
175
- function linkSchemas() {
176
- brickSchema = changeout(location.href, \"_brick_schema\");
177
- if (brickSchema) {
178
- [... document.getElementsByTagName(\"A\")].forEach(function (a) { a.href = changeout(a.href, \"_brick_schema\", brickSchema); });
179
- [... document.getElementsByTagName(\"FORM\")].forEach(function (form) { form.action = changeout(form.action, \"_brick_schema\", brickSchema); });
180
- }
181
- }
182
222
  </script>
183
223
  #{_brick_content}".html_safe
184
224
  end
@@ -288,7 +328,14 @@ function linkSchemas() {
288
328
  else
289
329
  [[fk_name, pk.length == 1 ? pk.first : pk.inspect]]
290
330
  end
291
- keys << [hm_assoc.inverse_of.foreign_type, hm_assoc.active_record.name] if hm_assoc.options.key?(:as)
331
+ if hm_assoc.options.key?(:as)
332
+ poly_type = if hm_assoc.active_record.column_names.include?(hm_assoc.active_record.inheritance_column)
333
+ '[sti_type]'
334
+ else
335
+ hm_assoc.active_record.name
336
+ end
337
+ keys << [hm_assoc.inverse_of.foreign_type, poly_type]
338
+ end
292
339
  keys.to_h
293
340
  end
294
341
 
@@ -362,9 +409,14 @@ function linkSchemas() {
362
409
  end
363
410
  when 'show', 'new', 'update'
364
411
  hm_stuff << if hm_fk_name
365
- if hm_assoc.klass.column_names.include?(hm_fk_name)
412
+ if hm_assoc.klass.column_names.include?(hm_fk_name) ||
413
+ (hm_fk_name.is_a?(String) && hm_fk_name.include?('.')) # HMT? (Could do a better check for this)
366
414
  predicates = path_keys(hm_assoc, hm_fk_name, pk).map do |k, v|
367
- v.is_a?(String) ? "#{k}: '#{v}'" : "#{k}: @#{obj_name}.#{v}"
415
+ if v == '[sti_type]'
416
+ "'#{k}': @#{obj_name}.#{hm_assoc.active_record.inheritance_column}"
417
+ else
418
+ v.is_a?(String) ? "'#{k}': '#{v}'" : "'#{k}': @#{obj_name}.#{v}"
419
+ end
368
420
  end.join(', ')
369
421
  "<%= link_to '#{assoc_name}', #{hm_assoc.klass._brick_index}_path({ #{predicates} }) %>\n"
370
422
  else
@@ -622,13 +674,24 @@ def hide_bcrypt(val, max_len = 200)
622
674
  end
623
675
  end
624
676
  def display_value(col_type, val)
677
+ is_mssql_geography = nil
678
+ # 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)
679
+ if col_type == :binary && val && val.length < 31 && (val.length - 6) % 8 == 0 && val[0..5].bytes == [230, 16, 0, 0, 1, 12]
680
+ col_type = 'geography'
681
+ is_mssql_geography = true
682
+ end
625
683
  case col_type
626
684
  when 'geometry', 'geography'
627
685
  if Object.const_defined?('RGeo')
628
- @is_mysql = ActiveRecord::Base.connection.adapter_name == 'Mysql2' if @is_mysql.nil?
686
+ @is_mysql = ['Mysql2', 'Trilogy'].include?(ActiveRecord::Base.connection.adapter_name) if @is_mysql.nil?
629
687
  @is_mssql = ActiveRecord::Base.connection.adapter_name == 'SQLServer' if @is_mssql.nil?
630
688
  val_err = nil
631
- if @is_mysql || @is_mssql
689
+
690
+ if @is_mysql || (is_mssql_geography ||=
691
+ (@is_mssql ||
692
+ (val && val.length < 31 && (val.length - 6) % 8 == 0 && val[0..5].bytes == [230, 16, 0, 0, 1, 12])
693
+ )
694
+ )
632
695
  # MySQL's \"Internal Geometry Format\" and MSSQL's Geography are like WKB, but with an initial 4 bytes that indicates the SRID.
633
696
  if (srid = val&.[](0..3)&.unpack('I'))
634
697
  val = val.dup.force_encoding('BINARY')[4..-1].bytes
@@ -642,23 +705,30 @@ def display_value(col_type, val)
642
705
  # xx1x xxxx = IsWholeGlobe
643
706
  # Convert Microsoft's unique geography binary to standard WKB
644
707
  # (MSSQL point usually has two doubles, lng / lat, and can also have Z)
645
- if @is_mssql
708
+ if is_mssql_geography
646
709
  if val[0] == 1 && (val[1] & 8 > 0) && # Single point?
647
- (val.length - 2) % 8 == 0 && val.length < 27 # And containing up to three 8-byte values?
648
- idx = 2
649
- new_val = [0, 0, 0, 0, 1]
650
- new_val.concat(val[idx - 8...idx].reverse) while (idx += 8) <= val.length
651
- val = new_val
710
+ (val.length - 2) % 8 == 0 && val.length < 27 # And containing up to three 8-byte values?
711
+ val = [0, 0, 0, 0, 1] + val[2..-1].reverse
652
712
  else
653
713
  val_err = '(Microsoft internal SQL geography type)'
654
714
  end
655
715
  end
656
716
  end
657
717
  end
658
- val_err || (val ? RGeo::WKRep::WKBParser.new.parse(val.pack('c*')) : nil)
718
+ unless val_err || val.nil?
719
+ if (geometry = RGeo::WKRep::WKBParser.new.parse(val.pack('c*'))).is_a?(RGeo::Cartesian::PointImpl) &&
720
+ !(geometry.y == 0.0 && geometry.x == 0.0)
721
+ # 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
722
+ geometry = \"<a href=\\\"https://www.google.com/maps/place/#\{geometry.y}+#\{geometry.x}/@#\{geometry.y},#\{geometry.x},12z\\\" target=\\\"blank\\\">#\{geometry.to_s}</a>\"
723
+ end
724
+ val = geometry
725
+ end
726
+ val_err || val
659
727
  else
660
728
  '(Add RGeo gem to parse geometry detail)'
661
729
  end
730
+ when :binary
731
+ display_binary(val) if val
662
732
  else
663
733
  if col_type
664
734
  hide_bcrypt(val)
@@ -667,6 +737,40 @@ def display_value(col_type, val)
667
737
  end
668
738
  end
669
739
  end
740
+
741
+ def image_signatures
742
+ @image_signatures ||= { \"\\xFF\\xD8\\xFF\\xEE\".force_encoding('ASCII-8BIT') => 'jpeg',
743
+ \"\\xFF\\xD8\\xFF\\xE0\\x00\\x10\\x4A\\x46\\x49\\x46\\x00\\x01\".force_encoding('ASCII-8BIT') => 'jpeg',
744
+ \"\\x89PNG\\r\\n\\x1A\\n\".force_encoding('ASCII-8BIT') => 'png',
745
+ '<svg' => 'svg+xml', # %%% Not yet very good detection for SVG
746
+ 'BM'.force_encoding('ASCII-8BIT') => 'bmp',
747
+ 'GIF87a'.force_encoding('ASCII-8BIT') => 'gif',
748
+ 'GIF89a'.force_encoding('ASCII-8BIT') => 'gif' }
749
+ end
750
+ def display_binary(val)
751
+ if val[0..1] == \"\\x15\\x1C\" # One of those goofy Microsoft OLE containers?
752
+ package_header_length = val[2..3].bytes.reverse.inject(0) {|m, b| (m << 8) + b }
753
+ # This will often be just FF FF FF FF
754
+ # object_size = val[16..19].bytes.reverse.inject(0) {|m, b| (m << 8) + b }
755
+ friendly_and_class_names = val[20...package_header_length].split(\"\\0\")
756
+ object_type_name_length = val[package_header_length + 8..package_header_length+11].bytes.reverse.inject(0) {|m, b| (m << 8) + b }
757
+ friendly_and_class_names << val[package_header_length + 12...package_header_length + 12 + object_type_name_length].strip
758
+ # friendly_and_class_names will now be something like: ['Bitmap Image', 'Paint.Picture', 'PBrush']
759
+ 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 }
760
+ object_start = package_header_length + 24 + object_type_name_length
761
+ val = val[object_start...object_start + real_object_size]
762
+ end
763
+ if (signature = image_signatures.find { |k, _v| val[0...k.length] == k })
764
+ if val.length < 500_000
765
+ \"<img src=\\\"data:image/#\{signature.last};base64,#\{Base64.encode64(val)}\\\">\"
766
+ else
767
+ \"&lt;&nbsp;#\{signature.last} image, #\{val.length} bytes&nbsp;>\"
768
+ end
769
+ else
770
+ \"&lt;&nbsp;Binary, #\{val.length} bytes&nbsp;>\"
771
+ end
772
+ end
773
+
670
774
  # Accommodate composite primary keys that include strings with forward-slash characters
671
775
  def slashify(*vals)
672
776
  vals.map { |val_part| val_part.is_a?(String) ? val_part.gsub('/', '^^sl^^') : val_part }
@@ -705,56 +809,8 @@ callbacks = {} %>
705
809
 
706
810
  # %%% When doing schema select, if we're on a new page go to index
707
811
  script = "<script>
708
- var schemaSelect = document.getElementById(\"schema\");
709
- var tblSelect = document.getElementById(\"tbl\");
710
- var brickSchema;
711
812
  var #{table_name}HtColumns;
712
813
 
713
- // This PageTransitionEvent fires when the page first loads, as well as after any other history
714
- // transition such as when using the browser's Back and Forward buttons.
715
- window.addEventListener(\"pageshow\", function() {
716
- if (tblSelect) { // Always present
717
- var i = #{::Brick.config.path_prefix ? '0' : 'schemaSelect ? 1 : 0'},
718
- changeoutList = changeout(location.href);
719
- for (; i < changeoutList.length; ++i) {
720
- tblSelect.value = changeoutList[i];
721
- if (tblSelect.value !== \"\") break;
722
- }
723
-
724
- tblSelect.addEventListener(\"change\", function () {
725
- var lhr = changeout(location.href, null, this.value);
726
- if (brickSchema)
727
- lhr = changeout(lhr, \"_brick_schema\", schemaSelect.value);
728
- location.href = lhr;
729
- });
730
- }
731
-
732
- if (schemaSelect && schemaSelect.options.length > 1) { // First drop-down is only present if multitenant
733
- brickSchema = changeout(location.href, \"_brick_schema\");
734
- if (brickSchema) {
735
- [... document.getElementsByTagName(\"A\")].forEach(function (a) { a.href = changeout(a.href, \"_brick_schema\", brickSchema); });
736
- }
737
- schemaSelect.value = brickSchema || \"public\";
738
- schemaSelect.focus();
739
- schemaSelect.addEventListener(\"change\", function () {
740
- // If there's an ID then remove it (trim after selected table)
741
- location.href = changeout(location.href, \"_brick_schema\", this.value, tblSelect.value);
742
- });
743
- }
744
-
745
- [... document.getElementsByTagName(\"FORM\")].forEach(function (form) {
746
- if (brickSchema)
747
- form.action = changeout(form.action, \"_brick_schema\", brickSchema);
748
- form.addEventListener('submit', function (ev) {
749
- [... ev.target.getElementsByTagName(\"SELECT\")].forEach(function (select) {
750
- if (select.value === \"^^^brick_NULL^^^\")
751
- select.value = null;
752
- });
753
- return true;
754
- });
755
- });
756
- });
757
-
758
814
  // Add \"Are you sure?\" behaviour to any data-confirm buttons out there
759
815
  document.querySelectorAll(\"input[type=submit][data-confirm]\").forEach(function (btn) {
760
816
  btn.addEventListener(\"click\", function (evt) {
@@ -1082,8 +1138,11 @@ erDiagram
1082
1138
  id = id.first if id.is_a?(Array) && id.length == 1
1083
1139
  origin = (key_parts = k.split('.')).length == 1 ? model : model.reflect_on_association(key_parts.first).klass
1084
1140
  if (destination_fk = Brick.relations[origin.table_name][:fks].values.find { |fk| fk[:fk] == key_parts.last }) &&
1085
- (obj = (destination = origin.reflect_on_association(destination_fk[:assoc_name])&.klass)&.find(id)) %>
1086
- <h3>for <%= link_to \"#{"#\{obj.brick_descrip\} (#\{destination.name\})\""}, send(\"#\{destination._brick_index(:singular)\}_path\".to_sym, id) %></h3><%
1141
+ (objs = (destination = origin.reflect_on_association(destination_fk[:assoc_name])&.klass)&.find(id))
1142
+ objs = [objs] unless objs.is_a?(Array) %>
1143
+ <h3>for <% objs.each do |obj| %><%=
1144
+ link_to \"#{"#\{obj.brick_descrip\} (#\{destination.name\})\""}, send(\"#\{destination._brick_index(:singular)\}_path\".to_sym, id)
1145
+ %><% end %></h3><%
1087
1146
  end
1088
1147
  end %>
1089
1148
  (<%= link_to \"See all #\{model.base_class.name.split('::').last.pluralize}\", #{@_brick_model._brick_index}_path %>)
@@ -1227,7 +1286,9 @@ erDiagram
1227
1286
  <head>
1228
1287
  #{css}
1229
1288
  <title><%=
1230
- page_title = (\"#{model_name}: #\{(obj = @#{obj_name})&.brick_descrip || controller_name}\")
1289
+ model = (obj = @#{obj_name})&.class
1290
+ model_name = @#{obj_name}.#{inh_col = @_brick_model.inheritance_column} if obj.respond_to?(:#{inh_col})
1291
+ page_title = (\"#\{model_name ||= model.name}: #\{obj&.brick_descrip || controller_name}\")
1231
1292
  %></title>
1232
1293
  </head>
1233
1294
  <body>
@@ -1261,7 +1322,7 @@ end
1261
1322
  <% if obj
1262
1323
  # path_options = [obj.#{pk}]
1263
1324
  # path_options << { '_brick_schema': } if
1264
- # url = send(:#\{model_name._brick_index(:singular)}_path, obj.#{pk})
1325
+ # url = send(:#\{model._brick_index(:singular)}_path, obj.#{pk})
1265
1326
  options = {}
1266
1327
  options[:url] = send(\"#\{#{model_name}._brick_index(:singular)}_path\".to_sym, obj) if ::Brick.config.path_prefix
1267
1328
  %>
@@ -1357,7 +1418,17 @@ end
1357
1418
  # In Postgres labels of data stored in a hierarchical tree-like structure
1358
1419
  # If it's not yet enabled then: create extension ltree;
1359
1420
  val %>
1360
- <% when :binary, :primary_key
1421
+ <% when :binary %>
1422
+ <%= is_revert = false
1423
+ if val
1424
+ # %%% This same kind of geography check is done two other times above ... would be great to DRY it up.
1425
+ if val.length < 31 && (val.length - 6) % 8 == 0 && val[0..5].bytes == [230, 16, 0, 0, 1, 12]
1426
+ display_value('geography', val)
1427
+ else
1428
+ display_binary(val)
1429
+ end.html_safe
1430
+ end %>
1431
+ <% when :primary_key
1361
1432
  is_revert = false %>
1362
1433
  <% else %>
1363
1434
  <%= is_revert = false
@@ -1381,8 +1452,8 @@ end
1381
1452
  <% end %>
1382
1453
 
1383
1454
  #{unless args.first == 'new'
1384
- # 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?' } }"
1385
- confirm_are_you_sure = "data: { confirm: 'Delete #{model_name} -- Are you sure?' }"
1455
+ # 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?' } }"
1456
+ confirm_are_you_sure = "data: { confirm: 'Delete #\{model_name} -- Are you sure?' }"
1386
1457
  hms_headers.each_with_object(+'') do |hm, s|
1387
1458
  # %%% Would be able to remove this when multiple foreign keys to same destination becomes bulletproof
1388
1459
  next if hm.first.options[:through] && !hm.first.through_reflection
@@ -1390,11 +1461,24 @@ end
1390
1461
  if (pk = hm.first.klass.primary_key)
1391
1462
  hm_singular_name = (hm_name = hm.first.name.to_s).singularize.underscore
1392
1463
  obj_pk = (pk.is_a?(Array) ? pk : [pk]).each_with_object([]) { |pk_part, s| s << "#{hm_singular_name}.#{pk_part}" }.join(', ')
1464
+ poly_fix = if (poly_type = (hm.first.options[:as] && hm.first.type))
1465
+ "
1466
+ # Let's fix an unexpected \"feature\" of AR -- when going through a polymorphic has_many
1467
+ # association that points to an STI model then filtering for the __able_type column is done
1468
+ # with a .where(). And the polymorphic class name it points to is the base class name of
1469
+ # the STI model instead of its subclass.
1470
+ if (poly_type = #{poly_type.inspect}) &&
1471
+ @#{obj_name}.respond_to?(:#{@_brick_model.inheritance_column}) &&
1472
+ (base_type = collection.where_values_hash[poly_type])
1473
+ collection = collection.rewhere(poly_type => [base_type, @#{obj_name}.#{@_brick_model.inheritance_column}])
1474
+ end"
1475
+ end
1393
1476
  s << "<table id=\"#{hm_name}\" class=\"shadow\">
1394
- <tr><th>#{hm[3]}</th></tr>
1477
+ <tr><th>#{hm[1]}#{' poly' if hm[0].options[:as]} #{hm[3]}</th></tr>
1395
1478
  <% collection = @#{obj_name}.#{hm_name}
1396
1479
  collection = case collection
1397
- when ActiveRecord::Associations::CollectionProxy
1480
+ when ActiveRecord::Associations::CollectionProxy#{
1481
+ poly_fix}
1398
1482
  collection.order(#{pk.inspect})
1399
1483
  when ActiveRecord::Base # Object from a has_one
1400
1484
  [collection]
@@ -1576,11 +1660,10 @@ document.querySelectorAll(\"input, select\").forEach(function (inp) {
1576
1660
  private
1577
1661
 
1578
1662
  alias _brick_render_template render_template
1579
- def render_template(view, template, *args)
1580
- result = _brick_render_template(view, template, *args)
1581
- if template.instance_variable_get(:@is_brick)
1582
- Apartment::Tenant.switch!(::Brick.apartment_default_tenant) if ::Brick.apartment_multitenant
1583
- end
1663
+ def render_template(view, template, layout_name, *args)
1664
+ layout_name = nil if (is_brick = template.instance_variable_get(:@is_brick)) && layout_name.is_a?(Proc)
1665
+ result = _brick_render_template(view, template, layout_name, *args)
1666
+ Apartment::Tenant.switch!(::Brick.apartment_default_tenant) if is_brick && ::Brick.apartment_multitenant
1584
1667
  result
1585
1668
  end
1586
1669
  end # TemplateRenderer
@@ -3,7 +3,7 @@ module Brick::Rails::FormTags
3
3
  def brick_grid(relation, bt_descrip, sequence = nil, inclusions, exclusions,
4
4
  cols, poly_cols, bts, hms_keys, hms_cols)
5
5
  out = "<table id=\"headerTop\"></table>
6
- <table id=\"#{relation.table_name}\" class=\"shadow\">
6
+ <table id=\"#{relation.table_name.split('.').last}\" class=\"shadow\">
7
7
  <thead><tr>"
8
8
  pk = (klass = relation.klass).primary_key || []
9
9
  pk = [pk] unless pk.is_a?(Array)
@@ -90,6 +90,7 @@ module Brick::Rails::FormTags
90
90
  # 0..62 because Postgres column names are limited to 63 characters
91
91
  obj, descrips[0..-2].map { |id| obj.send(id.last[0..62]) }, bt_id_col
92
92
  )
93
+ bt_txt = display_binary(bt_txt).html_safe if bt_txt&.encoding&.name == 'ASCII-8BIT'
93
94
  bt_txt ||= "<span class=\"orphan\">&lt;&lt; Orphaned ID: #{val} >></span>" if val
94
95
  bt_id = bt_id_col&.map { |id_col| obj.respond_to?(id_sym = id_col.to_sym) ? obj.send(id_sym) : id_col }
95
96
  out << (bt_id&.first ? link_to(bt_txt, send("#{bt_class.base_class._brick_index(:singular)}_path".to_sym, bt_id)) : bt_txt || '')
@@ -5,7 +5,7 @@ module Brick
5
5
  module VERSION
6
6
  MAJOR = 1
7
7
  MINOR = 0
8
- TINY = 101
8
+ TINY = 103
9
9
 
10
10
  # PRE is nil unless it's a pre-release (beta, RC, etc.)
11
11
  PRE = nil
data/lib/brick.rb CHANGED
@@ -26,7 +26,7 @@ end
26
26
  require 'brick/util'
27
27
 
28
28
  # Allow ActiveRecord < 3.2 to work with Ruby 2.7 and later
29
- if (ruby_version = ::Gem::Version.new(RUBY_VERSION)) >= ::Gem::Version.new('2.7')
29
+ if (is_ruby_2_7 = (ruby_version = ::Gem::Version.new(RUBY_VERSION)) >= ::Gem::Version.new('2.7'))
30
30
  if ActiveRecord.version < ::Gem::Version.new('3.2')
31
31
  # Remove circular reference for "now"
32
32
  ::Brick::Util._patch_require(
@@ -1043,6 +1043,18 @@ ActiveSupport.on_load(:active_record) do
1043
1043
  end
1044
1044
  # rubocop:enable Lint/ConstantDefinitionInBlock
1045
1045
 
1046
+ arsc = ::ActiveRecord::StatementCache
1047
+ if is_ruby_2_7 && (params = arsc.method(:create).parameters).length == 2 && params.last == [:opt, :block]
1048
+ arsc.class_exec do
1049
+ def self.create(connection, callable = nil, &block)
1050
+ relation = (callable || block).call ::ActiveRecord::StatementCache::Params.new
1051
+ bind_map = ::ActiveRecord::StatementCache::BindMap.new relation.bound_attributes
1052
+ query_builder = connection.cacheable_query(self, relation.arel)
1053
+ new query_builder, bind_map
1054
+ end
1055
+ end
1056
+ end
1057
+
1046
1058
  # Rails < 4.2 is not innately compatible with Ruby 2.4 and later, and comes up with:
1047
1059
  # "TypeError: Cannot visit Integer" unless we patch like this:
1048
1060
  if ruby_version >= ::Gem::Version.new('2.4') &&
@@ -1081,11 +1093,54 @@ ActiveSupport.on_load(:active_record) do
1081
1093
  end
1082
1094
  end
1083
1095
 
1096
+ if Psych.respond_to?(:unsafe_load) && ActiveRecord.version < ::Gem::Version.new('6.1')
1097
+ Psych.class_exec do
1098
+ class << self
1099
+ alias _original_load load
1100
+ def load(yaml, *args, **kwargs)
1101
+ if caller.first.end_with?("`database_configuration'") && kwargs[:aliases].nil?
1102
+ kwargs[:aliases] = true
1103
+ end
1104
+ _original_load(yaml, *args, **kwargs)
1105
+ end
1106
+ end
1107
+ end
1108
+ end
1109
+
1110
+ # def aliased_table_for(arel_table, table_name = nil)
1111
+ # table_name ||= arel_table.name
1112
+
1113
+ # if aliases[table_name] == 0
1114
+ # # If it's zero, we can have our table_name
1115
+ # aliases[table_name] = 1
1116
+ # arel_table = arel_table.alias(table_name) if arel_table.name != table_name
1117
+ # else
1118
+ # # Otherwise, we need to use an alias
1119
+ # aliased_name = @connection.table_alias_for(yield)
1120
+
1121
+ # # Update the count
1122
+ # count = aliases[aliased_name] += 1
1123
+
1124
+ # aliased_name = "#{truncate(aliased_name)}_#{count}" if count > 1
1125
+
1126
+ # arel_table = arel_table.alias(aliased_name)
1127
+ # end
1128
+
1129
+ # arel_table
1130
+ # end
1131
+ # def aliased_table_for(table_name, aliased_name, type_caster)
1132
+
1084
1133
  class ActiveRecord::Associations::JoinDependency
1085
- if JoinBase.instance_method(:initialize).arity == 2 # Older ActiveRecord 4.x?
1086
- def initialize(base, associations, joins)
1087
- @alias_tracker = ::ActiveRecord::Associations::AliasTracker.create(base.connection, joins)
1088
- @alias_tracker.aliased_table_for(base.table_name, base.table_name) # Updates the count for base.table_name to 1
1134
+ if JoinBase.instance_method(:initialize).arity == 2 # Older ActiveRecord <= 5.1?
1135
+ def initialize(base, associations, joins, eager_loading: true)
1136
+ araat = ::ActiveRecord::Associations::AliasTracker
1137
+ if araat.respond_to?(:create_with_joins) # Rails 5.0 and 5.1
1138
+ @alias_tracker = araat.create_with_joins(base.connection, base.table_name, joins)
1139
+ @eager_loading = eager_loading # (Unused in Rails 5.0)
1140
+ else # Rails 4.2
1141
+ @alias_tracker = araat.create(base.connection, joins)
1142
+ @alias_tracker.aliased_table_for(base, base.table_name) # Updates the count for base.table_name to 1
1143
+ end
1089
1144
  tree = self.class.make_tree associations
1090
1145
 
1091
1146
  # Provide a way to find the original relation that this tree is being used for
@@ -1098,7 +1153,7 @@ ActiveSupport.on_load(:active_record) do
1098
1153
  @join_root.children.each { |child| construct_tables! @join_root, child }
1099
1154
  end
1100
1155
 
1101
- else # For ActiveRecord 5.0 - 7.1
1156
+ else # For ActiveRecord 5.2 - 7.1
1102
1157
 
1103
1158
  def initialize(base, table, associations, join_type = nil)
1104
1159
  tree = self.class.make_tree associations
@@ -1245,13 +1300,13 @@ module ActiveRecord
1245
1300
 
1246
1301
  if private_instance_methods.include?(:build_join_query)
1247
1302
  alias _brick_build_join_query build_join_query
1248
- def build_join_query(manager, buckets, *args)
1303
+ def build_join_query(manager, buckets, *args) # , **kwargs)
1249
1304
  # %%% Better way to bring relation into the mix
1250
1305
  if (aj = buckets.fetch(:association_join, nil))
1251
1306
  aj.instance_variable_set(:@relation, self)
1252
1307
  end
1253
1308
 
1254
- _brick_build_join_query(manager, buckets, *args)
1309
+ _brick_build_join_query(manager, buckets, *args) # , **kwargs)
1255
1310
  end
1256
1311
 
1257
1312
  else
@@ -1433,4 +1488,85 @@ if Gem::Specification.all_names.any? { |g| g.start_with?('ransack-') }
1433
1488
  end
1434
1489
  end
1435
1490
 
1491
+ # Keyword arguments updates for Rails <= 5.2.x and Ruby >= 3.0
1492
+ if ActiveRecord.version < ::Gem::Version.new('6.0') && ruby_version >= ::Gem::Version.new('3.0')
1493
+ admsm = ActionDispatch::MiddlewareStack::Middleware
1494
+ admsm.class_exec do
1495
+ # redefine #build
1496
+ def build(app, **kwargs)
1497
+ # puts klass.name
1498
+ if args.length > 1 && args.last.is_a?(Hash)
1499
+ kwargs.merge!(args.pop)
1500
+ end
1501
+ # binding.pry if klass == ActionDispatch::Static # ActionDispatch::Reloader
1502
+ klass.new(app, *args, **kwargs, &block)
1503
+ end
1504
+ end
1505
+
1506
+ require 'active_model'
1507
+ require 'active_model/type'
1508
+ require 'active_model/type/value'
1509
+ class ActiveModel::Type::Value
1510
+ def initialize(*args, precision: nil, limit: nil, scale: nil)
1511
+ @precision = precision
1512
+ @scale = scale
1513
+ @limit = limit
1514
+ end
1515
+ end
1516
+
1517
+ if Object.const_defined?('I18n')
1518
+ module I18n::Base
1519
+ alias _brick_translate translate
1520
+ def translate(key = nil, *args, throw: false, raise: false, locale: nil, **options)
1521
+ options.merge!(args.pop) if args.length > 0 && args.last.is_a?(Hash)
1522
+ _brick_translate(key = nil, throw: false, raise: false, locale: nil, **options)
1523
+ end
1524
+ end
1525
+ end
1526
+
1527
+ module ActionController::RequestForgeryProtection
1528
+ private
1529
+
1530
+ # Creates the authenticity token for the current request.
1531
+ def form_authenticity_token(*args, form_options: {}) # :doc:
1532
+ form_options.merge!(args.pop) if args.length > 0 && args.last.is_a?(Hash)
1533
+ masked_authenticity_token(session, form_options: form_options)
1534
+ end
1535
+ end
1536
+
1537
+ module ActiveSupport
1538
+ class MessageEncryptor
1539
+ def encrypt_and_sign(value, *args, expires_at: nil, expires_in: nil, purpose: nil)
1540
+ encrypted = if method(:_encrypt).arity == 1
1541
+ _encrypt(value) # Rails <= 5.1
1542
+ else
1543
+ if args.length > 0 && args.last.is_a?(Hash)
1544
+ expires_at ||= args.last[:expires_at]
1545
+ expires_in ||= args.last[:expires_in]
1546
+ purpose ||= args.last[:purpose]
1547
+ end
1548
+ _encrypt(value, expires_at: expires_at, expires_in: expires_in, purpose: purpose)
1549
+ end
1550
+ verifier.generate(encrypted)
1551
+ end
1552
+ end
1553
+ if const_defined?('Messages')
1554
+ class Messages::Metadata
1555
+ def self.wrap(message, *args, expires_at: nil, expires_in: nil, purpose: nil)
1556
+ if args.length > 0 && args.last.is_a?(Hash)
1557
+ expires_at ||= args.last[:expires_at]
1558
+ expires_in ||= args.last[:expires_in]
1559
+ purpose ||= args.last[:purpose]
1560
+ end
1561
+ if expires_at || expires_in || purpose
1562
+ JSON.encode new(encode(message), pick_expiry(expires_at, expires_in), purpose)
1563
+ else
1564
+ message
1565
+ end
1566
+ end
1567
+ end
1568
+ end
1569
+ end
1570
+ end
1571
+
1436
1572
  require 'brick/extensions'
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.101
4
+ version: 1.0.103
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lorin Thwaits
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-12-17 00:00:00.000000000 Z
11
+ date: 2023-01-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -164,6 +164,20 @@ dependencies:
164
164
  - - "~>"
165
165
  - !ruby/object:Gem::Version
166
166
  version: 1.42.0
167
+ - !ruby/object:Gem::Dependency
168
+ name: mysql2
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - "~>"
172
+ - !ruby/object:Gem::Version
173
+ version: '0.5'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - "~>"
179
+ - !ruby/object:Gem::Version
180
+ version: '0.5'
167
181
  - !ruby/object:Gem::Dependency
168
182
  name: pg
169
183
  requirement: !ruby/object:Gem::Requirement