brick 1.0.123 → 1.0.125

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: baad039242ee5623097ad6170fec07e5feb0eb2fed6d41a9079f0775cccaceea
4
- data.tar.gz: 603377f6b2c4437c448630bce6581d3650b8dfc13187e82afbaab1d425d171e2
3
+ metadata.gz: dc3caefac264427d0a4a7efa11160fc3b7c1cc6c82afa01bf7340941dbab18e1
4
+ data.tar.gz: 7d9aebd13fe256d43ee3ec24f94065a64ab4955e3ee379023352013db59fe3ba
5
5
  SHA512:
6
- metadata.gz: 1f4e48c1a437684929a81ace3e75ab15bb627c006680dba5a7454825fba358142560712f016bf944575aefeb27655716c3212d72a2b9b5a083a09cd2dfa8537c
7
- data.tar.gz: ea9c376866230e4c2bab7b6c34dfc435a8cf81b568bc22af3a0e69497accca1620152ae311eab6c96649b79ecedf978b880afa88888dc6d1c4d3700bc3ef3060
6
+ metadata.gz: c02e8bdceca8f726d24a8f7eddb6057427d2e31c3db3934bd72da0393509aaf2d5e7246ba65e8bdd773fc3d995c538567137dec729eb2d062ba0f7e006ace7c6
7
+ data.tar.gz: c6061b00b761638ebd0f9750423643615c08dffb9c62bec423d1bf5c047dcfa82c9b2bccaea1491d9f7a4c23932b9299be1254e708e26ca624165a5695557c63
@@ -68,6 +68,12 @@ module ActiveRecord
68
68
  self
69
69
  end
70
70
  end
71
+
72
+ def json_column?(col)
73
+ col.type == :json || ::Brick.config.json_columns[table_name]&.include?(col.name) ||
74
+ ((attr_types = attribute_types[col.name]).respond_to?(:coder) &&
75
+ (attr_types.coder.is_a?(Class) ? attr_types.coder : attr_types.coder&.class)&.name&.end_with?('JSON'))
76
+ end
71
77
  end
72
78
 
73
79
  def self._brick_primary_key(relation = nil)
@@ -638,6 +644,7 @@ module ActiveRecord
638
644
  # Add derived table JOIN for the has_many counts
639
645
  nix = []
640
646
  klass._br_hm_counts.each do |k, hm|
647
+ num_bt_things = 0
641
648
  count_column = if hm.options[:through]
642
649
  # Build the chain of JOINs going to the final destination HMT table
643
650
  # (Usually just one JOIN, but could be many.)
@@ -668,7 +675,9 @@ module ActiveRecord
668
675
  from_clause << if (src_ref = a.source_reflection).macro == :belongs_to
669
676
  nm = hmt_assoc.source_reflection.inverse_of&.name
670
677
  link_back << nm
671
- "ON br_t#{idx}.id = br_t#{idx - 1}.#{a.foreign_key}"
678
+ num_bt_things += 1
679
+ # puts "BT #{a.table_name}"
680
+ "ON br_t#{idx}.#{a.active_record.primary_key} = br_t#{idx - 1}.#{a.foreign_key}"
672
681
  elsif src_ref.options[:as]
673
682
  "ON br_t#{idx}.#{src_ref.type} = '#{src_ref.active_record.name}'" + # "polymorphable_type"
674
683
  " AND br_t#{idx}.#{src_ref.foreign_key} = br_t#{idx - 1}.id"
@@ -679,18 +688,19 @@ module ActiveRecord
679
688
  bail_out = true
680
689
  break
681
690
  # "ON br_t#{idx}.#{a.foreign_type} = '#{src_ref.options[:source_type]}' AND " \
682
- # "br_t#{idx}.#{a.foreign_key} = br_t#{idx - 1}.id"
691
+ # "br_t#{idx}.#{a.foreign_key} = br_t#{idx - 1}.#{a.active_record.primary_key}"
683
692
  else # Works for HMT through a polymorphic HO
684
693
  link_back << hmt_assoc.source_reflection.inverse_of&.name # Some polymorphic "_able" thing
685
694
  "ON br_t#{idx - 1}.#{a.foreign_type} = '#{src_ref.options[:source_type]}' AND " \
686
- "br_t#{idx - 1}.#{a.foreign_key} = br_t#{idx}.id"
695
+ "br_t#{idx - 1}.#{a.foreign_key} = br_t#{idx}.#{a.active_record.primary_key}"
687
696
  end
688
697
  else # Standard has_many or has_one
698
+ # puts "HM #{a.table_name}"
689
699
  # binding.pry unless (
690
700
  nm = hmt_assoc.source_reflection.inverse_of&.name
691
701
  # )
692
702
  link_back << nm # if nm
693
- "ON br_t#{idx}.#{a.foreign_key} = br_t#{idx - 1}.id"
703
+ "ON br_t#{idx}.#{a.foreign_key} = br_t#{idx - 1}.#{a.active_record.primary_key}"
694
704
  end
695
705
  link_back.unshift(a.source_reflection.name)
696
706
  [a.table_name, a.foreign_key, a.source_reflection.macro]
@@ -708,7 +718,13 @@ module ActiveRecord
708
718
  # binding.pry if link_back.length > 2
709
719
  "br_t#{idx}.#{hm.foreign_key}"
710
720
  else # A HMT that goes HM -> HM, something like Categories -> Products -> LineItems
711
- # binding.pry if link_back.length > 2
721
+ # %%% Currently flaky, so will revisit this soon, probably while implementing the whole link_back architecture
722
+ if num_bt_things > 1
723
+ # binding.pry
724
+ nix << k
725
+ next
726
+ end
727
+
712
728
  "br_t#{idx}.#{src_ref.active_record.primary_key}"
713
729
  end
714
730
  else
@@ -846,8 +862,7 @@ JOIN (SELECT #{hm_selects.map { |s| "#{'br_t0.' if from_clause}#{s}" }.join(', '
846
862
  alias _brick_find_sti_class find_sti_class
847
863
  def find_sti_class(type_name)
848
864
  if ::Brick.sti_models.key?(type_name ||= name)
849
- # Used to be: ::Brick.sti_models[type_name].fetch(:base, nil) || _brick_find_sti_class(type_name)
850
- _brick_find_sti_class(type_name)
865
+ ::Brick.sti_models[type_name].fetch(:base, nil) || _brick_find_sti_class(type_name)
851
866
  else
852
867
  # This auto-STI is more of a brute-force approach, building modules where needed
853
868
  # The more graceful alternative is the overload of ActiveSupport::Dependencies#autoload_module! found below
@@ -855,9 +870,13 @@ JOIN (SELECT #{hm_selects.map { |s| "#{'br_t0.' if from_clause}#{s}" }.join(', '
855
870
  module_prefixes = type_name.split('::')
856
871
  module_prefixes.unshift('') unless module_prefixes.first.blank?
857
872
  module_name = module_prefixes[0..-2].join('::')
858
- if (snp = ::Brick.config.sti_namespace_prefixes)&.key?("::#{module_name}::") || snp&.key?("#{module_name}::") ||
873
+ if (base_name = ::Brick.config.sti_namespace_prefixes&.fetch("#{module_name}::", nil)) ||
859
874
  File.exist?(candidate_file = ::Rails.root.join('app/models' + module_prefixes.map(&:underscore).join('/') + '.rb'))
860
- _brick_find_sti_class(type_name) # Find this STI class normally
875
+ if base_name
876
+ base_name == "::#{name}" ? self : base_name.constantize
877
+ else
878
+ _brick_find_sti_class(type_name) # Find this STI class normally
879
+ end
861
880
  else
862
881
  # Build missing prefix modules if they don't yet exist
863
882
  this_module = Object
@@ -978,6 +997,8 @@ Module.class_exec do
978
997
  end
979
998
  Object
980
999
  else
1000
+ sti_base = (::Brick.config.sti_namespace_prefixes&.fetch("::#{name}::#{requested}", nil) ||
1001
+ ::Brick.config.sti_namespace_prefixes&.fetch("::#{name}::", nil))&.constantize
981
1002
  self
982
1003
  end
983
1004
  # puts "#{self.name} - #{args.first}"
@@ -985,7 +1006,7 @@ Module.class_exec do
985
1006
  if ((is_defined = self.const_defined?(args.first)) && (possible = self.const_get(args.first)) &&
986
1007
  # Reset `possible` if it's a controller request that's not a perfect match
987
1008
  # Was: (possible = nil) but changed to #local_variable_set in order to suppress the "= should be ==" warning
988
- (possible.name == desired_classname || (is_controller && binding.local_variable_set(:possible, nil)))) ||
1009
+ (possible&.name == desired_classname || (is_controller && binding.local_variable_set(:possible, nil)))) ||
989
1010
  # Try to require the respective Ruby file
990
1011
  ((filename = ActiveSupport::Dependencies.search_for_file(desired_classname.underscore) ||
991
1012
  (self != Object && ActiveSupport::Dependencies.search_for_file((desired_classname = requested).underscore))
@@ -995,9 +1016,11 @@ Module.class_exec do
995
1016
  # If any class has turned up so far (and we're not in the middle of eager loading)
996
1017
  # then return what we've found.
997
1018
  (is_defined && !::Brick.is_eager_loading) # Used to also have: && possible != self
998
- if (!brick_root && (filename || possible.instance_of?(Class))) ||
999
- (possible.instance_of?(Module) && possible&.module_parent == self) ||
1000
- (possible.instance_of?(Class) && possible == self) # Are we simply searching for ourselves?
1019
+ if ((!brick_root && (filename || possible.instance_of?(Class))) ||
1020
+ (possible.instance_of?(Module) && possible&.module_parent == self) ||
1021
+ (possible.instance_of?(Class) && possible == self)) && # Are we simply searching for ourselves?
1022
+ # Skip when what we found as `possible` is not related to the base class of an STI model
1023
+ (!sti_base || possible.is_a?(sti_base))
1001
1024
  return possible
1002
1025
  end
1003
1026
  end
@@ -1129,7 +1152,8 @@ class Object
1129
1152
 
1130
1153
  def build_model(relations, base_module, base_name, class_name, inheritable_name = nil)
1131
1154
  tnp = ::Brick.config.table_name_prefixes&.find { |p| p.last == base_module.name }&.first
1132
- if (base_model = ::Brick.config.sti_namespace_prefixes&.fetch("::#{base_module.name}::", nil)&.constantize) || # Are we part of an auto-STI namespace? ...
1155
+ if (base_model = (::Brick.config.sti_namespace_prefixes&.fetch("::#{base_module.name}::#{class_name}", nil) || # Are we part of an auto-STI namespace? ...
1156
+ ::Brick.config.sti_namespace_prefixes&.fetch("::#{base_module.name}::", nil))&.constantize) ||
1133
1157
  base_module != Object # ... or otherwise already in some namespace?
1134
1158
  schema_name = [(singular_schema_name = base_name.underscore),
1135
1159
  (schema_name = singular_schema_name.pluralize),
@@ -1141,7 +1165,6 @@ class Object
1141
1165
  singular_table_name = ActiveSupport::Inflector.underscore(model_name).gsub('/', '.')
1142
1166
 
1143
1167
  if base_model
1144
- schema_name = base_name.underscore # For the auto-STI namespace models
1145
1168
  table_name = base_model.table_name
1146
1169
  build_model_worker(base_module, inheritable_name, model_name, singular_table_name, table_name, relations, table_name)
1147
1170
  else
@@ -1171,7 +1194,7 @@ class Object
1171
1194
  full_name = if relation || schema_name.blank?
1172
1195
  inheritable_name || model_name
1173
1196
  else # Prefix the schema to the table name + prefix the schema namespace to the class name
1174
- schema_module = if schema_name.instance_of?(Module) # from an auto-STI namespace?
1197
+ schema_module = if schema_name.is_a?(Module) # from an auto-STI namespace?
1175
1198
  schema_name
1176
1199
  else
1177
1200
  matching = "#{schema_name}.#{matching}"
@@ -1870,7 +1893,7 @@ class Object
1870
1893
  upd_hash['invitation_accepted_at'] = nil if upd_hash['invitation_accepted_at'].blank?
1871
1894
  end
1872
1895
  end
1873
- if (json_cols = model.columns.select { |c| c.type == :json || json_overrides&.include?(c.name) }.map(&:name)).present?
1896
+ if (json_cols = model.columns.select { |c| model.json_column?(c) }.map(&:name)).present?
1874
1897
  upd_hash ||= upd_params.to_h
1875
1898
  json_cols.each do |c|
1876
1899
  begin
@@ -2498,9 +2521,9 @@ module Brick
2498
2521
  # rubocop:enable Style/CommentedKeyword
2499
2522
 
2500
2523
  class << self
2501
- def _add_bt_and_hm(fk, relations, is_polymorphic = false, is_optional = false)
2524
+ def _add_bt_and_hm(fk, relations, polymorphic_class = nil, is_optional = false)
2502
2525
  bt_assoc_name = ::Brick.namify(fk[2], :downcase)
2503
- unless is_polymorphic
2526
+ unless polymorphic_class
2504
2527
  bt_assoc_name = if bt_assoc_name.underscore.end_with?('_id')
2505
2528
  bt_assoc_name[-3] == '_' ? bt_assoc_name[0..-4] : bt_assoc_name[0..-3]
2506
2529
  elsif bt_assoc_name.downcase.end_with?('id') && bt_assoc_name.exclude?('_')
@@ -2555,7 +2578,7 @@ module Brick
2555
2578
  puts "Brick: Additional reference #{fk.inspect} refers to non-existent #{'table'.pluralize(missing.length)} #{missing.join(' and ')}. (Available tables include #{tables.join(', ')}.)"
2556
2579
  return
2557
2580
  end
2558
- unless (cols = relations[fk[1]][:cols]).key?(fk[2]) || (is_polymorphic && cols.key?("#{fk[2]}_id") && cols.key?("#{fk[2]}_type"))
2581
+ unless (cols = relations[fk[1]][:cols]).key?(fk[2]) || (polymorphic_class && cols.key?("#{fk[2]}_id") && cols.key?("#{fk[2]}_type"))
2559
2582
  columns = cols.map { |k, v| "#{k} (#{v.first.split(' ').first})" }
2560
2583
  puts "Brick: Additional reference #{fk.inspect} refers to non-existent column #{fk[2]}. (Columns present in #{fk[1]} are #{columns.join(', ')}.)"
2561
2584
  return
@@ -2572,9 +2595,10 @@ module Brick
2572
2595
  return unless bts # Rails 5.0 and older can have bts end up being nil
2573
2596
 
2574
2597
  if (assoc_bt = bts[cnstr_name])
2575
- if is_polymorphic
2598
+ if polymorphic_class
2576
2599
  # Assuming same fk (don't yet support composite keys for polymorphics)
2577
2600
  assoc_bt[:inverse_table] << fk[4]
2601
+ assoc_bt[:polymorphic] << polymorphic_class
2578
2602
  else # Expect we could have a composite key going
2579
2603
  if assoc_bt[:fk].is_a?(String)
2580
2604
  assoc_bt[:fk] = [assoc_bt[:fk], fk[2]] unless fk[2] == assoc_bt[:fk]
@@ -2584,10 +2608,10 @@ module Brick
2584
2608
  assoc_bt[:assoc_name] = "#{assoc_bt[:assoc_name]}_#{fk[2]}"
2585
2609
  end
2586
2610
  else
2587
- inverse_table = [primary_table] if is_polymorphic
2611
+ inverse_table = [primary_table] if polymorphic_class
2588
2612
  assoc_bt = bts[cnstr_name] = { is_bt: true, fk: fk[2], assoc_name: bt_assoc_name, inverse_table: inverse_table || primary_table }
2589
2613
  assoc_bt[:optional] = true if is_optional
2590
- assoc_bt[:polymorphic] = true if is_polymorphic
2614
+ assoc_bt[:polymorphic] = [polymorphic_class] if polymorphic_class
2591
2615
  end
2592
2616
  if is_class
2593
2617
  # For use in finding the proper :source for a HMT association that references an STI subclass
@@ -2613,7 +2637,7 @@ module Brick
2613
2637
  end
2614
2638
  assoc_hm = hms[hm_cnstr_name] = { is_bt: false, fk: fk[2], assoc_name: fk_namified.pluralize, alternate_name: bt_assoc_name,
2615
2639
  inverse_table: inv_tbl, inverse: assoc_bt }
2616
- assoc_hm[:polymorphic] = true if is_polymorphic
2640
+ assoc_hm[:polymorphic] = true if polymorphic_class
2617
2641
  hm_counts = relation.fetch(:hm_counts) { relation[:hm_counts] = {} }
2618
2642
  this_hm_count = hm_counts[fk[1]] = hm_counts.fetch(fk[1]) { 0 } + 1
2619
2643
  end
@@ -2671,11 +2695,13 @@ module Brick
2671
2695
  end
2672
2696
  end
2673
2697
  end
2674
- ::Brick.relations.keys.map do |v|
2675
- tbl_parts = v.split('.')
2698
+ ::Brick.relations.map do |k, v|
2699
+ tbl_parts = k.split('.')
2676
2700
  tbl_parts.shift if ::Brick.apartment_multitenant && tbl_parts.length > 1 && tbl_parts.first == ::Brick.apartment_default_tenant
2677
2701
  res = tbl_parts.join('.')
2678
- [v, (model = models[res])&.last&.table_name, migrations&.fetch(res, nil), model&.first]
2702
+ [k, (model = models[res])&.last&.table_name || v[:class_name].constantize.table_name,
2703
+ migrations&.fetch(res, nil),
2704
+ model&.first]
2679
2705
  end
2680
2706
  end
2681
2707
 
@@ -1245,6 +1245,7 @@ erDiagram
1245
1245
  }
1246
1246
  <% end
1247
1247
  # callback < %= cb_k % > erdClick
1248
+ @_brick_monetized_attributes = model.respond_to?(:monetized_attributes) ? model.monetized_attributes.values : {}
1248
1249
  %>
1249
1250
  </div>
1250
1251
  "
@@ -1612,7 +1613,7 @@ end
1612
1613
  end %>
1613
1614
  </table>
1614
1615
  <%
1615
- if (description = (relation = Brick.relations[tbl_name = #{model_name}.table_name])&.fetch(:description, nil)) %><%=
1616
+ if (description = (relation = Brick.relations[#{model_name}.table_name])&.fetch(:description, nil)) %><%=
1616
1617
  description %><br><%
1617
1618
  end
1618
1619
  %><%= link_to \"(See all #\{model_name.pluralize})\", see_all_path %>
@@ -1686,22 +1687,25 @@ end
1686
1687
  elsif val
1687
1688
  \"<span class=\\\"orphan\\\">Orphaned ID: #\{val}</span>\".html_safe
1688
1689
  end %>
1689
- <% else
1690
- col_type = if ::Brick.config.json_columns[tbl_name]&.include?(k)
1691
- :json
1692
- elsif col&.sql_type == 'geography'
1693
- col.sql_type
1694
- else
1695
- col&.type
1696
- 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
1697
1700
  case (col_type ||= col&.sql_type)
1698
1701
  when :string, :text
1699
1702
  if is_bcrypt?(val) # || .readonly?
1700
1703
  is_revert = false %>
1701
1704
  <%= hide_bcrypt(val, nil, 1000) %>
1702
1705
  <% elsif col_type == :string
1703
- if model.respond_to?(:enumerized_attributes) && (opts = model.enumerized_attributes[k]&.options).present? %>
1704
- <%= f.select(k.to_sym, [[\"(No #\{k} chosen)\", '^^^brick_NULL^^^']] + opts, { value: val || '^^^brick_NULL^^^' }, html_options) %><%
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) %><%
1705
1709
  else %>
1706
1710
  <%= f.text_field(k.to_sym, html_options) %><%
1707
1711
  end
@@ -20,40 +20,50 @@ module Brick::Rails::FormTags
20
20
  s << col_name
21
21
  cols[col_name] = col
22
22
  end
23
+ composite_bts = bts.select { |k, _v| k.is_a?(Array) }
24
+ composite_bt_names = {}
25
+ composite_bt_cols = composite_bts.each_with_object([]) do |bt, s|
26
+ composite_bt_names[bt.first.join('__')] = bt.last
27
+ bt.first.each { |bt_col| s << bt_col unless s.include?(bt_col.first) }
28
+ end
23
29
  unless sequence # If no sequence is defined, start with all inclusions
24
30
  cust_cols = klass._br_cust_cols
25
31
  # HOT columns, kept as symbols
26
32
  hots = klass._br_bt_descrip.keys.select { |k| bts.key?(k) }
27
- sequence = col_keys + cust_cols.keys + hots + hms_keys.reject { |assoc_name| inclusions&.exclude?(assoc_name) }
33
+ sequence = (col_keys - composite_bt_cols) +
34
+ composite_bt_names.keys + cust_cols.keys + hots +
35
+ hms_keys.reject { |assoc_name| inclusions&.exclude?(assoc_name) }
28
36
  end
29
37
  sequence.reject! { |nm| exclusions.include?(nm) } if exclusions
30
38
  out << sequence.each_with_object(+'') do |col_name, s|
31
- if (col = cols[col_name]).is_a?(ActiveRecord::ConnectionAdapters::Column)
32
- s << '<th'
33
- s << " title=\"#{col.comment}\"" if col.respond_to?(:comment) && !col.comment.blank?
34
- s << if (bt = bts[col_name])
35
- # Allow sorting for any BT except polymorphics
36
- "#{' x-order="' + bt.first.to_s + '"' unless bt[2]}>BT " +
39
+ if (col = cols[col_name]).is_a?(ActiveRecord::ConnectionAdapters::Column)
40
+ s << '<th '
41
+ s << "title=\"#{col.comment}\"" if col.respond_to?(:comment) && !col.comment.blank?
42
+ s << if (bt = bts[col_name])
43
+ # Allow sorting for any BT except polymorphics
44
+ "x-order=\"#{bt.first.to_s + '"' unless bt[2]}>BT " +
45
+ bt[1].map { |bt_pair| bt_pair.first.bt_link(bt.first) }.join(' ')
46
+ else # Normal column
47
+ "x-order=\"#{col_name + '"' if true}>#{col_name}"
48
+ end
49
+ elsif col # HM column
50
+ options = {}
51
+ options[col[1].inheritance_column] = col[1].name unless col[1] == col[1].base_class
52
+ s << "<th x-order=\"#{col_name + '"' if true}>#{col[2]} "
53
+ s << (col.first ? "#{col[3]}" : "#{link_to(col[3], send("#{col[1]._brick_index}_path", options))}")
54
+ elsif cust_cols.key?(col_name) # Custom column
55
+ s << "<th x-order=\"#{col_name}\">#{col_name}"
56
+ elsif col_name.is_a?(Symbol) && (hot = bts[col_name]) # has_one :through
57
+ s << "<th x-order=\"#{hot.first.to_s}\">HOT " +
58
+ hot[1].map { |hot_pair| hot_pair.first.bt_link(col_name) }.join(' ')
59
+ elsif (bt = composite_bt_names[col_name])
60
+ s << "<th x-order=\"#{bt.first.to_s + '"' unless bt[2]}>BT comp " +
37
61
  bt[1].map { |bt_pair| bt_pair.first.bt_link(bt.first) }.join(' ')
38
- else # Normal column
39
- "#{' x-order="' + col_name + '"' if true}>#{col_name}"
40
- end
41
- elsif col # HM column
42
- options = {}
43
- options[col[1].inheritance_column] = col[1].name unless col[1] == col[1].base_class
44
- s << "<th#{' x-order="' + col_name + '"' if true}>#{col[2]} "
45
- s << (col.first ? "#{col[3]}" : "#{link_to(col[3], send("#{col[1]._brick_index}_path", options))}")
46
- elsif cust_cols.key?(col_name) # Custom column
47
- s << "<th x-order=\"#{col_name}\">#{col_name}"
48
- elsif col_name.is_a?(Symbol) && (hot = bts[col_name]) # has_one :through
49
- s << "<th x-order=\"#{hot.first.to_s}\">HOT " +
50
- hot[1].map { |hot_pair| hot_pair.first.bt_link(col_name) }.join(' ')
51
- hot[1].first
52
- else # Bad column name!
53
- s << "<th title=\"<< Unknown column >>\">#{col_name}"
62
+ else # Bad column name!
63
+ s << "<th title=\"<< Unknown column >>\">#{col_name}"
64
+ end
65
+ s << '</th>'
54
66
  end
55
- s << '</th>'
56
- end
57
67
  out << "</tr></thead>
58
68
  <tbody>"
59
69
  # %%% Have once gotten this error with MSSQL referring to http://localhost:3000/warehouse/cold_room_temperatures__archive
@@ -70,7 +80,7 @@ module Brick::Rails::FormTags
70
80
  out << ' class=\"dimmed\"' unless cols.key?(col_name) || (cust_col = cust_cols[col_name]) ||
71
81
  (col_name.is_a?(Symbol) && bts.key?(col_name)) # HOT
72
82
  out << '>'
73
- if (bt = bts[col_name])
83
+ if (bt = bts[col_name] || composite_bt_names[col_name])
74
84
  if bt[2] # Polymorphic?
75
85
  if (poly_id = obj.send("#{bt.first}_id"))
76
86
  # Was: obj.send("#{bt.first}_type")
@@ -119,8 +129,12 @@ module Brick::Rails::FormTags
119
129
  end
120
130
  elsif (col = cols[col_name]).is_a?(ActiveRecord::ConnectionAdapters::Column)
121
131
  # binding.pry if col.is_a?(Array)
122
- col_type = col&.sql_type == 'geography' ? col.sql_type : col&.type
123
- out << display_value(col_type || col&.sql_type, val).to_s
132
+ out << if @_brick_monetized_attributes&.include?(col_name)
133
+ val ? Money.new(val.to_i).format : ''
134
+ else
135
+ col_type = col&.sql_type == 'geography' ? col.sql_type : col&.type
136
+ display_value(col_type || col&.sql_type, val).to_s
137
+ end
124
138
  elsif cust_col
125
139
  data = cust_col.first.map { |cc_part| obj.send(cc_part.last) }
126
140
  cust_txt = klass.brick_descrip(cust_col[-2], data)
@@ -170,11 +184,8 @@ module Brick::Rails::FormTags
170
184
  if klass
171
185
  type_col = klass.inheritance_column # Usually 'type'
172
186
  filter_parts << "#{type_col}=#{sti_type}" if sti_type && klass.column_names.include?(type_col)
173
- path_params = request.path_parameters.dup
174
- path_params.delete(:controller)
175
- path_params.delete(:action)
187
+ path_params = request.path_parameters
176
188
  pk = (klass.primary_key || ActiveRecord::Base.primary_key).to_sym
177
- # Used to also have this but it's a bit too permissive to identify a primary key: (path_params.length == 1 && path_params.values.first) ||
178
189
  if ((id = (path_params[pk] || path_params[:id] || path_params["#{klass.name.underscore}_id".to_sym])) && (obj = klass.find_by(pk => id))) ||
179
190
  (['show', 'edit', 'update', 'destroy'].include?(action_name) && (obj = klass.first))
180
191
  obj
@@ -183,7 +194,18 @@ module Brick::Rails::FormTags
183
194
  # %%% If there is a polymorphic association that might relate to stuff in the path_params,
184
195
  # try to identify an appropriate ___able_id and ___able_type filter
185
196
  ((klass.column_names - [pk.to_s]) & path_params.keys.map(&:to_s)).each do |path_param|
186
- filter_parts << "#{path_param}=#{path_params[path_param.to_sym]}"
197
+ next if [:controller, :action].include?(path_param)
198
+
199
+ foreign_id = path_params[path_param.to_sym]
200
+ # Need to convert a friendly_id slug to a real ID?
201
+ if Object.const_defined?('FriendlyId') &&
202
+ (assoc = klass.reflect_on_all_associations.find { |a| a.belongs_to? && a.foreign_key == path_param }) &&
203
+ (assoc_klass = assoc.klass).instance_variable_get(:@friendly_id_config) &&
204
+ (new_id = assoc_klass.where(assoc_klass.friendly_id_config.query_field => foreign_id)
205
+ .pluck(assoc_klass.primary_key).first)
206
+ foreign_id = new_id
207
+ end
208
+ filter_parts << "#{path_param}=#{foreign_id}"
187
209
  end
188
210
  klass
189
211
  end
@@ -5,7 +5,7 @@ module Brick
5
5
  module VERSION
6
6
  MAJOR = 1
7
7
  MINOR = 0
8
- TINY = 123
8
+ TINY = 125
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
@@ -208,7 +208,7 @@ module Brick
208
208
  if a.polymorphic?
209
209
  rel_poly_bt = relations[model.table_name][:fks].find { |_k, fk| fk[:assoc_name] == a.name.to_s }
210
210
  if (primary_tables = rel_poly_bt&.last&.fetch(:inverse_table, [])).is_a?(Array)
211
- models = primary_tables&.map { |table| table.singularize.camelize.constantize }
211
+ models = rel_poly_bt[1][:polymorphic]&.map { |table| table.singularize.camelize.constantize }
212
212
  s.first[a.foreign_key.to_s] = [a.name, models, true]
213
213
  else
214
214
  # This will come up when using Devise invitable when invited_by_class_name is not
@@ -218,7 +218,8 @@ module Brick
218
218
  puts " belongs_to :#{a.name}, polymorphic: true"
219
219
  end
220
220
  else
221
- s.first[a.foreign_key.to_s] = [a.name, a.klass]
221
+ bt_key = a.foreign_key.is_a?(Array) ? a.foreign_key : a.foreign_key.to_s
222
+ s.first[bt_key] = [a.name, a.klass]
222
223
  end
223
224
  else # This gets all forms of has_many and has_one
224
225
  if through # has_many :through or has_one :through
@@ -523,8 +524,13 @@ module Brick
523
524
  table_name, poly = k.split('.')
524
525
  v ||= ActiveRecord::Base.execute_sql("SELECT DISTINCT #{poly}_type AS typ FROM #{table_name}").each_with_object([]) { |result, s| s << result['typ'] if result['typ'] }
525
526
  v.each do |type|
526
- if relations.key?(primary_table = type.underscore.pluralize)
527
- ::Brick._add_bt_and_hm([nil, table_name, poly, nil, primary_table, "(brick) #{table_name}_#{poly}"], relations, true, is_optional)
527
+ # Allow polymorphic BT to relate to an STI subclass
528
+ base_type = ::Brick.config.sti_namespace_prefixes["::#{type}"] ||
529
+ ::Brick.config.sti_namespace_prefixes.find { |k, _v| k.end_with?('::') && type.start_with?(k[2..-1]) }&.last&.[](2..-1)
530
+ if relations.key?(primary_table = (base_type || type).underscore.pluralize)
531
+ ::Brick._add_bt_and_hm([nil, table_name, poly, nil, primary_table, "(brick) #{table_name}_#{poly}"], relations,
532
+ type, # Polymorphic class
533
+ is_optional)
528
534
  else
529
535
  missing_stis[primary_table] = type unless ::Brick.existing_stis.key?(type)
530
536
  end
@@ -600,7 +606,8 @@ In config/initializers/brick.rb appropriate entries would look something like:
600
606
  ::Rails.configuration.eager_load_namespaces.select { |ns| ns < ::Rails::Application }.each(&:eager_load!)
601
607
  end
602
608
  else
603
- Zeitwerk::Loader.eager_load_all
609
+ # Same as: Zeitwerk::Loader.eager_load_all -- plus retry when something skips a beat
610
+ Zeitwerk::Registry.loaders.each { |loader| load_with_retry(loader) }
604
611
  end
605
612
  abstract_ar_bases = if do_ar_abstract_bases
606
613
  ActiveRecord::Base.descendants.select { |ar| ar.abstract_class? }.map(&:name)
@@ -609,6 +616,20 @@ In config/initializers/brick.rb appropriate entries would look something like:
609
616
  abstract_ar_bases
610
617
  end
611
618
 
619
+ # Some classes (like Phlex::Testing::Rails) will successfully auto-load after a retry
620
+ def load_with_retry(loader, autoloaded = nil)
621
+ autoloaded ||= loader.send(:autoloaded_dirs).dup
622
+ begin
623
+ loader.eager_load
624
+ rescue Zeitwerk::SetupRequired
625
+ # This is fine -- we eager load what can be eager loaded
626
+ rescue Zeitwerk::NameError
627
+ if autoloaded != (new_auto = loader.send(:autoloaded_dirs))
628
+ load_with_retry(loader, new_auto.dup) # Try one more time and it could come together
629
+ end
630
+ end
631
+ end
632
+
612
633
  def display_classes(prefix, rels, max_length)
613
634
  rels.sort.each do |rel|
614
635
  ::Brick.auto_models << rel.first
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.123
4
+ version: 1.0.125
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-03-24 00:00:00.000000000 Z
11
+ date: 2023-04-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord