brick 1.0.123 → 1.0.125

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