brick 1.0.139 → 1.0.140

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