brick 1.0.199 → 1.0.200

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.
@@ -436,7 +436,11 @@ module ActiveRecord
436
436
  order_by = ordering&.each_with_object([]) do |ord_part, s| # %%% If a term is also used as an eqi-condition in the WHERE clause, it can be omitted from ORDER BY
437
437
  case ord_part
438
438
  when String
439
- ord_expr = _br_quoted_name(ord_part.gsub('^^^', table_name))
439
+ ord_expr = if ord_part.index('(') # Any kind of SQL function at play here?
440
+ ord_part.gsub('^^^', _br_quoted_name(table_name))
441
+ else
442
+ _br_quoted_name(ord_part.gsub('^^^', table_name))
443
+ end
440
444
  s << Arel.sql(ord_expr)
441
445
  order_by_txt&.<<(ord_expr.index('.') ? "Arel.sql(#{ord_expr.inspect})" : ord_part.inspect)
442
446
  else # Expecting only Symbol
@@ -887,12 +891,16 @@ module ActiveRecord
887
891
 
888
892
  tbl_alias = unique63("b_r_#{hm.name}", previous)
889
893
  on_clause = []
890
- hm_selects = if fk_col.is_a?(Array) # Composite key?
891
- fk_col.each_with_index { |fk_col_part, idx| on_clause << "#{tbl_alias}.#{fk_col_part} = #{pri_tbl.table_name}.#{pri_key[idx]}" }
892
- fk_col.dup
893
- else
894
+ hm_selects = if !pri_key.is_a?(Array) # Probable standard key?
895
+ if fk_col.is_a?(Array) # Foreign is composite but not Primary? OK, or choose the first part of the foreign key if nothing else
896
+ fk_col = fk_col.find { |col_name| col_name == pri_key } || # Try to associate with the same-named part of the foreign key ...
897
+ fk_col.first # ... and if no good match, just choose the first part
898
+ end
894
899
  on_clause << "#{_br_quoted_name("#{tbl_alias}.#{fk_col}")} = #{_br_quoted_name("#{pri_tbl.table_name}.#{pri_key}")}"
895
900
  [fk_col]
901
+ else # Composite key
902
+ fk_col.each_with_index { |fk_col_part, idx| on_clause << "#{tbl_alias}.#{fk_col_part} = #{pri_tbl.table_name}.#{pri_key[idx]}" }
903
+ fk_col.dup
896
904
  end
897
905
  if poly_type
898
906
  hm_selects << poly_type
@@ -1166,6 +1174,34 @@ Might want to add this in your brick.rb:
1166
1174
  end
1167
1175
  end
1168
1176
 
1177
+ if ::Brick.enable_routes? && Object.const_defined?('ActionDispatch')
1178
+ require 'brick/route_mapper'
1179
+ ActionDispatch::Routing::RouteSet.class_exec do
1180
+ # In order to defer auto-creation of any routes that already exist, calculate Brick routes only after having loaded all others
1181
+ prepend ::Brick::RouteSet
1182
+ end
1183
+ ActionDispatch::Routing::Mapper.class_exec do
1184
+ include ::Brick::RouteMapper
1185
+ end
1186
+
1187
+ # Do the root route before the Rails Welcome one would otherwise take precedence
1188
+ if (route = ::Brick.config.default_route_fallback).present?
1189
+ action = "#{route}#{'#index' unless route.index('#')}"
1190
+ if ::Brick.config.path_prefix
1191
+ ::Rails.application.routes.append do
1192
+ send(:namespace, ::Brick.config.path_prefix) do
1193
+ send(:root, action)
1194
+ end
1195
+ end
1196
+ elsif ::Rails.application.routes.named_routes.send(:routes)[:root].nil?
1197
+ ::Rails.application.routes.append do
1198
+ send(:root, action)
1199
+ end
1200
+ end
1201
+ ::Brick.established_drf = "/#{::Brick.config.path_prefix}#{action[action.index('#')..-1]}"
1202
+ end
1203
+ end
1204
+
1169
1205
  if Object.const_defined?('ActionView')
1170
1206
  require 'brick/frameworks/rails/form_tags'
1171
1207
  require 'brick/frameworks/rails/form_builder'
@@ -1298,19 +1334,23 @@ end
1298
1334
  unless (is_tnp_module = (is_brick_prefix && !is_controller && ::Brick.config.table_name_prefixes.values.include?(requested)))
1299
1335
  # ... first look around for an existing module or class.
1300
1336
  desired_classname = (self == Object || !name) ? requested : "#{name}::#{requested}"
1301
- if ((is_defined = self.const_defined?(args.first)) && (possible = self.const_get(args.first)) &&
1337
+ if (self.const_defined?(args.first) && (possible = self.const_get(args.first)) &&
1302
1338
  # Reset `possible` if it's a controller request that's not a perfect match
1303
1339
  # Was: (possible = nil) but changed to #local_variable_set in order to suppress the "= should be ==" warning
1304
1340
  (possible&.name == desired_classname || (is_controller && binding.local_variable_set(:possible, nil)))) ||
1341
+
1305
1342
  # Try to require the respective Ruby file
1343
+ # ((filename = ActiveSupport::Dependencies.search_for_file(desired_classname.underscore)) &&
1344
+ # (require_dependency(filename) || true) &&
1306
1345
  ((filename = ActiveSupport::Dependencies.search_for_file(desired_classname.underscore) ||
1307
1346
  (self != Object && ActiveSupport::Dependencies.search_for_file((desired_classname = requested).underscore))
1308
1347
  ) && (require_dependency(filename) || true) &&
1309
1348
  ((possible = self.const_get(args.first)) && possible.name == desired_classname)
1310
1349
  ) ||
1350
+
1311
1351
  # If any class has turned up so far (and we're not in the middle of eager loading)
1312
1352
  # then return what we've found.
1313
- (is_defined && !::Brick.is_eager_loading) # Used to also have: && possible != self
1353
+ (possible&.module_parent == base_module && !::Brick.is_eager_loading) # Used to also have: && possible != self
1314
1354
  if ((!brick_root && (filename || possible.instance_of?(Class))) ||
1315
1355
  (possible.instance_of?(Module) && possible&.module_parent == self) ||
1316
1356
  (possible.instance_of?(Class) && possible == self)) && # Are we simply searching for ourselves?
@@ -1342,13 +1382,14 @@ end
1342
1382
  singular_class_name = ::Brick.namify(plural_class_name, :underscore).singularize.camelize
1343
1383
  full_class_name << "::#{singular_class_name}"
1344
1384
  skip_controller = nil
1345
- if plural_class_name == 'BrickOpenapi' ||
1346
- (
1347
- (::Brick.config.add_status || ::Brick.config.add_orphans) &&
1348
- plural_class_name == 'BrickGem'
1349
- ) ||
1350
- begin
1351
- model = self.const_get(full_class_name)
1385
+ begin
1386
+ if plural_class_name == 'BrickOpenapi' ||
1387
+ (
1388
+ (::Brick.config.add_status || ::Brick.config.add_orphans) &&
1389
+ plural_class_name == 'BrickGem'
1390
+ # Was: ) || (model = self.const_get(full_class_name))
1391
+ ) || (model = Object.const_get(full_class_name))
1392
+ # puts "#{self.name} - #{full_class_name}"
1352
1393
 
1353
1394
  # In the very rare case that we've picked up a MODULE which has the same name as what would be the
1354
1395
  # resource's MODEL name, just build out an appropriate auto-model on-the-fly. (RailsDevs code has this in PayCustomer.)
@@ -1356,10 +1397,10 @@ end
1356
1397
  if model && !model.is_a?(Class)
1357
1398
  model, _code = Object.send(:build_model, relations, model.module_parent, model.module_parent.name, singular_class_name)
1358
1399
  end
1359
- rescue NameError # If the const_get for the model has failed...
1360
- skip_controller = true
1361
- # ... then just fall through and allow it to fail when trying to load the ____Controller class normally.
1362
1400
  end
1401
+ rescue NameError # If the const_get for the model has failed...
1402
+ skip_controller = true
1403
+ # ... then just fall through and allow it to fail when trying to load the ____Controller class normally.
1363
1404
  end
1364
1405
  unless skip_controller
1365
1406
  Object.send(:build_controller, self, class_name, plural_class_name, model, relations)
@@ -1452,7 +1493,7 @@ end
1452
1493
  base_module._brick_const_missing(*args)
1453
1494
  # elsif base_module != Object
1454
1495
  # module_parent.const_missing(*args)
1455
- elsif Rails.respond_to?(:autoloaders) && # After finding nothing else, if Zeitwerk is enabled ...
1496
+ elsif Object.const_defined?('Rails') && ::Rails.respond_to?(:autoloaders) && # After finding nothing else, if Zeitwerk is enabled ...
1456
1497
  (Rails::Autoloaders.respond_to?(:zeitwerk_enabled?) ? Rails::Autoloaders.zeitwerk_enabled? : true)
1457
1498
  self._brick_const_missing(*args) # ... rely solely on Zeitwerk.
1458
1499
  else # Classic mode
@@ -1477,7 +1518,9 @@ class Object
1477
1518
  private
1478
1519
 
1479
1520
  def build_model(relations, base_module, base_name, class_name, inheritable_name = nil)
1480
- tnp = ::Brick.config.table_name_prefixes&.find { |p| p.last == base_module.name }&.first
1521
+ tnp = ::Brick.config.table_name_prefixes&.find { |p| p.last == base_module.name }
1522
+ # return [base_module, ''] if !base_module.is_a?(Class) && base_name == tnp&.last
1523
+
1481
1524
  if (base_model = (::Brick.config.sti_namespace_prefixes&.fetch("::#{base_module.name}::#{class_name}", nil) || # Are we part of an auto-STI namespace? ...
1482
1525
  ::Brick.config.sti_namespace_prefixes&.fetch("::#{base_module.name}::", nil))&.constantize) ||
1483
1526
  base_module != Object # ... or otherwise already in some namespace?
@@ -1499,7 +1542,7 @@ class Object
1499
1542
  table_name = if (base_model = ::Brick.sti_models[model_name]&.fetch(:base, nil) || ::Brick.existing_stis[model_name]&.constantize)
1500
1543
  base_model.table_name
1501
1544
  else
1502
- "#{tnp}#{ActiveSupport::Inflector.pluralize(singular_table_name)}"
1545
+ "#{tnp&.first}#{ActiveSupport::Inflector.pluralize(singular_table_name)}"
1503
1546
  end
1504
1547
  if ::Brick.apartment_multitenant &&
1505
1548
  Apartment.excluded_models.include?(table_name.singularize.camelize)
@@ -1553,7 +1596,7 @@ class Object
1553
1596
  else
1554
1597
  # Class for auto-generated models to inherit from
1555
1598
  base_model = (::Brick.config.models_inherit_from ||= (begin
1556
- ::ApplicationRecord
1599
+ Object.const_defined?('ApplicationRecord') ? ::ApplicationRecord : ::ActiveRecord::Base
1557
1600
  rescue StandardError => ex
1558
1601
  ::ActiveRecord::Base
1559
1602
  end))
@@ -1728,7 +1771,8 @@ class Object
1728
1771
  end
1729
1772
 
1730
1773
  def build_bt_or_hm(full_name, relations, relation, hmts, assoc, inverse_assoc_name, inverse_table, code)
1731
- singular_table_name = inverse_table&.singularize
1774
+ return unless (singular_table_name = inverse_table&.singularize)
1775
+
1732
1776
  options = {}
1733
1777
  macro = if assoc[:is_bt]
1734
1778
  # Try to take care of screwy names if this is a belongs_to going to an STI subclass
@@ -2592,586 +2636,46 @@ end
2592
2636
  # ==========================================================
2593
2637
  # Get info on all relations during first database connection
2594
2638
  # ==========================================================
2639
+ if Object.const_defined?('OTR') && OTR.const_defined?('ActiveRecord')
2640
+ OTR::ActiveRecord.class_exec do
2641
+ class << self
2642
+ alias _brick_establish_connection! establish_connection!
2643
+ def establish_connection!(*args)
2644
+ conn = _brick_establish_connection!(*args)
2645
+ return conn unless ::Brick.config.mode == :on
2595
2646
 
2596
- if ActiveRecord.const_defined?('ConnectionHandling')
2597
- ActiveRecord::ConnectionHandling
2598
- else
2599
- ActiveRecord::ConnectionAdapters::ConnectionHandler
2600
- end.class_exec do
2601
- alias _brick_establish_connection establish_connection
2602
- def establish_connection(*args)
2603
- conn = _brick_establish_connection(*args)
2604
- return conn unless ::Brick.config.mode == :on
2605
-
2606
- begin
2607
- # Overwrite SQLite's #begin_db_transaction so it opens in IMMEDIATE mode instead of
2608
- # the default DEFERRED mode.
2609
- # https://discuss.rubyonrails.org/t/failed-write-transaction-upgrades-in-sqlite3/81480/2
2610
- if ActiveRecord::Base.connection.adapter_name == 'SQLite'
2611
- arca = ::ActiveRecord::ConnectionAdapters
2612
- db_statements = arca::SQLite3.const_defined?('DatabaseStatements') ? arca::SQLite3::DatabaseStatements : arca::SQLite3::SchemaStatements
2613
- # Rails 7.1 and later
2614
- if arca::AbstractAdapter.private_instance_methods.include?(:with_raw_connection)
2615
- db_statements.define_method(:begin_db_transaction) do
2616
- log("begin immediate transaction", "TRANSACTION") do
2617
- with_raw_connection(allow_retry: true, materialize_transactions: false) do |conn|
2618
- conn.transaction(:immediate)
2619
- end
2620
- end
2621
- end
2622
- else # Rails < 7.1
2623
- db_statements.define_method(:begin_db_transaction) do
2624
- log('begin immediate transaction', 'TRANSACTION') { @connection.transaction(:immediate) }
2625
- end
2647
+ begin
2648
+ # ::Brick.is_db_present = true
2649
+ ::Brick.reflect_tables
2650
+ rescue ActiveRecord::NoDatabaseError
2651
+ # ::Brick.is_db_present = false
2626
2652
  end
2653
+ Module.class_exec &::Brick::ADD_CONST_MISSING
2654
+ conn
2627
2655
  end
2628
- # ::Brick.is_db_present = true
2629
- _brick_reflect_tables
2630
- rescue ActiveRecord::NoDatabaseError
2631
- # ::Brick.is_db_present = false
2632
2656
  end
2633
- conn
2634
2657
  end
2658
+ else
2659
+ if ActiveRecord.const_defined?('ConnectionHandling')
2660
+ ActiveRecord::ConnectionHandling
2661
+ else
2662
+ ActiveRecord::ConnectionAdapters::ConnectionHandler
2663
+ end.class_exec do
2664
+ alias _brick_establish_connection establish_connection
2665
+ def establish_connection(*args)
2666
+ conn = _brick_establish_connection(*args)
2667
+ return conn unless ::Brick.config.mode == :on
2635
2668
 
2636
- # This is done separately so that during testing it can be called right after a migration
2637
- # in order to make sure everything is good.
2638
- def _brick_reflect_tables
2639
- return unless ::Brick.config.mode == :on
2640
-
2641
- # return if ActiveRecord::Base.connection.current_database == 'postgres'
2642
-
2643
- orig_schema = nil
2644
- if (relations = ::Brick.relations).keys == [:db_name]
2645
- ::Brick.remove_instance_variable(:@_additional_references_loaded) if ::Brick.instance_variable_defined?(:@_additional_references_loaded)
2646
-
2647
- # --------------------------------------------
2648
- # 1. Load three initializers early
2649
- # (inflectsions.rb, brick.rb, apartment.rb)
2650
- # Very first thing, load inflections since we'll be using .pluralize and .singularize on table and model names
2651
- if File.exist?(inflections = ::Rails.root&.join('config/initializers/inflections.rb') || '')
2652
- load inflections
2653
- end
2654
- # Now the Brick initializer since there may be important schema things configured
2655
- if !::Brick.initializer_loaded && File.exist?(brick_initializer = ::Rails.root&.join('config/initializers/brick.rb') || '')
2656
- ::Brick.initializer_loaded = load brick_initializer
2657
-
2658
- # After loading the initializer, add compatibility for ActiveStorage and ActionText if those haven't already been
2659
- # defined. (Further JSON configuration for ActiveStorage metadata happens later in the after_initialize hook.)
2660
- ['ActiveStorage', 'ActionText'].each do |ar_extension|
2661
- if Object.const_defined?(ar_extension) &&
2662
- (extension = Object.const_get(ar_extension)).respond_to?(:table_name_prefix) &&
2663
- !::Brick.config.table_name_prefixes.key?(as_tnp = extension.table_name_prefix)
2664
- ::Brick.config.table_name_prefixes[as_tnp] = ar_extension
2665
- end
2666
- end
2667
-
2668
- # Support the followability gem: https://github.com/nejdetkadir/followability
2669
- if Object.const_defined?('Followability') && !::Brick.config.table_name_prefixes.key?('followability_')
2670
- ::Brick.config.table_name_prefixes['followability_'] = 'Followability'
2671
- end
2672
- end
2673
- # Load the initializer for the Apartment gem a little early so that if .excluded_models and
2674
- # .default_schema are specified then we can work with non-tenanted models more appropriately
2675
- if (apartment = Object.const_defined?('Apartment')) &&
2676
- File.exist?(apartment_initializer = ::Rails.root.join('config/initializers/apartment.rb'))
2677
- require 'apartment/adapters/abstract_adapter'
2678
- Apartment::Adapters::AbstractAdapter.class_exec do
2679
- if instance_methods.include?(:process_excluded_models)
2680
- def process_excluded_models
2681
- # All other models will share a connection (at Apartment.connection_class) and we can modify at will
2682
- Apartment.excluded_models.each do |excluded_model|
2683
- begin
2684
- process_excluded_model(excluded_model)
2685
- rescue NameError => e
2686
- (@bad_models ||= []) << excluded_model
2687
- end
2688
- end
2689
- end
2690
- end
2691
- end
2692
- unless @_apartment_loaded
2693
- load apartment_initializer
2694
- @_apartment_loaded = true
2695
- end
2696
- end
2697
- # Only for Postgres (Doesn't work in sqlite3 or MySQL)
2698
- # puts ActiveRecord::Base.execute_sql("SELECT current_setting('SEARCH_PATH')").to_a.inspect
2699
-
2700
- # ---------------------------
2701
- # 2. Figure out schema things
2702
- is_postgres = nil
2703
- is_mssql = ActiveRecord::Base.connection.adapter_name == 'SQLServer'
2704
- case ActiveRecord::Base.connection.adapter_name
2705
- when 'PostgreSQL', 'SQLServer'
2706
- is_postgres = !is_mssql
2707
- db_schemas = if is_postgres
2708
- ActiveRecord::Base.execute_sql('SELECT nspname AS table_schema, MAX(oid) AS dt FROM pg_namespace GROUP BY 1 ORDER BY 1;')
2709
- else
2710
- ActiveRecord::Base.execute_sql('SELECT DISTINCT table_schema, NULL AS dt FROM INFORMATION_SCHEMA.tables;')
2711
- end
2712
- ::Brick.db_schemas = db_schemas.each_with_object({}) do |row, s|
2713
- row = case row
2714
- when Array
2715
- row
2716
- else
2717
- [row['table_schema'], row['dt']]
2718
- end
2719
- # Remove any system schemas
2720
- s[row.first] = { dt: row.last } unless ['information_schema', 'pg_catalog', 'pg_toast', 'heroku_ext',
2721
- 'INFORMATION_SCHEMA', 'sys'].include?(row.first)
2722
- end
2723
- possible_schema, possible_schemas, multitenancy = ::Brick.get_possible_schemas
2724
- if possible_schemas
2725
- if possible_schema
2726
- ::Brick.default_schema = ::Brick.apartment_default_tenant
2727
- schema = possible_schema
2728
- orig_schema = ActiveRecord::Base.execute_sql('SELECT current_schemas(true)').first['current_schemas'][1..-2].split(',')
2729
- ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?", schema)
2730
- # When testing, just find the most recently-created schema
2731
- elsif begin
2732
- Rails.env == 'test' ||
2733
- ActiveRecord::Base.execute_sql("SELECT value FROM ar_internal_metadata WHERE key='environment';").first&.fetch('value', nil) == 'test'
2734
- rescue
2735
- end
2736
- ::Brick.default_schema = ::Brick.apartment_default_tenant
2737
- ::Brick.test_schema = schema = ::Brick.db_schemas.to_a.sort { |a, b| b.last[:dt] <=> a.last[:dt] }.first.first
2738
- if possible_schema.blank?
2739
- puts "While running tests, using the most recently-created schema, #{schema}."
2740
- else
2741
- 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}."
2742
- end
2743
- orig_schema = ActiveRecord::Base.execute_sql('SELECT current_schemas(true)').first['current_schemas'][1..-2].split(',')
2744
- ::Brick.config.schema_behavior = { multitenant: {} } # schema_to_analyse: [schema]
2745
- ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?", schema)
2746
- else
2747
- 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. ***"
2748
- if ::Brick.db_schemas.key?(::Brick.apartment_default_tenant)
2749
- ::Brick.default_schema = schema = ::Brick.apartment_default_tenant
2750
- end
2751
- end
2752
- end
2753
- when 'Mysql2', 'Trilogy'
2754
- ::Brick.default_schema = schema = ActiveRecord::Base.connection.current_database
2755
- when 'OracleEnhanced'
2756
- # ActiveRecord::Base.connection.current_database will be something like "XEPDB1"
2757
- ::Brick.default_schema = schema = ActiveRecord::Base.connection.raw_connection.username
2758
- ::Brick.db_schemas = {}
2759
- ActiveRecord::Base.execute_sql("SELECT username FROM sys.all_users WHERE ORACLE_MAINTAINED != 'Y'").each { |s| ::Brick.db_schemas[s.first] = {} }
2760
- when 'SQLite'
2761
- sql = "SELECT m.name AS relation_name, UPPER(m.type) AS table_type,
2762
- p.name AS column_name, p.type AS data_type,
2763
- CASE p.pk WHEN 1 THEN 'PRIMARY KEY' END AS const
2764
- FROM sqlite_master AS m
2765
- INNER JOIN pragma_table_info(m.name) AS p
2766
- WHERE m.name NOT IN ('sqlite_sequence', ?, ?)
2767
- ORDER BY m.name, p.cid"
2768
- else
2769
- puts "Unfamiliar with connection adapter #{ActiveRecord::Base.connection.adapter_name}"
2770
- end
2771
-
2772
- ::Brick.db_schemas ||= {}
2773
-
2774
- # ---------------------
2775
- # 3. Tables and columns
2776
- # %%% Retrieve internal ActiveRecord table names like this:
2777
- # ActiveRecord::Base.internal_metadata_table_name, ActiveRecord::Base.schema_migrations_table_name
2778
- # For if it's not SQLite -- so this is the Postgres and MySQL version
2779
- measures = []
2780
- ::Brick.is_oracle = true if ActiveRecord::Base.connection.adapter_name == 'OracleEnhanced'
2781
- case ActiveRecord::Base.connection.adapter_name
2782
- when 'PostgreSQL', 'SQLite' # These bring back a hash for each row because the query uses column aliases
2783
- # schema ||= 'public' if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
2784
- retrieve_schema_and_tables(sql, is_postgres, is_mssql, schema).each do |r|
2785
- # If Apartment gem lists the table as being associated with a non-tenanted model then use whatever it thinks
2786
- # is the default schema, usually 'public'.
2787
- schema_name = if ::Brick.config.schema_behavior[:multitenant]
2788
- ::Brick.apartment_default_tenant if ::Brick.is_apartment_excluded_table(r['relation_name'])
2789
- elsif ![schema, 'public'].include?(r['schema'])
2790
- r['schema']
2791
- end
2792
- relation_name = schema_name ? "#{schema_name}.#{r['relation_name']}" : r['relation_name']
2793
- # Both uppers and lowers as well as underscores?
2794
- ::Brick.apply_double_underscore_patch if relation_name =~ /[A-Z]/ && relation_name =~ /[a-z]/ && relation_name.index('_')
2795
- relation = relations[relation_name]
2796
- relation[:isView] = true if r['table_type'] == 'VIEW'
2797
- relation[:description] = r['table_description'] if r['table_description']
2798
- col_name = r['column_name']
2799
- key = case r['const']
2800
- when 'PRIMARY KEY'
2801
- relation[:pkey][r['key'] || relation_name] ||= []
2802
- when 'UNIQUE'
2803
- relation[:ukeys][r['key'] || "#{relation_name}.#{col_name}"] ||= []
2804
- # key = (relation[:ukeys] = Hash.new { |h, k| h[k] = [] }) if key.is_a?(Array)
2805
- # key[r['key']]
2806
- else
2807
- if r['data_type'] == 'uuid'
2808
- # && r['column_name'] == ::Brick.ar_base.primary_key
2809
- # binding.pry
2810
- relation[:pkey][r['key'] || relation_name] ||= []
2811
- end
2812
- end
2813
- # binding.pry if key && r['data_type'] == 'uuid'
2814
- key << col_name if key
2815
- cols = relation[:cols] # relation.fetch(:cols) { relation[:cols] = [] }
2816
- cols[col_name] = [r['data_type'], r['max_length'], measures&.include?(col_name), r['is_nullable'] == 'NO']
2817
- # puts "KEY! #{r['relation_name']}.#{col_name} #{r['key']} #{r['const']}" if r['key']
2818
- relation[:col_descrips][col_name] = r['column_description'] if r['column_description']
2819
- end
2820
- else # MySQL2, OracleEnhanced, and MSSQL act a little differently, bringing back an array for each row
2821
- schema_and_tables = case ActiveRecord::Base.connection.adapter_name
2822
- when 'OracleEnhanced'
2823
- sql =
2824
- "SELECT c.owner AS schema, c.table_name AS relation_name,
2825
- CASE WHEN v.owner IS NULL THEN 'BASE_TABLE' ELSE 'VIEW' END AS table_type,
2826
- c.column_name, c.data_type,
2827
- COALESCE(c.data_length, c.data_precision) AS max_length,
2828
- CASE ac.constraint_type WHEN 'P' THEN 'PRIMARY KEY' END AS const,
2829
- ac.constraint_name AS \"key\",
2830
- CASE c.nullable WHEN 'Y' THEN 'YES' ELSE 'NO' END AS is_nullable
2831
- FROM all_tab_cols c
2832
- LEFT OUTER JOIN all_cons_columns acc ON acc.owner = c.owner AND acc.table_name = c.table_name AND acc.column_name = c.column_name
2833
- LEFT OUTER JOIN all_constraints ac ON ac.owner = acc.owner AND ac.table_name = acc.table_name AND ac.constraint_name = acc.constraint_name AND constraint_type = 'P'
2834
- LEFT OUTER JOIN all_views v ON c.owner = v.owner AND c.table_name = v.view_name
2835
- WHERE c.owner IN (#{::Brick.db_schemas.keys.map { |s| "'#{s}'" }.join(', ')})
2836
- AND c.table_name NOT IN (?, ?)
2837
- ORDER BY 1, 2, c.internal_column_id, acc.position"
2838
- ActiveRecord::Base.execute_sql(sql, *ar_tables)
2839
- else
2840
- retrieve_schema_and_tables(sql)
2841
- end
2842
-
2843
- schema_and_tables.each do |r|
2844
- next if r[1].index('$') # Oracle can have goofy table names with $
2845
-
2846
- if (relation_name = r[1]) =~ /^[A-Z0-9_]+$/
2847
- relation_name.downcase!
2848
- # Both uppers and lowers as well as underscores?
2849
- elsif relation_name =~ /[A-Z]/ && relation_name =~ /[a-z]/ && relation_name.index('_')
2850
- ::Brick.apply_double_underscore_patch
2851
- end
2852
- # Expect the default schema for SQL Server to be 'dbo'.
2853
- if (::Brick.is_oracle && r[0] != schema) || (is_mssql && r[0] != 'dbo')
2854
- relation_name = "#{r[0]}.#{relation_name}"
2855
- end
2856
-
2857
- relation = relations[relation_name] # here relation represents a table or view from the database
2858
- relation[:isView] = true if r[2] == 'VIEW' # table_type
2859
- col_name = ::Brick.is_oracle ? connection.send(:oracle_downcase, r[3]) : r[3]
2860
- key = case r[6] # constraint type
2861
- when 'PRIMARY KEY'
2862
- # key
2863
- relation[:pkey][r[7] || relation_name] ||= []
2864
- when 'UNIQUE'
2865
- relation[:ukeys][r[7] || "#{relation_name}.#{col_name}"] ||= []
2866
- # key = (relation[:ukeys] = Hash.new { |h, k| h[k] = [] }) if key.is_a?(Array)
2867
- # key[r['key']]
2868
- end
2869
- key << col_name if key
2870
- cols = relation[:cols] # relation.fetch(:cols) { relation[:cols] = [] }
2871
- # 'data_type', 'max_length', measure, 'is_nullable'
2872
- cols[col_name] = [r[4], r[5], measures&.include?(col_name), r[8] == 'NO']
2873
- # puts "KEY! #{r['relation_name']}.#{col_name} #{r['key']} #{r['const']}" if r['key']
2874
- end
2875
- end
2876
-
2877
- # PostGIS adds three views which would confuse Rails if models were to be built for them.
2878
- if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
2879
- if relations.key?('geography_columns') && relations.key?('geometry_columns') && relations.key?('spatial_ref_sys')
2880
- (::Brick.config.exclude_tables ||= []) << 'geography_columns'
2881
- ::Brick.config.exclude_tables << 'geometry_columns'
2882
- ::Brick.config.exclude_tables << 'spatial_ref_sys'
2883
- end
2884
- end
2885
-
2886
- # # Add unique OIDs
2887
- # if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
2888
- # ActiveRecord::Base.execute_sql(
2889
- # "SELECT c.oid, n.nspname, c.relname
2890
- # FROM pg_catalog.pg_namespace AS n
2891
- # INNER JOIN pg_catalog.pg_class AS c ON n.oid = c.relnamespace
2892
- # WHERE c.relkind IN ('r', 'v')"
2893
- # ).each do |r|
2894
- # next if ['pg_catalog', 'information_schema', ''].include?(r['nspname']) ||
2895
- # ['ar_internal_metadata', 'schema_migrations'].include?(r['relname'])
2896
- # relation = relations.fetch(r['relname'], nil)
2897
- # if relation
2898
- # (relation[:oid] ||= {})[r['nspname']] = r['oid']
2899
- # else
2900
- # puts "Where is #{r['nspname']} #{r['relname']} ?"
2901
- # end
2902
- # end
2903
- # end
2904
- # schema = ::Brick.default_schema # Reset back for this next round of fun
2905
-
2906
- # ---------------------------------------------
2907
- # 4. Foreign key info
2908
- # (done in two parts which get JOINed together in Ruby code)
2909
- kcus = nil
2910
- entry_type = nil
2911
- case ActiveRecord::Base.connection.adapter_name
2912
- when 'PostgreSQL', 'Mysql2', 'Trilogy', 'SQLServer'
2913
- # Part 1 -- all KCUs
2914
- sql = "SELECT CONSTRAINT_CATALOG, CONSTRAINT_SCHEMA, CONSTRAINT_NAME, ORDINAL_POSITION,
2915
- TABLE_NAME, COLUMN_NAME
2916
- FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE#{"
2917
- WHERE CONSTRAINT_SCHEMA = COALESCE(current_setting('SEARCH_PATH'), 'public')" if is_postgres && schema }#{"
2918
- WHERE CONSTRAINT_SCHEMA = '#{ActiveRecord::Base.connection.current_database&.tr("'", "''")}'" if is_mysql
2919
- }"
2920
- kcus = ActiveRecord::Base.execute_sql(sql).each_with_object({}) do |v, s|
2921
- if (entry_type ||= v.is_a?(Array) ? :array : :hash) == :hash
2922
- key = "#{v['constraint_name']}.#{v['constraint_schema']}.#{v['constraint_catalog']}.#{v['ordinal_position']}"
2923
- key << ".#{v['table_name']}.#{v['column_name']}" unless is_postgres || is_mssql
2924
- s[key] = [v['constraint_schema'], v['table_name']]
2925
- else # Array
2926
- key = "#{v[2]}.#{v[1]}.#{v[0]}.#{v[3]}"
2927
- key << ".#{v[4]}.#{v[5]}" unless is_postgres || is_mssql
2928
- s[key] = [v[1], v[4]]
2929
- end
2930
- end
2931
-
2932
- # Part 2 -- fk_references
2933
- sql = "SELECT kcu.CONSTRAINT_SCHEMA, kcu.TABLE_NAME, kcu.COLUMN_NAME,
2934
- #{# These will get filled in with real values (effectively doing the JOIN in Ruby)
2935
- is_postgres || is_mssql ? 'NULL as primary_schema, NULL as primary_table' :
2936
- 'kcu.REFERENCED_TABLE_NAME, kcu.REFERENCED_COLUMN_NAME'},
2937
- kcu.CONSTRAINT_NAME AS CONSTRAINT_SCHEMA_FK,
2938
- rc.UNIQUE_CONSTRAINT_NAME, rc.UNIQUE_CONSTRAINT_SCHEMA, rc.UNIQUE_CONSTRAINT_CATALOG, kcu.ORDINAL_POSITION
2939
- FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS AS rc
2940
- INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS kcu
2941
- ON kcu.CONSTRAINT_CATALOG = rc.CONSTRAINT_CATALOG
2942
- AND kcu.CONSTRAINT_SCHEMA = rc.CONSTRAINT_SCHEMA
2943
- AND kcu.CONSTRAINT_NAME = rc.CONSTRAINT_NAME#{"
2944
- WHERE kcu.CONSTRAINT_SCHEMA = COALESCE(current_setting('SEARCH_PATH'), 'public')" if is_postgres && schema}#{"
2945
- WHERE kcu.CONSTRAINT_SCHEMA = '#{ActiveRecord::Base.connection.current_database&.tr("'", "''")}'" if is_mysql}"
2946
- fk_references = ActiveRecord::Base.execute_sql(sql)
2947
- when 'SQLite'
2948
- sql = "SELECT NULL AS constraint_schema, m.name, fkl.\"from\", NULL AS primary_schema, fkl.\"table\", m.name || '_' || fkl.\"from\" AS constraint_name
2949
- FROM sqlite_master m
2950
- INNER JOIN pragma_foreign_key_list(m.name) fkl ON m.type = 'table'
2951
- ORDER BY m.name, fkl.seq"
2952
- fk_references = ActiveRecord::Base.execute_sql(sql)
2953
- when 'OracleEnhanced'
2954
- schemas = ::Brick.db_schemas.keys.map { |s| "'#{s}'" }.join(', ')
2955
- sql =
2956
- "SELECT -- fk
2957
- ac.owner AS constraint_schema, acc_fk.table_name, acc_fk.column_name,
2958
- -- referenced pk
2959
- ac.r_owner AS primary_schema, acc_pk.table_name AS primary_table, acc_fk.constraint_name AS constraint_schema_fk
2960
- -- , acc_pk.column_name
2961
- FROM all_cons_columns acc_fk
2962
- INNER JOIN all_constraints ac ON acc_fk.owner = ac.owner
2963
- AND acc_fk.constraint_name = ac.constraint_name
2964
- INNER JOIN all_cons_columns acc_pk ON ac.r_owner = acc_pk.owner
2965
- AND ac.r_constraint_name = acc_pk.constraint_name
2966
- WHERE ac.constraint_type = 'R'
2967
- AND ac.owner IN (#{schemas})
2968
- AND ac.r_owner IN (#{schemas})"
2969
- fk_references = ActiveRecord::Base.execute_sql(sql)
2970
- end
2971
- ::Brick.is_oracle = true if ActiveRecord::Base.connection.adapter_name == 'OracleEnhanced'
2972
- # ::Brick.default_schema ||= schema ||= 'public' if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
2973
- ::Brick.default_schema ||= 'public' if is_postgres
2974
- fk_references&.each do |fk|
2975
- fk = fk.values unless fk.is_a?(Array)
2976
- # Virtually JOIN KCUs to fk_references in order to fill in the primary schema and primary table
2977
- kcu_key = "#{fk[6]}.#{fk[7]}.#{fk[8]}.#{fk[9]}"
2978
- kcu_key << ".#{fk[3]}.#{fk[4]}" unless is_postgres || is_mssql
2979
- if (kcu = kcus&.fetch(kcu_key, nil))
2980
- fk[3] = kcu[0]
2981
- fk[4] = kcu[1]
2982
- end
2983
- # Multitenancy makes things a little more general overall, except for non-tenanted tables
2984
- if ::Brick.is_apartment_excluded_table(::Brick.namify(fk[1]))
2985
- fk[0] = ::Brick.apartment_default_tenant
2986
- elsif (is_postgres && (fk[0] == 'public' || (multitenancy && fk[0] == schema))) ||
2987
- (::Brick.is_oracle && fk[0] == schema) ||
2988
- (is_mssql && fk[0] == 'dbo') ||
2989
- (!is_postgres && !::Brick.is_oracle && !is_mssql && ['mysql', 'performance_schema', 'sys'].exclude?(fk[0]))
2990
- fk[0] = nil
2991
- end
2992
- if ::Brick.is_apartment_excluded_table(fk[4])
2993
- fk[3] = ::Brick.apartment_default_tenant
2994
- elsif (is_postgres && (fk[3] == 'public' || (multitenancy && fk[3] == schema))) ||
2995
- (::Brick.is_oracle && fk[3] == schema) ||
2996
- (is_mssql && fk[3] == 'dbo') ||
2997
- (!is_postgres && !::Brick.is_oracle && !is_mssql && ['mysql', 'performance_schema', 'sys'].exclude?(fk[3]))
2998
- fk[3] = nil
2999
- end
3000
- if ::Brick.is_oracle
3001
- fk[1].downcase! if fk[1] =~ /^[A-Z0-9_]+$/
3002
- fk[4].downcase! if fk[4] =~ /^[A-Z0-9_]+$/
3003
- fk[2] = connection.send(:oracle_downcase, fk[2])
3004
- end
3005
- ::Brick._add_bt_and_hm(fk, relations)
3006
- end
3007
- kcus = nil # Allow this large item to be garbage collected
3008
- end
3009
-
3010
- table_name_lookup = (::Brick.table_name_lookup ||= {})
3011
- relations.each do |k, v|
3012
- next if k.is_a?(Symbol)
3013
-
3014
- rel_name = k.split('.').map { |rel_part| ::Brick.namify(rel_part, :underscore) }
3015
- schema_names = rel_name[0..-2]
3016
- schema_names.shift if ::Brick.apartment_multitenant && schema_names.first == ::Brick.apartment_default_tenant
3017
- v[:schema] = schema_names.join('.') unless schema_names.empty?
3018
- # %%% If more than one schema has the same table name, will need to add a schema name prefix to have uniqueness
3019
- if (singular = rel_name.last.singularize).blank?
3020
- singular = rel_name.last
3021
- end
3022
- name_parts = if (tnp = ::Brick.config.table_name_prefixes
3023
- &.find { |k1, _v1| singular.start_with?(k1) && singular.length > k1.length }
3024
- ).present?
3025
- v[:auto_prefixed_schema] = tnp.first
3026
- # v[:resource] = rel_name.last[tnp.first.length..-1]
3027
- [tnp.last, singular[tnp.first.length..-1]]
3028
- else
3029
- # v[:resource] = rel_name.last
3030
- [singular]
3031
- end
3032
- proposed_name_parts = (schema_names + name_parts).map { |p| ::Brick.namify(p, :underscore).camelize }
3033
- # Find out if the proposed name leads to a module or class that already exists and is not an AR class
3034
- colliding_thing = nil
3035
- loop do
3036
- klass = Object
3037
- proposed_name_parts.each do |part|
3038
- if klass.const_defined?(part)
3039
- klass = klass.const_get(part)
3040
- else
3041
- klass = nil
3042
- break
3043
- end
3044
- end
3045
- break if !klass || (klass < ActiveRecord::Base) # Break if all good -- no conflicts
3046
-
3047
- # Find a unique name since there's already something that's non-AR with that same name
3048
- last_idx = proposed_name_parts.length - 1
3049
- proposed_name_parts[last_idx] = ::Brick.ensure_unique(proposed_name_parts[last_idx], 'X')
3050
- colliding_thing ||= klass
3051
- end
3052
- v[:class_name] = proposed_name_parts.join('::')
3053
- # Was: v[:resource] = v[:class_name].underscore.tr('/', '.')
3054
- v[:resource] = proposed_name_parts.last.underscore
3055
- if colliding_thing
3056
- message_start = if colliding_thing.is_a?(Module) && Object.const_defined?(:Rails) &&
3057
- colliding_thing.constants.find { |c| colliding_thing.const_get(c) < Rails::Application }
3058
- "The module for the Rails application itself, \"#{colliding_thing.name}\","
3059
- else
3060
- "Non-AR #{colliding_thing.class.name.downcase} \"#{colliding_thing.name}\""
3061
- end
3062
- puts "WARNING: #{message_start} already exists.\n Will set up to auto-create model #{v[:class_name]} for table #{k}."
3063
- end
3064
- # Track anything that's out-of-the-ordinary
3065
- table_name_lookup[v[:class_name]] = k unless v[:class_name].underscore.pluralize == k
3066
- end
3067
- ::Brick.load_additional_references if ::Brick.initializer_loaded
3068
-
3069
- if is_postgres
3070
- params = []
3071
- ActiveRecord::Base.execute_sql("-- inherited and partitioned tables counts
3072
- SELECT n.nspname, parent.relname,
3073
- ((SUM(child.reltuples::float) / greatest(SUM(child.relpages), 1))) *
3074
- (SUM(pg_relation_size(child.oid))::float / (current_setting('block_size')::float))::integer AS rowcount
3075
- FROM pg_inherits
3076
- INNER JOIN pg_class parent ON pg_inherits.inhparent = parent.oid
3077
- INNER JOIN pg_class child ON pg_inherits.inhrelid = child.oid
3078
- INNER JOIN pg_catalog.pg_namespace n ON n.oid = parent.relnamespace#{
3079
- if schema
3080
- params << schema
3081
- "
3082
- WHERE n.nspname = COALESCE(?, 'public')"
3083
- end}
3084
- GROUP BY n.nspname, parent.relname, child.reltuples, child.relpages, child.oid
3085
-
3086
- UNION ALL
3087
-
3088
- -- table count
3089
- SELECT n.nspname, pg_class.relname,
3090
- (pg_class.reltuples::float / greatest(pg_class.relpages, 1)) *
3091
- (pg_relation_size(pg_class.oid)::float / (current_setting('block_size')::float))::integer AS rowcount
3092
- FROM pg_class
3093
- INNER JOIN pg_catalog.pg_namespace n ON n.oid = pg_class.relnamespace#{
3094
- if schema
3095
- params << schema
3096
- "
3097
- WHERE n.nspname = COALESCE(?, 'public')"
3098
- end}
3099
- GROUP BY n.nspname, pg_class.relname, pg_class.reltuples, pg_class.relpages, pg_class.oid", params).each do |tblcount|
3100
- # %%% What is the default schema here?
3101
- prefix = "#{tblcount['nspname']}." unless tblcount['nspname'] == (schema || 'public')
3102
- relations.fetch("#{prefix}#{tblcount['relname']}", nil)&.[]=(:rowcount, tblcount['rowcount'].to_i.round)
2669
+ begin
2670
+ # ::Brick.is_db_present = true
2671
+ ::Brick.reflect_tables
2672
+ rescue ActiveRecord::NoDatabaseError
2673
+ # ::Brick.is_db_present = false
3103
2674
  end
2675
+ conn
3104
2676
  end
3105
-
3106
- if orig_schema && (orig_schema = (orig_schema - ['pg_catalog', 'pg_toast', 'heroku_ext']).first)
3107
- puts "Now switching back to \"#{orig_schema}\" schema."
3108
- ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?", orig_schema)
3109
- end
3110
- end
3111
-
3112
- def retrieve_schema_and_tables(sql = nil, is_postgres = nil, is_mssql = nil, schema = nil)
3113
- is_mssql = ActiveRecord::Base.connection.adapter_name == 'SQLServer' if is_mssql.nil?
3114
- params = ar_tables
3115
- sql ||= "SELECT t.table_schema AS \"schema\", t.table_name AS relation_name, t.table_type,#{"
3116
- pg_catalog.obj_description(
3117
- ('\"' || t.table_schema || '\".\"' || t.table_name || '\"')::regclass::oid, 'pg_class'
3118
- ) AS table_description,
3119
- pg_catalog.col_description(
3120
- ('\"' || t.table_schema || '\".\"' || t.table_name || '\"')::regclass::oid, c.ordinal_position
3121
- ) AS column_description," if is_postgres}
3122
- c.column_name, #{is_postgres ? "CASE c.data_type WHEN 'USER-DEFINED' THEN pg_t.typname ELSE c.data_type END AS data_type" : 'c.data_type'},
3123
- COALESCE(c.character_maximum_length, c.numeric_precision) AS max_length,
3124
- kcu.constraint_type AS const, kcu.constraint_name AS \"key\",
3125
- c.is_nullable
3126
- FROM INFORMATION_SCHEMA.tables AS t
3127
- LEFT OUTER JOIN INFORMATION_SCHEMA.columns AS c ON t.table_schema = c.table_schema
3128
- AND t.table_name = c.table_name
3129
- LEFT OUTER JOIN
3130
- (SELECT kcu1.constraint_schema, kcu1.table_name, kcu1.column_name, kcu1.ordinal_position,
3131
- tc.constraint_type, kcu1.constraint_name
3132
- FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS kcu1
3133
- INNER JOIN INFORMATION_SCHEMA.table_constraints AS tc
3134
- ON kcu1.CONSTRAINT_SCHEMA = tc.CONSTRAINT_SCHEMA
3135
- AND kcu1.TABLE_NAME = tc.TABLE_NAME
3136
- AND kcu1.CONSTRAINT_NAME = tc.constraint_name
3137
- AND tc.constraint_type != 'FOREIGN KEY' -- For MSSQL
3138
- ) AS kcu ON
3139
- -- kcu.CONSTRAINT_CATALOG = t.table_catalog AND
3140
- kcu.CONSTRAINT_SCHEMA = c.table_schema
3141
- AND kcu.TABLE_NAME = c.table_name
3142
- AND kcu.column_name = c.column_name#{"
3143
- -- AND kcu.position_in_unique_constraint IS NULL" unless is_mssql}#{"
3144
- INNER JOIN pg_catalog.pg_namespace pg_n ON pg_n.nspname = t.table_schema
3145
- INNER JOIN pg_catalog.pg_class pg_c ON pg_n.oid = pg_c.relnamespace AND pg_c.relname = c.table_name
3146
- INNER JOIN pg_catalog.pg_attribute pg_a ON pg_c.oid = pg_a.attrelid AND pg_a.attname = c.column_name
3147
- INNER JOIN pg_catalog.pg_type pg_t ON pg_t.oid = pg_a.atttypid" if is_postgres}
3148
- WHERE t.table_schema #{is_postgres || is_mssql ?
3149
- "NOT IN ('information_schema', 'pg_catalog', 'pg_toast', 'heroku_ext',
3150
- 'INFORMATION_SCHEMA', 'sys')"
3151
- :
3152
- "= '#{ActiveRecord::Base.connection.current_database&.tr("'", "''")}'"}#{
3153
- if is_postgres && schema
3154
- params = params.unshift(schema) # Used to use this SQL: current_setting('SEARCH_PATH')
3155
- "
3156
- AND t.table_schema = COALESCE(?, 'public')"
3157
- end}
3158
- -- AND t.table_type IN ('VIEW') -- 'BASE TABLE', 'FOREIGN TABLE'
3159
- AND t.table_name NOT IN ('pg_stat_statements', ?, ?)
3160
- ORDER BY 1, t.table_type DESC, 2, c.ordinal_position"
3161
- ActiveRecord::Base.execute_sql(sql, *params)
3162
- end
3163
-
3164
- def ar_tables
3165
- ar_smtn = if ActiveRecord::Base.respond_to?(:schema_migrations_table_name)
3166
- ActiveRecord::Base.schema_migrations_table_name
3167
- else
3168
- 'schema_migrations'
3169
- end
3170
- ar_imtn = ActiveRecord.version >= ::Gem::Version.new('5.0') ? ActiveRecord::Base.internal_metadata_table_name : 'ar_internal_metadata'
3171
- [ar_smtn, ar_imtn]
3172
2677
  end
3173
2678
  end
3174
-
3175
2679
  # ==========================================
3176
2680
 
3177
2681
  # :nodoc:
@@ -3464,7 +2968,7 @@ module Brick
3464
2968
  end
3465
2969
  end
3466
2970
 
3467
- def _brick_index(tbl_name, mode = nil, separator = nil, relation = nil)
2971
+ def _brick_index(tbl_name, mode = nil, separator = nil, relation = nil, not_path = nil)
3468
2972
  separator ||= '_'
3469
2973
  res_name = (tbl_name_parts = tbl_name.split('.'))[0..-2].first
3470
2974
  res_name << '.' if res_name
@@ -3472,22 +2976,28 @@ module Brick
3472
2976
 
3473
2977
  res_parts = ((mode == :singular) ? res_name.singularize : res_name).split('.')
3474
2978
  res_parts.shift if ::Brick.apartment_multitenant && res_parts.length > 1 && res_parts.first == ::Brick.apartment_default_tenant
2979
+ index2 = []
2980
+ if ::Brick.config.path_prefix
2981
+ res_parts.unshift(::Brick.config.path_prefix)
2982
+ index2 << ::Brick.config.path_prefix
2983
+ end
3475
2984
  if (aps = relation&.fetch(:auto_prefixed_schema, nil)) # && res_parts.last.start_with?(aps)
3476
2985
  aps = aps[0..-2] if aps[-1] == '_'
3477
2986
  last_part = res_parts.last # [aps.length..-1]
3478
2987
  res_parts[-1] = aps
3479
2988
  res_parts << last_part
3480
- end
3481
- path_prefix = []
3482
- if ::Brick.config.path_prefix
3483
- res_parts.unshift(::Brick.config.path_prefix)
3484
- path_prefix << ::Brick.config.path_prefix
2989
+ index2 << aps
3485
2990
  end
3486
2991
  index = res_parts.map(&:underscore).join(separator)
3487
- index = index.tr('_', 'x') if separator == 'x'
3488
- # Rails applies an _index suffix to that route when the resource name isn't something plural
3489
- index << '_index' if mode != :singular && separator == '_' &&
3490
- index == (path_prefix + [relation[:class_name]&.underscore&.tr('/', '_') || '_']).join(separator)
2992
+ if separator == 'x'
2993
+ index = index.tr('_', 'x')
2994
+ else
2995
+ # Rails applies an _index suffix to that route when the resource name isn't something plural
2996
+ index << '_index' if mode != :singular && !not_path &&
2997
+ index == (
2998
+ index2 + [relation[:class_name][(relation&.fetch(:auto_prefixed_class, nil)&.length&.+ 2) || 0..-1]&.underscore&.tr('/', '_') || '_']
2999
+ ).join(separator)
3000
+ end
3491
3001
  index
3492
3002
  end
3493
3003