aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib')
-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/builder/collection_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb2
-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/attribute_methods.rb4
-rw-r--r--activerecord/lib/active_record/attribute_methods/read.rb4
-rw-r--r--activerecord/lib/active_record/attribute_methods/write.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb17
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/quoting.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb26
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/quoting.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb12
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb10
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb2
-rw-r--r--activerecord/lib/active_record/fixture_set/model_metadata.rb40
-rw-r--r--activerecord/lib/active_record/fixture_set/render_context.rb17
-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.rb587
-rw-r--r--activerecord/lib/active_record/inheritance.rb2
-rw-r--r--activerecord/lib/active_record/integration.rb2
-rw-r--r--activerecord/lib/active_record/migration.rb4
-rw-r--r--activerecord/lib/active_record/model_schema.rb10
-rw-r--r--activerecord/lib/active_record/railtie.rb16
-rw-r--r--activerecord/lib/active_record/relation.rb36
-rw-r--r--activerecord/lib/active_record/relation/delegation.rb2
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb6
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb4
-rw-r--r--activerecord/lib/active_record/tasks/postgresql_database_tasks.rb4
-rw-r--r--activerecord/lib/active_record/test_fixtures.rb201
-rw-r--r--activerecord/lib/arel/nodes/delete_statement.rb6
-rw-r--r--activerecord/lib/arel/nodes/update_statement.rb6
-rw-r--r--activerecord/lib/arel/tree_manager.rb5
-rw-r--r--activerecord/lib/arel/visitors/mysql.rb52
-rw-r--r--activerecord/lib/arel/visitors/to_sql.rb68
42 files changed, 823 insertions, 692 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/builder/collection_association.rb b/activerecord/lib/active_record/associations/builder/collection_association.rb
index 35a72c3850..ff57c40121 100644
--- a/activerecord/lib/active_record/associations/builder/collection_association.rb
+++ b/activerecord/lib/active_record/associations/builder/collection_association.rb
@@ -24,7 +24,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
if block_given?
extension_module_name = "#{model.name.demodulize}#{name.to_s.camelize}AssociationExtension"
extension = Module.new(&Proc.new)
- model.parent.const_set(extension_module_name, extension)
+ model.module_parent.const_set(extension_module_name, extension)
end
end
diff --git a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
index e3070e0472..0140aa15c8 100644
--- a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
+++ b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
@@ -63,7 +63,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
def middle_reflection(join_model)
middle_name = [lhs_model.name.downcase.pluralize,
- association_name].join("_".freeze).gsub("::".freeze, "_".freeze).to_sym
+ association_name].join("_").gsub("::", "_").to_sym
middle_options = middle_options join_model
HasMany.create_reflection(lhs_model,
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/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index 42eba27b2a..221ebea8ea 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -272,9 +272,9 @@ module ActiveRecord
case name
when :to_partial_path
- name = "to_partial_path".freeze
+ name = "to_partial_path"
when :to_model
- name = "to_model".freeze
+ name = "to_model"
else
name = name.to_s
end
diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb
index 0f7bcba564..903fe86e04 100644
--- a/activerecord/lib/active_record/attribute_methods/read.rb
+++ b/activerecord/lib/active_record/attribute_methods/read.rb
@@ -27,7 +27,7 @@ module ActiveRecord
# Making it frozen means that it doesn't get duped when used to
# key the @attributes in read_attribute.
def define_method_attribute(name)
- safe_name = name.unpack1("h*".freeze)
+ safe_name = name.unpack1("h*")
temp_method = "__temp__#{safe_name}"
ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name
@@ -59,7 +59,7 @@ module ActiveRecord
end
primary_key = self.class.primary_key
- name = primary_key if name == "id".freeze && primary_key
+ name = primary_key if name == "id" && primary_key
sync_with_transaction_state if name == primary_key
_read_attribute(name, &block)
end
diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb
index c7521422bb..62743bc9d8 100644
--- a/activerecord/lib/active_record/attribute_methods/write.rb
+++ b/activerecord/lib/active_record/attribute_methods/write.rb
@@ -13,7 +13,7 @@ module ActiveRecord
private
def define_method_attribute=(name)
- safe_name = name.unpack1("h*".freeze)
+ safe_name = name.unpack1("h*")
ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name
sync_with_transaction_state = "sync_with_transaction_state" if name == primary_key
@@ -40,7 +40,7 @@ module ActiveRecord
end
primary_key = self.class.primary_key
- name = primary_key if name == "id".freeze && primary_key
+ name = primary_key if name == "id" && primary_key
sync_with_transaction_state if name == primary_key
_write_attribute(name, value)
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
index feacdf6931..0059f0b773 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -410,16 +410,6 @@ module ActiveRecord
end
end
- # The default strategy for an UPDATE with joins is to use a subquery. This doesn't work
- # on MySQL (even when aliasing the tables), but MySQL allows using JOIN directly in
- # an UPDATE statement, so in the MySQL adapters we redefine this to do that.
- def join_to_update(update, select, key) # :nodoc:
- subselect = subquery_for(key, select)
-
- update.where key.in(subselect)
- end
- alias join_to_delete join_to_update
-
private
def default_insert_value(column)
Arel.sql("DEFAULT")
@@ -460,13 +450,6 @@ module ActiveRecord
total_sql.join(";\n")
end
- # Returns a subquery for the given key using the join information.
- def subquery_for(key, select)
- subselect = select.clone
- subselect.projections = [key]
- subselect
- end
-
# Returns an ActiveRecord::Result instance.
def select(sql, name = nil, binds = [])
exec_query(sql, name, binds, prepare: false)
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
index 98b1348135..07e86afe9a 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
@@ -60,7 +60,7 @@ module ActiveRecord
# Quotes a string, escaping any ' (single quote) and \ (backslash)
# characters.
def quote_string(s)
- s.gsub('\\'.freeze, '\&\&'.freeze).gsub("'".freeze, "''".freeze) # ' (for ruby-mode)
+ s.gsub('\\', '\&\&').gsub("'", "''") # ' (for ruby-mode)
end
# Quotes the column name. Defaults to no quoting.
@@ -95,7 +95,7 @@ module ActiveRecord
end
def quoted_true
- "TRUE".freeze
+ "TRUE"
end
def unquoted_true
@@ -103,7 +103,7 @@ module ActiveRecord
end
def quoted_false
- "FALSE".freeze
+ "FALSE"
end
def unquoted_false
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
index 582ac516c7..015204c056 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -527,7 +527,9 @@ module ActiveRecord
#
# See TableDefinition#column for details of the options you can use.
def column(column_name, type, options = {})
+ index_options = options.delete(:index)
@base.add_column(name, column_name, type, options)
+ index(column_name, index_options.is_a?(Hash) ? index_options : {}) if index_options
end
# Checks to see if a column exists.
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index 79aafc956f..fa10f18cb7 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -65,7 +65,7 @@ module ActiveRecord
# Most of the methods in the adapter are useful during migrations. Most
# notably, the instance methods provided by SchemaStatements are very useful.
class AbstractAdapter
- ADAPTER_NAME = "Abstract".freeze
+ ADAPTER_NAME = "Abstract"
include ActiveSupport::Callbacks
define_callbacks :checkout, :checkin
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
index 09242a0f14..d40f38fb77 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -223,18 +223,6 @@ module ActiveRecord
execute "ROLLBACK"
end
- # In the simple case, MySQL allows us to place JOINs directly into the UPDATE
- # query. However, this does not allow for LIMIT, OFFSET and ORDER. To support
- # these, we must use a subquery.
- def join_to_update(update, select, key) # :nodoc:
- if select.limit || select.offset || select.orders.any?
- super
- else
- update.table select.source
- update.wheres = select.constraints
- end
- end
-
def empty_insert_statement_value(primary_key = nil)
"VALUES ()"
end
@@ -733,20 +721,6 @@ module ActiveRecord
[remove_column_for_alter(table_name, :updated_at), remove_column_for_alter(table_name, :created_at)]
end
- # MySQL is too stupid to create a temporary table for use subquery, so we have
- # to give it some prompting in the form of a subsubquery. Ugh!
- def subquery_for(key, select)
- subselect = select.clone
- subselect.projections = [key]
-
- # Materialize subquery by adding distinct
- # to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on'
- subselect.distinct unless select.limit || select.offset || select.orders.any?
-
- key_name = quote_column_name(key.name)
- Arel::SelectManager.new(subselect.as("__active_record_temp")).project(Arel.sql(key_name))
- end
-
def supports_rename_index?
mariadb? ? false : version >= "5.7.6"
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb b/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb
index be038403b8..75564a61d6 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb
@@ -5,7 +5,7 @@ module ActiveRecord
module MySQL
module Quoting # :nodoc:
def quote_column_name(name)
- @quoted_column_names[name] ||= "`#{super.gsub('`', '``')}`".freeze
+ @quoted_column_names[name] ||= "`#{super.gsub('`', '``')}`"
end
def quote_table_name(name)
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index 10c8c8e8ab..9bdaa00336 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -14,7 +14,7 @@ module ActiveRecord
config[:flags] ||= 0
if config[:flags].kind_of? Array
- config[:flags].push "FOUND_ROWS".freeze
+ config[:flags].push "FOUND_ROWS"
else
config[:flags] |= Mysql2::Client::FOUND_ROWS
end
@@ -32,7 +32,7 @@ module ActiveRecord
module ConnectionAdapters
class Mysql2Adapter < AbstractMysqlAdapter
- ADAPTER_NAME = "Mysql2".freeze
+ ADAPTER_NAME = "Mysql2"
include MySQL::DatabaseStatements
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb
index 79351bc3a4..83c21ba6ea 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb
@@ -19,10 +19,10 @@ module ActiveRecord
def run(records)
nodes = records.reject { |row| @store.key? row["oid"].to_i }
mapped = nodes.extract! { |row| @store.key? row["typname"] }
- ranges = nodes.extract! { |row| row["typtype"] == "r".freeze }
- enums = nodes.extract! { |row| row["typtype"] == "e".freeze }
- domains = nodes.extract! { |row| row["typtype"] == "d".freeze }
- arrays = nodes.extract! { |row| row["typinput"] == "array_in".freeze }
+ ranges = nodes.extract! { |row| row["typtype"] == "r" }
+ enums = nodes.extract! { |row| row["typtype"] == "e" }
+ domains = nodes.extract! { |row| row["typtype"] == "d" }
+ arrays = nodes.extract! { |row| row["typinput"] == "array_in" }
composites = nodes.extract! { |row| row["typelem"].to_i != 0 }
mapped.each { |row| register_mapped_type(row) }
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb b/activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb
index ffd3be26b0..cd69d28139 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb
@@ -17,7 +17,7 @@ module ActiveRecord
end
def sql_type
- super.gsub(/\[\]$/, "".freeze)
+ super.gsub(/\[\]$/, "")
end
def ==(other)
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 11593f71c9..bc6eb11572 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -78,7 +78,7 @@ module ActiveRecord
# In addition, default connection parameters of libpq can be set per environment variables.
# See https://www.postgresql.org/docs/current/static/libpq-envars.html .
class PostgreSQLAdapter < AbstractAdapter
- ADAPTER_NAME = "PostgreSQL".freeze
+ ADAPTER_NAME = "PostgreSQL"
NATIVE_DATABASE_TYPES = {
primary_key: "bigserial primary key",
@@ -448,7 +448,7 @@ module ActiveRecord
end
end
- def get_oid_type(oid, fmod, column_name, sql_type = "".freeze)
+ def get_oid_type(oid, fmod, column_name, sql_type = "")
if !type_map.key?(oid)
load_additional_types([oid])
end
@@ -537,13 +537,13 @@ module ActiveRecord
# Quoted types
when /\A[\(B]?'(.*)'.*::"?([\w. ]+)"?(?:\[\])?\z/m
# The default 'now'::date is CURRENT_DATE
- if $1 == "now".freeze && $2 == "date".freeze
+ if $1 == "now" && $2 == "date"
nil
else
- $1.gsub("''".freeze, "'".freeze)
+ $1.gsub("''", "'")
end
# Boolean types
- when "true".freeze, "false".freeze
+ when "true", "false"
default
# Numeric types
when /\A\(?(-?\d+(\.\d*)?)\)?(::bigint)?\z/
@@ -655,7 +655,7 @@ module ActiveRecord
#
# Check here for more details:
# https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573
- CACHED_PLAN_HEURISTIC = "cached plan must not change result type".freeze
+ CACHED_PLAN_HEURISTIC = "cached plan must not change result type"
def is_cached_plan_failure?(e)
pgerror = e.cause
code = pgerror.result.result_error_field(PG::PG_DIAG_SQLSTATE)
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb
index abedf01f10..b2dcdb5373 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb
@@ -13,7 +13,7 @@ module ActiveRecord
end
def quote_column_name(name)
- @quoted_column_names[name] ||= %Q("#{super.gsub('"', '""')}").freeze
+ @quoted_column_names[name] ||= %Q("#{super.gsub('"', '""')}")
end
def quoted_time(value)
@@ -26,19 +26,19 @@ module ActiveRecord
end
def quoted_true
- ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer ? "1".freeze : "'t'".freeze
+ ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer ? "1" : "'t'"
end
def unquoted_true
- ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer ? 1 : "t".freeze
+ ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer ? 1 : "t"
end
def quoted_false
- ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer ? "0".freeze : "'f'".freeze
+ ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer ? "0" : "'f'"
end
def unquoted_false
- ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer ? 0 : "f".freeze
+ ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer ? 0 : "f"
end
private
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index baa0a29afd..81882f6cc1 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -56,7 +56,7 @@ module ActiveRecord
#
# * <tt>:database</tt> - Path to the database file.
class SQLite3Adapter < AbstractAdapter
- ADAPTER_NAME = "SQLite".freeze
+ ADAPTER_NAME = "SQLite"
include SQLite3::Quoting
include SQLite3::SchemaStatements
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/render_context.rb b/activerecord/lib/active_record/fixture_set/render_context.rb
new file mode 100644
index 0000000000..c90b5343dc
--- /dev/null
+++ b/activerecord/lib/active_record/fixture_set/render_context.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+# NOTE: This class has to be defined in compact style in
+# order for rendering context subclassing to work correctly.
+class ActiveRecord::FixtureSet::RenderContext # :nodoc:
+ def self.create_subclass
+ Class.new(ActiveRecord::FixtureSet.context_class) do
+ def get_binding
+ binding()
+ end
+
+ def binary(path)
+ %(!!binary "#{Base64.strict_encode64(File.read(path))}")
+ 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 0d1fdcfb28..b090c76a38 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -7,6 +7,9 @@ require "set"
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"
module ActiveRecord
@@ -440,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
@@ -502,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
@@ -528,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
- # FIXME: Apparently JK uses this.
- connection = block_given? ? yield : ActiveRecord::Base.connection
+ 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
- files_to_read = fixture_set_names.reject { |fs_name|
- fixture_is_cached?(connection, fs_name)
- }
+ def reset_cache
+ @@all_cached_fixtures.clear
+ end
- unless files_to_read.empty?
- fixtures_map = {}
+ def cache_for_connection(connection)
+ @@all_cached_fixtures[connection]
+ end
+
+ def fixture_is_cached?(connection, table_name)
+ cache_for_connection(connection)[table_name]
+ end
+
+ 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
- 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
+ # 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
+
+ # 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
@@ -570,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)
@@ -632,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?
@@ -841,226 +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."
- end
- end
- end
-end
-
-module ActiveRecord
- module TestFixtures
- extend ActiveSupport::Concern
-
- def before_setup # :nodoc:
- setup_fixtures
- super
- end
-
- def after_teardown # :nodoc:
- super
- teardown_fixtures
- end
-
- included do
- class_attribute :fixture_path, instance_writer: false
- 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 :config, default: ActiveRecord::Base
- class_attribute :lock_threads, default: true
- end
-
- module ClassMethods
- # Sets the model class for a fixture when the class name cannot be inferred from the fixture name.
- #
- # Examples:
- #
- # set_fixture_class some_fixture: SomeModel,
- # 'namespaced/fixture' => Another::Model
- #
- # The keys must be the fixture names, that coincide with the short paths to the fixture files.
- def set_fixture_class(class_names = {})
- self.fixture_class_names = fixture_class_names.merge(class_names.stringify_keys)
- end
-
- def fixtures(*fixture_set_names)
- if fixture_set_names.first == :all
- raise StandardError, "No fixture path found. Please set `#{self}.fixture_path`." if fixture_path.blank?
- fixture_set_names = Dir["#{fixture_path}/{**,*}/*.{yml}"].uniq
- fixture_set_names.map! { |f| f[(fixture_path.to_s.size + 1)..-5] }
- else
- fixture_set_names = fixture_set_names.flatten.map(&:to_s)
- end
-
- self.fixture_table_names |= fixture_set_names
- setup_fixture_accessors(fixture_set_names)
- end
-
- def setup_fixture_accessors(fixture_set_names = nil)
- fixture_set_names = Array(fixture_set_names || fixture_table_names)
- methods = Module.new do
- fixture_set_names.each do |fs_name|
- fs_name = fs_name.to_s
- accessor_name = fs_name.tr("/", "_").to_sym
-
- define_method(accessor_name) do |*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
- private accessor_name
- end
- end
- include methods
- end
-
- def uses_transaction(*methods)
- @uses_transaction = [] unless defined?(@uses_transaction)
- @uses_transaction.concat methods.map(&:to_s)
- end
-
- def uses_transaction?(method)
- @uses_transaction = [] unless defined?(@uses_transaction)
- @uses_transaction.include?(method.to_s)
- end
- end
-
- def run_in_transaction?
- use_transactional_tests &&
- !self.class.uses_transaction?(method_name)
- end
-
- 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
-
- # 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
- 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|
- spec_name = payload[:spec_name] if payload.key?(:spec_name)
-
- if spec_name
- begin
- connection = ActiveRecord::Base.connection_handler.retrieve_connection(spec_name)
- rescue ConnectionNotEstablished
- connection = nil
- end
-
- if connection && !@fixture_connections.include?(connection)
- connection.begin_transaction joinable: false
- connection.pool.lock_thread = true if lock_threads
- @fixture_connections << connection
- 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
-
- 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
- else
- ActiveRecord::FixtureSet.reset_cache
- end
-
- ActiveRecord::Base.clear_active_connections!
- end
-
- def enlist_fixture_connections
- ActiveRecord::Base.connection_handler.connection_pool_list.map(&:connection)
- end
-
- private
- def load_fixtures(config)
- fixtures = ActiveRecord::FixtureSet.create_fixtures(fixture_path, fixture_table_names, fixture_class_names, config)
- Hash[fixtures.map { |f| [f.name, f] }]
- end
-
- 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
-
- def load_instances?
- use_instantiated_fixtures != :no_instances
- end
- end
-end
-
-class ActiveRecord::FixtureSet::RenderContext # :nodoc:
- def self.create_subclass
- Class.new ActiveRecord::FixtureSet.context_class do
- def get_binding
- binding()
- end
-
- def binary(path)
- %(!!binary "#{Base64.strict_encode64(File.read(path))}")
+ 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/inheritance.rb b/activerecord/lib/active_record/inheritance.rb
index b25057acda..138fd1cf53 100644
--- a/activerecord/lib/active_record/inheritance.rb
+++ b/activerecord/lib/active_record/inheritance.rb
@@ -180,7 +180,7 @@ module ActiveRecord
# Returns the class type of the record using the current module as a prefix. So descendants of
# MyApp::Business::Account would appear as MyApp::Business::AccountSubclass.
def compute_type(type_name)
- if type_name.start_with?("::".freeze)
+ if type_name.start_with?("::")
# If the type is prefixed with a scope operator then we assume that
# the type_name is an absolute reference.
ActiveSupport::Dependencies.constantize(type_name)
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/migration.rb b/activerecord/lib/active_record/migration.rb
index d712d4b3cf..6e5a610642 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -140,8 +140,8 @@ module ActiveRecord
end
class ConcurrentMigrationError < MigrationError #:nodoc:
- DEFAULT_MESSAGE = "Cannot run migrations because another migration process is currently running.".freeze
- RELEASE_LOCK_FAILED_MESSAGE = "Failed to release advisory lock".freeze
+ DEFAULT_MESSAGE = "Cannot run migrations because another migration process is currently running."
+ RELEASE_LOCK_FAILED_MESSAGE = "Failed to release advisory lock"
def initialize(message = DEFAULT_MESSAGE)
super
diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb
index 9b985e049b..5a4a1fb969 100644
--- a/activerecord/lib/active_record/model_schema.rb
+++ b/activerecord/lib/active_record/model_schema.rb
@@ -218,11 +218,11 @@ module ActiveRecord
end
def full_table_name_prefix #:nodoc:
- (parents.detect { |p| p.respond_to?(:table_name_prefix) } || self).table_name_prefix
+ (module_parents.detect { |p| p.respond_to?(:table_name_prefix) } || self).table_name_prefix
end
def full_table_name_suffix #:nodoc:
- (parents.detect { |p| p.respond_to?(:table_name_suffix) } || self).table_name_suffix
+ (module_parents.detect { |p| p.respond_to?(:table_name_suffix) } || self).table_name_suffix
end
# The array of names of environments where destructive actions should be prohibited. By default,
@@ -503,9 +503,9 @@ module ActiveRecord
def compute_table_name
if base_class?
# Nested classes are prefixed with singular parent table name.
- if parent < Base && !parent.abstract_class?
- contained = parent.table_name
- contained = contained.singularize if parent.pluralize_table_names
+ if module_parent < Base && !module_parent.abstract_class?
+ contained = module_parent.table_name
+ contained = contained.singularize if module_parent.pluralize_table_names
contained += "_"
end
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index 81ad9ef3a2..538659d6bd 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -168,21 +168,7 @@ end_error
initializer "active_record.initialize_database" do
ActiveSupport.on_load(:active_record) do
self.configurations = Rails.application.config.database_configuration
-
- begin
- establish_connection
- rescue ActiveRecord::NoDatabaseError
- warn <<-end_warning
-Oops - You have a database configured, but it doesn't exist yet!
-
-Here's how to get started:
-
- 1. Configure your database in config/database.yml.
- 2. Run `rails db:create` to create the database.
- 3. Run `rails db:setup` to load your database schema.
-end_warning
- raise
- end
+ establish_connection
end
end
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 5bad5bfcc2..d5b6082d13 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -348,7 +348,12 @@ module ActiveRecord
end
stmt = Arel::UpdateManager.new
- stmt.table(table)
+ stmt.table(arel.join_sources.empty? ? table : arel.source)
+ stmt.key = arel_attribute(primary_key)
+ stmt.take(arel.limit)
+ stmt.offset(arel.offset)
+ stmt.order(*arel.orders)
+ stmt.wheres = arel.constraints
if updates.is_a?(Hash)
stmt.set _substitute_values(updates)
@@ -356,15 +361,6 @@ module ActiveRecord
stmt.set Arel.sql(klass.sanitize_sql_for_assignment(updates, table.name))
end
- if has_join_values? || offset_value
- @klass.connection.join_to_update(stmt, arel, arel_attribute(primary_key))
- else
- stmt.key = arel_attribute(primary_key)
- stmt.take(arel.limit)
- stmt.order(*arel.orders)
- stmt.wheres = arel.constraints
- end
-
@klass.connection.update stmt, "#{@klass} Update All"
end
@@ -482,16 +478,12 @@ module ActiveRecord
end
stmt = Arel::DeleteManager.new
- stmt.from(table)
-
- if has_join_values? || offset_value
- @klass.connection.join_to_delete(stmt, arel, arel_attribute(primary_key))
- else
- stmt.key = arel_attribute(primary_key)
- stmt.take(arel.limit)
- stmt.order(*arel.orders)
- stmt.wheres = arel.constraints
- end
+ stmt.from(arel.join_sources.empty? ? table : arel.source)
+ stmt.key = arel_attribute(primary_key)
+ stmt.take(arel.limit)
+ stmt.offset(arel.offset)
+ stmt.order(*arel.orders)
+ stmt.wheres = arel.constraints
affected = @klass.connection.delete(stmt, "#{@klass} Destroy")
@@ -646,10 +638,6 @@ module ActiveRecord
end
end
- def has_join_values?
- joins_values.any? || left_outer_joins_values.any?
- end
-
def exec_queries(&block)
skip_query_cache_if_necessary do
@records =
diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb
index 488f71cdde..8f657840f5 100644
--- a/activerecord/lib/active_record/relation/delegation.rb
+++ b/activerecord/lib/active_record/relation/delegation.rb
@@ -17,7 +17,7 @@ module ActiveRecord
delegate = Class.new(klass) {
include ClassSpecificRelation
}
- mangled_name = klass.name.gsub("::".freeze, "_".freeze)
+ mangled_name = klass.name.gsub("::", "_")
const_set mangled_name, delegate
private_constant mangled_name
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index f734cd0ad8..b59ff912fe 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -27,7 +27,7 @@ module ActiveRecord
key
else
key = key.to_s
- key.split(".".freeze).first if key.include?(".".freeze)
+ key.split(".").first if key.include?(".")
end
end.compact
end
@@ -115,11 +115,11 @@ module ActiveRecord
def convert_dot_notation_to_hash(attributes)
dot_notation = attributes.select do |k, v|
- k.include?(".".freeze) && !v.is_a?(Hash)
+ k.include?(".") && !v.is_a?(Hash)
end
dot_notation.each_key do |key|
- table_name, column_name = key.split(".".freeze)
+ table_name, column_name = key.split(".")
value = attributes.delete(key)
attributes[table_name] ||= {}
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 56497e11cb..5170d38fa8 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -939,7 +939,7 @@ module ActiveRecord
arel.having(having_clause.ast) unless having_clause.empty?
if limit_value
limit_attribute = ActiveModel::Attribute.with_cast_value(
- "LIMIT".freeze,
+ "LIMIT",
connection.sanitize_limit(limit_value),
Type.default_value,
)
@@ -947,7 +947,7 @@ module ActiveRecord
end
if offset_value
offset_attribute = ActiveModel::Attribute.with_cast_value(
- "OFFSET".freeze,
+ "OFFSET",
offset_value.to_i,
Type.default_value,
)
diff --git a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
index d3c85b161e..8acb11f75f 100644
--- a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
@@ -6,8 +6,8 @@ module ActiveRecord
module Tasks # :nodoc:
class PostgreSQLDatabaseTasks # :nodoc:
DEFAULT_ENCODING = ENV["CHARSET"] || "utf8"
- ON_ERROR_STOP_1 = "ON_ERROR_STOP=1".freeze
- SQL_COMMENT_BEGIN = "--".freeze
+ ON_ERROR_STOP_1 = "ON_ERROR_STOP=1"
+ SQL_COMMENT_BEGIN = "--"
delegate :connection, :establish_connection, :clear_active_connections!,
to: ActiveRecord::Base
diff --git a/activerecord/lib/active_record/test_fixtures.rb b/activerecord/lib/active_record/test_fixtures.rb
new file mode 100644
index 0000000000..7b7b3f7112
--- /dev/null
+++ b/activerecord/lib/active_record/test_fixtures.rb
@@ -0,0 +1,201 @@
+# frozen_string_literal: true
+
+module ActiveRecord
+ module TestFixtures
+ extend ActiveSupport::Concern
+
+ def before_setup # :nodoc:
+ setup_fixtures
+ super
+ end
+
+ def after_teardown # :nodoc:
+ super
+ teardown_fixtures
+ end
+
+ included do
+ class_attribute :fixture_path, instance_writer: false
+ 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 :config, default: ActiveRecord::Base
+ class_attribute :lock_threads, default: true
+ end
+
+ module ClassMethods
+ # Sets the model class for a fixture when the class name cannot be inferred from the fixture name.
+ #
+ # Examples:
+ #
+ # set_fixture_class some_fixture: SomeModel,
+ # 'namespaced/fixture' => Another::Model
+ #
+ # The keys must be the fixture names, that coincide with the short paths to the fixture files.
+ def set_fixture_class(class_names = {})
+ self.fixture_class_names = fixture_class_names.merge(class_names.stringify_keys)
+ end
+
+ def fixtures(*fixture_set_names)
+ if fixture_set_names.first == :all
+ raise StandardError, "No fixture path found. Please set `#{self}.fixture_path`." if fixture_path.blank?
+ fixture_set_names = Dir["#{fixture_path}/{**,*}/*.{yml}"].uniq
+ fixture_set_names.map! { |f| f[(fixture_path.to_s.size + 1)..-5] }
+ else
+ fixture_set_names = fixture_set_names.flatten.map(&:to_s)
+ end
+
+ self.fixture_table_names |= fixture_set_names
+ setup_fixture_accessors(fixture_set_names)
+ end
+
+ def setup_fixture_accessors(fixture_set_names = nil)
+ fixture_set_names = Array(fixture_set_names || fixture_table_names)
+ methods = Module.new do
+ fixture_set_names.each do |fs_name|
+ fs_name = fs_name.to_s
+ accessor_name = fs_name.tr("/", "_").to_sym
+
+ define_method(accessor_name) do |*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
+ private accessor_name
+ end
+ end
+ include methods
+ end
+
+ def uses_transaction(*methods)
+ @uses_transaction = [] unless defined?(@uses_transaction)
+ @uses_transaction.concat methods.map(&:to_s)
+ end
+
+ def uses_transaction?(method)
+ @uses_transaction = [] unless defined?(@uses_transaction)
+ @uses_transaction.include?(method.to_s)
+ end
+ end
+
+ def run_in_transaction?
+ use_transactional_tests &&
+ !self.class.uses_transaction?(method_name)
+ end
+
+ 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
+
+ # 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
+ 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|
+ spec_name = payload[:spec_name] if payload.key?(:spec_name)
+
+ if spec_name
+ begin
+ connection = ActiveRecord::Base.connection_handler.retrieve_connection(spec_name)
+ rescue ConnectionNotEstablished
+ connection = nil
+ end
+
+ if connection && !@fixture_connections.include?(connection)
+ connection.begin_transaction joinable: false
+ connection.pool.lock_thread = true if lock_threads
+ @fixture_connections << connection
+ 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
+
+ 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
+ else
+ ActiveRecord::FixtureSet.reset_cache
+ end
+
+ ActiveRecord::Base.clear_active_connections!
+ end
+
+ def enlist_fixture_connections
+ ActiveRecord::Base.connection_handler.connection_pool_list.map(&:connection)
+ end
+
+ private
+ def load_fixtures(config)
+ fixtures = ActiveRecord::FixtureSet.create_fixtures(fixture_path, fixture_table_names, fixture_class_names, config)
+ Hash[fixtures.map { |f| [f.name, f] }]
+ end
+
+ 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
+
+ def load_instances?
+ use_instantiated_fixtures != :no_instances
+ end
+ end
+end
diff --git a/activerecord/lib/arel/nodes/delete_statement.rb b/activerecord/lib/arel/nodes/delete_statement.rb
index 5be42a084a..a419975335 100644
--- a/activerecord/lib/arel/nodes/delete_statement.rb
+++ b/activerecord/lib/arel/nodes/delete_statement.rb
@@ -3,7 +3,7 @@
module Arel # :nodoc: all
module Nodes
class DeleteStatement < Arel::Nodes::Node
- attr_accessor :left, :right, :orders, :limit, :key
+ attr_accessor :left, :right, :orders, :limit, :offset, :key
alias :relation :left
alias :relation= :left=
@@ -16,6 +16,7 @@ module Arel # :nodoc: all
@right = wheres
@orders = []
@limit = nil
+ @offset = nil
@key = nil
end
@@ -26,7 +27,7 @@ module Arel # :nodoc: all
end
def hash
- [self.class, @left, @right, @orders, @limit, @key].hash
+ [self.class, @left, @right, @orders, @limit, @offset, @key].hash
end
def eql?(other)
@@ -35,6 +36,7 @@ module Arel # :nodoc: all
self.right == other.right &&
self.orders == other.orders &&
self.limit == other.limit &&
+ self.offset == other.offset &&
self.key == other.key
end
alias :== :eql?
diff --git a/activerecord/lib/arel/nodes/update_statement.rb b/activerecord/lib/arel/nodes/update_statement.rb
index 017a553c4c..cfaa19e392 100644
--- a/activerecord/lib/arel/nodes/update_statement.rb
+++ b/activerecord/lib/arel/nodes/update_statement.rb
@@ -3,7 +3,7 @@
module Arel # :nodoc: all
module Nodes
class UpdateStatement < Arel::Nodes::Node
- attr_accessor :relation, :wheres, :values, :orders, :limit, :key
+ attr_accessor :relation, :wheres, :values, :orders, :limit, :offset, :key
def initialize
@relation = nil
@@ -11,6 +11,7 @@ module Arel # :nodoc: all
@values = []
@orders = []
@limit = nil
+ @offset = nil
@key = nil
end
@@ -21,7 +22,7 @@ module Arel # :nodoc: all
end
def hash
- [@relation, @wheres, @values, @orders, @limit, @key].hash
+ [@relation, @wheres, @values, @orders, @limit, @offset, @key].hash
end
def eql?(other)
@@ -31,6 +32,7 @@ module Arel # :nodoc: all
self.values == other.values &&
self.orders == other.orders &&
self.limit == other.limit &&
+ self.offset == other.offset &&
self.key == other.key
end
alias :== :eql?
diff --git a/activerecord/lib/arel/tree_manager.rb b/activerecord/lib/arel/tree_manager.rb
index 149c69ce7a..0476399618 100644
--- a/activerecord/lib/arel/tree_manager.rb
+++ b/activerecord/lib/arel/tree_manager.rb
@@ -10,6 +10,11 @@ module Arel # :nodoc: all
self
end
+ def offset(offset)
+ @ast.offset = Nodes::Offset.new(Nodes.build_quoted(offset)) if offset
+ self
+ end
+
def order(*expr)
@ast.orders = expr
self
diff --git a/activerecord/lib/arel/visitors/mysql.rb b/activerecord/lib/arel/visitors/mysql.rb
index 0f7d5aa803..9d9294fecc 100644
--- a/activerecord/lib/arel/visitors/mysql.rb
+++ b/activerecord/lib/arel/visitors/mysql.rb
@@ -56,18 +56,6 @@ module Arel # :nodoc: all
super
end
- def visit_Arel_Nodes_UpdateStatement(o, collector)
- collector << "UPDATE "
- collector = visit o.relation, collector
-
- unless o.values.empty?
- collector << " SET "
- collector = inject_join o.values, collector, ", "
- end
-
- collect_where_for(o, collector)
- end
-
def visit_Arel_Nodes_Concat(o, collector)
collector << " CONCAT("
visit o.left, collector
@@ -77,18 +65,42 @@ module Arel # :nodoc: all
collector
end
- def collect_where_for(o, collector)
- unless o.wheres.empty?
- collector << " WHERE "
- collector = inject_join o.wheres, collector, " AND "
+ # In the simple case, MySQL allows us to place JOINs directly into the UPDATE
+ # 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 o.offset || has_join_sources?(o) && has_limit_or_offset_or_orders?(o)
+ super
+ else
+ o
end
+ end
- unless o.orders.empty?
- collector << " ORDER BY "
- collector = inject_join o.orders, collector, ", "
+ def prepare_delete_statement(o)
+ if o.offset || has_join_sources?(o)
+ super
+ else
+ o
+ end
+ end
+
+ # MySQL is too stupid to create a temporary table for use subquery, so we have
+ # to give it some prompting in the form of a subsubquery.
+ def build_subselect(key, o)
+ subselect = super
+
+ # Materialize subquery by adding distinct
+ # to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on'
+ unless has_limit_or_offset_or_orders?(subselect)
+ core = subselect.cores.last
+ core.set_quantifier = Arel::Nodes::Distinct.new
end
- maybe_visit o.limit, collector
+ Nodes::SelectStatement.new.tap do |stmt|
+ core = stmt.cores.last
+ core.froms = Nodes::Grouping.new(subselect).as("__active_record_temp")
+ core.projections = [Arel.sql(quote_column_name(key.name))]
+ end
end
end
end
diff --git a/activerecord/lib/arel/visitors/to_sql.rb b/activerecord/lib/arel/visitors/to_sql.rb
index 575bfd6e36..7c0f6c2e97 100644
--- a/activerecord/lib/arel/visitors/to_sql.rb
+++ b/activerecord/lib/arel/visitors/to_sql.rb
@@ -74,25 +74,17 @@ module Arel # :nodoc: all
private
def visit_Arel_Nodes_DeleteStatement(o, collector)
+ o = prepare_delete_statement(o)
+
collector << "DELETE FROM "
collector = visit o.relation, collector
collect_where_for(o, collector)
end
- # FIXME: we should probably have a 2-pass visitor for this
- def build_subselect(key, o)
- stmt = Nodes::SelectStatement.new
- core = stmt.cores.first
- core.froms = o.relation
- core.wheres = o.wheres
- core.projections = [key]
- stmt.limit = o.limit
- stmt.orders = o.orders
- stmt
- end
-
def visit_Arel_Nodes_UpdateStatement(o, collector)
+ o = prepare_update_statement(o)
+
collector << "UPDATE "
collector = visit o.relation, collector
unless o.values.empty?
@@ -799,19 +791,57 @@ module Arel # :nodoc: all
}
end
- def collect_where_for(o, collector)
- if o.orders.empty? && o.limit.nil?
- wheres = o.wheres
+ def has_join_sources?(o)
+ o.relation.is_a?(Nodes::JoinSource) && !o.relation.right.empty?
+ end
+
+ def has_limit_or_offset_or_orders?(o)
+ o.limit || o.offset || !o.orders.empty?
+ end
+
+ # The default strategy for an UPDATE with joins is to use a subquery. This doesn't work
+ # on MySQL (even when aliasing the tables), but MySQL allows using JOIN directly in
+ # an UPDATE statement, so in the MySQL visitor we redefine this to do that.
+ def prepare_update_statement(o)
+ if o.key && (has_limit_or_offset_or_orders?(o) || has_join_sources?(o))
+ stmt = o.clone
+ stmt.limit = nil
+ stmt.offset = nil
+ stmt.orders = []
+ stmt.wheres = [Nodes::In.new(o.key, [build_subselect(o.key, o)])]
+ stmt.relation = o.relation.left if has_join_sources?(o)
+ stmt
else
- wheres = [Nodes::In.new(o.key, [build_subselect(o.key, o)])]
+ o
end
+ end
+ alias :prepare_delete_statement :prepare_update_statement
- unless wheres.empty?
+ # FIXME: we should probably have a 2-pass visitor for this
+ def build_subselect(key, o)
+ stmt = Nodes::SelectStatement.new
+ core = stmt.cores.first
+ core.froms = o.relation
+ core.wheres = o.wheres
+ core.projections = [key]
+ stmt.limit = o.limit
+ stmt.offset = o.offset
+ stmt.orders = o.orders
+ stmt
+ end
+
+ def collect_where_for(o, collector)
+ unless o.wheres.empty?
collector << " WHERE "
- collector = inject_join wheres, collector, " AND "
+ collector = inject_join o.wheres, collector, " AND "
end
- collector
+ unless o.orders.empty?
+ collector << " ORDER BY "
+ collector = inject_join o.orders, collector, ", "
+ end
+
+ maybe_visit o.limit, collector
end
def infix_value(o, collector, value)