brick 1.0.199 → 1.0.200

Sign up to get free protection for your applications and to get access to all the features.
@@ -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