brick 1.0.92 → 1.0.93

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: 6bb6854dd2295476ae00996f23e801587869463aa7114d9240c0e97b1cce25b1
4
- data.tar.gz: f174948106c6a06be049f8285fb3358813d09b9970ce484554ad3c86bc3cb8fb
3
+ metadata.gz: 8488acac0623c04f11e916c67303fd06d471154231c92613318cc7458fcfe23f
4
+ data.tar.gz: af566c853bb25e7497a6afbba539ad0d14ec23ad000c053e6dbdf757deefbaa0
5
5
  SHA512:
6
- metadata.gz: 9384ef5db0a03fcc8fda6a3ceafc763a468f88033420d785814d657303027593c7fef64770087975d73d4041f577864c1d934629cb50083fb93d2591f719f136
7
- data.tar.gz: e2b3e21982b9a6d132a3d9d05041759e06a3ac3ffbfdf2ea3675ee0140150f2d33b6e60cbeb0033a7f7469bba227f198a6cc6a0ef34c229ba7a1132cd1175b0d
6
+ metadata.gz: d0d8428b6f80bf3a79abb16265fb8625404f92caf30034da94186a5c5982a78543f29e0a49cc0dc515eb8b6d6da24ccdd53e8d089c90dce46ce214ce5bee4339
7
+ data.tar.gz: 562bd7522f0f45843a89464645e1cc153e455e1e5b4e3dec5f1b38a8bfaefd8bd359a2538aa274b90b50353285d2175f315b7e304e54e60516a5ce2ed3b2fc7e
@@ -42,21 +42,6 @@
42
42
  # Dynamically create model or controller classes when needed
43
43
  # ==========================================================
44
44
 
45
- # By default all models indicate that they are not views
46
- module Arel
47
- class Table
48
- def _arel_table_type
49
- # AR < 4.2 doesn't have type_caster at all, so rely on an instance variable getting set
50
- # AR 4.2 - 5.1 have buggy type_caster entries for the root node
51
- instance_variable_get(:@_arel_table_type) ||
52
- # 5.2-7.0 does type_caster just fine, no bugs there, but the property with the type differs:
53
- # 5.2 has "types" as public, 6.0 "types" as private, and >= 6.1 "klass" as private.
54
- ((tc = send(:type_caster)) && tc.instance_variable_get(:@types)) ||
55
- tc.send(:klass)
56
- end
57
- end
58
- end
59
-
60
45
  module ActiveRecord
61
46
  class Base
62
47
  def self.is_brick?
@@ -270,7 +255,7 @@ module ActiveRecord
270
255
 
271
256
  def self._brick_index(mode = nil)
272
257
  tbl_parts = ((mode == :singular) ? table_name.singularize : table_name).split('.')
273
- tbl_parts.shift if ::Brick.apartment_multitenant && tbl_parts.length > 1 && tbl_parts.first == Apartment.default_schema
258
+ tbl_parts.shift if ::Brick.apartment_multitenant && tbl_parts.length > 1 && tbl_parts.first == ::Brick.apartment_default_tenant
274
259
  tbl_parts.unshift(::Brick.config.path_prefix) if ::Brick.config.path_prefix
275
260
  index = tbl_parts.map(&:underscore).join('_')
276
261
  # Rails applies an _index suffix to that route when the resource name is singular
@@ -407,86 +392,12 @@ module ActiveRecord
407
392
  end
408
393
 
409
394
  class Relation
410
- attr_reader :_arel_applied_aliases
411
-
412
- # Links from ActiveRecord association pathing names over to real
413
- # table correlation names built from AREL aliasing
395
+ # Links from ActiveRecord association pathing names over to real table correlation names
396
+ # that get chosen when the AREL AST tree is walked.
414
397
  def brick_links
415
398
  @brick_links ||= {}
416
399
  end
417
400
 
418
- # CLASS STUFF
419
- def _recurse_arel(piece, prefix = '')
420
- names = []
421
- # Our JOINs mashup of nested arrays and hashes
422
- # binding.pry if defined?(@arel)
423
- case piece
424
- when Array
425
- names += piece.inject([]) { |s, v| s + _recurse_arel(v, prefix) }
426
- when Hash
427
- names += piece.inject([]) do |s, v|
428
- new_prefix = "#{prefix}#{v.first}_"
429
- s << [v.last.shift, new_prefix]
430
- s + _recurse_arel(v.last, new_prefix)
431
- end
432
-
433
- # ActiveRecord AREL objects
434
- when Arel::Nodes::Join # INNER or OUTER JOIN
435
- # rubocop:disable Style/IdenticalConditionalBranches
436
- if piece.right.is_a?(Arel::Table) # Came in from AR < 3.2?
437
- # Arel 2.x and older is a little curious because these JOINs work "back to front".
438
- # The left side here is either another earlier JOIN, or at the end of the whole tree, it is
439
- # the first table.
440
- names += _recurse_arel(piece.left)
441
- # The right side here at the top is the very last table, and anywhere else down the tree it is
442
- # the later "JOIN" table of this pair. (The table that comes after all the rest of the JOINs
443
- # from the left side.)
444
- names << [piece.right._arel_table_type, (piece.right.table_alias || piece.right.name)]
445
- else # "Normal" setup, fed from a JoinSource which has an array of JOINs
446
- # The left side is the "JOIN" table
447
- names += _recurse_arel(table = piece.left)
448
- # The expression on the right side is the "ON" clause
449
- # on = piece.right.expr
450
- # # Find the table which is not ourselves, and thus must be the "path" that led us here
451
- # parent = piece.left == on.left.relation ? on.right.relation : on.left.relation
452
- # binding.pry if piece.left.is_a?(Arel::Nodes::TableAlias)
453
- if table.is_a?(Arel::Nodes::TableAlias)
454
- @_arel_applied_aliases << (alias_name = table.right)
455
- table = table.left
456
- end
457
- end
458
- # rubocop:enable Style/IdenticalConditionalBranches
459
- when Arel::Table # Table
460
- names << [piece._arel_table_type, (piece.table_alias || piece.name)]
461
- when Arel::Nodes::TableAlias # Alias
462
- # Can get the real table name from: self._recurse_arel(piece.left)
463
- names << [piece.left._arel_table_type, piece.right.to_s] # This is simply a string; the alias name itself
464
- when Arel::Nodes::JoinSource # Leaving this until the end because AR < 3.2 doesn't know at all about JoinSource!
465
- # The left side is the "FROM" table
466
- names << (this_name = [piece.left._arel_table_type, (piece.left.table_alias || piece.left.name)])
467
- # The right side is an array of all JOINs
468
- piece.right.each { |join| names << _recurse_arel(join) }
469
- end
470
- names
471
- end
472
-
473
- # INSTANCE STUFF
474
- def _arel_alias_names
475
- @_arel_applied_aliases = []
476
- # %%% If with Rails 3.1 and older you get "NoMethodError: undefined method `eq' for nil:NilClass"
477
- # when trying to call relation.arel, then somewhere along the line while navigating a has_many
478
- # relationship it can't find the proper foreign key.
479
- core = arel.ast.cores.first
480
- # Accommodate AR < 3.2
481
- if core.froms.is_a?(Arel::Table)
482
- # All recent versions of AR have #source which brings up an Arel::Nodes::JoinSource
483
- _recurse_arel(core.source)
484
- else
485
- # With AR < 3.2, "froms" brings up the top node, an Arel::Nodes::InnerJoin
486
- _recurse_arel(core.froms)
487
- end
488
- end
489
-
490
401
  def brick_select(params, selects = [], order_by = nil, translations = {}, join_array = ::Brick::JoinArray.new)
491
402
  is_add_bts = is_add_hms = true
492
403
 
@@ -502,13 +413,16 @@ module ActiveRecord
502
413
  params.each do |k, v|
503
414
  next if ['_brick_schema', '_brick_order', 'controller', 'action'].include?(k)
504
415
 
505
- case (ks = k.split('.')).length
416
+ if (where_col = (ks = k.split('.')).last)[-1] == '!'
417
+ where_col = where_col[0..-2]
418
+ end
419
+ case ks.length
506
420
  when 1
507
- next unless klass.column_names.any?(k) || klass._brick_get_fks.include?(k)
421
+ next unless klass.column_names.any?(where_col) || klass._brick_get_fks.include?(where_col)
508
422
  when 2
509
423
  assoc_name = ks.first.to_sym
510
424
  # Make sure it's a good association name and that the model has that column name
511
- next unless klass.reflect_on_association(assoc_name)&.klass&.column_names&.any?(ks.last)
425
+ next unless klass.reflect_on_association(assoc_name)&.klass&.column_names&.any?(where_col)
512
426
 
513
427
  join_array[assoc_name] = nil # Store this relation name in our special collection for .joins()
514
428
  is_distinct = true
@@ -546,8 +460,19 @@ module ActiveRecord
546
460
 
547
461
  if join_array.present?
548
462
  left_outer_joins!(join_array)
549
- # Without working from a duplicate, touching the AREL ast tree sets the @arel instance variable, which causes the relation to be immutable.
550
- (rel_dupe = dup)._arel_alias_names
463
+ # Touching AREL AST walks the JoinDependency tree, and in that process uses our
464
+ # "brick_links" patch to find how every AR chain of association names relates to exact
465
+ # table correlation names chosen by AREL. We use a duplicate relation object for this
466
+ # because an important side-effect of referencing the AST is that the @arel instance
467
+ # variable gets set, and this is a signal to ActiveRecord that a relation has now
468
+ # become immutable. (We aren't quite ready for our "real deal" relation object to be
469
+ # set in stone ... still need to add .select(), and possibly .where() and .order()
470
+ # things ... also if there are any HM counts then an OUTER JOIN for each of them out
471
+ # to a derived table to do that counting. All of these things need to know proper
472
+ # table correlation names, which will now become available in brick_links on the
473
+ # rel_dupe object.)
474
+ (rel_dupe = dup).arel.ast
475
+
551
476
  core_selects = selects.dup
552
477
  id_for_tables = Hash.new { |h, k| h[k] = [] }
553
478
  field_tbl_names = Hash.new { |h, k| h[k] = {} }
@@ -609,12 +534,12 @@ module ActiveRecord
609
534
  next unless (tbl_name = rel_dupe.brick_links[v.first.to_s]&.split('.')&.last)
610
535
 
611
536
  # If it's Oracle, quote any AREL aliases that had been applied
612
- tbl_name = "\"#{tbl_name}\"" if ::Brick.is_oracle && rel_dupe._arel_applied_aliases.include?(tbl_name)
537
+ tbl_name = "\"#{tbl_name}\"" if ::Brick.is_oracle && rel_dupe.brick_links.values.include?(tbl_name)
613
538
  field_tbl_name = nil
614
539
  v1.map { |x| [x[0..-2].map(&:to_s).join('.'), x.last] }.each_with_index do |sel_col, idx|
615
540
  field_tbl_name = rel_dupe.brick_links[sel_col.first].split('.').last
616
541
  # If it's Oracle, quote any AREL aliases that had been applied
617
- field_tbl_name = "\"#{field_tbl_name}\"" if ::Brick.is_oracle && rel_dupe._arel_applied_aliases.include?(field_tbl_name)
542
+ field_tbl_name = "\"#{field_tbl_name}\"" if ::Brick.is_oracle && rel_dupe.brick_links.values.include?(field_tbl_name)
618
543
 
619
544
  # Postgres can not use DISTINCT with any columns that are XML, so for any of those just convert to text
620
545
  is_xml = is_distinct && Brick.relations[field_tbl_name]&.[](:cols)&.[](sel_col.last)&.first&.start_with?('xml')
@@ -786,16 +711,23 @@ JOIN (SELECT #{hm_selects.map { |s| "#{'br_t0.' if from_clause}#{s}" }.join(', '
786
711
 
787
712
  unless wheres.empty?
788
713
  # Rewrite the wheres to reference table and correlation names built out by AREL
714
+ where_nots = {}
789
715
  wheres2 = wheres.each_with_object({}) do |v, s|
716
+ is_not = if v.first[-1] == '!'
717
+ v[0] = v[0][0..-2] # Take off ending ! from column name
718
+ end
790
719
  if (v_parts = v.first.split('.')).length == 1
791
- s[v.first] = v.last
720
+ (is_not ? where_nots : s)[v.first] = v.last
792
721
  else
793
722
  tbl_name = rel_dupe.brick_links[v_parts.first].split('.').last
794
- s["#{tbl_name}.#{v_parts.last}"] = v.last
723
+ (is_not ? where_nots : s)["#{tbl_name}.#{v_parts.last}"] = v.last
795
724
  end
796
725
  end
797
726
  if respond_to?(:where!)
798
- where!(wheres2)
727
+ where!(wheres2) if wheres2.present?
728
+ if where_nots.present?
729
+ self.where_clause += WhereClause.new(predicate_builder.build_from_hash(where_nots)).invert
730
+ end
799
731
  else # AR < 4.0
800
732
  self.where_values << build_where(wheres2)
801
733
  end
@@ -1091,7 +1023,8 @@ Module.class_exec do
1091
1023
  (table_name = singular_table_name.pluralize),
1092
1024
  ::Brick.is_oracle ? class_name.upcase : class_name,
1093
1025
  (plural_class_name = class_name.pluralize)].find { |s| Brick.db_schemas&.include?(s) }&.camelize ||
1094
- (::Brick.config.sti_namespace_prefixes&.key?("::#{class_name}::") && class_name))
1026
+ (::Brick.config.sti_namespace_prefixes&.key?("::#{class_name}::") && class_name) ||
1027
+ (::Brick.config.table_name_prefixes&.values.include?(class_name) && class_name))
1095
1028
  return self.const_get(schema_name) if self.const_defined?(schema_name)
1096
1029
 
1097
1030
  # Build out a module for the schema if it's namespaced
@@ -1142,6 +1075,7 @@ class Object
1142
1075
  private
1143
1076
 
1144
1077
  def build_model(relations, base_module, base_name, class_name, inheritable_name = nil)
1078
+ tnp = ::Brick.config.table_name_prefixes&.find { |p| p.last == base_module.name }&.first
1145
1079
  if (base_model = ::Brick.config.sti_namespace_prefixes&.fetch("::#{base_module.name}::", nil)&.constantize) || # Are we part of an auto-STI namespace? ...
1146
1080
  base_module != Object # ... or otherwise already in some namespace?
1147
1081
  schema_name = [(singular_schema_name = base_name.underscore),
@@ -1163,11 +1097,11 @@ class Object
1163
1097
  table_name = if (base_model = ::Brick.sti_models[model_name]&.fetch(:base, nil) || ::Brick.existing_stis[model_name]&.constantize)
1164
1098
  base_model.table_name
1165
1099
  else
1166
- ActiveSupport::Inflector.pluralize(singular_table_name)
1100
+ "#{tnp}#{ActiveSupport::Inflector.pluralize(singular_table_name)}"
1167
1101
  end
1168
1102
  if ::Brick.apartment_multitenant &&
1169
1103
  Apartment.excluded_models.include?(table_name.singularize.camelize)
1170
- schema_name = Apartment.default_schema
1104
+ schema_name = ::Brick.apartment_default_tenant
1171
1105
  end
1172
1106
  # Maybe, just maybe there's a database table that will satisfy this need
1173
1107
  if (matching = [table_name, singular_table_name, plural_class_name, model_name, table_name.titleize].find { |m| relations.key?(schema_name ? "#{schema_name}.#{m}" : m) })
@@ -1178,7 +1112,7 @@ class Object
1178
1112
 
1179
1113
  def build_model_worker(schema_name, inheritable_name, model_name, singular_table_name, table_name, relations, matching)
1180
1114
  if ::Brick.apartment_multitenant &&
1181
- schema_name == Apartment.default_schema
1115
+ schema_name == ::Brick.apartment_default_tenant
1182
1116
  relation = relations["#{schema_name}.#{matching}"]
1183
1117
  end
1184
1118
  full_name = if relation || schema_name.blank?
@@ -1380,7 +1314,7 @@ class Object
1380
1314
  # If it's multitenant with something like: public.____ ...
1381
1315
  if (it_parts = inverse_table.split('.')).length > 1 &&
1382
1316
  ::Brick.apartment_multitenant &&
1383
- it_parts.first == Apartment.default_schema
1317
+ it_parts.first == ::Brick.apartment_default_tenant
1384
1318
  it_parts.shift # ... then ditch the generic schema name
1385
1319
  end
1386
1320
  inverse_assoc_name, _x = _brick_get_hm_assoc_name(relations[inverse_table], inverse, it_parts.join('_').singularize)
@@ -1477,7 +1411,7 @@ class Object
1477
1411
  instance_variable_set(:@resources, ::Brick.get_status_of_resources)
1478
1412
  end
1479
1413
  self.define_method :orphans do
1480
- instance_variable_set(:@orphans, ::Brick.find_orphans(::Brick.set_db_schema(params)))
1414
+ instance_variable_set(:@orphans, ::Brick.find_orphans(::Brick.set_db_schema(params).first))
1481
1415
  end
1482
1416
  return [new_controller_class, code + "end # BrickGem controller\n"]
1483
1417
  when 'BrickOpenapi'
@@ -1495,7 +1429,7 @@ class Object
1495
1429
  api_params = referrer_params&.to_h
1496
1430
  end
1497
1431
  end
1498
- ::Brick.set_db_schema(params || api_params)
1432
+ _schema, @_is_show_schema_list = ::Brick.set_db_schema(params || api_params)
1499
1433
 
1500
1434
  if is_openapi
1501
1435
  json = { 'openapi': '3.0.1', 'info': { 'title': Rswag::Ui.config.config_object[:urls].last&.fetch(:name, 'API documentation'), 'version': ::Brick.config.api_version },
@@ -1597,7 +1531,6 @@ class Object
1597
1531
  end
1598
1532
 
1599
1533
  unless is_openapi
1600
- ::Brick.set_db_schema
1601
1534
  _, order_by_txt = model._brick_calculate_ordering(default_ordering(table_name, pk)) if pk
1602
1535
  code << " def index\n"
1603
1536
  code << " @#{table_name.pluralize} = #{model.name}#{pk&.present? ? ".order(#{order_by_txt.join(', ')})" : '.all'}\n"
@@ -1610,7 +1543,7 @@ class Object
1610
1543
  code << " #{find_by_name = "find_#{singular_table_name}"}\n"
1611
1544
  code << " end\n"
1612
1545
  self.define_method :show do
1613
- ::Brick.set_db_schema(params)
1546
+ _schema, @_is_show_schema_list = ::Brick.set_db_schema(params)
1614
1547
  instance_variable_set("@#{singular_table_name}".to_sym, find_obj)
1615
1548
  end
1616
1549
  end
@@ -1621,7 +1554,7 @@ class Object
1621
1554
  code << " @#{singular_table_name} = #{model.name}.new\n"
1622
1555
  code << " end\n"
1623
1556
  self.define_method :new do
1624
- ::Brick.set_db_schema(params)
1557
+ _schema, @_is_show_schema_list = ::Brick.set_db_schema(params)
1625
1558
  instance_variable_set("@#{singular_table_name}".to_sym, model.new)
1626
1559
  end
1627
1560
 
@@ -1660,7 +1593,7 @@ class Object
1660
1593
  code << " #{find_by_name}\n"
1661
1594
  code << " end\n"
1662
1595
  self.define_method :edit do
1663
- ::Brick.set_db_schema(params)
1596
+ _schema, @_is_show_schema_list = ::Brick.set_db_schema(params)
1664
1597
  instance_variable_set("@#{singular_table_name}".to_sym, find_obj)
1665
1598
  end
1666
1599
 
@@ -1889,13 +1822,22 @@ end.class_exec do
1889
1822
  s[row.first] = { dt: row.last } unless ['information_schema', 'pg_catalog', 'pg_toast', 'heroku_ext',
1890
1823
  'INFORMATION_SCHEMA', 'sys'].include?(row.first)
1891
1824
  end
1892
- if (is_multitenant = (multitenancy = ::Brick.config.schema_behavior[:multitenant]) &&
1893
- (sta = multitenancy[:schema_to_analyse]) != 'public') &&
1894
- ::Brick.db_schemas.key?(sta)
1895
- # Take note of the current schema so we can go back to it at the end of all this
1896
- orig_schema = ActiveRecord::Base.execute_sql('SELECT current_schemas(true)').first['current_schemas'][1..-2].split(',')
1897
- ::Brick.default_schema = schema = sta
1898
- ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?", schema)
1825
+ if (possible_schemas = (multitenancy = ::Brick.config.schema_behavior&.[](:multitenant)) &&
1826
+ multitenancy&.[](:schema_to_analyse))
1827
+ possible_schemas = [possible_schemas] unless possible_schemas.is_a?(Array)
1828
+ if (possible_schema = possible_schemas.find { |ps| ::Brick.db_schemas.key?(ps) })
1829
+ ::Brick.default_schema = ::Brick.apartment_default_tenant
1830
+ schema = possible_schema
1831
+ orig_schema = ActiveRecord::Base.execute_sql('SELECT current_schemas(true)').first['current_schemas'][1..-2].split(',')
1832
+ ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?", schema)
1833
+ elsif Rails.env == 'test' # When testing, just find the most recently-created schema
1834
+ ::Brick.default_schema = schema = ::Brick.db_schemas.to_a.sort { |a, b| b.last[:dt] <=> a.last[:dt] }.first.first
1835
+ puts "While running tests, had noticed in the brick.rb initializer that the line \"::Brick.schema_behavior = ...\" refers to a schema called \"#{possible_schema}\" which does not exist. Reading table structure from the most recently-created schema, #{schema}."
1836
+ orig_schema = ActiveRecord::Base.execute_sql('SELECT current_schemas(true)').first['current_schemas'][1..-2].split(',')
1837
+ ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?", schema)
1838
+ else
1839
+ puts "*** In the brick.rb initializer the line \"::Brick.schema_behavior = ...\" refers to schema(s) called #{possible_schemas.map { |s| "\"#{s}\"" }.join(', ')}. No mentioned schema exists. ***"
1840
+ end
1899
1841
  end
1900
1842
  when 'Mysql2'
1901
1843
  ::Brick.default_schema = schema = ActiveRecord::Base.connection.current_database
@@ -1918,24 +1860,6 @@ end.class_exec do
1918
1860
 
1919
1861
  ::Brick.db_schemas ||= {}
1920
1862
 
1921
- if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
1922
- if (possible_schemas = ::Brick.config.schema_behavior&.[](:multitenant)&.[](:schema_to_analyse))
1923
- possible_schemas = [possible_schemas] unless possible_schemas.is_a?(Array)
1924
- if (possible_schema = possible_schemas.find { |ps| ::Brick.db_schemas.key?(ps) })
1925
- ::Brick.default_schema = schema = possible_schema
1926
- orig_schema = ActiveRecord::Base.execute_sql('SELECT current_schemas(true)').first['current_schemas'][1..-2].split(',')
1927
- ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?", schema)
1928
- elsif Rails.env == 'test' # When testing, just find the most recently-created schema
1929
- ::Brick.default_schema = schema = ::Brick.db_schemas.to_a.sort { |a, b| b.last[:dt] <=> a.last[:dt] }.first.first
1930
- puts "While running tests, had noticed in the brick.rb initializer that the line \"::Brick.schema_behavior = ...\" refers to a schema called \"#{possible_schema}\" which does not exist. Reading table structure from the most recently-created schema, #{schema}."
1931
- orig_schema = ActiveRecord::Base.execute_sql('SELECT current_schemas(true)').first['current_schemas'][1..-2].split(',')
1932
- ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?", schema)
1933
- else
1934
- puts "*** In the brick.rb initializer the line \"::Brick.schema_behavior = ...\" refers to schema(s) called #{possible_schemas.map { |s| "\"#{s}\"" }.join(', ')}. No mentioned schema exists. ***"
1935
- end
1936
- end
1937
- end
1938
-
1939
1863
  # %%% Retrieve internal ActiveRecord table names like this:
1940
1864
  # ActiveRecord::Base.internal_metadata_table_name, ActiveRecord::Base.schema_migrations_table_name
1941
1865
  # For if it's not SQLite -- so this is the Postgres and MySQL version
@@ -1948,7 +1872,7 @@ end.class_exec do
1948
1872
  # If Apartment gem lists the table as being associated with a non-tenanted model then use whatever it thinks
1949
1873
  # is the default schema, usually 'public'.
1950
1874
  schema_name = if ::Brick.config.schema_behavior[:multitenant]
1951
- Apartment.default_schema if apartment_excluded&.include?(r['relation_name'].singularize.camelize)
1875
+ ::Brick.apartment_default_tenant if apartment_excluded&.include?(r['relation_name'].singularize.camelize)
1952
1876
  elsif ![schema, 'public'].include?(r['schema'])
1953
1877
  r['schema']
1954
1878
  end
@@ -2099,16 +2023,16 @@ ORDER BY 1, 2, c.internal_column_id, acc.position"
2099
2023
  fk = fk.values unless fk.is_a?(Array)
2100
2024
  # Multitenancy makes things a little more general overall, except for non-tenanted tables
2101
2025
  if apartment_excluded&.include?(::Brick.namify(fk[1]).singularize.camelize)
2102
- fk[0] = Apartment.default_schema
2103
- elsif (is_postgres && (fk[0] == 'public' || (is_multitenant && fk[0] == schema))) ||
2026
+ fk[0] = ::Brick.apartment_default_tenant
2027
+ elsif (is_postgres && (fk[0] == 'public' || (multitenancy && fk[0] == schema))) ||
2104
2028
  (::Brick.is_oracle && fk[0] == schema) ||
2105
2029
  (is_mssql && fk[0] == 'dbo') ||
2106
2030
  (!is_postgres && !::Brick.is_oracle && !is_mssql && ['mysql', 'performance_schema', 'sys'].exclude?(fk[0]))
2107
2031
  fk[0] = nil
2108
2032
  end
2109
2033
  if apartment_excluded&.include?(fk[4].singularize.camelize)
2110
- fk[3] = Apartment.default_schema
2111
- elsif (is_postgres && (fk[3] == 'public' || (is_multitenant && fk[3] == schema))) ||
2034
+ fk[3] = ::Brick.apartment_default_tenant
2035
+ elsif (is_postgres && (fk[3] == 'public' || (multitenancy && fk[3] == schema))) ||
2112
2036
  (::Brick.is_oracle && fk[3] == schema) ||
2113
2037
  (is_mssql && fk[3] == 'dbo') ||
2114
2038
  (!is_postgres && !::Brick.is_oracle && !is_mssql && ['mysql', 'performance_schema', 'sys'].exclude?(fk[3]))
@@ -2126,7 +2050,7 @@ ORDER BY 1, 2, c.internal_column_id, acc.position"
2126
2050
  relations.each do |k, v|
2127
2051
  rel_name = k.split('.').map { |rel_part| ::Brick.namify(rel_part, :underscore) }
2128
2052
  schema_names = rel_name[0..-2]
2129
- schema_names.shift if ::Brick.apartment_multitenant && schema_names.first == Apartment.default_schema
2053
+ schema_names.shift if ::Brick.apartment_multitenant && schema_names.first == ::Brick.apartment_default_tenant
2130
2054
  v[:schema] = schema_names.join('.') unless schema_names.empty?
2131
2055
  # %%% If more than one schema has the same table name, will need to add a schema name prefix to have uniqueness
2132
2056
  v[:resource] = rel_name.last
@@ -2137,7 +2061,7 @@ ORDER BY 1, 2, c.internal_column_id, acc.position"
2137
2061
  end
2138
2062
  ::Brick.load_additional_references if initializer_loaded
2139
2063
 
2140
- if orig_schema && (orig_schema = (orig_schema - ['pg_catalog', 'heroku_ext']).first)
2064
+ if orig_schema && (orig_schema = (orig_schema - ['pg_catalog', 'pg_toast', 'heroku_ext']).first)
2141
2065
  puts "Now switching back to \"#{orig_schema}\" schema."
2142
2066
  ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?", orig_schema)
2143
2067
  end
@@ -2175,7 +2099,7 @@ ORDER BY 1, 2, c.internal_column_id, acc.position"
2175
2099
  AND kcu.column_name = c.column_name#{"
2176
2100
  -- AND kcu.position_in_unique_constraint IS NULL" unless is_mssql}
2177
2101
  WHERE t.table_schema #{is_postgres || is_mssql ?
2178
- "NOT IN ('information_schema', 'pg_catalog',
2102
+ "NOT IN ('information_schema', 'pg_catalog', 'pg_toast', 'heroku_ext',
2179
2103
  'INFORMATION_SCHEMA', 'sys')"
2180
2104
  :
2181
2105
  "= '#{ActiveRecord::Base.connection.current_database.tr("'", "''")}'"}#{"
@@ -2274,7 +2198,7 @@ module Brick
2274
2198
  for_tbl = fk[1]
2275
2199
  fk_namified = ::Brick.namify(fk[1])
2276
2200
  apartment = Object.const_defined?('Apartment') && Apartment
2277
- fk[0] = Apartment.default_schema if apartment && apartment.excluded_models.include?(fk_namified.singularize.camelize)
2201
+ fk[0] = ::Brick.apartment_default_tenant if apartment && apartment.excluded_models.include?(fk_namified.singularize.camelize)
2278
2202
  fk[1] = "#{fk[0]}.#{fk[1]}" if fk[0] # && fk[0] != ::Brick.default_schema
2279
2203
  bts = (relation = relations.fetch(fk[1], nil))&.fetch(:fks) { relation[:fks] = {} }
2280
2204
 
@@ -2290,7 +2214,7 @@ module Brick
2290
2214
  # If Apartment gem lists the primary table as being associated with a non-tenanted model
2291
2215
  # then use 'public' schema for the primary table
2292
2216
  if apartment && apartment&.excluded_models.include?(fk[4].singularize.camelize)
2293
- fk[3] = Apartment.default_schema
2217
+ fk[3] = ::Brick.apartment_default_tenant
2294
2218
  true
2295
2219
  end
2296
2220
  else
@@ -2365,7 +2289,7 @@ module Brick
2365
2289
  end
2366
2290
  assoc_hm[:alternate_name] = "#{assoc_hm[:alternate_name]}_#{bt_assoc_name}" unless assoc_hm[:alternate_name] == bt_assoc_name
2367
2291
  else
2368
- inv_tbl = if ::Brick.config.schema_behavior[:multitenant] && apartment && fk[0] == Apartment.default_schema
2292
+ inv_tbl = if ::Brick.config.schema_behavior[:multitenant] && apartment && fk[0] == ::Brick.apartment_default_tenant
2369
2293
  for_tbl
2370
2294
  else
2371
2295
  fk[1]
@@ -2424,7 +2348,7 @@ module Brick
2424
2348
  end
2425
2349
  ::Brick.relations.keys.map do |v|
2426
2350
  tbl_parts = v.split('.')
2427
- tbl_parts.shift if ::Brick.apartment_multitenant && tbl_parts.length > 1 && tbl_parts.first == Apartment.default_schema
2351
+ tbl_parts.shift if ::Brick.apartment_multitenant && tbl_parts.length > 1 && tbl_parts.first == ::Brick.apartment_default_tenant
2428
2352
  res = tbl_parts.join('.')
2429
2353
  [v, (model = models[res])&.last&.table_name, migrations&.fetch(res, nil), model&.first]
2430
2354
  end
@@ -2454,14 +2378,14 @@ module Brick
2454
2378
 
2455
2379
  # Locate orphaned records
2456
2380
  def find_orphans(multi_schema)
2457
- is_default_schema = multi_schema&.==(Apartment.default_schema)
2381
+ is_default_schema = multi_schema&.==(::Brick.apartment_default_tenant)
2458
2382
  relations.each_with_object([]) do |v, s|
2459
2383
  frn_tbl = v.first
2460
2384
  next if (relation = v.last).key?(:isView) || config.exclude_tables.include?(frn_tbl) ||
2461
2385
  !(for_pk = (relation[:pkey].values.first&.first))
2462
2386
 
2463
2387
  is_default_frn_schema = !is_default_schema && multi_schema &&
2464
- ((frn_parts = frn_tbl.split('.')).length > 1 && frn_parts.first)&.==(Apartment.default_schema)
2388
+ ((frn_parts = frn_tbl.split('.')).length > 1 && frn_parts.first)&.==(::Brick.apartment_default_tenant)
2465
2389
  relation[:fks].select { |_k, assoc| assoc[:is_bt] }.each do |_k, bt|
2466
2390
  begin
2467
2391
  if bt.key?(:polymorphic)
@@ -2477,7 +2401,7 @@ module Brick
2477
2401
  # Skip if database is multitenant, we're not focused on "public", and the foreign and primary tables
2478
2402
  # are both in the "public" schema
2479
2403
  next if is_default_frn_schema &&
2480
- ((pri_parts = pri_tbl&.split('.'))&.length > 1 && pri_parts.first)&.==(Apartment.default_schema)
2404
+ ((pri_parts = pri_tbl&.split('.'))&.length > 1 && pri_parts.first)&.==(::Brick.apartment_default_tenant)
2481
2405
 
2482
2406
  selects << "SELECT '#{pri_tbl}' AS pri_tbl, frn.#{fk_type_col} AS pri_type, frn.#{fk_id_col} AS pri_id, frn.#{for_pk} AS frn_id
2483
2407
  FROM #{frn_tbl} AS frn
@@ -2496,7 +2420,7 @@ module Brick
2496
2420
  # are both in the "public" schema
2497
2421
  pri_tbl = bt.key?(:inverse_table) && bt[:inverse_table]
2498
2422
  next if is_default_frn_schema &&
2499
- ((pri_parts = pri_tbl&.split('.'))&.length > 1 && pri_parts.first)&.==(Apartment.default_schema)
2423
+ ((pri_parts = pri_tbl&.split('.'))&.length > 1 && pri_parts.first)&.==(::Brick.apartment_default_tenant)
2500
2424
 
2501
2425
  pri_pk = relations[pri_tbl].fetch(:pkey, nil)&.values&.first&.first ||
2502
2426
  _class_pk(pri_tbl, multi_schema)
@@ -192,13 +192,13 @@ module Brick
192
192
  end
193
193
  end
194
194
 
195
- apartment_default_schema = ::Brick.apartment_multitenant && Apartment.default_schema
196
- schema_options = if ::Brick.apartment_multitenant &&
197
- (cur_schema = Apartment::Tenant.current) != apartment_default_schema
198
- "<option selected value=\"#{cur_schema}\">#{cur_schema}</option>"
199
- else
200
- ::Brick.db_schemas.keys.each_with_object(+'') { |v, s| s << "<option value=\"#{v}\">#{v}</option>" }
201
- end.html_safe
195
+ apartment_default_schema = ::Brick.apartment_multitenant && ::Brick.apartment_default_tenant
196
+ if ::Brick.apartment_multitenant && ::Brick.db_schemas.length > 1
197
+ schema_options = +'<select id="schema"><% if @_is_show_schema_list %>'
198
+ ::Brick.db_schemas.keys.each { |v| schema_options << "\n <option value=\"#{v}\">#{v}</option>" }
199
+ schema_options << "\n<% else %><option selected value=\"#{Apartment::Tenant.current}\">#{Apartment::Tenant.current}</option>\n"
200
+ schema_options << '<% end %></select>'
201
+ end
202
202
  # %%% If we are not auto-creating controllers (or routes) then omit by default, and if enabled anyway, such as in a development
203
203
  # environment or whatever, then get either the controllers or routes list instead
204
204
  prefix = "#{::Brick.config.path_prefix}/" if ::Brick.config.path_prefix
@@ -466,6 +466,22 @@ var #{table_name}HtColumns;
466
466
  // This PageTransitionEvent fires when the page first loads, as well as after any other history
467
467
  // transition such as when using the browser's Back and Forward buttons.
468
468
  window.addEventListener(\"pageshow\", function() {
469
+ if (tblSelect) { // Always present
470
+ var i = #{::Brick.config.path_prefix ? '0' : 'schemaSelect ? 1 : 0'},
471
+ changeoutList = changeout(location.href);
472
+ for (; i < changeoutList.length; ++i) {
473
+ tblSelect.value = changeoutList[i];
474
+ if (tblSelect.value !== \"\") break;
475
+ }
476
+
477
+ tblSelect.addEventListener(\"change\", function () {
478
+ var lhr = changeout(location.href, null, this.value);
479
+ if (brickSchema)
480
+ lhr = changeout(lhr, \"_brick_schema\", schemaSelect.value);
481
+ location.href = lhr;
482
+ });
483
+ }
484
+
469
485
  if (schemaSelect && schemaSelect.options.length > 1) { // First drop-down is only present if multitenant
470
486
  brickSchema = changeout(location.href, \"_brick_schema\");
471
487
  if (brickSchema) {
@@ -478,6 +494,7 @@ window.addEventListener(\"pageshow\", function() {
478
494
  location.href = changeout(location.href, \"_brick_schema\", this.value, tblSelect.value);
479
495
  });
480
496
  }
497
+
481
498
  [... document.getElementsByTagName(\"FORM\")].forEach(function (form) {
482
499
  if (brickSchema)
483
500
  form.action = changeout(form.action, \"_brick_schema\", brickSchema);
@@ -489,22 +506,6 @@ window.addEventListener(\"pageshow\", function() {
489
506
  return true;
490
507
  });
491
508
  });
492
-
493
- if (tblSelect) { // Always present
494
- var i = #{::Brick.config.path_prefix ? '0' : 'schemaSelect ? 1 : 0'},
495
- changeoutList = changeout(location.href);
496
- for (; i < changeoutList.length; ++i) {
497
- tblSelect.value = changeoutList[i];
498
- if (tblSelect.value !== \"\") break;
499
- }
500
-
501
- tblSelect.addEventListener(\"change\", function () {
502
- var lhr = changeout(location.href, null, this.value);
503
- if (brickSchema)
504
- lhr = changeout(lhr, \"_brick_schema\", schemaSelect.value);
505
- location.href = lhr;
506
- });
507
- }
508
509
  });
509
510
 
510
511
  // Add \"Are you sure?\" behaviour to any data-confirm buttons out there
@@ -834,7 +835,7 @@ erDiagram
834
835
  </head>
835
836
  <body>
836
837
  <p style=\"color: green\"><%= notice %></p>#{"
837
- <select id=\"schema\">#{schema_options}</select>" if ::Brick.config.schema_behavior[:multitenant] && ::Brick.db_schemas.length > 1}
838
+ #{schema_options}" if schema_options}
838
839
  <select id=\"tbl\">#{table_options}</select>
839
840
  <table id=\"resourceName\"><tr>
840
841
  <td><h1>#{model_name}</h1></td>
@@ -954,10 +955,12 @@ erDiagram
954
955
  poly_id = #{obj_name}.send(\"#\{bt.first}_id\")
955
956
  %><%= link_to(\"#\{bt_class} ##\{poly_id}\", send(\"#\{base_class_underscored}_path\".to_sym, poly_id)) if poly_id %><%
956
957
  else
957
- # binding.pry if @_brick_bt_descrip[bt.first][bt[1].first.first].nil?
958
958
  bt_class = bt[1].first.first
959
959
  descrips = @_brick_bt_descrip[bt.first][bt_class]
960
- bt_id_col = if descrips.length == 1
960
+ bt_id_col = if descrips.nil?
961
+ puts \"Caught it in the act for #{obj_name} / #\{col_name}!\"
962
+ # binding.pry
963
+ elsif descrips.length == 1
961
964
  [#{obj_name}.class.reflect_on_association(bt.first)&.foreign_key]
962
965
  else
963
966
  descrips.last
@@ -974,16 +977,16 @@ erDiagram
974
977
  if hms_col.length == 1 %>
975
978
  <%= hms_col.first %>
976
979
  <% else
977
- klass = (col = cols[col_name])[1]
978
- txt = if col[2] == 'HO'
979
- descrips = @_brick_bt_descrip[col_name.to_sym][klass]
980
- ho_txt = klass.brick_descrip(#{obj_name}, descrips[0..-2].map { |id| #{obj_name}.send(id.last[0..62]) }, (ho_id_col = descrips.last))
981
- ho_id = ho_id_col.map { |id_col| #{obj_name}.send(id_col.to_sym) }
982
- ho_id&.first ? link_to(ho_txt, send(\"#\{klass.base_class._brick_index(:singular)}_path\".to_sym, ho_id)) : ho_txt
983
- else
984
- \"#\{hms_col[1] || 'View'} #\{hms_col.first}\"
985
- end %>
986
- <%= link_to txt, send(\"#\{klass._brick_index}_path\".to_sym, hms_col[2]) unless hms_col[1]&.zero? %>
980
+ %><%= klass = (col = cols[col_name])[1]
981
+ if col[2] == 'HO'
982
+ descrips = @_brick_bt_descrip[col_name.to_sym][klass]
983
+ if (ho_id = (ho_id_col = descrips.last).map { |id_col| #{obj_name}.send(id_col.to_sym) })&.first
984
+ ho_txt = klass.brick_descrip(#{obj_name}, descrips[0..-2].map { |id| #{obj_name}.send(id.last[0..62]) }, ho_id_col)
985
+ link_to(ho_txt, send(\"#\{klass.base_class._brick_index(:singular)}_path\".to_sym, ho_id))
986
+ end
987
+ elsif hms_col[1]&.positive?
988
+ link_to \"#\{hms_col[1] || 'View'} #\{hms_col.first}\", send(\"#\{klass._brick_index}_path\".to_sym, hms_col[2])
989
+ end %>
987
990
  <% end
988
991
  elsif (col = cols[col_name])
989
992
  col_type = col&.sql_type == 'geography' ? col.sql_type : col&.type
@@ -1019,7 +1022,7 @@ erDiagram
1019
1022
  # Easily could be multiple files involved (STI for instance)
1020
1023
  +"#{css}
1021
1024
  <p style=\"color: green\"><%= notice %></p>#{"
1022
- <select id=\"schema\">#{schema_options}</select>" if ::Brick.config.schema_behavior[:multitenant] && ::Brick.db_schemas.length > 1}
1025
+ #{schema_options}" if schema_options}
1023
1026
  <select id=\"tbl\">#{table_options}</select>
1024
1027
  <h1>Status</h1>
1025
1028
  <table id=\"status\" class=\"shadow\"><thead><tr>
@@ -1068,7 +1071,7 @@ erDiagram
1068
1071
  if is_orphans
1069
1072
  +"#{css}
1070
1073
  <p style=\"color: green\"><%= notice %></p>#{"
1071
- <select id=\"schema\">#{schema_options}</select>" if ::Brick.config.schema_behavior[:multitenant] && ::Brick.db_schemas.length > 1}
1074
+ #{schema_options}" if schema_options}
1072
1075
  <select id=\"tbl\">#{table_options}</select>
1073
1076
  <h1>Orphans<%= \" for #\{}\" if false %></h1>
1074
1077
  <% @orphans.each do |o|
@@ -1099,7 +1102,7 @@ erDiagram
1099
1102
  </svg>
1100
1103
 
1101
1104
  <p style=\"color: green\"><%= notice %></p>#{"
1102
- <select id=\"schema\">#{schema_options}</select>" if ::Brick.config.schema_behavior[:multitenant] && ::Brick.db_schemas.length > 1}
1105
+ #{schema_options}" if schema_options}
1103
1106
  <select id=\"tbl\">#{table_options}</select>
1104
1107
  <h1><%= page_title %></h1><%
1105
1108
  if (description = (relation = Brick.relations[#{model_name}.table_name])&.fetch(:description, nil)) %><%=
@@ -1411,9 +1414,26 @@ document.querySelectorAll(\"input, select\").forEach(function (inp) {
1411
1414
  # As if it were an inline template (see #determine_template in actionview-5.2.6.2/lib/action_view/renderer/template_renderer.rb)
1412
1415
  keys = options.has_key?(:locals) ? options[:locals].keys : []
1413
1416
  handler = ActionView::Template.handler_for_extension(options[:type] || 'erb')
1414
- ActionView::Template.new(inline, "auto-generated #{args.first} template", handler, locals: keys)
1417
+ ActionView::Template.new(inline, "auto-generated #{args.first} template", handler, locals: keys).tap do |t|
1418
+ t.instance_variable_set(:@is_brick, true)
1419
+ end
1415
1420
  end
1416
- end
1421
+ end # LookupContext
1422
+
1423
+ # For any auto-generated template, if multitenancy is active via some flavour of an Apartment gem, switch back to the default tenant.
1424
+ # (Underlying reason -- ros-apartment can hold on to a selected tenant between requests when there is no elevator middleware.)
1425
+ ActionView::TemplateRenderer.class_exec do
1426
+ private
1427
+
1428
+ alias _brick_render_template render_template
1429
+ def render_template(view, template, *args)
1430
+ result = _brick_render_template(view, template, *args)
1431
+ if template.instance_variable_get(:@is_brick)
1432
+ Apartment::Tenant.switch!(::Brick.apartment_default_tenant) if ::Brick.apartment_multitenant
1433
+ end
1434
+ result
1435
+ end
1436
+ end # TemplateRenderer
1417
1437
  end
1418
1438
 
1419
1439
  if ::Brick.enable_routes?
@@ -5,7 +5,7 @@ module Brick
5
5
  module VERSION
6
6
  MAJOR = 1
7
7
  MINOR = 0
8
- TINY = 92
8
+ TINY = 93
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
@@ -136,17 +136,19 @@ module Brick
136
136
  attr_accessor :default_schema, :db_schemas, :routes_done, :is_oracle, :is_eager_loading, :auto_models
137
137
 
138
138
  def set_db_schema(params = nil)
139
- schema = (params ? params['_brick_schema'] : ::Brick.default_schema)
140
- chosen = if schema && ::Brick.db_schemas&.key?(schema)
141
- ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?;", schema)
142
- schema
143
- elsif ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
144
- # Just return the current schema
145
- orig_schema = ActiveRecord::Base.execute_sql('SELECT current_schemas(true)').first['current_schemas'][1..-2].split(',')
146
- # ::Brick.apartment_multitenant && tbl_parts.first == Apartment.default_schema
147
- (orig_schema - ['pg_catalog']).first
148
- end
149
- chosen == ::Brick.default_schema ? nil : chosen
139
+ # If Apartment::Tenant.current is not still the default (usually 'public') then an elevator has brought us into
140
+ # a different tenant. If so then don't allow schema navigation.
141
+ chosen = if (is_show_schema_list = (apartment_multitenant && Apartment::Tenant.current == ::Brick.default_schema)) &&
142
+ (schema = (params ? params['_brick_schema'] : ::Brick.default_schema)) &&
143
+ ::Brick.db_schemas&.key?(schema)
144
+ Apartment::Tenant.switch!(schema)
145
+ schema
146
+ elsif ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
147
+ # Just return the current schema
148
+ current_schema = ActiveRecord::Base.execute_sql('SELECT current_schemas(true)').first['current_schemas'][1..-2].split(',')
149
+ (current_schema - ['pg_catalog', 'pg_toast', 'heroku_ext']).first
150
+ end
151
+ [chosen == ::Brick.default_schema ? nil : chosen, is_show_schema_list]
150
152
  end
151
153
 
152
154
  # All tables and views (what Postgres calls "relations" including column and foreign key info)
@@ -162,6 +164,10 @@ module Brick
162
164
  @apartment_multitenant
163
165
  end
164
166
 
167
+ def apartment_default_tenant
168
+ Apartment.default_tenant || 'public'
169
+ end
170
+
165
171
  # If multitenancy is enabled, a list of non-tenanted "global" models
166
172
  def non_tenanted_models
167
173
  @pending_models ||= {}
@@ -1086,21 +1092,9 @@ ActiveSupport.on_load(:active_record) do
1086
1092
  end
1087
1093
  end
1088
1094
 
1089
- # First part of arel_table_type stuff:
1090
- # ------------------------------------
1091
- # (more found below)
1092
1095
  # was: ActiveRecord.version >= ::Gem::Version.new('3.2') &&
1093
1096
  if ActiveRecord.version < ::Gem::Version.new('5.0')
1094
- # Used by Util#_arel_table_type
1095
1097
  module ActiveRecord
1096
- class Base
1097
- def self.arel_table
1098
- @arel_table ||= Arel::Table.new(table_name, arel_engine).tap do |x|
1099
- x.instance_variable_set(:@_arel_table_type, self)
1100
- end
1101
- end
1102
- end
1103
-
1104
1098
  # Final pieces for left_outer_joins support, which was derived from this commit:
1105
1099
  # https://github.com/rails/rails/commit/3f46ef1ddab87482b730a3f53987e04308783d8b
1106
1100
  module Associations
@@ -1185,13 +1179,9 @@ if is_postgres && ActiveRecord.version < ::Gem::Version.new('5.0') # Was: && Ob
1185
1179
  PGError = PG::Error
1186
1180
  end
1187
1181
 
1188
- # More arel_table_type stuff:
1189
- # ---------------------------
1190
1182
  if ActiveRecord.version < ::Gem::Version.new('5.2')
1191
1183
  # Specifically for AR 3.1 and 3.2 to avoid: "undefined method `delegate' for ActiveRecord::Reflection::ThroughReflection:Class"
1192
1184
  require 'active_support/core_ext/module/delegation' if ActiveRecord.version < ::Gem::Version.new('4.0')
1193
- # Used by Util#_arel_table_type
1194
- # rubocop:disable Style/CommentedKeyword
1195
1185
  module ActiveRecord
1196
1186
  module Reflection
1197
1187
  # AR < 4.0 doesn't know about join_table and derive_join_table
@@ -1209,61 +1199,23 @@ if ActiveRecord.version < ::Gem::Version.new('5.2')
1209
1199
  end
1210
1200
  end
1211
1201
  end
1212
-
1213
- module Associations
1214
- # Specific to AR 4.2 - 5.1:
1215
- if self.const_defined?('JoinDependency') && JoinDependency.private_instance_methods.include?(:table_aliases_for)
1216
- class JoinDependency
1217
- private
1218
-
1219
- if ActiveRecord.version < ::Gem::Version.new('5.1') # 4.2 or 5.0
1220
- def table_aliases_for(parent, node)
1221
- node.reflection.chain.map do |reflection|
1222
- alias_tracker.aliased_table_for(
1223
- reflection.table_name,
1224
- table_alias_for(reflection, parent, reflection != node.reflection)
1225
- ).tap do |x|
1226
- # %%% Specific only to Rails 4.2 (and maybe 4.1?)
1227
- x = x.left if x.is_a?(Arel::Nodes::TableAlias)
1228
- y = reflection.chain.find { |c| c.table_name == x.name }
1229
- x.instance_variable_set(:@_arel_table_type, y.klass)
1230
- end
1231
- end
1232
- end
1233
- end
1234
- end
1235
- elsif Associations.const_defined?('JoinHelper') && JoinHelper.private_instance_methods.include?(:construct_tables)
1236
- module JoinHelper
1237
- private
1238
-
1239
- # AR > 3.0 and < 4.2 (%%% maybe only < 4.1?) uses construct_tables like this:
1240
- def construct_tables
1241
- tables = []
1242
- chain.each do |reflection|
1243
- tables << alias_tracker.aliased_table_for(
1244
- table_name_for(reflection),
1245
- table_alias_for(reflection, reflection != self.reflection)
1246
- ).tap do |x|
1247
- x = x.left if x.is_a?(Arel::Nodes::TableAlias)
1248
- x.instance_variable_set(:@_arel_table_type, reflection.chain.find { |c| c.table_name == x.name }.klass)
1249
- end
1250
-
1251
- next unless reflection.source_macro == :has_and_belongs_to_many
1252
-
1253
- tables << alias_tracker.aliased_table_for(
1254
- (reflection.source_reflection || reflection).join_table,
1255
- table_alias_for(reflection, true)
1256
- )
1257
- end
1258
- tables
1259
- end
1260
- end
1261
- end
1262
- end
1263
- end # module ActiveRecord
1264
- # rubocop:enable Style/CommentedKeyword
1202
+ end
1265
1203
  end
1266
1204
 
1205
+ # The "brick_links" patch -- this finds how every AR chain of association names
1206
+ # relates back to an exact table correlation name chosen by AREL when the AST tree is
1207
+ # walked. For instance, from a Customer model there could be a join_tree such as
1208
+ # { orders: { line_items: :product} }, which would end up recording three entries, the
1209
+ # last of which for products would have a key of "orders.line_items.product" after
1210
+ # having gone through two HMs and one BT. AREL would have chosen a correlation name of
1211
+ # "products", being able to use the same name as the table name because it's the first
1212
+ # time that table is used in this query. But let's see what happens if each customer
1213
+ # also had a BT to a favourite product, referenced earlier in the join_tree like this:
1214
+ # [:favourite_product, orders: { line_items: :product}] -- then the second reference to
1215
+ # "products" would end up being called "products_line_items" in order to differentiate
1216
+ # it from the first reference, which would have already snagged the simpler name
1217
+ # "products". It's essential that The Brick can find accurate correlation names when
1218
+ # there are multiple JOINs to the same table.
1267
1219
  module ActiveRecord
1268
1220
  module QueryMethods
1269
1221
  private
@@ -1290,7 +1242,7 @@ module ActiveRecord
1290
1242
  end
1291
1243
  end
1292
1244
 
1293
- # require 'activerecord/associations/join_dependency'
1245
+ # require 'active_record/associations/join_dependency'
1294
1246
  module Associations
1295
1247
  # For AR >= 4.2
1296
1248
  if self.const_defined?('JoinDependency')
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.92
4
+ version: 1.0.93
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lorin Thwaits
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-11-15 00:00:00.000000000 Z
11
+ date: 2022-11-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -164,20 +164,6 @@ dependencies:
164
164
  - - "~>"
165
165
  - !ruby/object:Gem::Version
166
166
  version: 1.42.0
167
- - !ruby/object:Gem::Dependency
168
- name: mysql2
169
- requirement: !ruby/object:Gem::Requirement
170
- requirements:
171
- - - "~>"
172
- - !ruby/object:Gem::Version
173
- version: '0.5'
174
- type: :development
175
- prerelease: false
176
- version_requirements: !ruby/object:Gem::Requirement
177
- requirements:
178
- - - "~>"
179
- - !ruby/object:Gem::Version
180
- version: '0.5'
181
167
  - !ruby/object:Gem::Dependency
182
168
  name: pg
183
169
  requirement: !ruby/object:Gem::Requirement