brick 1.0.156 → 1.0.158

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: e001ce24602c8fef817c24a0eb88e014e990d98828474acd66d868b50dd68692
4
- data.tar.gz: 8378593754641ffc86059672fe6148ba767d91a867361b21ec8377a82903af6b
3
+ metadata.gz: 326ec09b1c28cf7c54e54bae9ea6ceed7d7e476a177be0a41af14e04f7cc3d11
4
+ data.tar.gz: 0507c22a2acf86c58737ecfedeb1a1f5ed6979b5263b28c6d6e77b0e9500ab7c
5
5
  SHA512:
6
- metadata.gz: 84fa7d9831dfca26b9b08ef46bd852c3d4f96e1f8420ff086526f97964090b3f95d71b5d66d9ff7ba47ec5f74e50938c9714731d21d0c59ac2365305ab8826bc
7
- data.tar.gz: cf790bbb66852142fe07cb1619517043dc3ef8c47b0f020b22bc635cd902fface9b52631507b2fdd9520b604c98eae00b58a1bad29ace185c8d342e79e99a062
6
+ metadata.gz: d6803593a5c3e17ef40005c405017ed4e7519024ca172a077ad377c201a7b2e2caf2a8b4bc5a0ef63f091124a44199ffd3b386db7d6b5d51352f2e3327537d81
7
+ data.tar.gz: 486b0a15422b0d3150b575c8d19c24cc3434e3fd8f35335d2e490e1e69e423995a2032de913def73b4f4df0e2789c6f4af7d61930db5cc144d0a0cb76be3ebdc
data/lib/brick/config.rb CHANGED
@@ -332,6 +332,14 @@ module Brick
332
332
  @mutex.synchronize { @table_name_prefixes = value }
333
333
  end
334
334
 
335
+ def treat_as_module
336
+ @mutex.synchronize { @treat_as_module || [] }
337
+ end
338
+
339
+ def treat_as_module=(mod_names)
340
+ @mutex.synchronize { @treat_as_module = mod_names }
341
+ end
342
+
335
343
  def order
336
344
  @mutex.synchronize { @order || {} }
337
345
  end
@@ -59,7 +59,7 @@ module ActiveRecord
59
59
  end
60
60
 
61
61
  def is_brick?
62
- instance_variables.include?(:@_brick_built) && instance_variable_get(:@_brick_built)
62
+ instance_variables.include?(:@_brick_relation) && instance_variable_get(:@_brick_relation)
63
63
  end
64
64
 
65
65
  def _assoc_names
@@ -89,6 +89,13 @@ module ActiveRecord
89
89
  def brick_foreign_type(assoc)
90
90
  reflect_on_association(assoc).foreign_type || "#{assoc}_type"
91
91
  end
92
+
93
+ def _brick_all_fields
94
+ rtans = if respond_to?(:rich_text_association_names)
95
+ rich_text_association_names&.map { |rtan| rtan.to_s.start_with?('rich_text_') ? rtan[10..-1] : rtan }
96
+ end
97
+ columns_hash.keys.map(&:to_sym) + (rtans || [])
98
+ end
92
99
  end
93
100
 
94
101
  def self._brick_primary_key(relation = nil)
@@ -357,7 +364,7 @@ module ActiveRecord
357
364
  # Support nested attributes which use the friendly_id gem
358
365
  assoc.klass._brick_nested_friendly_id if Object.const_defined?('FriendlyId') &&
359
366
  assoc.klass.instance_variable_get(:@friendly_id_config)
360
- new_attrib_text = assoc.klass._brick_find_permits(assoc, (new_permits = assoc.klass.columns_hash.keys.map(&:to_sym)), done_permits)
367
+ new_attrib_text = assoc.klass._brick_find_permits(assoc, (new_permits = assoc.klass._brick_all_fields), done_permits)
361
368
  new_permits << :_destroy
362
369
  current_permits << { "#{assoc.name}_attributes".to_sym => new_permits }
363
370
  s << "#{assoc.name}_attributes: #{new_attrib_text}"
@@ -544,11 +551,9 @@ module ActiveRecord
544
551
  wheres = {}
545
552
  params.each do |k, v|
546
553
  k = k.to_s # Rails < 4.2 comes in as a symbol
547
- next if ['_brick_schema', '_brick_order',
548
- '_brick_erd', '_brick_exclude', '_brick_unexclude',
549
- '_brick_page', '_brick_page_size', '_brick_offset', '_brick_limit',
550
- '_brick_is_api', 'controller', 'action'].include?(k)
554
+ next unless k.start_with?('__')
551
555
 
556
+ k = k[2..-1] # Take off leading "__"
552
557
  if (where_col = (ks = k.split('.')).last)[-1] == '!'
553
558
  where_col = where_col[0..-2]
554
559
  end
@@ -1163,6 +1168,9 @@ if ActiveSupport::Dependencies.respond_to?(:autoload_module!) # %%% Only works w
1163
1168
  end
1164
1169
 
1165
1170
  ::Brick::ADD_CONST_MISSING = lambda do
1171
+ return if @_brick_const_missing_done
1172
+
1173
+ @_brick_const_missing_done = true
1166
1174
  alias _brick_const_missing const_missing
1167
1175
  def const_missing(*args)
1168
1176
  requested = args.first.to_s
@@ -1187,9 +1195,13 @@ end
1187
1195
  end
1188
1196
  base_module = if self < ActiveRecord::Migration || !self.name
1189
1197
  brick_root || Object
1190
- elsif (split_self_name || self.name.split('::')).length > 1 # Classic mode
1198
+ elsif split_self_name&.length&.> 1 # Classic mode
1191
1199
  begin
1192
- return self._brick_const_missing(*args)
1200
+ base = self
1201
+ unless (base_goal = requested.split('::')[0..-2].join('::')).empty?
1202
+ base = base.parent while base.name != base_goal && base != Object
1203
+ end
1204
+ return base._brick_const_missing(*args)
1193
1205
 
1194
1206
  rescue NameError # %%% Avoid the error "____ cannot be autoloaded from an anonymous class or module"
1195
1207
  return self.const_get(args.first) if self.const_defined?(args.first)
@@ -1294,10 +1306,16 @@ end
1294
1306
  # Build out a module for the schema if it's namespaced
1295
1307
  # schema_name = schema_name.camelize
1296
1308
  base_module.const_set(schema_name.to_sym, (built_module = Module.new))
1297
-
1298
1309
  [built_module, "module #{schema_name}; end\n"]
1299
1310
  # %%% Perhaps an option to use the first module just as schema, and additional modules as namespace with a table name prefix applied
1300
1311
 
1312
+ # MODULE (overrides from "treat_as_module")
1313
+ elsif (::Brick.enable_models? || ::Brick.enable_controllers?) &&
1314
+ (possible_module = (base_module == Object ? '' : "#{base_module.name}::") + class_name) &&
1315
+ ::Brick.config.treat_as_module.include?(possible_module)
1316
+ base_module.const_set(class_name.to_sym, (built_module = Module.new))
1317
+ [built_module, "module #{possible_module}; end\n"]
1318
+
1301
1319
  # AVO Resource
1302
1320
  elsif base_module == Object && Object.const_defined?('Avo') && ::Avo.respond_to?(:railtie_namespace) && requested.end_with?('Resource') &&
1303
1321
  # Expect that anything called MotorResource or SpinaResource could be from those administrative gems
@@ -1469,6 +1487,7 @@ class Object
1469
1487
  code = +"class #{full_name} < #{base_model.name}\n"
1470
1488
  built_model = Class.new(base_model) do |new_model_class|
1471
1489
  (schema_module || Object).const_set((chosen_name = (inheritable_name || model_name)).to_sym, new_model_class)
1490
+ @_brick_relation = relation
1472
1491
  if inheritable_name
1473
1492
  new_model_class.define_singleton_method :inherited do |subclass|
1474
1493
  super(subclass)
@@ -1478,7 +1497,7 @@ class Object
1478
1497
  puts "should be \"class #{model_name} < #{inheritable_name}\"\n (not \"#{subclass.name} < #{inheritable_name}\")"
1479
1498
  end
1480
1499
  end
1481
- self.abstract_class = true
1500
+ new_model_class.abstract_class = true
1482
1501
  code << " self.abstract_class = true\n"
1483
1502
  elsif Object.const_defined?('BCrypt') && relation[:cols].include?('password_digest') &&
1484
1503
  !instance_methods.include?(:password) && respond_to?(:has_secure_password)
@@ -1489,7 +1508,7 @@ class Object
1489
1508
  # Accommodate singular or camel-cased table names such as "order_detail" or "OrderDetails"
1490
1509
  code << " self.table_name = '#{self.table_name = matching}'\n" if inheritable_name || self.table_name != matching
1491
1510
  if (inh_col = ::Brick.config.sti_type_column.find { |_k, v| v.include?(matching) }&.first)
1492
- self.inheritance_column = inh_col
1511
+ new_model_class.inheritance_column = inh_col
1493
1512
  code << " self.inheritance_column = '#{inh_col}'\n"
1494
1513
  end
1495
1514
 
@@ -1542,19 +1561,19 @@ class Object
1542
1561
  unless is_sti
1543
1562
  fks = relation[:fks] || {}
1544
1563
  # Do the bulk of the has_many / belongs_to processing, and store details about HMT so they can be done at the very last
1545
- fks.each_with_object(Hash.new { |h, k| h[k] = [] }) do |fk, hmts|
1546
- # The key in each hash entry (fk.first) is the constraint name
1547
- inverse_assoc_name = (assoc = fk.last)[:inverse]&.fetch(:assoc_name, nil)
1548
- if (invs = assoc[:inverse_table]).is_a?(Array)
1549
- if assoc[:is_bt]
1550
- invs = invs.first # Just do the first one of what would be multiple identical polymorphic belongs_to
1551
- else
1552
- invs.each { |inv| build_bt_or_hm(full_name, relations, relation, hmts, assoc, inverse_assoc_name, inv, code) }
1553
- end
1554
- end
1555
- build_bt_or_hm(full_name, relations, relation, hmts, assoc, inverse_assoc_name, invs, code) unless invs.is_a?(Array)
1556
- hmts
1557
- end
1564
+ hmts = fks.each_with_object(Hash.new { |h, k| h[k] = [] }) do |fk, hmts2|
1565
+ # The key in each hash entry (fk.first) is the constraint name
1566
+ inverse_assoc_name = (assoc = fk.last)[:inverse]&.fetch(:assoc_name, nil)
1567
+ if (invs = assoc[:inverse_table]).is_a?(Array)
1568
+ if assoc[:is_bt]
1569
+ invs = invs.first # Just do the first one of what would be multiple identical polymorphic belongs_to
1570
+ else
1571
+ invs.each { |inv| build_bt_or_hm(full_name, relations, relation, hmts2, assoc, inverse_assoc_name, inv, code) }
1572
+ end
1573
+ else
1574
+ build_bt_or_hm(full_name, relations, relation, hmts2, assoc, inverse_assoc_name, invs, code)
1575
+ end
1576
+ end
1558
1577
  # # Not NULLables
1559
1578
  # # %%% For the minute we've had to pull this out because it's been troublesome implementing the NotNull validator
1560
1579
  # relation[:cols].each do |col, datatype|
@@ -1572,58 +1591,54 @@ class Object
1572
1591
  # self.broadcasts_to ->(model) { (model&.class&.name || chosen_name).underscore.pluralize.to_sym }
1573
1592
  # code << " broadcasts_to ->(#{chosen_name}) { #{chosen_name}&.class&.name&.underscore&.pluralize&.to_sym }\n"
1574
1593
  # end
1575
- end # class definition
1576
- # Having this separate -- will this now work out better?
1577
- built_model.class_exec do
1578
- @_brick_built = true
1579
- @_brick_relation = relation
1580
- hmts&.each do |hmt_fk, hms|
1581
- hmt_fk = hmt_fk.tr('.', '_')
1582
- hms.each do |hm|
1583
- # %%% Need to confirm that HMTs work when they are built from has_manys with custom names
1584
- through = ::Brick.config.schema_behavior[:multitenant] ? hm.first[:assoc_name] : hm.first[:inverse_table].tr('.', '_').pluralize
1585
- options = {}
1586
- hmt_name = if hms.length > 1
1587
- if hms[0].first[:inverse][:assoc_name] == hms[1].first[:inverse][:assoc_name] # Same BT names pointing back to us? (Most common scenario)
1588
- "#{hmt_fk}_through_#{hm.first[:assoc_name]}"
1589
- else # Use BT names to provide uniqueness
1590
- if self.name.underscore.singularize == hm.first[:alternate_name]
1591
- # Has previously been:
1592
- # # If it folds back on itself then look at the other side
1593
- # # (At this point just infer the source be the inverse of the first has_many that
1594
- # # we find that is not ourselves. If there are more than two then uh oh, can't
1595
- # # yet handle that rare circumstance!)
1596
- # other = hms.find { |hm1| hm1 != hm } # .first[:fk]
1597
- # options[:source] = other.first[:inverse][:assoc_name].to_sym
1598
- # And also has been:
1599
- # hm.first[:inverse][:assoc_name].to_sym
1600
- options[:source] = hm.last.to_sym
1601
- else
1602
- through = hm.first.fetch(:alternate_chosen_name, hm.first[:alternate_name])
1603
- end
1604
- singular_assoc_name = hm.first[:inverse][:assoc_name].singularize
1605
- "#{singular_assoc_name}_#{hmt_fk}"
1606
- end
1607
- else
1608
- hmt_fk
1609
- end
1610
- options[:through] = through.to_sym
1611
- if relation[:fks].any? { |k, v| v[:assoc_name] == hmt_name }
1612
- hmt_name = "#{hmt_name.singularize}_#{hm.first[:assoc_name]}"
1613
- # Was:
1614
- # options[:class_name] = hm.first[:inverse_table].singularize.camelize
1615
- # options[:foreign_key] = hm.first[:fk].to_sym
1616
- far_assoc = relations[hm.first[:inverse_table]][:fks].find { |_k, v| v[:assoc_name] == hm.last }
1617
- options[:class_name] = far_assoc.last[:inverse_table].singularize.camelize
1618
- options[:foreign_key] = far_assoc.last[:fk].to_sym
1619
- end
1620
- options[:source] ||= hm.last.to_sym unless hmt_name.singularize == hm.last
1621
- code << " has_many :#{hmt_name}#{options.map { |opt| ", #{opt.first}: #{opt.last.inspect}" }.join}\n"
1622
- self.send(:has_many, hmt_name.to_sym, **options)
1594
+
1595
+ hmts&.each do |hmt_fk, hms|
1596
+ hmt_fk = hmt_fk.tr('.', '_')
1597
+ hms.each do |hm|
1598
+ # %%% Need to confirm that HMTs work when they are built from has_manys with custom names
1599
+ through = ::Brick.config.schema_behavior[:multitenant] ? hm.first[:assoc_name] : hm.first[:inverse_table].tr('.', '_').pluralize
1600
+ options = {}
1601
+ hmt_name = if hms.length > 1
1602
+ if hms[0].first[:inverse][:assoc_name] == hms[1].first[:inverse][:assoc_name] # Same BT names pointing back to us? (Most common scenario)
1603
+ "#{hmt_fk}_through_#{hm.first[:assoc_name]}"
1604
+ else # Use BT names to provide uniqueness
1605
+ if self.name.underscore.singularize == hm.first[:alternate_name]
1606
+ # Has previously been:
1607
+ # # If it folds back on itself then look at the other side
1608
+ # # (At this point just infer the source be the inverse of the first has_many that
1609
+ # # we find that is not ourselves. If there are more than two then uh oh, can't
1610
+ # # yet handle that rare circumstance!)
1611
+ # other = hms.find { |hm1| hm1 != hm } # .first[:fk]
1612
+ # options[:source] = other.first[:inverse][:assoc_name].to_sym
1613
+ # And also has been:
1614
+ # hm.first[:inverse][:assoc_name].to_sym
1615
+ options[:source] = hm.last.to_sym
1616
+ else
1617
+ through = hm.first.fetch(:alternate_chosen_name, hm.first[:alternate_name])
1618
+ end
1619
+ singular_assoc_name = hm.first[:inverse][:assoc_name].singularize
1620
+ "#{singular_assoc_name}_#{hmt_fk}"
1621
+ end
1622
+ else
1623
+ hmt_fk
1624
+ end
1625
+ options[:through] = through.to_sym
1626
+ if relation[:fks].any? { |k, v| v[:assoc_name] == hmt_name }
1627
+ hmt_name = "#{hmt_name.singularize}_#{hm.first[:assoc_name]}"
1628
+ # Was:
1629
+ # options[:class_name] = hm.first[:inverse_table].singularize.camelize
1630
+ # options[:foreign_key] = hm.first[:fk].to_sym
1631
+ far_assoc = relations[hm.first[:inverse_table]][:fks].find { |_k, v| v[:assoc_name] == hm.last }
1632
+ options[:class_name] = far_assoc.last[:inverse_table].singularize.camelize
1633
+ options[:foreign_key] = far_assoc.last[:fk].to_sym
1623
1634
  end
1635
+ options[:source] ||= hm.last.to_sym unless hmt_name.singularize == hm.last
1636
+ code << " has_many :#{hmt_name}#{options.map { |opt| ", #{opt.first}: #{opt.last.inspect}" }.join}\n"
1637
+ new_model_class.send(:has_many, hmt_name.to_sym, **options)
1624
1638
  end
1625
1639
  end
1626
1640
  code << "end # model #{full_name}\n"
1641
+ end # model class definition
1627
1642
  [built_model, code]
1628
1643
  end
1629
1644
 
@@ -2144,7 +2159,18 @@ class Object
2144
2159
  code << " end\n"
2145
2160
  self.define_method :new do
2146
2161
  _schema, @_is_show_schema_list = ::Brick.set_db_schema(params)
2147
- if (new_obj = model.new).respond_to?(:serializable_hash)
2162
+ new_params = model.attribute_names.each_with_object({}) do |a, s|
2163
+ if (val = params["__#{a}"])
2164
+ # val = case new_obj.class.column_for_attribute(a).type
2165
+ # when :datetime, :date, :time, :timestamp
2166
+ # val.
2167
+ # else
2168
+ # val
2169
+ # end
2170
+ s[a] = val
2171
+ end
2172
+ end
2173
+ if (new_obj = model.new(new_params)).respond_to?(:serializable_hash)
2148
2174
  # Convert any Filename objects with nil into an empty string so that #encode can be called on them
2149
2175
  new_obj.serializable_hash.each do |k, v|
2150
2176
  new_obj.send("#{k}=", ActiveStorage::Filename.new('')) if v.is_a?(ActiveStorage::Filename) && !v.instance_variable_get(:@filename)
@@ -2291,7 +2317,7 @@ class Object
2291
2317
 
2292
2318
  if is_need_params
2293
2319
  code << " def #{params_name}\n"
2294
- permits_txt = model._brick_find_permits(model, permits = model.columns_hash.keys.map(&:to_sym))
2320
+ permits_txt = model._brick_find_permits(model, permits = model._brick_all_fields)
2295
2321
  code << " params.require(:#{require_name = model.name.underscore.tr('/', '_')
2296
2322
  }).permit(#{permits_txt.map(&:inspect).join(', ')})\n"
2297
2323
  code << " end\n"
@@ -713,12 +713,9 @@ window.addEventListener(\"popstate\", linkSchemas);
713
713
  next unless @_brick_model.instance_methods.include?(through) &&
714
714
  (associative = @_brick_model._br_associatives.fetch(hm.first, nil))
715
715
 
716
- tbl_nm = if (source = hm_assoc.source_reflection).macro == :has_many
717
- source.inverse_of&.name # For HM -> HM style HMT
718
- else # belongs_to or has_one
719
- hm_assoc.through_reflection&.name # for standard HMT, which is HM -> BT
720
- end
721
- # If there is no inverse available for the source belongs_to association, make one based on the class name
716
+ # Should handle standard HMT, which is HM -> BT, as well as HM -> HM style HMT
717
+ tbl_nm = hm_assoc.source_reflection&.inverse_of&.name
718
+ # If there is no inverse available for the source belongs_to association, infer one based on the class name
722
719
  unless tbl_nm
723
720
  tbl_nm = associative.class_name.underscore
724
721
  tbl_nm.slice!(0) if tbl_nm[0] == '/'
@@ -750,12 +747,12 @@ window.addEventListener(\"popstate\", linkSchemas);
750
747
  (hm_fk_name.is_a?(String) && hm_fk_name.include?('.')) # HMT? (Could do a better check for this)
751
748
  predicates = path_keys(hm_assoc, hm_fk_name, pk).map do |k, v|
752
749
  if v == '[sti_type]'
753
- "'#{k}': (@#{obj_name}.#{hm_assoc.active_record.inheritance_column})&.constantize&.base_class&.name"
750
+ "'__#{k}': (@#{obj_name}.#{hm_assoc.active_record.inheritance_column})&.constantize&.base_class&.name"
754
751
  else
755
- v.is_a?(String) ? "'#{k}': '#{v}'" : "'#{k}': @#{obj_name}.#{v}"
752
+ v.is_a?(String) ? "'__#{k}': '#{v}'" : "'__#{k}': @#{obj_name}.#{v}"
756
753
  end
757
754
  end.join(', ')
758
- "<%= link_to '#{assoc_name}', #{hm_assoc.klass._brick_index}_path({ #{predicates} }) %>\n"
755
+ "<%= link_to '#{assoc_name}', #{hm_assoc.klass._brick_index}_path(predicates = { #{predicates} }) %>\n"
759
756
  else
760
757
  puts "Warning: has_many :#{hm_assoc.name} in model #{hm_assoc.active_record.name} currently looks for a foreign key called \"#{hm_assoc.foreign_key}\". "\
761
758
  "Instead it should use the clause \"foreign_key: :#{hm_assoc.inverse_of&.foreign_key}\"."
@@ -897,6 +894,9 @@ tr th {
897
894
  tr th a {
898
895
  color: #80FFB8;
899
896
  }
897
+ .add-hm-related {
898
+ float: right;
899
+ }
900
900
 
901
901
  tr th, tr td {
902
902
  padding: 0.2em 0.5em;
@@ -1169,7 +1169,10 @@ if (window.brickFontFamily) {
1169
1169
  x.style.fontFamily = brickFontFamily.toString();
1170
1170
  });
1171
1171
  }
1172
- </script>"
1172
+ </script>
1173
+ <% if (apartment_default_schema = ::Brick.apartment_multitenant && ::Brick.apartment_default_tenant)
1174
+ Apartment::Tenant.switch!(apartment_default_schema)
1175
+ end %>"
1173
1176
 
1174
1177
  erd_markup = if @_brick_model
1175
1178
  "<div id=\"mermaidErd\" class=\"mermaid\">
@@ -1658,7 +1661,7 @@ end
1658
1661
  # path_options = [obj.#{pk}]
1659
1662
  # path_options << { '_brick_schema': } if
1660
1663
  options = {}
1661
- if ::Brick.config.path_prefix
1664
+ if ::Brick.config.path_prefix || (obj.class.table_name.singularize == obj.class.table_name)
1662
1665
  path_helper = obj.new_record? ? #{model_name}._brick_index : #{model_name}._brick_index(:singular)
1663
1666
  options[:url] = send(\"#\{path_helper}_path\".to_sym, obj)
1664
1667
  end
@@ -1673,12 +1676,22 @@ end
1673
1676
  @#{obj_name}.send(\"#\{model.brick_foreign_type(v.first)}=\", v[1].first&.first&.name)
1674
1677
  end
1675
1678
  end if @#{obj_name}.new_record?
1676
- @#{obj_name}.attributes.each do |k, val|
1677
- next if !(col = #{model_name}.columns_hash[k]) ||
1678
- (#{(pk.map(&:to_s) || []).inspect}.include?(k) && !bts.key?(k)) ||
1679
- ::Brick.config.metadata_columns.include?(k) %>
1679
+ rtans = #{model_name}.rich_text_association_names if #{model_name}.respond_to?(:rich_text_association_names)
1680
+ (#{model_name}.column_names + (rtans || [])).each do |k|
1681
+ next if (#{(pk.map(&:to_s) || []).inspect}.include?(k) && !bts.key?(k)) ||
1682
+ ::Brick.config.metadata_columns.include?(k)
1683
+
1684
+ col = #{model_name}.columns_hash[k]
1685
+ if !col && rtans&.include?(k)
1686
+ k = k[10..-1] if k.start_with?('rich_text_')
1687
+ col = (rt_col ||= ActiveRecord::ConnectionAdapters::Column.new(
1688
+ '', nil, ActiveRecord::ConnectionAdapters::SqlTypeMetadata.new(sql_type: 'varchar', type: :text)
1689
+ )
1690
+ )
1691
+ end
1692
+ val = @#{obj_name}.attributes[k] %>
1680
1693
  <tr>
1681
- <th class=\"show-field\"<%= \" title=\\\"#\{col.comment}\\\"\".html_safe if col.respond_to?(:comment) && !col.comment.blank? %>>
1694
+ <th class=\"show-field\"<%= \" title=\\\"#\{col&.comment}\\\"\".html_safe if col&.respond_to?(:comment) && !col&.comment.blank? %>>
1682
1695
  <% has_fields = true
1683
1696
  if (bt = bts[k])
1684
1697
  # Add a final member in this array with descriptive options to be used in <select> drop-downs
@@ -1710,7 +1723,7 @@ end
1710
1723
  collection&.brick_(:each) do |obj|
1711
1724
  option_detail << [
1712
1725
  obj.brick_descrip(
1713
- descrip_cols&.first&.map { |col| obj.send(col.last) },
1726
+ descrip_cols&.first&.map { |col2| obj.send(col2.last) },
1714
1727
  obj_pk
1715
1728
  ), obj.send(obj_pk)
1716
1729
  ]
@@ -1763,7 +1776,14 @@ end
1763
1776
  end"}"
1764
1777
  end
1765
1778
  s << "<table id=\"#{hm_name}\" class=\"shadow\">
1766
- <tr><th>#{hm[1]}#{' poly' if hm[0].options[:as]} #{hm[3]}</th></tr>
1779
+ <tr><th>#{hm[1]}#{' poly' if hm[0].options[:as]} #{hm[3]}
1780
+ <span class = \"add-hm-related\"><%=
1781
+ pk_val = (obj_pk = model.primary_key).is_a?(String) ? obj.send(obj_pk) : obj_pk.map { |pk_part| obj.send(pk_part) }
1782
+ pk_val_arr = [pk_val] unless pk_val.is_a?(Array)
1783
+ link_to('<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"#fff\" d=\"M24 10h-10v-10h-4v10h-10v4h10v10h4v-10h10z\"/></svg>'.html_safe,
1784
+ new_#{hm.first.klass._brick_index(:singular)}_path(predicates))
1785
+ %></span>
1786
+ </th></tr>
1767
1787
  <% if (assoc = @#{obj_name}.class.reflect_on_association(:#{hm_name})).macro == :has_one &&
1768
1788
  assoc.options&.fetch(:through, nil).nil?
1769
1789
  # In order to apply DSL properly, evaluate this HO the other way around as if it were as a BT
@@ -2034,9 +2054,7 @@ document.querySelectorAll(\"input, select\").forEach(function (inp) {
2034
2054
  alias _brick_render_template render_template
2035
2055
  def render_template(view, template, layout_name, *args)
2036
2056
  layout_name = nil if (is_brick = template.instance_variable_get(:@is_brick)) && layout_name.is_a?(Proc)
2037
- result = _brick_render_template(view, template, layout_name, *args)
2038
- Apartment::Tenant.switch!(::Brick.apartment_default_tenant) if is_brick && ::Brick.apartment_multitenant
2039
- result
2057
+ _brick_render_template(view, template, layout_name, *args)
2040
2058
  end
2041
2059
  end # TemplateRenderer
2042
2060
  end
@@ -20,7 +20,14 @@ module Brick::Rails::FormBuilder
20
20
 
21
21
  html_options[:prompt] = "Select #{bt_name}"
22
22
  out << self.select(method.to_sym, bt[3], { value: val || '^^^brick_NULL^^^' }, html_options)
23
- bt_link = if (bt_obj = bt_class&.find_by(bt_pair[1] => val))
23
+ bt_obj = nil
24
+ begin
25
+ bt_obj = bt_class&.find_by(bt_pair[1] => val)
26
+ rescue ActiveRecord::SubclassNotFound => e
27
+ # %%% Would be cool to indicate to the user that a subclass is missing.
28
+ # Its name starts at: e.message.index('failed to locate the subclass: ') + 31
29
+ end
30
+ bt_link = if bt_obj
24
31
  bt_path = template.send(
25
32
  "#{bt_class.base_class._brick_index(:singular)}_path".to_sym,
26
33
  bt_obj.send(bt_class.primary_key.to_sym)
@@ -71,11 +78,11 @@ module Brick::Rails::FormBuilder
71
78
  # Postgres naturally uses the +uuid_generate_v4()+ function from the uuid-ossp extension
72
79
  # If it's not yet enabled then: create extension \"uuid-ossp\";
73
80
  # ActiveUUID gem created a new :uuid type
74
- out << val
81
+ out << val if val
75
82
  when :ltree
76
83
  # In Postgres labels of data stored in a hierarchical tree-like structure
77
84
  # If it's not yet enabled then: create extension ltree;
78
- out << val
85
+ out << val if val
79
86
  when :binary
80
87
  is_revert = false
81
88
  if val
@@ -95,12 +102,12 @@ module Brick::Rails::FormBuilder
95
102
  else
96
103
  eheij = ActiveSupport::JSON::Encoding.escape_html_entities_in_json
97
104
  ActiveSupport::JSON::Encoding.escape_html_entities_in_json = false if eheij
98
- val_str = val.to_json
105
+ val_str = val&.to_json
99
106
  ActiveSupport::JSON::Encoding.escape_html_entities_in_json = eheij
100
107
  end
101
108
  # Because there are so danged many quotes in JSON, escape them specially by converting to backticks.
102
109
  # (and previous to this, escape backticks with our own goofy code of ^^br_btick__ )
103
- out << (json_field = self.hidden_field(method.to_sym, { class: 'jsonpicker', value: val_str.gsub('`', '^^br_btick__').tr('\"', '`').html_safe }))
110
+ out << (json_field = self.hidden_field(method.to_sym, { class: 'jsonpicker', value: val_str&.gsub('`', '^^br_btick__')&.tr('\"', '`')&.html_safe }))
104
111
  out << "<div id=\"_br_json_#{self.field_id(method)}\"></div>"
105
112
  else
106
113
  is_revert = false
@@ -157,7 +157,7 @@ module Brick::Rails::FormTags
157
157
  out << link_to(ho_txt, send("#{hm_klass.base_class._brick_index(:singular)}_path".to_sym, ho_id))
158
158
  end
159
159
  elsif obj.respond_to?(ct_col = hms_col[1].to_sym) && (ct = obj.send(ct_col)&.to_i)&.positive?
160
- predicates = hms_col[2].each_with_object({}) { |v, s| s[v.first] = v.last.is_a?(String) ? v.last : obj.send(v.last) }
160
+ predicates = hms_col[2].each_with_object({}) { |v, s| s["__#{v.first}"] = v.last.is_a?(String) ? v.last : obj.send(v.last) }
161
161
  predicates.each { |k, v| predicates[k] = klass.name if v == '[sti_type]' }
162
162
  out << "#{link_to("#{ct || 'View'} #{hms_col.first}",
163
163
  send("#{hm_klass._brick_index}_path".to_sym, predicates))}\n"
@@ -5,7 +5,7 @@ module Brick
5
5
  module VERSION
6
6
  MAJOR = 1
7
7
  MINOR = 0
8
- TINY = 156
8
+ TINY = 158
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
@@ -113,21 +113,23 @@ module Brick
113
113
  def set_db_schema(params = nil)
114
114
  # If Apartment::Tenant.current is not still the default (usually 'public') then an elevator has brought us into
115
115
  # a different tenant. If so then don't allow schema navigation.
116
- chosen = if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL' &&
117
- (current_schema = (ActiveRecord::Base.execute_sql('SELECT current_schemas(true)').first['current_schemas'][1..-2]
118
- .split(',') - ['pg_catalog', 'pg_toast', 'heroku_ext']).first) &&
119
- (is_show_schema_list = (apartment_multitenant && current_schema == ::Brick.default_schema)) &&
120
- (schema = (params ? params['_brick_schema'] : ::Brick.default_schema)) &&
121
- ::Brick.db_schemas&.key?(schema)
122
- Apartment::Tenant.switch!(schema)
123
- schema
124
- elsif ::Brick.test_schema
125
- is_show_schema_list = true
126
- Apartment::Tenant.switch!(::Brick.test_schema)
127
- ::Brick.test_schema
128
- else
129
- current_schema # Just return the current schema
130
- end
116
+ if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL' && apartment_multitenant
117
+ current_schema = (ActiveRecord::Base.execute_sql('SELECT current_schemas(true)')
118
+ .first['current_schemas'][1..-2]
119
+ .split(',') - ['pg_catalog', 'pg_toast', 'heroku_ext']).first
120
+ is_show_schema_list = current_schema == ::Brick.default_schema
121
+ schema = (is_show_schema_list && params && params['_brick_schema']) || ::Brick.default_schema
122
+ chosen = if is_show_schema_list && ::Brick.db_schemas&.key?(schema)
123
+ Apartment::Tenant.switch!(schema)
124
+ schema
125
+ elsif ::Brick.test_schema
126
+ is_show_schema_list = true
127
+ Apartment::Tenant.switch!(::Brick.test_schema)
128
+ ::Brick.test_schema
129
+ else
130
+ current_schema # Just return the current schema
131
+ end
132
+ end
131
133
  [chosen == ::Brick.default_schema ? nil : chosen, is_show_schema_list]
132
134
  end
133
135
 
@@ -219,8 +221,8 @@ module Brick
219
221
  hm_models = ActiveRecord::Base.descendants.select do |m|
220
222
  m.reflect_on_all_associations.any? { |assoc| !assoc.belongs_to? && assoc.options[:as]&.to_sym == a.name }
221
223
  end
222
- # No need to include subclassed models if their parent is already in the list
223
- hm_models.reject! { |m| hm_models.any? { |parent| parent != m && m < parent } }
224
+ # No need to include models with no table, or subclassed models if their parent is already in the list
225
+ hm_models.reject! { |m| !m.table_exists? || hm_models.any? { |parent| parent != m && m < parent } }
224
226
  if hm_models.empty?
225
227
  puts "Missing any real indication as to which models \"has_many\" this polymorphic BT in model #{a.active_record.name}:"
226
228
  puts " belongs_to :#{a.name}, polymorphic: true"
@@ -416,6 +418,12 @@ module Brick
416
418
  Brick.config.table_name_prefixes = value
417
419
  end
418
420
 
421
+ # @api public
422
+ # Causes Decidim to work with this line: Brick.treat_as_module = ['Decidim::ContentBlocks']
423
+ def treat_as_module=(value)
424
+ Brick.config.treat_as_module = value
425
+ end
426
+
419
427
  # @api public
420
428
  def metadata_columns=(value)
421
429
  Brick.config.metadata_columns = value
@@ -1147,8 +1155,11 @@ ActiveSupport.on_load(:active_record) do
1147
1155
  class << self
1148
1156
  def execute_sql(sql, *param_array)
1149
1157
  param_array = param_array.first if param_array.length == 1 && param_array.first.is_a?(Array)
1150
- if ['OracleEnhanced', 'SQLServer'].include?(ActiveRecord::Base.connection.adapter_name)
1158
+ case ActiveRecord::Base.connection.adapter_name
1159
+ when 'OracleEnhanced', 'SQLServer'
1151
1160
  connection.exec_query(send(:sanitize_sql_array, [sql] + param_array)).rows
1161
+ when 'Trilogy'
1162
+ connection.execute(send(:sanitize_sql_array, [sql] + param_array)).rows
1152
1163
  else
1153
1164
  connection.execute(send(:sanitize_sql_array, [sql] + param_array))
1154
1165
  end
@@ -1452,7 +1463,8 @@ ActiveSupport.on_load(:active_record) do
1452
1463
  class << self
1453
1464
  alias _original_load load
1454
1465
  def load(yaml, *args, **kwargs)
1455
- if kwargs[:aliases].nil? && caller[0..4].any? { |line| line.end_with?("`database_configuration'") }
1466
+ if kwargs[:aliases].nil? && caller[0..4].any? { |line| line.end_with?("`database_configuration'") ||
1467
+ line.end_with?("`secrets'") }
1456
1468
  kwargs[:aliases] = true
1457
1469
  end
1458
1470
  _original_load(yaml, *args, **kwargs)
@@ -28,7 +28,7 @@ module Brick
28
28
  relations = ::Brick.relations
29
29
  if is_brick_file
30
30
  # Need to remove any currently-existing additional_references so that it doesn't cloud the discovery process:
31
- ::Brick.config.additional_references.each do |ar|
31
+ ::Brick.config.additional_references&.each do |ar|
32
32
  if (fks = relations.fetch(ar[0], nil)&.fetch(:fks, nil))
33
33
  fks.delete(fks.find { |k, v| v[:is_bt] && k.start_with?('(brick) ') && v[:fk] == ar[1] }&.first)
34
34
  end