aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/lib/active_record/association_relation.rb6
-rw-r--r--activerecord/lib/active_record/associations/association.rb44
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb28
-rw-r--r--activerecord/lib/active_record/associations/preloader.rb52
-rw-r--r--activerecord/lib/active_record/fixture_set/model_metadata.rb40
-rw-r--r--activerecord/lib/active_record/fixture_set/table_row.rb79
-rw-r--r--activerecord/lib/active_record/fixture_set/table_rows.rb129
-rw-r--r--activerecord/lib/active_record/fixtures.rb371
-rw-r--r--activerecord/lib/active_record/integration.rb2
-rw-r--r--activerecord/lib/active_record/persistence.rb2
-rw-r--r--activerecord/lib/active_record/railties/databases.rake13
-rw-r--r--activerecord/lib/active_record/tasks/database_tasks.rb15
-rw-r--r--activerecord/lib/arel/visitors/mysql.rb10
-rw-r--r--activerecord/test/cases/associations/belongs_to_associations_test.rb4
-rw-r--r--activerecord/test/cases/associations/cascaded_eager_loading_test.rb10
-rw-r--r--activerecord/test/cases/associations/eager_load_nested_include_test.rb2
-rw-r--r--activerecord/test/cases/associations/eager_test.rb2
-rw-r--r--activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb18
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb97
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb20
-rw-r--r--activerecord/test/cases/associations/has_one_associations_test.rb16
-rw-r--r--activerecord/test/cases/associations/nested_through_associations_test.rb4
-rw-r--r--activerecord/test/cases/autosave_association_test.rb30
-rw-r--r--activerecord/test/cases/collection_cache_key_test.rb4
-rw-r--r--activerecord/test/cases/connection_adapters/connection_handler_test.rb5
-rw-r--r--activerecord/test/cases/dirty_test.rb4
-rw-r--r--activerecord/test/cases/integration_test.rb86
-rw-r--r--activerecord/test/cases/null_relation_test.rb14
-rw-r--r--activerecord/test/cases/persistence_test.rb12
-rw-r--r--activerecord/test/cases/query_cache_test.rb7
-rw-r--r--activerecord/test/cases/tasks/database_tasks_test.rb24
-rw-r--r--activerecord/test/cases/touch_later_test.rb2
32 files changed, 718 insertions, 434 deletions
diff --git a/activerecord/lib/active_record/association_relation.rb b/activerecord/lib/active_record/association_relation.rb
index 403667fb70..4c538ef2bd 100644
--- a/activerecord/lib/active_record/association_relation.rb
+++ b/activerecord/lib/active_record/association_relation.rb
@@ -31,9 +31,9 @@ module ActiveRecord
private
def exec_queries
- super do |r|
- @association.set_inverse_instance r
- yield r if block_given?
+ super do |record|
+ @association.set_inverse_instance_from_queries(record)
+ yield record if block_given?
end
end
end
diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb
index 44596f4424..4686b9f517 100644
--- a/activerecord/lib/active_record/associations/association.rb
+++ b/activerecord/lib/active_record/associations/association.rb
@@ -79,18 +79,6 @@ module ActiveRecord
target_scope.merge!(association_scope)
end
- # The scope for this association.
- #
- # Note that the association_scope is merged into the target_scope only when the
- # scope method is called. This is because at that point the call may be surrounded
- # by scope.scoping { ... } or with_scope { ... } etc, which affects the scope which
- # actually gets built.
- def association_scope
- if klass
- @association_scope ||= AssociationScope.scope(self)
- end
- end
-
def reset_scope
@association_scope = nil
end
@@ -103,6 +91,13 @@ module ActiveRecord
record
end
+ def set_inverse_instance_from_queries(record)
+ if inverse = inverse_association_for(record)
+ inverse.inversed_from_queries(owner)
+ end
+ record
+ end
+
# Remove the inverse association, if possible
def remove_inverse_instance(record)
if inverse = inverse_association_for(record)
@@ -114,6 +109,7 @@ module ActiveRecord
self.target = record
@inversed = !!record
end
+ alias :inversed_from_queries :inversed_from
# Returns the class of the target. belongs_to polymorphic overrides this to look at the
# polymorphic_type field on the owner.
@@ -121,12 +117,6 @@ module ActiveRecord
reflection.klass
end
- # Can be overridden (i.e. in ThroughAssociation) to merge in other scopes (i.e. the
- # through association's scope)
- def target_scope
- AssociationRelation.create(klass, self).merge!(klass.all)
- end
-
def extensions
extensions = klass.default_extensions | reflection.extensions
@@ -187,6 +177,24 @@ module ActiveRecord
end
private
+ # The scope for this association.
+ #
+ # Note that the association_scope is merged into the target_scope only when the
+ # scope method is called. This is because at that point the call may be surrounded
+ # by scope.scoping { ... } or unscoped { ... } etc, which affects the scope which
+ # actually gets built.
+ def association_scope
+ if klass
+ @association_scope ||= AssociationScope.scope(self)
+ end
+ end
+
+ # Can be overridden (i.e. in ThroughAssociation) to merge in other scopes (i.e. the
+ # through association's scope)
+ def target_scope
+ AssociationRelation.create(klass, self).merge!(klass.all)
+ end
+
def scope_for_create
scope.scope_for_create
end
diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb
index 617956c768..f84ac65fa2 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -21,20 +21,6 @@ module ActiveRecord
super
end
- def concat_records(records)
- ensure_not_nested
-
- records = super(records, true)
-
- if owner.new_record? && records
- records.flatten.each do |record|
- build_through_record(record)
- end
- end
-
- records
- end
-
def insert_record(record, validate = true, raise = false)
ensure_not_nested
@@ -48,6 +34,20 @@ module ActiveRecord
end
private
+ def concat_records(records)
+ ensure_not_nested
+
+ records = super(records, true)
+
+ if owner.new_record? && records
+ records.flatten.each do |record|
+ build_through_record(record)
+ end
+ end
+
+ records
+ end
+
# The through record (built with build_record) is temporarily cached
# so that it may be reused if insert_record is subsequently called.
#
diff --git a/activerecord/lib/active_record/associations/preloader.rb b/activerecord/lib/active_record/associations/preloader.rb
index a8f94b574d..8997579527 100644
--- a/activerecord/lib/active_record/associations/preloader.rb
+++ b/activerecord/lib/active_record/associations/preloader.rb
@@ -98,12 +98,11 @@ module ActiveRecord
# Loads all the given data into +records+ for the +association+.
def preloaders_on(association, records, scope, polymorphic_parent = false)
- if association.respond_to?(:to_hash)
+ case association
+ when Hash
preloaders_for_hash(association, records, scope, polymorphic_parent)
- elsif association.is_a?(Symbol)
+ when Symbol, String
preloaders_for_one(association, records, scope, polymorphic_parent)
- elsif association.respond_to?(:to_str)
- preloaders_for_one(association.to_sym, records, scope, polymorphic_parent)
else
raise ArgumentError, "#{association.inspect} was not recognized for preload"
end
@@ -111,23 +110,21 @@ module ActiveRecord
def preloaders_for_hash(association, records, scope, polymorphic_parent)
association.flat_map { |parent, child|
- loaders = preloaders_for_one parent, records, scope, polymorphic_parent
-
- recs = loaders.flat_map(&:preloaded_records).uniq
-
- reflection = records.first.class._reflect_on_association(parent)
- polymorphic_parent = reflection && reflection.options[:polymorphic]
-
- loaders.concat Array.wrap(child).flat_map { |assoc|
- preloaders_on assoc, recs, scope, polymorphic_parent
- }
- loaders
+ grouped_records(parent, records, polymorphic_parent).flat_map do |reflection, reflection_records|
+ loaders = preloaders_for_reflection(reflection, reflection_records, scope)
+ recs = loaders.flat_map(&:preloaded_records)
+ child_polymorphic_parent = reflection && reflection.options[:polymorphic]
+ loaders.concat Array.wrap(child).flat_map { |assoc|
+ preloaders_on assoc, recs, scope, child_polymorphic_parent
+ }
+ loaders
+ end
}
end
# Loads all the given data into +records+ for a singular +association+.
#
- # Functions by instantiating a preloader class such as Preloader::HasManyThrough and
+ # Functions by instantiating a preloader class such as Preloader::Association and
# call the +run+ method for each passed in class in the +records+ argument.
#
# Not all records have the same class, so group then preload group on the reflection
@@ -138,12 +135,17 @@ module ActiveRecord
# classes, depending on the polymorphic_type field. So we group by the classes as
# well.
def preloaders_for_one(association, records, scope, polymorphic_parent)
- grouped_records(association, records, polymorphic_parent).flat_map do |reflection, klasses|
- klasses.map do |rhs_klass, rs|
- loader = preloader_for(reflection, rs).new(rhs_klass, rs, reflection, scope)
- loader.run self
- loader
+ grouped_records(association, records, polymorphic_parent)
+ .flat_map do |reflection, reflection_records|
+ preloaders_for_reflection reflection, reflection_records, scope
end
+ end
+
+ def preloaders_for_reflection(reflection, records, scope)
+ records.group_by { |record| record.association(reflection.name).klass }.map do |rhs_klass, rs|
+ loader = preloader_for(reflection, rs).new(rhs_klass, rs, reflection, scope)
+ loader.run self
+ loader
end
end
@@ -151,11 +153,9 @@ module ActiveRecord
h = {}
records.each do |record|
next unless record
- next if polymorphic_parent && !record.class._reflect_on_association(association)
- assoc = record.association(association)
- next unless assoc.klass
- klasses = h[assoc.reflection] ||= {}
- (klasses[assoc.klass] ||= []) << record
+ reflection = record.class._reflect_on_association(association)
+ next if polymorphic_parent && !reflection || !record.association(association).klass
+ (h[reflection] ||= []) << record
end
h
end
diff --git a/activerecord/lib/active_record/fixture_set/model_metadata.rb b/activerecord/lib/active_record/fixture_set/model_metadata.rb
new file mode 100644
index 0000000000..edc03939c8
--- /dev/null
+++ b/activerecord/lib/active_record/fixture_set/model_metadata.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+module ActiveRecord
+ class FixtureSet
+ class ModelMetadata # :nodoc:
+ def initialize(model_class, table_name)
+ @model_class = model_class
+ @table_name = table_name
+ end
+
+ def primary_key_name
+ @primary_key_name ||= @model_class && @model_class.primary_key
+ end
+
+ def primary_key_type
+ @primary_key_type ||= @model_class && @model_class.type_for_attribute(@model_class.primary_key).type
+ end
+
+ def has_primary_key_column?
+ @has_primary_key_column ||= primary_key_name &&
+ @model_class.columns.any? { |col| col.name == primary_key_name }
+ end
+
+ def timestamp_column_names
+ @timestamp_column_names ||=
+ %w(created_at created_on updated_at updated_on) & column_names
+ end
+
+ def inheritance_column_name
+ @inheritance_column_name ||= @model_class && @model_class.inheritance_column
+ end
+
+ private
+
+ def column_names
+ @column_names ||= @model_class.connection.columns(@table_name).collect(&:name)
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/fixture_set/table_row.rb b/activerecord/lib/active_record/fixture_set/table_row.rb
new file mode 100644
index 0000000000..5f72c1df38
--- /dev/null
+++ b/activerecord/lib/active_record/fixture_set/table_row.rb
@@ -0,0 +1,79 @@
+# frozen_string_literal: true
+
+module ActiveRecord
+ class FixtureSet
+ class TableRow # :nodoc:
+ def initialize(fixture, table_rows:, label:, now:)
+ @table_rows = table_rows
+ @label = label
+ @now = now
+ @row = fixture.to_hash
+ fill_row_model_attributes
+ end
+
+ def to_hash
+ @row
+ end
+
+ private
+
+ def model_metadata
+ @table_rows.model_metadata
+ end
+
+ def model_class
+ @table_rows.model_class
+ end
+
+ def fill_row_model_attributes
+ return unless model_class
+ fill_timestamps
+ interpolate_label
+ generate_primary_key
+ resolve_enums
+ @table_rows.resolve_sti_reflections(@row)
+ end
+
+ def reflection_class
+ @reflection_class ||= if @row.include?(model_metadata.inheritance_column_name)
+ @row[model_metadata.inheritance_column_name].constantize rescue model_class
+ else
+ model_class
+ end
+ end
+
+ def fill_timestamps
+ # fill in timestamp columns if they aren't specified and the model is set to record_timestamps
+ if model_class.record_timestamps
+ model_metadata.timestamp_column_names.each do |c_name|
+ @row[c_name] = @now unless @row.key?(c_name)
+ end
+ end
+ end
+
+ def interpolate_label
+ # interpolate the fixture label
+ @row.each do |key, value|
+ @row[key] = value.gsub("$LABEL", @label.to_s) if value.is_a?(String)
+ end
+ end
+
+ def generate_primary_key
+ # generate a primary key if necessary
+ if model_metadata.has_primary_key_column? && !@row.include?(model_metadata.primary_key_name)
+ @row[model_metadata.primary_key_name] = ActiveRecord::FixtureSet.identify(
+ @label, model_metadata.primary_key_type
+ )
+ end
+ end
+
+ def resolve_enums
+ model_class.defined_enums.each do |name, values|
+ if @row.include?(name)
+ @row[name] = values.fetch(@row[name], @row[name])
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/fixture_set/table_rows.rb b/activerecord/lib/active_record/fixture_set/table_rows.rb
new file mode 100644
index 0000000000..e8335a2e10
--- /dev/null
+++ b/activerecord/lib/active_record/fixture_set/table_rows.rb
@@ -0,0 +1,129 @@
+# frozen_string_literal: true
+
+require "active_record/fixture_set/table_row"
+require "active_record/fixture_set/model_metadata"
+
+module ActiveRecord
+ class FixtureSet
+ class TableRows # :nodoc:
+ class ReflectionProxy # :nodoc:
+ def initialize(association)
+ @association = association
+ end
+
+ def join_table
+ @association.join_table
+ end
+
+ def name
+ @association.name
+ end
+
+ def primary_key_type
+ @association.klass.type_for_attribute(@association.klass.primary_key).type
+ end
+ end
+
+ class HasManyThroughProxy < ReflectionProxy # :nodoc:
+ def rhs_key
+ @association.foreign_key
+ end
+
+ def lhs_key
+ @association.through_reflection.foreign_key
+ end
+
+ def join_table
+ @association.through_reflection.table_name
+ end
+ end
+
+ def initialize(table_name, model_class:, fixtures:, config:)
+ @table_name = table_name
+ @model_class = model_class
+
+ # track any join tables we need to insert later
+ @tables = Hash.new { |h, table| h[table] = [] }
+
+ build_table_rows_from(fixtures, config)
+ end
+
+ attr_reader :table_name, :model_class
+
+ def to_hash
+ @tables.transform_values { |rows| rows.map(&:to_hash) }
+ end
+
+ def model_metadata
+ @model_metadata ||= ModelMetadata.new(model_class, table_name)
+ end
+
+ def resolve_sti_reflections(row)
+ # If STI is used, find the correct subclass for association reflection
+ reflection_class = reflection_class_for(row)
+
+ reflection_class._reflections.each_value do |association|
+ case association.macro
+ when :belongs_to
+ # Do not replace association name with association foreign key if they are named the same
+ fk_name = (association.options[:foreign_key] || "#{association.name}_id").to_s
+
+ if association.name.to_s != fk_name && value = row.delete(association.name.to_s)
+ if association.polymorphic? && value.sub!(/\s*\(([^\)]*)\)\s*$/, "")
+ # support polymorphic belongs_to as "label (Type)"
+ row[association.foreign_type] = $1
+ end
+
+ fk_type = reflection_class.type_for_attribute(fk_name).type
+ row[fk_name] = ActiveRecord::FixtureSet.identify(value, fk_type)
+ end
+ when :has_many
+ if association.options[:through]
+ add_join_records(row, HasManyThroughProxy.new(association))
+ end
+ end
+ end
+ end
+
+ private
+
+ def build_table_rows_from(fixtures, config)
+ now = config.default_timezone == :utc ? Time.now.utc : Time.now
+
+ @tables[table_name] = fixtures.map do |label, fixture|
+ TableRow.new(
+ fixture,
+ table_rows: self,
+ label: label,
+ now: now,
+ )
+ end
+ end
+
+ def reflection_class_for(row)
+ if row.include?(model_metadata.inheritance_column_name)
+ row[model_metadata.inheritance_column_name].constantize rescue model_class
+ else
+ model_class
+ end
+ end
+
+ def add_join_records(row, association)
+ # This is the case when the join table has no fixtures file
+ if (targets = row.delete(association.name.to_s))
+ table_name = association.join_table
+ column_type = association.primary_key_type
+ lhs_key = association.lhs_key
+ rhs_key = association.rhs_key
+
+ targets = targets.is_a?(Array) ? targets : targets.split(/\s*,\s*/)
+ joins = targets.map do |target|
+ { lhs_key => row[model_metadata.primary_key_name],
+ rhs_key => ActiveRecord::FixtureSet.identify(target, column_type) }
+ end
+ @tables[table_name].concat(joins)
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index e697c30bb6..b090c76a38 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -8,6 +8,7 @@ require "active_support/dependencies"
require "active_support/core_ext/digest/uuid"
require "active_record/fixture_set/file"
require "active_record/fixture_set/render_context"
+require "active_record/fixture_set/table_rows"
require "active_record/test_fixtures"
require "active_record/errors"
@@ -442,60 +443,6 @@ module ActiveRecord
@@all_cached_fixtures = Hash.new { |h, k| h[k] = {} }
- def self.default_fixture_model_name(fixture_set_name, config = ActiveRecord::Base) # :nodoc:
- config.pluralize_table_names ?
- fixture_set_name.singularize.camelize :
- fixture_set_name.camelize
- end
-
- def self.default_fixture_table_name(fixture_set_name, config = ActiveRecord::Base) # :nodoc:
- "#{ config.table_name_prefix }"\
- "#{ fixture_set_name.tr('/', '_') }"\
- "#{ config.table_name_suffix }".to_sym
- end
-
- def self.reset_cache
- @@all_cached_fixtures.clear
- end
-
- def self.cache_for_connection(connection)
- @@all_cached_fixtures[connection]
- end
-
- def self.fixture_is_cached?(connection, table_name)
- cache_for_connection(connection)[table_name]
- end
-
- def self.cached_fixtures(connection, keys_to_fetch = nil)
- if keys_to_fetch
- cache_for_connection(connection).values_at(*keys_to_fetch)
- else
- cache_for_connection(connection).values
- end
- end
-
- def self.cache_fixtures(connection, fixtures_map)
- cache_for_connection(connection).update(fixtures_map)
- end
-
- def self.instantiate_fixtures(object, fixture_set, load_instances = true)
- if load_instances
- fixture_set.each do |fixture_name, fixture|
- begin
- object.instance_variable_set "@#{fixture_name}", fixture.find
- rescue FixtureClassNotFound
- nil
- end
- end
- end
- end
-
- def self.instantiate_all_loaded_fixtures(object, load_instances = true)
- all_loaded_fixtures.each_value do |fixture_set|
- instantiate_fixtures(object, fixture_set, load_instances)
- end
- end
-
cattr_accessor :all_loaded_fixtures, default: {}
class ClassCache
@@ -504,14 +451,16 @@ module ActiveRecord
@config = config
# Remove string values that aren't constants or subclasses of AR
- @class_names.delete_if { |klass_name, klass| !insert_class(@class_names, klass_name, klass) }
+ @class_names.delete_if do |klass_name, klass|
+ !insert_class(@class_names, klass_name, klass)
+ end
end
def [](fs_name)
- @class_names.fetch(fs_name) {
+ @class_names.fetch(fs_name) do
klass = default_fixture_model(fs_name, @config).safe_constantize
insert_class(@class_names, fs_name, klass)
- }
+ end
end
private
@@ -530,38 +479,129 @@ module ActiveRecord
end
end
- def self.create_fixtures(fixtures_directory, fixture_set_names, class_names = {}, config = ActiveRecord::Base)
- fixture_set_names = Array(fixture_set_names).map(&:to_s)
- class_names = ClassCache.new class_names, config
+ class << self
+ def default_fixture_model_name(fixture_set_name, config = ActiveRecord::Base) # :nodoc:
+ config.pluralize_table_names ?
+ fixture_set_name.singularize.camelize :
+ fixture_set_name.camelize
+ end
+
+ def default_fixture_table_name(fixture_set_name, config = ActiveRecord::Base) # :nodoc:
+ "#{ config.table_name_prefix }"\
+ "#{ fixture_set_name.tr('/', '_') }"\
+ "#{ config.table_name_suffix }".to_sym
+ end
+
+ def reset_cache
+ @@all_cached_fixtures.clear
+ end
- # FIXME: Apparently JK uses this.
- connection = block_given? ? yield : ActiveRecord::Base.connection
+ def cache_for_connection(connection)
+ @@all_cached_fixtures[connection]
+ end
- files_to_read = fixture_set_names.reject { |fs_name|
- fixture_is_cached?(connection, fs_name)
- }
+ def fixture_is_cached?(connection, table_name)
+ cache_for_connection(connection)[table_name]
+ end
- unless files_to_read.empty?
- fixtures_map = {}
+ def cached_fixtures(connection, keys_to_fetch = nil)
+ if keys_to_fetch
+ cache_for_connection(connection).values_at(*keys_to_fetch)
+ else
+ cache_for_connection(connection).values
+ end
+ end
+
+ def cache_fixtures(connection, fixtures_map)
+ cache_for_connection(connection).update(fixtures_map)
+ end
+
+ def instantiate_fixtures(object, fixture_set, load_instances = true)
+ return unless load_instances
+ fixture_set.each do |fixture_name, fixture|
+ begin
+ object.instance_variable_set "@#{fixture_name}", fixture.find
+ rescue FixtureClassNotFound
+ nil
+ end
+ end
+ end
+
+ def instantiate_all_loaded_fixtures(object, load_instances = true)
+ all_loaded_fixtures.each_value do |fixture_set|
+ instantiate_fixtures(object, fixture_set, load_instances)
+ end
+ end
+
+ def create_fixtures(fixtures_directory, fixture_set_names, class_names = {}, config = ActiveRecord::Base)
+ fixture_set_names = Array(fixture_set_names).map(&:to_s)
+ class_names = ClassCache.new class_names, config
+
+ # FIXME: Apparently JK uses this.
+ connection = block_given? ? yield : ActiveRecord::Base.connection
+
+ fixture_files_to_read = fixture_set_names.reject do |fs_name|
+ fixture_is_cached?(connection, fs_name)
+ end
+
+ if fixture_files_to_read.any?
+ fixtures_map = read_and_insert(
+ fixtures_directory,
+ fixture_files_to_read,
+ class_names,
+ connection,
+ )
+ cache_fixtures(connection, fixtures_map)
+ end
+ cached_fixtures(connection, fixture_set_names)
+ end
+
+ # Returns a consistent, platform-independent identifier for +label+.
+ # Integer identifiers are values less than 2^30. UUIDs are RFC 4122 version 5 SHA-1 hashes.
+ def identify(label, column_type = :integer)
+ if column_type == :uuid
+ Digest::UUID.uuid_v5(Digest::UUID::OID_NAMESPACE, label.to_s)
+ else
+ Zlib.crc32(label.to_s) % MAX_ID
+ end
+ end
- fixture_sets = files_to_read.map do |fs_name|
- klass = class_names[fs_name]
- conn = klass ? klass.connection : connection
- fixtures_map[fs_name] = new( # ActiveRecord::FixtureSet.new
+ # Superclass for the evaluation contexts used by ERB fixtures.
+ def context_class
+ @context_class ||= Class.new
+ end
+
+ private
+
+ def read_and_insert(fixtures_directory, fixture_files, class_names, connection) # :nodoc:
+ fixtures_map = {}
+ fixture_sets = fixture_files.map do |fixture_set_name|
+ klass = class_names[fixture_set_name]
+ conn = klass&.connection || connection
+ fixtures_map[fixture_set_name] = new( # ActiveRecord::FixtureSet.new
conn,
- fs_name,
+ fixture_set_name,
klass,
- ::File.join(fixtures_directory, fs_name))
+ ::File.join(fixtures_directory, fixture_set_name)
+ )
end
+ update_all_loaded_fixtures(fixtures_map)
- update_all_loaded_fixtures fixtures_map
- fixture_sets_by_connection = fixture_sets.group_by { |fs| fs.model_class ? fs.model_class.connection : connection }
+ insert(fixture_sets, connection)
+
+ fixtures_map
+ end
+
+ def insert(fixture_sets, connection) # :nodoc:
+ fixture_sets_by_connection = fixture_sets.group_by do |fixture_set|
+ fixture_set.model_class&.connection || connection
+ end
fixture_sets_by_connection.each do |conn, set|
table_rows_for_connection = Hash.new { |h, k| h[k] = [] }
- set.each do |fs|
- fs.table_rows.each do |table, rows|
+ set.each do |fixture_set|
+ fixture_set.table_rows.each do |table, rows|
table_rows_for_connection[table].unshift(*rows)
end
end
@@ -572,31 +612,13 @@ module ActiveRecord
set.each { |fs| conn.reset_pk_sequence!(fs.table_name) }
end
end
-
- cache_fixtures(connection, fixtures_map)
end
- cached_fixtures(connection, fixture_set_names)
- end
- # Returns a consistent, platform-independent identifier for +label+.
- # Integer identifiers are values less than 2^30. UUIDs are RFC 4122 version 5 SHA-1 hashes.
- def self.identify(label, column_type = :integer)
- if column_type == :uuid
- Digest::UUID.uuid_v5(Digest::UUID::OID_NAMESPACE, label.to_s)
- else
- Zlib.crc32(label.to_s) % MAX_ID
+ def update_all_loaded_fixtures(fixtures_map) # :nodoc:
+ all_loaded_fixtures.update(fixtures_map)
end
end
- # Superclass for the evaluation contexts used by ERB fixtures.
- def self.context_class
- @context_class ||= Class.new
- end
-
- def self.update_all_loaded_fixtures(fixtures_map) # :nodoc:
- all_loaded_fixtures.update(fixtures_map)
- end
-
attr_reader :table_name, :name, :fixtures, :model_class, :config
def initialize(connection, name, class_name, path, config = ActiveRecord::Base)
@@ -634,152 +656,18 @@ module ActiveRecord
# Returns a hash of rows to be inserted. The key is the table, the value is
# a list of rows to insert to that table.
def table_rows
- now = config.default_timezone == :utc ? Time.now.utc : Time.now
-
# allow a standard key to be used for doing defaults in YAML
fixtures.delete("DEFAULTS")
- # track any join tables we need to insert later
- rows = Hash.new { |h, table| h[table] = [] }
-
- rows[table_name] = fixtures.map do |label, fixture|
- row = fixture.to_hash
-
- if model_class
- # fill in timestamp columns if they aren't specified and the model is set to record_timestamps
- if model_class.record_timestamps
- timestamp_column_names.each do |c_name|
- row[c_name] = now unless row.key?(c_name)
- end
- end
-
- # interpolate the fixture label
- row.each do |key, value|
- row[key] = value.gsub("$LABEL", label.to_s) if value.is_a?(String)
- end
-
- # generate a primary key if necessary
- if has_primary_key_column? && !row.include?(primary_key_name)
- row[primary_key_name] = ActiveRecord::FixtureSet.identify(label, primary_key_type)
- end
-
- # Resolve enums
- model_class.defined_enums.each do |name, values|
- if row.include?(name)
- row[name] = values.fetch(row[name], row[name])
- end
- end
-
- # If STI is used, find the correct subclass for association reflection
- reflection_class =
- if row.include?(inheritance_column_name)
- row[inheritance_column_name].constantize rescue model_class
- else
- model_class
- end
-
- reflection_class._reflections.each_value do |association|
- case association.macro
- when :belongs_to
- # Do not replace association name with association foreign key if they are named the same
- fk_name = (association.options[:foreign_key] || "#{association.name}_id").to_s
-
- if association.name.to_s != fk_name && value = row.delete(association.name.to_s)
- if association.polymorphic? && value.sub!(/\s*\(([^\)]*)\)\s*$/, "")
- # support polymorphic belongs_to as "label (Type)"
- row[association.foreign_type] = $1
- end
-
- fk_type = reflection_class.type_for_attribute(fk_name).type
- row[fk_name] = ActiveRecord::FixtureSet.identify(value, fk_type)
- end
- when :has_many
- if association.options[:through]
- add_join_records(rows, row, HasManyThroughProxy.new(association))
- end
- end
- end
- end
-
- row
- end
- rows
- end
-
- class ReflectionProxy # :nodoc:
- def initialize(association)
- @association = association
- end
-
- def join_table
- @association.join_table
- end
-
- def name
- @association.name
- end
-
- def primary_key_type
- @association.klass.type_for_attribute(@association.klass.primary_key).type
- end
- end
-
- class HasManyThroughProxy < ReflectionProxy # :nodoc:
- def rhs_key
- @association.foreign_key
- end
-
- def lhs_key
- @association.through_reflection.foreign_key
- end
-
- def join_table
- @association.through_reflection.table_name
- end
+ TableRows.new(
+ table_name,
+ model_class: model_class,
+ fixtures: fixtures,
+ config: config,
+ ).to_hash
end
private
- def primary_key_name
- @primary_key_name ||= model_class && model_class.primary_key
- end
-
- def primary_key_type
- @primary_key_type ||= model_class && model_class.type_for_attribute(model_class.primary_key).type
- end
-
- def add_join_records(rows, row, association)
- # This is the case when the join table has no fixtures file
- if (targets = row.delete(association.name.to_s))
- table_name = association.join_table
- column_type = association.primary_key_type
- lhs_key = association.lhs_key
- rhs_key = association.rhs_key
-
- targets = targets.is_a?(Array) ? targets : targets.split(/\s*,\s*/)
- rows[table_name].concat targets.map { |target|
- { lhs_key => row[primary_key_name],
- rhs_key => ActiveRecord::FixtureSet.identify(target, column_type) }
- }
- end
- end
-
- def has_primary_key_column?
- @has_primary_key_column ||= primary_key_name &&
- model_class.columns.any? { |c| c.name == primary_key_name }
- end
-
- def timestamp_column_names
- @timestamp_column_names ||=
- %w(created_at created_on updated_at updated_on) & column_names
- end
-
- def inheritance_column_name
- @inheritance_column_name ||= model_class && model_class.inheritance_column
- end
-
- def column_names
- @column_names ||= @connection.columns(@table_name).collect(&:name)
- end
def model_class=(class_name)
if class_name.is_a?(Class) # TODO: Should be an AR::Base type class, or any?
@@ -843,12 +731,9 @@ module ActiveRecord
alias :to_hash :fixture
def find
- if model_class
- model_class.unscoped do
- model_class.find(fixture[model_class.primary_key])
- end
- else
- raise FixtureClassNotFound, "No class attached to find."
+ raise FixtureClassNotFound, "No class attached to find." unless model_class
+ model_class.unscoped do
+ model_class.find(fixture[model_class.primary_key])
end
end
end
diff --git a/activerecord/lib/active_record/integration.rb b/activerecord/lib/active_record/integration.rb
index 6cf26a9792..456689ec6d 100644
--- a/activerecord/lib/active_record/integration.rb
+++ b/activerecord/lib/active_record/integration.rb
@@ -60,7 +60,7 @@ module ActiveRecord
# the cache key will also include a version.
#
# Product.cache_versioning = false
- # Person.find(5).cache_key # => "people/5-20071224150000" (updated_at available)
+ # Product.find(5).cache_key # => "products/5-20071224150000" (updated_at available)
def cache_key(*timestamp_names)
if new_record?
"#{model_name.cache_key}/new"
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index 6eb2bfb452..8404119631 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -758,6 +758,8 @@ module ActiveRecord
@_association_destroy_exception = nil
end
+ # The name of the method used to touch a +belongs_to+ association when the
+ # +:touch+ option is used.
def belongs_to_touch_method
:touch
end
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index be5ef350a9..748fd65aa2 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -149,18 +149,7 @@ db_namespace = namespace :db do
desc "Display status of migrations"
task status: :load_config do
- unless ActiveRecord::SchemaMigration.table_exists?
- abort "Schema migrations table does not exist yet."
- end
-
- # output
- puts "\ndatabase: #{ActiveRecord::Base.connection_config[:database]}\n\n"
- puts "#{'Status'.center(8)} #{'Migration ID'.ljust(14)} Migration Name"
- puts "-" * 50
- ActiveRecord::Base.connection.migration_context.migrations_status.each do |status, version, name|
- puts "#{status.center(8)} #{version.ljust(14)} #{name}"
- end
- puts
+ ActiveRecord::Tasks::DatabaseTasks.migrate_status
end
end
diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb
index 5e29085aff..974d7a1c0a 100644
--- a/activerecord/lib/active_record/tasks/database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/database_tasks.rb
@@ -197,6 +197,21 @@ module ActiveRecord
Migration.verbose = verbose_was
end
+ def migrate_status
+ unless ActiveRecord::SchemaMigration.table_exists?
+ Kernel.abort "Schema migrations table does not exist yet."
+ end
+
+ # output
+ puts "\ndatabase: #{ActiveRecord::Base.connection_config[:database]}\n\n"
+ puts "#{'Status'.center(8)} #{'Migration ID'.ljust(14)} Migration Name"
+ puts "-" * 50
+ ActiveRecord::Base.connection.migration_context.migrations_status.each do |status, version, name|
+ puts "#{status.center(8)} #{version.ljust(14)} #{name}"
+ end
+ puts
+ end
+
def check_target_version
if target_version && !(Migration::MigrationFilenameRegexp.match?(ENV["VERSION"]) || /\A\d+\z/.match?(ENV["VERSION"]))
raise "Invalid format of target version: `VERSION=#{ENV['VERSION']}`"
diff --git a/activerecord/lib/arel/visitors/mysql.rb b/activerecord/lib/arel/visitors/mysql.rb
index 081452caeb..9d9294fecc 100644
--- a/activerecord/lib/arel/visitors/mysql.rb
+++ b/activerecord/lib/arel/visitors/mysql.rb
@@ -69,13 +69,7 @@ module Arel # :nodoc: all
# query. However, this does not allow for LIMIT, OFFSET and ORDER. To support
# these, we must use a subquery.
def prepare_update_statement(o)
- if has_join_sources?(o)
- if has_limit_or_offset_or_orders?(o)
- super
- else
- o
- end
- elsif o.offset
+ if o.offset || has_join_sources?(o) && has_limit_or_offset_or_orders?(o)
super
else
o
@@ -83,7 +77,7 @@ module Arel # :nodoc: all
end
def prepare_delete_statement(o)
- if has_join_sources?(o) || o.offset
+ if o.offset || has_join_sources?(o)
super
else
o
diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb
index 1fca1be181..d520919332 100644
--- a/activerecord/test/cases/associations/belongs_to_associations_test.rb
+++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -694,7 +694,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
line_item = LineItem.create!
Invoice.create!(line_items: [line_item])
- assert_queries(0) { line_item.save }
+ assert_no_queries { line_item.save }
end
def test_belongs_to_with_touch_option_on_destroy
@@ -789,7 +789,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
def test_dont_find_target_when_foreign_key_is_null
tagging = taggings(:thinking_general)
- assert_queries(0) { tagging.super_tag }
+ assert_no_queries { tagging.super_tag }
end
def test_dont_find_target_when_saving_foreign_key_after_stale_association_loaded
diff --git a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
index ba2104eb26..a9e22c7643 100644
--- a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
+++ b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
@@ -160,6 +160,16 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
end
end
+ def test_preload_through_missing_records
+ post = Post.where.not(author_id: Author.select(:id)).preload(author: { comments: :post }).first!
+ assert_no_queries { assert_nil post.author }
+ end
+
+ def test_eager_association_loading_with_missing_first_record
+ posts = Post.where(id: 3).preload(author: { comments: :post }).to_a
+ assert_equal posts.size, 1
+ end
+
def test_eager_association_loading_with_recursive_cascading_four_levels_has_many_through
source = Vertex.all.merge!(includes: { sinks: { sinks: { sinks: :sinks } } }, order: "vertices.id").first
assert_equal vertices(:vertex_4), assert_no_queries { source.sinks.first.sinks.first.sinks.first }
diff --git a/activerecord/test/cases/associations/eager_load_nested_include_test.rb b/activerecord/test/cases/associations/eager_load_nested_include_test.rb
index c5b2b77bd4..525ad3197a 100644
--- a/activerecord/test/cases/associations/eager_load_nested_include_test.rb
+++ b/activerecord/test/cases/associations/eager_load_nested_include_test.rb
@@ -92,7 +92,7 @@ class EagerLoadPolyAssocsTest < ActiveRecord::TestCase
def test_include_query
res = ShapeExpression.all.merge!(includes: [ :shape, { paint: :non_poly } ]).to_a
assert_equal NUM_SHAPE_EXPRESSIONS, res.size
- assert_queries(0) do
+ assert_no_queries do
res.each do |se|
assert_not_nil se.paint.non_poly, "this is the association that was loading incorrectly before the change"
assert_not_nil se.shape, "just making sure other associations still work"
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index 79b3b4a6ad..39034746c9 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -1346,7 +1346,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_joins_with_includes_should_preload_via_joins
post = assert_queries(1) { Post.includes(:comments).joins(:comments).order("posts.id desc").to_a.first }
- assert_queries(0) do
+ assert_no_queries do
assert_not_equal 0, post.comments.to_a.count
end
end
diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
index 482302055d..38b121d37b 100644
--- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
@@ -310,7 +310,11 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
def test_build
devel = Developer.find(1)
- proj = assert_no_queries(ignore_none: false) { devel.projects.build("name" => "Projekt") }
+
+ # Load schema information so we don't query below if running just this test.
+ Project.define_attribute_methods
+
+ proj = assert_no_queries { devel.projects.build("name" => "Projekt") }
assert_not_predicate devel.projects, :loaded?
assert_equal devel.projects.last, proj
@@ -325,7 +329,11 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
def test_new_aliased_to_build
devel = Developer.find(1)
- proj = assert_no_queries(ignore_none: false) { devel.projects.new("name" => "Projekt") }
+
+ # Load schema information so we don't query below if running just this test.
+ Project.define_attribute_methods
+
+ proj = assert_no_queries { devel.projects.new("name" => "Projekt") }
assert_not_predicate devel.projects, :loaded?
assert_equal devel.projects.last, proj
@@ -546,7 +554,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
developer = project.developers.first
- assert_no_queries(ignore_none: false) do
+ assert_no_queries do
assert_predicate project.developers, :loaded?
assert_includes project.developers, developer
end
@@ -741,7 +749,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
def test_get_ids_for_loaded_associations
developer = developers(:david)
developer.projects.reload
- assert_queries(0) do
+ assert_no_queries do
developer.project_ids
developer.project_ids
end
@@ -859,7 +867,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
def test_has_and_belongs_to_many_associations_on_new_records_use_null_relations
projects = Developer.new.projects
- assert_no_queries(ignore_none: false) do
+ assert_no_queries do
assert_equal [], projects
assert_equal [], projects.where(title: "omg")
assert_equal [], projects.pluck(:title)
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index 0b44515e00..eb65f9e74f 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -458,7 +458,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_finder_method_with_dirty_target
company = companies(:first_firm)
new_clients = []
- assert_no_queries(ignore_none: false) do
+
+ # Load schema information so we don't query below if running just this test.
+ Client.define_attribute_methods
+
+ assert_no_queries do
new_clients << company.clients_of_firm.build(name: "Another Client")
new_clients << company.clients_of_firm.build(name: "Another Client II")
new_clients << company.clients_of_firm.build(name: "Another Client III")
@@ -478,7 +482,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_finder_bang_method_with_dirty_target
company = companies(:first_firm)
new_clients = []
- assert_no_queries(ignore_none: false) do
+
+ # Load schema information so we don't query below if running just this test.
+ Client.define_attribute_methods
+
+ assert_no_queries do
new_clients << company.clients_of_firm.build(name: "Another Client")
new_clients << company.clients_of_firm.build(name: "Another Client II")
new_clients << company.clients_of_firm.build(name: "Another Client III")
@@ -955,8 +963,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_transactions_when_adding_to_new_record
- assert_no_queries(ignore_none: false) do
- firm = Firm.new
+ # Load schema information so we don't query below if running just this test.
+ Client.define_attribute_methods
+
+ firm = Firm.new
+ assert_no_queries do
firm.clients_of_firm.concat(Client.new("name" => "Natural Company"))
end
end
@@ -970,7 +981,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_new_aliased_to_build
company = companies(:first_firm)
- new_client = assert_no_queries(ignore_none: false) { company.clients_of_firm.new("name" => "Another Client") }
+
+ # Load schema information so we don't query below if running just this test.
+ Client.define_attribute_methods
+
+ new_client = assert_no_queries { company.clients_of_firm.new("name" => "Another Client") }
assert_not_predicate company.clients_of_firm, :loaded?
assert_equal "Another Client", new_client.name
@@ -980,7 +995,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_build
company = companies(:first_firm)
- new_client = assert_no_queries(ignore_none: false) { company.clients_of_firm.build("name" => "Another Client") }
+
+ # Load schema information so we don't query below if running just this test.
+ Client.define_attribute_methods
+
+ new_client = assert_no_queries { company.clients_of_firm.build("name" => "Another Client") }
assert_not_predicate company.clients_of_firm, :loaded?
assert_equal "Another Client", new_client.name
@@ -1037,7 +1056,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_build_many
company = companies(:first_firm)
- new_clients = assert_no_queries(ignore_none: false) { company.clients_of_firm.build([{ "name" => "Another Client" }, { "name" => "Another Client II" }]) }
+
+ # Load schema information so we don't query below if running just this test.
+ Client.define_attribute_methods
+
+ new_clients = assert_no_queries { company.clients_of_firm.build([{ "name" => "Another Client" }, { "name" => "Another Client II" }]) }
assert_equal 2, new_clients.size
end
@@ -1049,10 +1072,12 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_build_without_loading_association
first_topic = topics(:first)
- Reply.column_names
assert_equal 1, first_topic.replies.length
+ # Load schema information so we don't query below if running just this test.
+ Reply.define_attribute_methods
+
assert_no_queries do
first_topic.replies.build(title: "Not saved", content: "Superstars")
assert_equal 2, first_topic.replies.size
@@ -1063,7 +1088,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_build_via_block
company = companies(:first_firm)
- new_client = assert_no_queries(ignore_none: false) { company.clients_of_firm.build { |client| client.name = "Another Client" } }
+
+ # Load schema information so we don't query below if running just this test.
+ Client.define_attribute_methods
+
+ new_client = assert_no_queries { company.clients_of_firm.build { |client| client.name = "Another Client" } }
assert_not_predicate company.clients_of_firm, :loaded?
assert_equal "Another Client", new_client.name
@@ -1073,7 +1102,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_build_many_via_block
company = companies(:first_firm)
- new_clients = assert_no_queries(ignore_none: false) do
+
+ # Load schema information so we don't query below if running just this test.
+ Client.define_attribute_methods
+
+ new_clients = assert_no_queries do
company.clients_of_firm.build([{ "name" => "Another Client" }, { "name" => "Another Client II" }]) do |client|
client.name = "changed"
end
@@ -1086,8 +1119,6 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_create_without_loading_association
first_firm = companies(:first_firm)
- Firm.column_names
- Client.column_names
assert_equal 2, first_firm.clients_of_firm.size
first_firm.clients_of_firm.reset
@@ -1266,7 +1297,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_calling_empty_with_counter_cache
post = posts(:welcome)
- assert_queries(0) do
+ assert_no_queries do
assert_not_empty post.comments
end
end
@@ -1364,8 +1395,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_transaction_when_deleting_new_record
- assert_no_queries(ignore_none: false) do
- firm = Firm.new
+ # Load schema information so we don't query below if running just this test.
+ Client.define_attribute_methods
+
+ firm = Firm.new
+ assert_no_queries do
client = Client.new("name" => "New Client")
firm.clients_of_firm << client
firm.clients_of_firm.destroy(client)
@@ -1800,7 +1834,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
firm.clients = []
firm.save
- assert_queries(0, ignore_none: true) do
+ assert_no_queries do
firm.clients = []
end
@@ -1822,8 +1856,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_transactions_when_replacing_on_new_record
- assert_no_queries(ignore_none: false) do
- firm = Firm.new
+ # Load schema information so we don't query below if running just this test.
+ Client.define_attribute_methods
+
+ firm = Firm.new
+ assert_no_queries do
firm.clients_of_firm = [Client.new("name" => "New Client")]
end
end
@@ -1835,7 +1872,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_get_ids_for_loaded_associations
company = companies(:first_firm)
company.clients.reload
- assert_queries(0) do
+ assert_no_queries do
company.client_ids
company.client_ids
end
@@ -1862,11 +1899,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_get_ids_for_association_on_new_record_does_not_try_to_find_records
- Company.columns # Load schema information so we don't query below
- Contract.columns # if running just this test.
+ # Load schema information so we don't query below if running just this test.
+ companies(:first_client).contract_ids
company = Company.new
- assert_queries(0) do
+ assert_no_queries do
company.contract_ids
end
@@ -1972,7 +2009,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
firm.clients.load_target
assert_predicate firm.clients, :loaded?
- assert_no_queries(ignore_none: false) do
+ assert_no_queries do
firm.clients.first
assert_equal 2, firm.clients.first(2).size
firm.clients.last
@@ -2356,6 +2393,18 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal [accounts(:signals37)], firm.accounts.open
end
+ def test_association_with_or_doesnt_set_inverse_instance_key
+ firm = companies(:first_firm)
+ accounts = firm.accounts.or(Account.where(firm_id: nil)).order(:id)
+ assert_equal [firm.id, nil], accounts.map(&:firm_id)
+ end
+
+ def test_association_with_rewhere_doesnt_set_inverse_instance_key
+ firm = companies(:first_firm)
+ accounts = firm.accounts.rewhere(firm_id: [firm.id, nil]).order(:id)
+ assert_equal [firm.id, nil], accounts.map(&:firm_id)
+ end
+
test "first_or_initialize adds the record to the association" do
firm = Firm.create! name: "omg"
client = firm.clients_of_firm.first_or_initialize
@@ -2385,7 +2434,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
test "has many associations on new records use null relations" do
post = Post.new
- assert_no_queries(ignore_none: false) do
+ assert_no_queries do
assert_equal [], post.comments
assert_equal [], post.comments.where(body: "omg")
assert_equal [], post.comments.pluck(:body)
diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb
index 442f4a93d4..7b405c74c4 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -274,7 +274,10 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert_queries(1) { posts(:thinking) }
new_person = nil # so block binding catches it
- assert_queries(0) do
+ # Load schema information so we don't query below if running just this test.
+ Person.define_attribute_methods
+
+ assert_no_queries do
new_person = Person.new first_name: "bob"
end
@@ -294,7 +297,10 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
def test_associate_new_by_building
assert_queries(1) { posts(:thinking) }
- assert_queries(0) do
+ # Load schema information so we don't query below if running just this test.
+ Person.define_attribute_methods
+
+ assert_no_queries do
posts(:thinking).people.build(first_name: "Bob")
posts(:thinking).people.new(first_name: "Ted")
end
@@ -571,10 +577,10 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
posts(:welcome).people = [people(:david)]
end
- assert_queries(0) {
+ assert_no_queries do
assert_includes posts(:welcome).people, people(:david)
assert_not_includes posts(:welcome).people, people(:michael)
- }
+ end
assert_includes posts(:welcome).reload.people.reload, people(:david)
assert_not_includes posts(:welcome).reload.people.reload, people(:michael)
@@ -698,7 +704,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
posts(:welcome).people.clear
end
- assert_queries(0) do
+ assert_no_queries do
assert_empty posts(:welcome).people
end
@@ -788,7 +794,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
def test_get_ids_for_loaded_associations
person = people(:michael)
person.posts.reload
- assert_queries(0) do
+ assert_no_queries do
person.post_ids
person.post_ids
end
@@ -1198,7 +1204,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
def test_has_many_through_associations_on_new_records_use_null_relations
person = Person.new
- assert_no_queries(ignore_none: false) do
+ assert_no_queries do
assert_equal [], person.posts
assert_equal [], person.posts.where(body: "omg")
assert_equal [], person.posts.pluck(:body)
diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb
index 9eea34d2b9..801720b214 100644
--- a/activerecord/test/cases/associations/has_one_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_associations_test.rb
@@ -37,10 +37,10 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
def test_has_one_cache_nils
firm = companies(:another_firm)
assert_queries(1) { assert_nil firm.account }
- assert_queries(0) { assert_nil firm.account }
+ assert_no_queries { assert_nil firm.account }
- firms = Firm.all.merge!(includes: :account).to_a
- assert_queries(0) { firms.each(&:account) }
+ firms = Firm.includes(:account).to_a
+ assert_no_queries { firms.each(&:account) }
end
def test_with_select
@@ -231,9 +231,13 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
end
def test_build_association_dont_create_transaction
- assert_no_queries(ignore_none: false) {
- Firm.new.build_account
- }
+ # Load schema information so we don't query below if running just this test.
+ Account.define_attribute_methods
+
+ firm = Firm.new
+ assert_no_queries do
+ firm.build_account
+ end
end
def test_building_the_associated_object_with_implicit_sti_base_class
diff --git a/activerecord/test/cases/associations/nested_through_associations_test.rb b/activerecord/test/cases/associations/nested_through_associations_test.rb
index 03ed1c1d47..5821744530 100644
--- a/activerecord/test/cases/associations/nested_through_associations_test.rb
+++ b/activerecord/test/cases/associations/nested_through_associations_test.rb
@@ -137,7 +137,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase
def test_has_many_through_has_one_through_with_has_one_source_reflection_preload
members = assert_queries(4) { Member.includes(:nested_sponsors).to_a }
mustache = sponsors(:moustache_club_sponsor_for_groucho)
- assert_no_queries(ignore_none: false) do
+ assert_no_queries do
assert_equal [mustache], members.first.nested_sponsors
end
end
@@ -196,7 +196,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase
# postgresql test if randomly executed then executes "SHOW max_identifier_length". Hence
# the need to ignore certain predefined sqls that deal with system calls.
- assert_no_queries(ignore_none: false) do
+ assert_no_queries do
assert_equal [groucho_details, other_details], members.first.organization_member_details_2.sort_by(&:id)
end
end
diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb
index db3a58eba9..88df0eed55 100644
--- a/activerecord/test/cases/autosave_association_test.rb
+++ b/activerecord/test/cases/autosave_association_test.rb
@@ -642,7 +642,11 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa
def test_build_before_save
company = companies(:first_firm)
- new_client = assert_no_queries(ignore_none: false) { company.clients_of_firm.build("name" => "Another Client") }
+
+ # Load schema information so we don't query below if running just this test.
+ Client.define_attribute_methods
+
+ new_client = assert_no_queries { company.clients_of_firm.build("name" => "Another Client") }
assert_not_predicate company.clients_of_firm, :loaded?
company.name += "-changed"
@@ -653,7 +657,11 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa
def test_build_many_before_save
company = companies(:first_firm)
- assert_no_queries(ignore_none: false) { company.clients_of_firm.build([{ "name" => "Another Client" }, { "name" => "Another Client II" }]) }
+
+ # Load schema information so we don't query below if running just this test.
+ Client.define_attribute_methods
+
+ assert_no_queries { company.clients_of_firm.build([{ "name" => "Another Client" }, { "name" => "Another Client II" }]) }
company.name += "-changed"
assert_queries(3) { assert company.save }
@@ -662,7 +670,11 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa
def test_build_via_block_before_save
company = companies(:first_firm)
- new_client = assert_no_queries(ignore_none: false) { company.clients_of_firm.build { |client| client.name = "Another Client" } }
+
+ # Load schema information so we don't query below if running just this test.
+ Client.define_attribute_methods
+
+ new_client = assert_no_queries { company.clients_of_firm.build { |client| client.name = "Another Client" } }
assert_not_predicate company.clients_of_firm, :loaded?
company.name += "-changed"
@@ -673,7 +685,11 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa
def test_build_many_via_block_before_save
company = companies(:first_firm)
- assert_no_queries(ignore_none: false) do
+
+ # Load schema information so we don't query below if running just this test.
+ Client.define_attribute_methods
+
+ assert_no_queries do
company.clients_of_firm.build([{ "name" => "Another Client" }, { "name" => "Another Client II" }]) do |client|
client.name = "changed"
end
@@ -1100,7 +1116,7 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
assert @pirate.save
Pirate.transaction do
- assert_queries(0) do
+ assert_no_queries do
assert @pirate.save
end
end
@@ -1181,12 +1197,12 @@ class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase
def test_changed_for_autosave_should_handle_cycles
@ship.pirate = @pirate
- assert_queries(0) { @ship.save! }
+ assert_no_queries { @ship.save! }
@parrot = @pirate.parrots.create(name: "some_name")
@parrot.name = "changed_name"
assert_queries(1) { @ship.save! }
- assert_queries(0) { @ship.save! }
+ assert_no_queries { @ship.save! }
end
def test_should_automatically_save_bang_the_associated_model
diff --git a/activerecord/test/cases/collection_cache_key_test.rb b/activerecord/test/cases/collection_cache_key_test.rb
index a5d908344a..844b2b2162 100644
--- a/activerecord/test/cases/collection_cache_key_test.rb
+++ b/activerecord/test/cases/collection_cache_key_test.rb
@@ -91,12 +91,12 @@ module ActiveRecord
developers = Developer.where(name: "David")
assert_queries(1) { developers.cache_key }
- assert_queries(0) { developers.cache_key }
+ assert_no_queries { developers.cache_key }
end
test "it doesn't trigger any query if the relation is already loaded" do
developers = Developer.where(name: "David").load
- assert_queries(0) { developers.cache_key }
+ assert_no_queries { developers.cache_key }
end
test "relation cache_key changes when the sql query changes" do
diff --git a/activerecord/test/cases/connection_adapters/connection_handler_test.rb b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
index 5e3447efde..51d0cc3d12 100644
--- a/activerecord/test/cases/connection_adapters/connection_handler_test.rb
+++ b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
@@ -170,6 +170,11 @@ module ActiveRecord
ActiveRecord::Base.configurations = config
ActiveRecord::Base.configurations.configs_for.each do |db_config|
assert_instance_of ActiveRecord::DatabaseConfigurations::HashConfig, db_config
+ assert_instance_of String, db_config.env_name
+ assert_instance_of String, db_config.spec_name
+ db_config.config.keys.each do |key|
+ assert_instance_of String, key
+ end
end
ensure
ActiveRecord::Base.configurations = @prev_configs
diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb
index b1ebd20d6b..dfd74bfcb4 100644
--- a/activerecord/test/cases/dirty_test.rb
+++ b/activerecord/test/cases/dirty_test.rb
@@ -336,7 +336,7 @@ class DirtyTest < ActiveRecord::TestCase
end
with_partial_writes Pirate, true do
- assert_queries(0) { 2.times { pirate.save! } }
+ assert_no_queries { 2.times { pirate.save! } }
assert_equal old_updated_on, pirate.reload.updated_on
assert_queries(1) { pirate.catchphrase = "bar"; pirate.save! }
@@ -355,7 +355,7 @@ class DirtyTest < ActiveRecord::TestCase
old_lock_version = person.lock_version
with_partial_writes Person, true do
- assert_queries(0) { 2.times { person.save! } }
+ assert_no_queries { 2.times { person.save! } }
assert_equal old_lock_version, person.reload.lock_version
assert_queries(1) { person.first_name = "bar"; person.save! }
diff --git a/activerecord/test/cases/integration_test.rb b/activerecord/test/cases/integration_test.rb
index 36cd63c4d4..5687afbc71 100644
--- a/activerecord/test/cases/integration_test.rb
+++ b/activerecord/test/cases/integration_test.rb
@@ -157,18 +157,40 @@ class IntegrationTest < ActiveRecord::TestCase
skip("Subsecond precision is not supported") unless subsecond_precision_supported?
dev = Developer.first
key = dev.cache_key
- dev.touch
+ travel_to dev.updated_at + 0.000001 do
+ dev.touch
+ end
assert_not_equal key, dev.cache_key
end
def test_cache_key_format_is_not_too_precise
- skip("Subsecond precision is not supported") unless subsecond_precision_supported?
dev = Developer.first
dev.touch
key = dev.cache_key
assert_equal key, dev.reload.cache_key
end
+ def test_cache_version_format_is_precise_enough
+ skip("Subsecond precision is not supported") unless subsecond_precision_supported?
+ with_cache_versioning do
+ dev = Developer.first
+ version = dev.cache_version.to_param
+ travel_to Developer.first.updated_at + 0.000001 do
+ dev.touch
+ end
+ assert_not_equal version, dev.cache_version.to_param
+ end
+ end
+
+ def test_cache_version_format_is_not_too_precise
+ with_cache_versioning do
+ dev = Developer.first
+ dev.touch
+ key = dev.cache_version.to_param
+ assert_equal key, dev.reload.cache_version.to_param
+ end
+ end
+
def test_named_timestamps_for_cache_key
assert_deprecated do
owner = owners(:blackbeard)
@@ -185,50 +207,52 @@ class IntegrationTest < ActiveRecord::TestCase
end
def test_cache_key_is_stable_with_versioning_on
- Developer.cache_versioning = true
-
- developer = Developer.first
- first_key = developer.cache_key
+ with_cache_versioning do
+ developer = Developer.first
+ first_key = developer.cache_key
- developer.touch
- second_key = developer.cache_key
+ developer.touch
+ second_key = developer.cache_key
- assert_equal first_key, second_key
- ensure
- Developer.cache_versioning = false
+ assert_equal first_key, second_key
+ end
end
def test_cache_version_changes_with_versioning_on
- Developer.cache_versioning = true
-
- developer = Developer.first
- first_version = developer.cache_version
+ with_cache_versioning do
+ developer = Developer.first
+ first_version = developer.cache_version
- travel 10.seconds do
- developer.touch
- end
+ travel 10.seconds do
+ developer.touch
+ end
- second_version = developer.cache_version
+ second_version = developer.cache_version
- assert_not_equal first_version, second_version
- ensure
- Developer.cache_versioning = false
+ assert_not_equal first_version, second_version
+ end
end
def test_cache_key_retains_version_when_custom_timestamp_is_used
- Developer.cache_versioning = true
+ with_cache_versioning do
+ developer = Developer.first
+ first_key = developer.cache_key_with_version
- developer = Developer.first
- first_key = developer.cache_key_with_version
+ travel 10.seconds do
+ developer.touch
+ end
- travel 10.seconds do
- developer.touch
- end
+ second_key = developer.cache_key_with_version
- second_key = developer.cache_key_with_version
+ assert_not_equal first_key, second_key
+ end
+ end
- assert_not_equal first_key, second_key
+ def with_cache_versioning(value = true)
+ @old_cache_versioning = ActiveRecord::Base.cache_versioning
+ ActiveRecord::Base.cache_versioning = value
+ yield
ensure
- Developer.cache_versioning = false
+ ActiveRecord::Base.cache_versioning = @old_cache_versioning
end
end
diff --git a/activerecord/test/cases/null_relation_test.rb b/activerecord/test/cases/null_relation_test.rb
index 17527568f8..c5d5ded5ab 100644
--- a/activerecord/test/cases/null_relation_test.rb
+++ b/activerecord/test/cases/null_relation_test.rb
@@ -10,26 +10,26 @@ class NullRelationTest < ActiveRecord::TestCase
fixtures :posts, :comments
def test_none
- assert_no_queries(ignore_none: false) do
+ assert_no_queries do
assert_equal [], Developer.none
assert_equal [], Developer.all.none
end
end
def test_none_chainable
- assert_no_queries(ignore_none: false) do
+ assert_no_queries do
assert_equal [], Developer.none.where(name: "David")
end
end
def test_none_chainable_to_existing_scope_extension_method
- assert_no_queries(ignore_none: false) do
+ assert_no_queries do
assert_equal 1, Topic.anonymous_extension.none.one
end
end
def test_none_chained_to_methods_firing_queries_straight_to_db
- assert_no_queries(ignore_none: false) do
+ assert_no_queries do
assert_equal [], Developer.none.pluck(:id, :name)
assert_equal 0, Developer.none.delete_all
assert_equal 0, Developer.none.update_all(name: "David")
@@ -39,7 +39,7 @@ class NullRelationTest < ActiveRecord::TestCase
end
def test_null_relation_content_size_methods
- assert_no_queries(ignore_none: false) do
+ assert_no_queries do
assert_equal 0, Developer.none.size
assert_equal 0, Developer.none.count
assert_equal true, Developer.none.empty?
@@ -61,7 +61,7 @@ class NullRelationTest < ActiveRecord::TestCase
[:count, :sum].each do |method|
define_method "test_null_relation_#{method}" do
- assert_no_queries(ignore_none: false) do
+ assert_no_queries do
assert_equal 0, Comment.none.public_send(method, :id)
assert_equal Hash.new, Comment.none.group(:post_id).public_send(method, :id)
end
@@ -70,7 +70,7 @@ class NullRelationTest < ActiveRecord::TestCase
[:average, :minimum, :maximum].each do |method|
define_method "test_null_relation_#{method}" do
- assert_no_queries(ignore_none: false) do
+ assert_no_queries do
assert_nil Comment.none.public_send(method, :id)
assert_equal Hash.new, Comment.none.group(:post_id).public_send(method, :id)
end
diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb
index 8073cabae6..4830ff2b5f 100644
--- a/activerecord/test/cases/persistence_test.rb
+++ b/activerecord/test/cases/persistence_test.rb
@@ -446,19 +446,17 @@ class PersistenceTest < ActiveRecord::TestCase
end
def test_update_attribute_does_not_run_sql_if_attribute_is_not_changed
- klass = Class.new(Topic) do
- def self.name; "Topic"; end
- end
- topic = klass.create(title: "Another New Topic")
- assert_queries(0) do
+ topic = Topic.create(title: "Another New Topic")
+ assert_no_queries do
assert topic.update_attribute(:title, "Another New Topic")
end
end
def test_update_does_not_run_sql_if_record_has_not_changed
topic = Topic.create(title: "Another New Topic")
- assert_queries(0) { assert topic.update(title: "Another New Topic") }
- assert_queries(0) { assert topic.update(title: "Another New Topic") }
+ assert_no_queries do
+ assert topic.update(title: "Another New Topic")
+ end
end
def test_delete
diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb
index 3eb4e04cb7..565190c476 100644
--- a/activerecord/test/cases/query_cache_test.rb
+++ b/activerecord/test/cases/query_cache_test.rb
@@ -190,7 +190,7 @@ class QueryCacheTest < ActiveRecord::TestCase
Task.cache do
assert_queries(2) { Task.find(1); Task.find(2) }
end
- assert_queries(0) { Task.find(1); Task.find(1); Task.find(2) }
+ assert_no_queries { Task.find(1); Task.find(1); Task.find(2) }
end
end
@@ -372,7 +372,7 @@ class QueryCacheTest < ActiveRecord::TestCase
end
# Check that if the same query is run again, no queries are executed
- assert_queries(0) do
+ assert_no_queries do
assert_equal 0, Post.where(title: "test").to_a.count
end
@@ -427,8 +427,9 @@ class QueryCacheTest < ActiveRecord::TestCase
# Clear places where type information is cached
Task.reset_column_information
Task.initialize_find_by_cache
+ Task.define_attribute_methods
- assert_queries(0) do
+ assert_no_queries do
Task.find(1)
end
end
diff --git a/activerecord/test/cases/tasks/database_tasks_test.rb b/activerecord/test/cases/tasks/database_tasks_test.rb
index d674bd562f..3fd1813d64 100644
--- a/activerecord/test/cases/tasks/database_tasks_test.rb
+++ b/activerecord/test/cases/tasks/database_tasks_test.rb
@@ -731,7 +731,7 @@ module ActiveRecord
end
if current_adapter?(:SQLite3Adapter) && !in_memory_db?
- class DatabaseTasksMigrateTest < ActiveRecord::TestCase
+ class DatabaseTasksMigrationTestCase < ActiveRecord::TestCase
self.use_transactional_tests = false
# Use a memory db here to avoid having to rollback at the end
@@ -751,7 +751,9 @@ module ActiveRecord
@conn.release_connection if @conn
ActiveRecord::Base.establish_connection :arunit
end
+ end
+ class DatabaseTasksMigrateTest < DatabaseTasksMigrationTestCase
def test_migrate_set_and_unset_verbose_and_version_env_vars
verbose, version = ENV["VERBOSE"], ENV["VERSION"]
ENV["VERSION"] = "2"
@@ -812,6 +814,26 @@ module ActiveRecord
end
end
end
+
+ class DatabaseTasksMigrateStatusTest < DatabaseTasksMigrationTestCase
+ def test_migrate_status_table
+ ActiveRecord::SchemaMigration.create_table
+ output = capture_migration_status
+ assert_match(/database: :memory:/, output)
+ assert_match(/down 001 Valid people have last names/, output)
+ assert_match(/down 002 We need reminders/, output)
+ assert_match(/down 003 Innocent jointable/, output)
+ ActiveRecord::SchemaMigration.drop_table
+ end
+
+ private
+
+ def capture_migration_status
+ capture(:stdout) do
+ ActiveRecord::Tasks::DatabaseTasks.migrate_status
+ end
+ end
+ end
end
class DatabaseTasksMigrateErrorTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/touch_later_test.rb b/activerecord/test/cases/touch_later_test.rb
index 925a4609a2..cd3d5ed7d1 100644
--- a/activerecord/test/cases/touch_later_test.rb
+++ b/activerecord/test/cases/touch_later_test.rb
@@ -100,7 +100,7 @@ class TouchLaterTest < ActiveRecord::TestCase
def test_touch_later_dont_hit_the_db
invoice = Invoice.create!
- assert_queries(0) do
+ assert_no_queries do
invoice.touch_later
end
end