123456789_123456789_123456789_123456789_123456789_

Module: ActiveRecord::TestFixtures

Relationships & Source Files
Namespace Children
Modules:
Super Chains via Extension / Inclusion / Inheritance
Class Chain:
Defined in: activerecord/lib/active_record/test_fixtures.rb

Class Method Summary

::ActiveSupport::Concern - Extended

class_methods

Define class methods from given block.

included

Evaluate given block in context of base class, so that you can write class macros here.

prepended

Evaluate given block in context of base class, so that you can write class macros here.

append_features, prepend_features

Instance Attribute Summary

Instance Method Summary

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(name, *args, **kwargs, &block) (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/test_fixtures.rb', line 275

def method_missing(name, *args, **kwargs, &block)
  if fs_name = fixture_sets[name.to_s]
    access_fixture(fs_name, *args, **kwargs, &block)
  else
    super
  end
end

DSL Calls

included

[ GitHub ]


20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# File 'activerecord/lib/active_record/test_fixtures.rb', line 20

included do
  ##
  # :singleton-method: fixture_paths
  #
  # Returns the ActiveRecord::FixtureSet collection

  ##
  # :singleton-method: fixture_paths=
  #
  # :call-seq:
  #   fixture_paths=(fixture_paths)
  class_attribute :fixture_paths, instance_writer: false, default: []
  class_attribute :fixture_table_names, default: []
  class_attribute :fixture_class_names, default: {}
  class_attribute :use_transactional_tests, default: true
  class_attribute :use_instantiated_fixtures, default: false # true, false, or :no_instances
  class_attribute :pre_loaded_fixtures, default: false
  class_attribute :lock_threads, default: true
  class_attribute :fixture_sets, default: {}

  ActiveSupport.run_load_hooks(:active_record_fixtures, self)
end

Instance Attribute Details

#load_instances?Boolean (readonly, private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/test_fixtures.rb', line 271

def load_instances?
  use_instantiated_fixtures != :no_instances
end

#run_in_transaction?Boolean (readonly)

[ GitHub ]

  
# File 'activerecord/lib/active_record/test_fixtures.rb', line 122

def run_in_transaction?
  use_transactional_tests &&
    !self.class.uses_transaction?(name)
end

Instance Method Details

#access_fixture(fs_name, *fixture_names) (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/test_fixtures.rb', line 291

def access_fixture(fs_name, *fixture_names)
  force_reload = fixture_names.pop if fixture_names.last == true || fixture_names.last == :reload
  return_single_record = fixture_names.size == 1

  fixture_names = @loaded_fixtures[fs_name].fixtures.keys if fixture_names.empty?
  @fixture_cache[fs_name] ||= {}

  instances = fixture_names.map do |f_name|
    f_name = f_name.to_s if f_name.is_a?(Symbol)
    @fixture_cache[fs_name].delete(f_name) if force_reload

    if @loaded_fixtures[fs_name][f_name]
      @fixture_cache[fs_name][f_name] ||= @loaded_fixtures[fs_name][f_name].find
    else
      raise StandardError, "No fixture named '#{f_name}' found for fixture set '#{fs_name}'"
    end
  end

  return_single_record ? instances.first : instances
end

#after_teardown

This method is for internal use only.
[ GitHub ]

  
# File 'activerecord/lib/active_record/test_fixtures.rb', line 14

def after_teardown # :nodoc:
  super
ensure
  teardown_fixtures
end

#before_setup

This method is for internal use only.
[ GitHub ]

  
# File 'activerecord/lib/active_record/test_fixtures.rb', line 9

def before_setup # :nodoc:
  setup_fixtures
  super
end

#enlist_fixture_connections

[ GitHub ]

  
# File 'activerecord/lib/active_record/test_fixtures.rb', line 206

def enlist_fixture_connections
  setup_shared_connection_pool

  ActiveRecord::Base.connection_handler.connection_pool_list(:writing).map(&:connection)
end

#fixture_path

This method is for internal use only.
[ GitHub ]

  
# File 'activerecord/lib/active_record/test_fixtures.rb', line 113

def fixture_path # :nodoc:
  ActiveRecord.deprecator.warn(<<~WARNING)
    TestFixtures#fixture_path is deprecated and will be removed in Rails 7.2. Use #fixture_paths instead.
    If multiple fixture paths have been configured with #fixture_paths, then #fixture_path will just return
    the first path.
  WARNING
  fixture_paths.first
end

#instantiate_fixtures (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/test_fixtures.rb', line 259

def instantiate_fixtures
  if pre_loaded_fixtures
    raise RuntimeError, "Load fixtures before instantiating them." if ActiveRecord::FixtureSet.all_loaded_fixtures.empty?
    ActiveRecord::FixtureSet.instantiate_all_loaded_fixtures(self, load_instances?)
  else
    raise RuntimeError, "Load fixtures before instantiating them." if @loaded_fixtures.nil?
    @loaded_fixtures.each_value do |fixture_set|
      ActiveRecord::FixtureSet.instantiate_fixtures(self, fixture_set, load_instances?)
    end
  end
end

#load_fixtures(config) (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/test_fixtures.rb', line 255

def load_fixtures(config)
  ActiveRecord::FixtureSet.create_fixtures(fixture_paths, fixture_table_names, fixture_class_names, config).index_by(&:name)
end

#respond_to_missing?(name, include_private = false) ⇒ Boolean (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/test_fixtures.rb', line 283

def respond_to_missing?(name, include_private = false)
  if include_private && fixture_sets.key?(name.to_s)
    true
  else
    super
  end
end

#setup_fixtures(config = ActiveRecord::Base)

[ GitHub ]

  
# File 'activerecord/lib/active_record/test_fixtures.rb', line 127

def setup_fixtures(config = ActiveRecord::Base)
  if pre_loaded_fixtures && !use_transactional_tests
    raise RuntimeError, "pre_loaded_fixtures requires use_transactional_tests"
  end

  @fixture_cache = {}
  @fixture_connections = []
  @@already_loaded_fixtures ||= {}
  @connection_subscriber = nil
  @saved_pool_configs = Hash.new { |hash, key| hash[key] = {} }

  # Load fixtures once and begin transaction.
  if run_in_transaction?
    if @@already_loaded_fixtures[self.class]
      @loaded_fixtures = @@already_loaded_fixtures[self.class]
    else
      @loaded_fixtures = load_fixtures(config)
      @@already_loaded_fixtures[self.class] = @loaded_fixtures
    end

    # Begin transactions for connections already established
    @fixture_connections = enlist_fixture_connections
    @fixture_connections.each do |connection|
      connection.begin_transaction joinable: false, _lazy: false
      connection.pool.lock_thread = true if lock_threads
    end

    # When connections are established in the future, begin a transaction too
    @connection_subscriber = ActiveSupport::Notifications.subscribe("!connection.active_record") do |_, _, _, _, payload|
      connection_name = payload[:connection_name] if payload.key?(:connection_name)
      shard = payload[:shard] if payload.key?(:shard)

      if connection_name
        begin
          connection = ActiveRecord::Base.connection_handler.retrieve_connection(connection_name, shard: shard)
        rescue ConnectionNotEstablished
          connection = nil
        end

        if connection
          setup_shared_connection_pool

          if !@fixture_connections.include?(connection)
            connection.begin_transaction joinable: false, _lazy: false
            connection.pool.lock_thread = true if lock_threads
            @fixture_connections << connection
          end
        end
      end
    end

  # Load fixtures for every test.
  else
    ActiveRecord::FixtureSet.reset_cache
    @@already_loaded_fixtures[self.class] = nil
    @loaded_fixtures = load_fixtures(config)
  end

  # Instantiate fixtures for every test if requested.
  instantiate_fixtures if use_instantiated_fixtures
end

#setup_shared_connection_pool (private)

Shares the writing connection pool with connections on other handlers.

In an application with a primary and replica the test fixtures need to share a connection pool so that the reading connection can see data in the open transaction on the writing connection.

[ GitHub ]

  
# File 'activerecord/lib/active_record/test_fixtures.rb', line 219

def setup_shared_connection_pool
  handler = ActiveRecord::Base.connection_handler

  handler.connection_pool_names.each do |name|
    pool_manager = handler.send(:connection_name_to_pool_manager)[name]
    pool_manager.shard_names.each do |shard_name|
      writing_pool_config = pool_manager.get_pool_config(ActiveRecord.writing_role, shard_name)
      @saved_pool_configs[name][shard_name] ||= {}
      pool_manager.role_names.each do |role|
        next unless pool_config = pool_manager.get_pool_config(role, shard_name)
        next if pool_config == writing_pool_config

        @saved_pool_configs[name][shard_name][role] = pool_config
        pool_manager.set_pool_config(role, shard_name, writing_pool_config)
      end
    end
  end
end

#teardown_fixtures

[ GitHub ]

  
# File 'activerecord/lib/active_record/test_fixtures.rb', line 189

def teardown_fixtures
  # Rollback changes if a transaction is active.
  if run_in_transaction?
    ActiveSupport::Notifications.unsubscribe(@connection_subscriber) if @connection_subscriber
    @fixture_connections.each do |connection|
      connection.rollback_transaction if connection.transaction_open?
      connection.pool.lock_thread = false
    end
    @fixture_connections.clear
    teardown_shared_connection_pool
  else
    ActiveRecord::FixtureSet.reset_cache
  end

  ActiveRecord::Base.connection_handler.clear_active_connections!(:all)
end

#teardown_shared_connection_pool (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/test_fixtures.rb', line 238

def teardown_shared_connection_pool
  handler = ActiveRecord::Base.connection_handler

  @saved_pool_configs.each_pair do |name, shards|
    pool_manager = handler.send(:connection_name_to_pool_manager)[name]
    shards.each_pair do |shard_name, roles|
      roles.each_pair do |role, pool_config|
        next unless pool_manager.get_pool_config(role, shard_name)

        pool_manager.set_pool_config(role, shard_name, pool_config)
      end
    end
  end

  @saved_pool_configs.clear
end