brick 1.0.139 → 1.0.140

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: e13ace06f3c894715f631be750cd0571b9ab1cfe44b15780e9278d7b6a1c88f3
4
- data.tar.gz: fd0d89eb66a8878fc968e95e7a3c27f1fcf619a06c652c0c58c468d4cf66e451
3
+ metadata.gz: 995f6ed970029309b465f80193521b91c63335e12400cbb21e0ef0d60fe60dd2
4
+ data.tar.gz: c610df65f26a9e9bf4f081c8d0172fea6fc69cda350d26cf5287362237e7ceca
5
5
  SHA512:
6
- metadata.gz: 58665ea6c29d49542ae83096fa3ad5fa371ed0a12920f86492111319e3ab9a46c8c7d83ab9277377b6dabb51a75adf0a7cb32c4aa37234f54d796fe206563172
7
- data.tar.gz: f5ee7ebdfa31f76133db652ff0beec175aa5eb319a7f1472da56f0e8e7c79aba12bceb9c26b73fc4c305e69143529416cb5d683660025d1b1e08aa72209a688b
6
+ metadata.gz: 534bde690d45d11d8371d0e666df86badc47879242bcf67a219aeff6dc71fb1ac668b02bba5059f2fceafa63ddb6312cf3e42e993c5ba1de47ebf276abd9bb7c
7
+ data.tar.gz: 18e0c976540749fbc93fd89be05735d2e9544db37d04a3738bc06e82fdc2811d9478398fa66881d17e9e18cb44e78720f66cc8db74660ddd6b5e23e523ea2db7
@@ -26,6 +26,7 @@ if Object.const_defined?('ActionPack') && !ActionPack.respond_to?(:version)
26
26
  end
27
27
  end
28
28
  end
29
+ require 'action_view' # Needed for Rails <= 4.0
29
30
  if Object.const_defined?('ActionView') && !ActionView.respond_to?(:version)
30
31
  module ActionView
31
32
  def self.version
data/lib/brick/config.rb CHANGED
@@ -347,6 +347,14 @@ module Brick
347
347
  @mutex.synchronize { @not_nullables = columns }
348
348
  end
349
349
 
350
+ def always_load_fields
351
+ @mutex.synchronize { @always_load_fields || Hash.new { |h, k| h[k] = [] } }
352
+ end
353
+
354
+ def always_load_fields=(field_set)
355
+ @mutex.synchronize { @always_load_fields = field_set }
356
+ end
357
+
350
358
  # Add status page showing all resources and what files have been built out for them
351
359
  def add_status
352
360
  true
@@ -82,7 +82,7 @@ module ActiveRecord
82
82
 
83
83
  def json_column?(col)
84
84
  col.type == :json || ::Brick.config.json_columns[table_name]&.include?(col.name) ||
85
- ((attr_types = attribute_types[col.name]).respond_to?(:coder) &&
85
+ (respond_to?(:attribute_types) && (attr_types = attribute_types[col.name]).respond_to?(:coder) &&
86
86
  (attr_types.coder.is_a?(Class) ? attr_types.coder : attr_types.coder&.class)&.name&.end_with?('JSON'))
87
87
  end
88
88
  end
@@ -235,7 +235,7 @@ module ActiveRecord
235
235
  caches.fetch(obj_name) { caches[obj_name] = this_obj&.send(part.to_sym) }
236
236
  rescue
237
237
  clsnm = part.camelize
238
- if (possible = this_obj.class.reflect_on_all_associations.select { |a| a.class_name == clsnm || a.klass.base_class.name == clsnm }.first)
238
+ if (possible = this_obj.class.reflect_on_all_associations.select { |a| !a.polymorphic? && (a.class_name == clsnm || a.klass.base_class.name == clsnm) }.first)
239
239
  caches[obj_name] = this_obj&.send(possible.name)
240
240
  end
241
241
  end
@@ -286,7 +286,7 @@ module ActiveRecord
286
286
  assoc_html_name = unless (assoc_name = assoc_name.to_s).camelize == name
287
287
  CGI.escapeHTML(assoc_name)
288
288
  end
289
- model_path = ::Rails.application.routes.url_helpers.send("#{_brick_index}_path".to_sym)
289
+ model_path = ::Rails.application.routes.url_helpers.send("#{_brick_index || table_name}_path".to_sym)
290
290
  model_path << "?#{self.inheritance_column}=#{self.name}" if self != base_class
291
291
  av_class = Class.new.extend(ActionView::Helpers::UrlHelper)
292
292
  av_class.extend(ActionView::Helpers::TagHelper) if ActionView.version < ::Gem::Version.new('7')
@@ -296,6 +296,8 @@ module ActiveRecord
296
296
 
297
297
  # Providing a relation object allows auto-modules built from table name prefixes to work
298
298
  def self._brick_index(mode = nil, separator = '_', relation = nil)
299
+ return if abstract_class
300
+
299
301
  tbl_parts = ((mode == :singular) ? table_name.singularize : table_name).split('.')
300
302
  tbl_parts.shift if ::Brick.apartment_multitenant && tbl_parts.length > 1 && tbl_parts.first == ::Brick.apartment_default_tenant
301
303
  if (aps = relation&.fetch(:auto_prefixed_schema, nil)) && tbl_parts.last.start_with?(aps)
@@ -352,7 +354,7 @@ module ActiveRecord
352
354
  pieces, my_dsl = brick_parse_dsl(join_array, [], translations, false, cc, true)
353
355
  _br_cust_cols[k] = [pieces, my_dsl, fk_col]
354
356
  end
355
- bts, hms, associatives = ::Brick.get_bts_and_hms(self)
357
+ bts, hms, associatives = ::Brick.get_bts_and_hms(self, true)
356
358
  bts.each do |_k, bt|
357
359
  next if bt[2] # Polymorphic?
358
360
 
@@ -431,8 +433,16 @@ module ActiveRecord
431
433
  [order_by, order_by_txt]
432
434
  end
433
435
 
434
- def self.brick_select(params = {}, selects = [], *args)
435
- (relation = all).brick_select(params, selects, *args)
436
+ def self.brick_select(*args, params: {}, brick_col_names: false, **kwargs)
437
+ selects = if args[0].is_a?(Array)
438
+ other_args = args[1..-1]
439
+ args[0]
440
+ else
441
+ other_args = []
442
+ args
443
+ end
444
+ (relation = all).brick_select(selects, *other_args,
445
+ params: params, brick_col_names: brick_col_names, **kwargs)
436
446
  relation.select(selects)
437
447
  end
438
448
 
@@ -472,10 +482,29 @@ module ActiveRecord
472
482
  @brick_links ||= { '' => table_name }
473
483
  end
474
484
 
475
- def brick_select(params, selects = [], order_by = nil, translations = {},
476
- join_array = ::Brick::JoinArray.new,
477
- cust_col_override = nil)
478
- is_add_bts = is_add_hms = true
485
+ def brick_select(*args, params: {}, order_by: nil, translations: {},
486
+ join_array: ::Brick::JoinArray.new,
487
+ cust_col_override: nil,
488
+ brick_col_names: true)
489
+ selects = args[0].is_a?(Array) ? args[0] : args
490
+ if selects.present? && cust_col_override.nil? # See if there's any fancy ones in the select list
491
+ idx = 0
492
+ while idx < selects.length
493
+ v = selects[idx]
494
+ if v.is_a?(String) && v.index('.')
495
+ # No prefixes and not polymorphic
496
+ pieces = self.brick_parse_dsl(join_array, [], translations, false, dsl = "[#{v}]")
497
+ (cust_col_override ||= {})[v.tr('.', '_').to_sym] = [pieces, dsl, true]
498
+ selects.delete_at(idx)
499
+ else
500
+ idx += 1
501
+ end
502
+ end
503
+ elsif selects.is_a?(Hash) && params.empty? && cust_col_override.nil? # Make sense of things if they've passed in only params
504
+ params = selects
505
+ selects = []
506
+ end
507
+ is_add_bts = is_add_hms = !cust_col_override
479
508
 
480
509
  # Build out cust_cols, bt_descrip and hm_counts now so that they are available on the
481
510
  # model early in case the user wants to do an ORDER BY based on any of that.
@@ -487,6 +516,7 @@ module ActiveRecord
487
516
  is_distinct = nil
488
517
  wheres = {}
489
518
  params.each do |k, v|
519
+ k = k.to_s # Rails < 4.2 comes in as a symbol
490
520
  next if ['_brick_schema', '_brick_order',
491
521
  '_brick_erd', '_brick_exclude', '_brick_unexclude',
492
522
  '_brick_page', '_brick_page_size', '_brick_offset', '_brick_limit',
@@ -544,9 +574,19 @@ module ActiveRecord
544
574
  "'<#{typ.end_with?('_TYP') ? typ[0..-5] : typ}>' AS #{col.name}"
545
575
  end
546
576
  end
577
+ else # Having some select columns chosen, add any missing always_load_fields for this model
578
+ ::Brick.config.always_load_fields.fetch(klass.name, nil)&.each do |alf|
579
+ selects << alf unless selects.include?(alf)
580
+ end
547
581
  end
548
582
 
549
- left_outer_joins!(join_array) if join_array.present?
583
+ if join_array.present?
584
+ if ActiveRecord.version < Gem::Version.new('4.2')
585
+ joins!(join_array)
586
+ else
587
+ left_outer_joins!(join_array)
588
+ end
589
+ end
550
590
 
551
591
  # If it's a CollectionProxy (which inherits from Relation) then need to dig out the
552
592
  # core Relation object which is found in the association scope.
@@ -594,11 +634,12 @@ module ActiveRecord
594
634
  # Deal with the conflict if there are two parts in the custom column named the same,
595
635
  # "category.name" and "product.name" for instance will end up with aliases of "name"
596
636
  # and "product__name".
637
+ col_prefix = 'br_cc_' if brick_col_names
597
638
  if (cc_part_idx = cc_part.length - 1).zero?
598
- col_alias = "br_cc_#{k}__#{table_name.tr('.', '_')}_#{cc_part.first}"
639
+ col_alias = "#{col_prefix}#{k}__#{table_name.tr('.', '_')}_#{cc_part.first}"
599
640
  else
600
641
  while cc_part_idx > 0 &&
601
- (col_alias = "br_cc_#{k}__#{cc_part[cc_part_idx..-1].map(&:to_s).join('__').tr('.', '_')}") &&
642
+ (col_alias = "#{col_prefix}#{k}__#{cc_part[cc_part_idx..-1].map(&:to_s).join('__').tr('.', '_')}") &&
602
643
  used_col_aliases.key?(col_alias)
603
644
  cc_part_idx -= 1
604
645
  end
@@ -611,7 +652,7 @@ module ActiveRecord
611
652
  key_tbl_name = tbl_name
612
653
  cc_part_idx = cc_part.length - 1
613
654
  while cc_part_idx > 0 &&
614
- (key_alias = "br_cc_#{k}__#{(cc_part[cc_part_idx..-2] + [dest_pk]).map(&:to_s).join('__')}") &&
655
+ (key_alias = "#{col_prefix}#{k}__#{(cc_part[cc_part_idx..-2] + [dest_pk]).map(&:to_s).join('__')}") &&
615
656
  key_alias != col_alias && # We break out if this key alias does exactly match the col_alias
616
657
  used_col_aliases.key?(key_alias)
617
658
  cc_part_idx -= 1
@@ -781,7 +822,7 @@ module ActiveRecord
781
822
 
782
823
  pri_tbl = hm.active_record
783
824
  pri_key = hm.options[:primary_key] || pri_tbl.primary_key
784
- unless hm.active_record.column_names.include?(pri_key)
825
+ if hm.active_record.abstract_class || hm.active_record.column_names.exclude?(pri_key)
785
826
  # %%% When this gets hit then if an attempt is made to display the ERD, it might end up being blank
786
827
  nix << k
787
828
  next
@@ -907,21 +948,43 @@ JOIN (SELECT #{hm_selects.map { |s| "#{'br_t0.' if from_clause}#{s}" }.join(', '
907
948
  # Get foreign keys for anything marked to be auto-preloaded, or a self-referencing JOIN
908
949
  klass_cols = klass.column_names
909
950
  reflect_on_all_associations.each do |a|
910
- selects << a.foreign_key if a.belongs_to? && (preload_values.include?(a.name) ||
911
- (!a.options[:polymorphic] && a.klass == klass && klass_cols.include?(a.foreign_key)))
951
+ selects << a.foreign_key if a.belongs_to? &&
952
+ (preload_values.include?(a.name) ||
953
+ (!a.options[:polymorphic] && a.klass == klass && klass_cols.include?(a.foreign_key))
954
+ )
912
955
  end
913
956
  # ActiveStorage compatibility
914
957
  selects << 'service_name' if klass.name == 'ActiveStorage::Blob' && ActiveStorage::Blob.columns_hash.key?('service_name')
915
958
  selects << 'blob_id' if klass.name == 'ActiveStorage::Attachment' && ActiveStorage::Attachment.columns_hash.key?('blob_id')
916
959
  pieces, my_dsl = klass.brick_parse_dsl(join_array = ::Brick::JoinArray.new, [], translations = {}, false, nil, true)
917
960
  brick_select(
918
- where_values_hash, selects, nil, translations, join_array,
919
- { '_br' => (descrip_cols = [pieces, my_dsl]) }
961
+ selects, where_values_hash, nil, translations: translations, join_array: join_array,
962
+ cust_col_override: { '_br' => (descrip_cols = [pieces, my_dsl]) }
920
963
  )
921
- order_values = klass.primary_key
964
+ order_values = "#{klass.table_name}.#{klass.primary_key}"
922
965
  [self.select(selects), descrip_cols]
923
966
  end
924
967
 
968
+ def brick_uniq
969
+ begin
970
+ uniq
971
+ rescue ActiveModel::MissingAttributeError => e
972
+ # If this model has an #after_initialize then it might try to reference attributes we haven't brought in
973
+ if (err_msg = e.message).start_with?('missing attribute: ') &&
974
+ klass.column_names.include?(col_name = e.message[19..-1])
975
+ (dup_rel = dup).select_values << col_name
976
+ ret = dup_rel.brick_uniq
977
+ puts "*** WARNING: Missing field!
978
+ Might want to add this in your brick.rb:
979
+ ::Brick.always_load_fields = { #{klass.name.inspect} => [#{col_name.inspect}]}"
980
+ ::Brick.config.always_load_fields[klass.name] << col_name
981
+ ret
982
+ else
983
+ []
984
+ end
985
+ end
986
+ end
987
+
925
988
  private
926
989
 
927
990
  def shift_or_first(ary)
@@ -935,6 +998,8 @@ JOIN (SELECT #{hm_selects.map { |s| "#{'br_t0.' if from_clause}#{s}" }.join(', '
935
998
 
936
999
  alias _brick_find_sti_class find_sti_class
937
1000
  def find_sti_class(type_name)
1001
+ return if type_name.is_a?(Numeric)
1002
+
938
1003
  if ::Brick.sti_models.key?(type_name ||= name)
939
1004
  ::Brick.sti_models[type_name].fetch(:base, nil) || _brick_find_sti_class(type_name)
940
1005
  else
@@ -1606,7 +1671,7 @@ class Object
1606
1671
 
1607
1672
  # Add a hash for the inline style to the content-security-policy if one is present
1608
1673
  self.define_method(:add_csp_hash) do |style_value = nil|
1609
- if (csp = request.content_security_policy)
1674
+ if request.respond_to?(:content_security_policy) && (csp = request.content_security_policy)
1610
1675
  if (cspd = csp.directives.fetch('style-src'))
1611
1676
  if style_value
1612
1677
  if (nonce = ::ActionDispatch::ContentSecurityPolicy::Request::NONCE)
@@ -1850,9 +1915,9 @@ class Object
1850
1915
 
1851
1916
  ar_relation = ActiveRecord.version < Gem::Version.new('4') ? real_model.preload : real_model.all
1852
1917
  params['_brick_is_api'] = true if (is_api = request.format == :js || current_api_root)
1853
- @_brick_params = ar_relation.brick_select(params, (selects ||= []), order_by,
1854
- translations = {},
1855
- join_array = ::Brick::JoinArray.new)
1918
+ @_brick_params = ar_relation.brick_select((selects ||= []), params: params, order_by: order_by,
1919
+ translations: (translations = {}),
1920
+ join_array: (join_array = ::Brick::JoinArray.new))
1856
1921
 
1857
1922
  if is_api # Asking for JSON?
1858
1923
  # Apply column renaming
@@ -209,11 +209,15 @@ function linkSchemas() {
209
209
  # paths['app/models'] << 'lib/brick/frameworks/active_record/models'
210
210
  config.brick = ActiveSupport::OrderedOptions.new
211
211
  ActiveSupport.on_load(:before_initialize) do |app|
212
- ::Rails.application.reloader.to_prepare { Module.class_exec &::Brick::ADD_CONST_MISSING }
212
+ if ::Rails.application.respond_to?(:reloader)
213
+ ::Rails.application.reloader.to_prepare { Module.class_exec &::Brick::ADD_CONST_MISSING }
214
+ else # For Rails < 5.0, just load it once at the start
215
+ Module.class_exec &::Brick::ADD_CONST_MISSING
216
+ end
217
+ require 'brick/join_array'
213
218
  is_development = (ENV['RAILS_ENV'] || ENV['RACK_ENV']) == 'development'
214
219
  ::Brick.enable_models = app.config.brick.fetch(:enable_models, true)
215
220
  ::Brick.enable_controllers = app.config.brick.fetch(:enable_controllers, is_development)
216
- require 'brick/join_array' if ::Brick.enable_controllers?
217
221
  ::Brick.enable_views = app.config.brick.fetch(:enable_views, is_development)
218
222
  ::Brick.enable_routes = app.config.brick.fetch(:enable_routes, is_development)
219
223
  ::Brick.skip_database_views = app.config.brick.fetch(:skip_database_views, false)
@@ -599,12 +603,14 @@ window.addEventListener(\"popstate\", linkSchemas);
599
603
  # ====================================
600
604
  if ::Brick.enable_views?
601
605
  # Add the params to the lookup_context so that we have context about STI classes when setting @_brick_model
602
- ActionView::ViewPaths.class_exec do
603
- alias :_brick_lookup_context :lookup_context
604
- def lookup_context(*args)
605
- ret = _brick_lookup_context(*args)
606
- @_lookup_context.instance_variable_set(:@_brick_req_params, params)
607
- ret
606
+ if ActionView.const_defined?('ViewPaths')
607
+ ActionView::ViewPaths.class_exec do
608
+ alias :_brick_lookup_context :lookup_context
609
+ def lookup_context(*args)
610
+ ret = _brick_lookup_context(*args)
611
+ @_lookup_context.instance_variable_set(:@_brick_req_params, params)
612
+ ret
613
+ end
608
614
  end
609
615
  end
610
616
 
@@ -679,11 +685,11 @@ window.addEventListener(\"popstate\", linkSchemas);
679
685
  rescue StandardError => e
680
686
  # Search through the routes to confirm that something might match (Devise stuff for instance, which has its own view templates),
681
687
  # and bubble the same exception (probably an ActionView::MissingTemplate) if a legitimate option is found.
682
- raise if ::Rails.application.routes.set.find { |x| args[1].include?(x.defaults[:controller]) && args[0] == x.defaults[:action] }
688
+ raise if ::Rails.application.routes.set.find { |x| args[1].include?(x.defaults[:controller]) && args[0] == x.defaults[:action] } &&
689
+ ActionView.version >= ::Gem::Version.new('5.0')
683
690
 
684
691
  find_template_err = e
685
692
  end
686
- # Used to also have: ActionView.version < ::Gem::Version.new('5.0') &&
687
693
  model_name = set_brick_model(args, @_brick_req_params)&.name
688
694
  end
689
695
 
@@ -741,7 +747,7 @@ window.addEventListener(\"popstate\", linkSchemas);
741
747
  (hm_fk_name.is_a?(String) && hm_fk_name.include?('.')) # HMT? (Could do a better check for this)
742
748
  predicates = path_keys(hm_assoc, hm_fk_name, pk).map do |k, v|
743
749
  if v == '[sti_type]'
744
- "'#{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"
745
751
  else
746
752
  v.is_a?(String) ? "'#{k}': '#{v}'" : "'#{k}': @#{obj_name}.#{v}"
747
753
  end
@@ -1462,7 +1468,7 @@ end
1462
1468
  #{schema_options}" if schema_options}
1463
1469
  <select id=\"tbl\">#{table_options}</select>
1464
1470
  <h1>Status</h1>
1465
- <table id=\"status\" class=\"shadow\"><thead><tr>
1471
+ <table id=\"resourceName\" class=\"shadow\"><thead><tr>
1466
1472
  <th>Resource</th>
1467
1473
  <th>Table</th>
1468
1474
  <th>Migration</th>
@@ -1481,7 +1487,11 @@ end
1481
1487
  kls = Object.const_get(::Brick.relations.fetch(r[0], nil)&.fetch(:class_name, nil))
1482
1488
  rescue
1483
1489
  end
1484
- kls.is_a?(Class) ? link_to(r[0], send(\"#\{kls._brick_index}_path\".to_sym)) : r[0] %></td>
1490
+ if kls.is_a?(Class) && (path_helper = respond_to?(bi_path = \"#\{kls._brick_index}_path\".to_sym) ? bi_path : nil)
1491
+ link_to(r[0], send(path_helper))
1492
+ else
1493
+ r[0]
1494
+ end %></td>
1485
1495
  <td<%= if r[1]
1486
1496
  ' class=\"orphan\"' unless ::Brick.relations.key?(r[1])
1487
1497
  else
@@ -1541,9 +1551,11 @@ end
1541
1551
  base_model = (model = (obj = @#{obj_name})&.class).base_class
1542
1552
  see_all_path = send(\"#\{base_model._brick_index}_path\")
1543
1553
  #{(inh_col = @_brick_model.inheritance_column).present? &&
1544
- " if obj.respond_to?(:#{inh_col}) && (model_name = @#{obj_name}.#{inh_col}) != base_model.name
1554
+ " if obj.respond_to?(:#{inh_col}) && (model_name = @#{obj_name}.#{inh_col}) &&
1555
+ !model_name.is_a?(Numeric) && model_name != base_model.name
1545
1556
  see_all_path << \"?#{inh_col}=#\{model_name}\"
1546
- end"}
1557
+ end
1558
+ model_name = base_model.name if model_name.is_a?(Numeric)"}
1547
1559
  page_title = (\"#\{model_name ||= model.name}: #\{obj&.brick_descrip || controller_name}\")
1548
1560
  %></title>
1549
1561
  </head>
@@ -1636,7 +1648,7 @@ end
1636
1648
  if bt.length < 4
1637
1649
  bt << (option_detail = [[\"(No #\{bt_name\} chosen)\", '^^^brick_NULL^^^']])
1638
1650
  # %%% Accommodate composite keys for obj.pk at the end here
1639
- collection, descrip_cols = bt_class&.order(obj_pk = bt_class.primary_key)&.brick_list
1651
+ collection, descrip_cols = bt_class&.order(Arel.sql(\"#\{bt_class.table_name}.#\{obj_pk = bt_class.primary_key}\"))&.brick_list
1640
1652
  collection&.each do |obj|
1641
1653
  option_detail << [
1642
1654
  obj.brick_descrip(
@@ -1717,14 +1729,21 @@ end
1717
1729
  else # We get an array back when AR < 4.2
1718
1730
  collection2 = collection.to_a.compact
1719
1731
  end
1720
- collection2 = collection2.uniq
1732
+ collection2 = collection2.brick_uniq
1721
1733
  if collection2.empty? %>
1722
1734
  <tr><td>(none)</td></tr>
1723
1735
  <% else
1724
1736
  collection2.each do |br_#{hm_singular_name}| %>
1725
- <tr><td><%= br_descrip = br_#{hm_singular_name}.brick_descrip(
1726
- descrip_cols&.first&.map { |col| br_#{hm_singular_name}.send(col.last) }
1727
- )
1737
+ <tr><td><%= br_descrip = if br_#{hm_singular_name}.respond_to?(descrip_cols&.first&.first&.last)
1738
+ br_#{hm_singular_name}.brick_descrip(
1739
+ descrip_cols&.first&.map { |col| br_#{hm_singular_name}.send(col.last) }
1740
+ )
1741
+ else # If the HM association has a scope, might not have picked up our SELECT detail
1742
+ pks = (klass = br_#{hm_singular_name}.class).primary_key
1743
+ pks = [pks] unless pks.is_a?(Array)
1744
+ pks.map! { |pk| br_#{hm_singular_name}.send(pk).to_s }
1745
+ \"#\{klass.name} ##\{pks.join(', ')}\"
1746
+ end
1728
1747
  link_to(br_descrip, #{hm.first.klass._brick_index(:singular)}_path(slashify(br_#{obj_pk}))) %></td></tr>
1729
1748
  <% end %>
1730
1749
  <% end %>
@@ -5,7 +5,7 @@ module Brick
5
5
  module VERSION
6
6
  MAJOR = 1
7
7
  MINOR = 0
8
- TINY = 139
8
+ TINY = 140
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
@@ -56,7 +56,7 @@ if (is_ruby_2_7 = (ruby_version = ::Gem::Version.new(RUBY_VERSION)) >= ::Gem::Ve
56
56
  # end
57
57
  end
58
58
 
59
- # Add left_outer_join! to Associations::JoinDependency and Relation::QueryMethods
59
+ # Add left_outer_joins! to Associations::JoinDependency and Relation::QueryMethods
60
60
  if ActiveRecord.version >= ::Gem::Version.new('4') && ActiveRecord.version < ::Gem::Version.new('5')
61
61
  ::Brick::Util._patch_require(
62
62
  'active_record/associations/join_dependency.rb', '/activerecord', # /associations
@@ -84,7 +84,7 @@ if ActiveRecord.version >= ::Gem::Version.new('4') && ActiveRecord.version < ::G
84
84
  ['build_joins(arel, joins_values.flatten) unless joins_values.empty?',
85
85
  "build_joins(arel, joins_values.flatten) unless joins_values.empty?
86
86
  build_left_outer_joins(arel, left_outer_joins_values.flatten) unless left_outer_joins_values.empty?"
87
- ],
87
+ ],
88
88
  # Change 2 - Line 992
89
89
  ["raise 'unknown class: %s' % join.class.name
90
90
  end
@@ -99,8 +99,8 @@ if ActiveRecord.version >= ::Gem::Version.new('4') && ActiveRecord.version < ::G
99
99
  def build_join_query(manager, buckets, join_type)"
100
100
  ],
101
101
  # Change 3 - Line 1012
102
- ['join_infos = join_dependency.join_constraints stashed_association_joins',
103
- 'join_infos = join_dependency.join_constraints stashed_association_joins, join_type'
102
+ ['s = join_dependency.join_constraints stashed_association_joins',
103
+ 's = join_dependency.join_constraints stashed_association_joins, join_type'
104
104
  ]
105
105
  ],
106
106
  :QueryMethods
@@ -202,7 +202,11 @@ module Brick
202
202
  end
203
203
  end
204
204
 
205
- def get_bts_and_hms(model)
205
+ def get_bts_and_hms(model, recalculate = nil)
206
+ if !recalculate && (ret = model.instance_variable_get(:@_brick_bts_and_hms))
207
+ return ret
208
+ end
209
+
206
210
  model_cols = model.columns_hash
207
211
  pk_type = if (mpk = model.primary_key).is_a?(Array)
208
212
  # Composite keys should really use: model.primary_key.map { |pk_part| model_cols[pk_part].type }
@@ -228,8 +232,24 @@ module Brick
228
232
  # This will come up when using Devise invitable when invited_by_class_name is not
229
233
  # specified because in that circumstance it adds a polymorphic :invited_by association,
230
234
  # along with appropriate invited_by_type and invited_by_id columns.
231
- puts "Missing any real indication as to which models \"has_many\" this polymorphic BT in model #{a.active_record.name}:"
232
- puts " belongs_to :#{a.name}, polymorphic: true"
235
+
236
+ # See if any currently-loaded models have a has_many association over to this polymorphic belongs_to
237
+ hm_models = ActiveRecord::Base.descendants.select do |m|
238
+ m.reflect_on_all_associations.any? { |assoc| !assoc.belongs_to? && assoc.options[:as]&.to_sym == a.name }
239
+ end
240
+ # No need to include subclassed models if their parent is already in the list
241
+ hm_models.reject! { |m| hm_models.any? { |parent| parent != m && m < parent } }
242
+ if hm_models.empty?
243
+ puts "Missing any real indication as to which models \"has_many\" this polymorphic BT in model #{a.active_record.name}:"
244
+ puts " belongs_to :#{a.name}, polymorphic: true"
245
+ else
246
+ puts "Having analysed all currently-loaded models to infer the various polymorphic has_many associations for #{model.name}, here are the current results:"
247
+ puts "::Brick.polymorphics = { \"#{model.table_name}.#{a.name}\" =>
248
+ #{hm_models.map(&:name).inspect}
249
+ }"
250
+ puts 'If you add the above to your brick.rb, it will "cement" these options into place, and avoid this lookup process.'
251
+ s.first[a.foreign_key.to_s] = [a.name, hm_models, true]
252
+ end
233
253
  end
234
254
  else
235
255
  bt_key = a.foreign_key.is_a?(Array) ? a.foreign_key : a.foreign_key.to_s
@@ -279,7 +299,7 @@ module Brick
279
299
  end
280
300
  end
281
301
  skip_hms.each { |k, _v| hms.delete(k) }
282
- [bts, hms]
302
+ model.instance_variable_set(:@_brick_bts_and_hms, [bts, hms]) # Cache and return this result
283
303
  end
284
304
 
285
305
  def exclude_column(table, col)
@@ -523,6 +543,10 @@ module Brick
523
543
  Brick.config.license = key
524
544
  end
525
545
 
546
+ def always_load_fields=(field_set)
547
+ Brick.config.always_load_fields = field_set
548
+ end
549
+
526
550
  # Load additional references (virtual foreign keys)
527
551
  # This is attempted early if a brick initialiser file is found, and then again as a failsafe at the end of our engine's initialisation
528
552
  # %%% Maybe look for differences the second time 'round and just add new stuff instead of entirely deferring
@@ -1294,7 +1318,8 @@ ActiveSupport.on_load(:active_record) do
1294
1318
  # rubocop:enable Lint/ConstantDefinitionInBlock
1295
1319
 
1296
1320
  arsc = ::ActiveRecord::StatementCache
1297
- if is_ruby_2_7 && (params = arsc.method(:create).parameters).length == 2 && params.last == [:opt, :block]
1321
+ if is_ruby_2_7 && arsc.respond_to?(:create) &&
1322
+ (params = arsc.method(:create).parameters).length == 2 && params.last == [:opt, :block]
1298
1323
  arsc.class_exec do
1299
1324
  def self.create(connection, callable = nil, &block)
1300
1325
  relation = (callable || block).call ::ActiveRecord::StatementCache::Params.new
@@ -1387,7 +1412,7 @@ ActiveSupport.on_load(:active_record) do
1387
1412
  # def aliased_table_for(table_name, aliased_name, type_caster)
1388
1413
 
1389
1414
  class ActiveRecord::Associations::JoinDependency
1390
- if JoinBase.instance_method(:initialize).arity == 2 # Older ActiveRecord <= 5.1?
1415
+ if JoinBase.instance_method(:initialize).arity < 3 # Older ActiveRecord <= 5.1?
1391
1416
  def initialize(base, associations, joins, eager_loading: true)
1392
1417
  araat = ::ActiveRecord::Associations::AliasTracker
1393
1418
  if araat.respond_to?(:create_with_joins) # Rails 5.0 and 5.1
@@ -1395,17 +1420,28 @@ ActiveSupport.on_load(:active_record) do
1395
1420
  cwj_options << base.type_caster if araat.method(:create_with_joins).arity > 3 # Rails <= 5.1
1396
1421
  @alias_tracker = araat.create_with_joins(*cwj_options)
1397
1422
  @eager_loading = eager_loading # (Unused in Rails 5.0)
1398
- else # Rails 4.2
1423
+ elsif araat.respond_to?(:create) # Rails 4.1 and 4.2
1399
1424
  @alias_tracker = araat.create(base.connection, joins)
1400
1425
  @alias_tracker.aliased_table_for(base, base.table_name) # Updates the count for base.table_name to 1
1426
+ else # Rails 4.0
1427
+ is_rails_4 = true
1428
+ @base_klass = base
1429
+ @table_joins = joins
1430
+ @join_parts = [JoinBase.new(base)]
1431
+ @associations = {}
1432
+ @reflections = []
1433
+ @alias_tracker = araat.new(base.connection, joins)
1434
+ @alias_tracker.aliased_name_for(base.table_name) # Updates the count for base.table_name to 1
1435
+ tree = build(associations)
1401
1436
  end
1402
- tree = self.class.make_tree associations
1437
+ tree ||= self.class.make_tree associations
1403
1438
 
1404
1439
  # Provide a way to find the original relation that this tree is being used for
1405
1440
  # (so that we can maintain a list of links for all tables used in JOINs)
1406
1441
  if (relation = associations.instance_variable_get(:@relation))
1407
1442
  tree.instance_variable_set(:@relation, relation)
1408
1443
  end
1444
+ return if is_rails_4 # Rails 4.0 doesn't know about the rest
1409
1445
 
1410
1446
  @join_root = JoinBase.new base, build(tree, base)
1411
1447
  @join_root.children.each { |child| construct_tables! @join_root, child }
@@ -1538,6 +1574,21 @@ if ActiveRecord.version < ::Gem::Version.new('5.2')
1538
1574
  end
1539
1575
  end
1540
1576
 
1577
+ # By default the awesome_nested_set gem from CollectiveIdea does not prefix the ORDER BY column with its table name.
1578
+ # You can see this snag in action in the popular Spree project -- check out the Taxonomy model. Here is a fix:
1579
+ if Gem::Specification.all_names.find { |g| g.start_with?('awesome_nested_set-') }
1580
+ require 'awesome_nested_set/columns'
1581
+ ::CollectiveIdea::Acts::NestedSet::Columns.class_exec do
1582
+ alias _brick_order_column_name order_column_name
1583
+ def order_column_name
1584
+ unless (ord_col = _brick_order_column_name).start_with?(tbl_prefix = "#{table_name}.")
1585
+ ord_col = tbl_prefix << ord_col
1586
+ end
1587
+ ord_col
1588
+ end
1589
+ end
1590
+ end
1591
+
1541
1592
  # The "brick_links" patch -- this finds how every AR chain of association names
1542
1593
  # relates back to an exact table correlation name chosen by AREL when the AST tree is
1543
1594
  # walked. For instance, from a Customer model there could be a join_tree such as
@@ -1567,21 +1618,40 @@ module ActiveRecord
1567
1618
  _brick_build_join_query(manager, buckets, *args) # , **kwargs)
1568
1619
  end
1569
1620
 
1570
- else
1571
-
1621
+ else # elsif private_instance_methods.include?(:select_association_list)
1572
1622
  alias _brick_select_association_list select_association_list
1573
1623
  def select_association_list(associations, stashed_joins = nil)
1574
1624
  result = _brick_select_association_list(associations, stashed_joins)
1575
1625
  result.instance_variable_set(:@relation, self)
1576
1626
  result
1577
1627
  end
1628
+
1629
+ # else # Rails 4.1 ? and older
1630
+ # alias _brick_build_joins build_joins
1631
+ # def build_joins(manager, joins)
1632
+ # result = _brick_build_joins(manager, joins)
1633
+ # result.instance_variable_set(:@relation, self)
1634
+ # result
1635
+ # end
1578
1636
  end
1579
1637
  end
1580
1638
 
1581
1639
  # require 'active_record/associations/join_dependency'
1582
1640
  module Associations
1583
- # For AR >= 4.2
1584
- if self.const_defined?('JoinDependency')
1641
+ if self.const_defined?('JoinHelper') # ActiveRecord < 4.1
1642
+ module JoinHelper
1643
+ alias _brick_construct_tables construct_tables
1644
+ def construct_tables
1645
+ result = _brick_construct_tables
1646
+ # Capture the table alias name that was chosen
1647
+ # if (relation = node.instance_variable_get(:@assocs)&.instance_variable_get(:@relation))
1648
+ # link_path = node.instance_variable_get(:@link_path)
1649
+ # relation.brick_links[link_path] = result.first.table_alias || result.first.table_name
1650
+ # end
1651
+ result
1652
+ end
1653
+ end
1654
+ else # For AR >= 4.2
1585
1655
  class JoinDependency
1586
1656
  # An intelligent .eager_load() and .includes() that creates t0_r0 style aliases only for the columns
1587
1657
  # used in .select(). To enable this behaviour, include the flag :_brick_eager_load as the first
@@ -1655,7 +1725,7 @@ module ActiveRecord
1655
1725
  associations.map do |name, right|
1656
1726
  reflection = find_reflection base_klass, name
1657
1727
  reflection.check_validity!
1658
- reflection.check_eager_loadable!
1728
+ reflection.check_eager_loadable! if reflection.respond_to?(:check_eager_loadable!) # Used in AR >= 4.2
1659
1729
 
1660
1730
  if reflection.polymorphic?
1661
1731
  raise EagerLoadPolymorphicError.new(reflection)
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.139
4
+ version: 1.0.140
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-05-10 00:00:00.000000000 Z
11
+ date: 2023-05-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord