aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/CHANGELOG.md10
-rw-r--r--activerecord/RUNNING_UNIT_TESTS.rdoc4
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb11
-rw-r--r--activerecord/lib/active_record/associations/preloader/association.rb12
-rw-r--r--activerecord/lib/active_record/associations/through_association.rb2
-rw-r--r--activerecord/lib/active_record/attribute_methods/dirty.rb11
-rw-r--r--activerecord/lib/active_record/attribute_methods/serialization.rb11
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb20
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb47
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb7
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb25
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb3
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb12
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb23
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb7
-rw-r--r--activerecord/lib/active_record/fixtures.rb4
-rw-r--r--activerecord/lib/active_record/reflection.rb54
-rw-r--r--activerecord/lib/active_record/relation.rb3
-rw-r--r--activerecord/lib/active_record/relation/batches.rb7
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb3
-rw-r--r--activerecord/lib/active_record/schema.rb2
-rw-r--r--activerecord/lib/active_record/schema_dumper.rb4
-rw-r--r--activerecord/lib/active_record/scoping/named.rb10
-rw-r--r--activerecord/lib/active_record/transactions.rb2
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb4
-rw-r--r--activerecord/lib/rails/generators/active_record/application_record/application_record_generator.rb11
-rw-r--r--activerecord/test/cases/adapter_test.rb36
-rw-r--r--activerecord/test/cases/adapters/postgresql/array_test.rb26
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb27
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb59
-rw-r--r--activerecord/test/cases/associations/join_model_test.rb6
-rw-r--r--activerecord/test/cases/attribute_set_test.rb2
-rw-r--r--activerecord/test/cases/enum_test.rb14
-rw-r--r--activerecord/test/cases/fixtures_test.rb3
-rw-r--r--activerecord/test/cases/inheritance_test.rb13
-rw-r--r--activerecord/test/cases/json_shared_test_cases.rb26
-rw-r--r--activerecord/test/cases/migration/column_attributes_test.rb20
-rw-r--r--activerecord/test/cases/persistence_test.rb36
-rw-r--r--activerecord/test/cases/relation/where_test.rb19
-rw-r--r--activerecord/test/cases/schema_dumper_test.rb25
-rw-r--r--activerecord/test/cases/timestamp_test.rb22
-rw-r--r--activerecord/test/cases/validations/uniqueness_validation_test.rb7
-rw-r--r--activerecord/test/fixtures/binaries.yml4
-rw-r--r--activerecord/test/models/post.rb7
48 files changed, 452 insertions, 223 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index c1c511a65c..b39d28ca31 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,13 @@
+* Ensure `sum` honors `distinct` on `has_many :through` associations
+
+ Fixes #16791.
+
+ *Aaron Wortham*
+
+* Add `binary` fixture helper method.
+
+ *Atsushi Yoshida*
+
* When using `Relation#or`, extract the common conditions and put them before the OR condition.
*Maxime Handfield Lapointe*
diff --git a/activerecord/RUNNING_UNIT_TESTS.rdoc b/activerecord/RUNNING_UNIT_TESTS.rdoc
index cd22f76d01..60561e2c0f 100644
--- a/activerecord/RUNNING_UNIT_TESTS.rdoc
+++ b/activerecord/RUNNING_UNIT_TESTS.rdoc
@@ -43,5 +43,9 @@ You can override the +connections:+ parameter in either file using the +ARCONN+
$ ARCONN=postgresql bundle exec ruby -Itest test/cases/base_test.rb
+Or
+
+ $ bundle exec rake test:postgresql TEST=test/cases/base_test.rb
+
You can specify a custom location for the config file using the +ARCONFIG+
environment variable.
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index 38e7ba39d3..ceedf150e3 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -55,13 +55,16 @@ module ActiveRecord
pk_type = reflection.association_primary_key_type
ids = Array(ids).reject(&:blank?)
ids.map! { |i| pk_type.cast(i) }
- records = klass.where(reflection.association_primary_key => ids).index_by do |r|
- r.send(reflection.association_primary_key)
+
+ primary_key = reflection.association_primary_key
+ records = klass.where(primary_key => ids).index_by do |r|
+ r.public_send(primary_key)
end.values_at(*ids).compact
+
if records.size != ids.size
- found_ids = records.map { |record| record.send(reflection.association_primary_key) }
+ found_ids = records.map { |record| record.public_send(primary_key) }
not_found_ids = ids - found_ids
- klass.all.raise_record_not_found_exception!(ids, records.size, ids.size, reflection.association_primary_key, not_found_ids)
+ klass.all.raise_record_not_found_exception!(ids, records.size, ids.size, primary_key, not_found_ids)
else
replace(records)
end
diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb
index 5ba03c555a..4915a37f06 100644
--- a/activerecord/lib/active_record/associations/preloader/association.rb
+++ b/activerecord/lib/active_record/associations/preloader/association.rb
@@ -116,18 +116,8 @@ module ActiveRecord
@reflection_scope ||= reflection.scope_for(klass)
end
- def klass_scope
- current_scope = klass.current_scope
-
- if current_scope && current_scope.empty_scope?
- klass.unscoped
- else
- klass.default_scoped
- end
- end
-
def build_scope
- scope = klass_scope
+ scope = klass.scope_for_association
if reflection.type
scope.where!(reflection.type => model.base_class.sti_name)
diff --git a/activerecord/lib/active_record/associations/through_association.rb b/activerecord/lib/active_record/associations/through_association.rb
index 76237c4a0c..cba565448f 100644
--- a/activerecord/lib/active_record/associations/through_association.rb
+++ b/activerecord/lib/active_record/associations/through_association.rb
@@ -15,7 +15,7 @@ module ActiveRecord
def target_scope
scope = super
reflection.chain.drop(1).each do |reflection|
- relation = reflection.klass.all
+ relation = reflection.klass.scope_for_association
scope.merge!(
relation.except(:select, :create_with, :includes, :preload, :joins, :eager_load)
)
diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb
index 48d33e6744..c622df132e 100644
--- a/activerecord/lib/active_record/attribute_methods/dirty.rb
+++ b/activerecord/lib/active_record/attribute_methods/dirty.rb
@@ -62,12 +62,6 @@ module ActiveRecord
clear_mutation_trackers
end
- def write_attribute_without_type_cast(attr_name, *) # :nodoc:
- result = super
- clear_attribute_change(attr_name)
- result
- end
-
def clear_attribute_changes(attr_names) # :nodoc:
super
attr_names.each do |attr_name|
@@ -183,6 +177,11 @@ module ActiveRecord
end
private
+ def write_attribute_without_type_cast(attr_name, _)
+ result = super
+ clear_attribute_change(attr_name)
+ result
+ end
def mutation_tracker
unless defined?(@mutation_tracker)
diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb
index acd47629dd..ebc2baed34 100644
--- a/activerecord/lib/active_record/attribute_methods/serialization.rb
+++ b/activerecord/lib/active_record/attribute_methods/serialization.rb
@@ -70,7 +70,7 @@ module ActiveRecord
end
decorate_attribute_type(attr_name, :serialize) do |type|
- if type_incompatible_with_serialize?(type)
+ if type_incompatible_with_serialize?(type, class_name_or_coder)
raise ColumnNotSerializableError.new(attr_name, type)
end
@@ -80,12 +80,9 @@ module ActiveRecord
private
- def type_incompatible_with_serialize?(type)
- type.is_a?(ActiveRecord::Type::Json) ||
- (
- defined?(ActiveRecord::ConnectionAdapters::PostgreSQL) &&
- type.is_a?(ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array)
- )
+ def type_incompatible_with_serialize?(type, class_name)
+ type.is_a?(ActiveRecord::Type::Json) && class_name == ::JSON ||
+ type.respond_to?(:type_cast_array, true) && class_name == ::Array
end
end
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 5febb5a59f..4078345abf 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -10,6 +10,11 @@ module ActiveRecord
# Converts an arel AST to SQL
def to_sql(arel_or_sql_string, binds = [])
+ sql, _ = to_sql_and_binds(arel_or_sql_string, binds)
+ sql
+ end
+
+ def to_sql_and_binds(arel_or_sql_string, binds = []) # :nodoc:
if arel_or_sql_string.respond_to?(:ast)
unless binds.empty?
raise "Passing bind parameters with an arel AST is forbidden. " \
@@ -21,6 +26,7 @@ module ActiveRecord
[arel_or_sql_string.dup.freeze, binds]
end
end
+ private :to_sql_and_binds
# This is used in the StatementCache object. It returns an object that
# can be used to query the database repeatedly.
@@ -39,7 +45,7 @@ module ActiveRecord
# Returns an ActiveRecord::Result instance.
def select_all(arel, name = nil, binds = [], preparable: nil)
arel = arel_from_relation(arel)
- sql, binds = to_sql(arel, binds)
+ sql, binds = to_sql_and_binds(arel, binds)
if !prepared_statements || (arel.is_a?(String) && preparable.nil?)
preparable = false
else
@@ -138,22 +144,22 @@ module ActiveRecord
#
# If the next id was calculated in advance (as in Oracle), it should be
# passed in as +id_value+.
- def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil)
- sql, binds = to_sql(arel)
+ def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [])
+ sql, binds = to_sql_and_binds(arel, binds)
value = exec_insert(sql, name, binds, pk, sequence_name)
id_value || last_inserted_id(value)
end
alias create insert
# Executes the update statement and returns the number of rows affected.
- def update(arel, name = nil)
- sql, binds = to_sql(arel)
+ def update(arel, name = nil, binds = [])
+ sql, binds = to_sql_and_binds(arel, binds)
exec_update(sql, name, binds)
end
# Executes the delete statement and returns the number of rows affected.
- def delete(arel, name = nil)
- sql, binds = to_sql(arel)
+ def delete(arel, name = nil, binds = [])
+ sql, binds = to_sql_and_binds(arel, binds)
exec_delete(sql, name, binds)
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
index 41d93c4322..25622e34c8 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
@@ -95,7 +95,7 @@ module ActiveRecord
def select_all(arel, name = nil, binds = [], preparable: nil)
if @query_cache_enabled && !locked?(arel)
arel = arel_from_relation(arel)
- sql, binds = to_sql(arel, binds)
+ sql, binds = to_sql_and_binds(arel, binds)
cache_sql(sql, name, binds) { super(sql, name, binds, preparable: preparable) }
else
super
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
index 9be26254b2..f17f1d35e2 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true
+require "active_support/core_ext/hash/compact"
+
module ActiveRecord
module ConnectionAdapters # :nodoc:
# The goal of this module is to move Adapter specific column
@@ -20,38 +22,6 @@ module ActiveRecord
spec
end
- # This can be overridden on an Adapter level basis to support other
- # extended datatypes (Example: Adding an array option in the
- # PostgreSQL::ColumnDumper)
- def prepare_column_options(column)
- spec = {}
-
- if limit = schema_limit(column)
- spec[:limit] = limit
- end
-
- if precision = schema_precision(column)
- spec[:precision] = precision
- end
-
- if scale = schema_scale(column)
- spec[:scale] = scale
- end
-
- default = schema_default(column) if column.has_default?
- spec[:default] = default unless default.nil?
-
- spec[:null] = "false" unless column.null
-
- if collation = schema_collation(column)
- spec[:collation] = collation
- end
-
- spec[:comment] = column.comment.inspect if column.comment.present?
-
- spec
- end
-
# Lists the valid migration options
def migration_keys # :nodoc:
column_options_keys
@@ -59,6 +29,18 @@ module ActiveRecord
deprecate :migration_keys
private
+ def prepare_column_options(column)
+ spec = {}
+ spec[:limit] = schema_limit(column)
+ spec[:precision] = schema_precision(column)
+ spec[:scale] = schema_scale(column)
+ spec[:default] = schema_default(column)
+ spec[:null] = "false" unless column.null
+ spec[:collation] = schema_collation(column)
+ spec[:comment] = column.comment.inspect if column.comment.present?
+ spec.compact!
+ spec
+ end
def default_primary_key?(column)
schema_type(column) == :bigint
@@ -98,6 +80,7 @@ module ActiveRecord
end
def schema_default(column)
+ return unless column.has_default?
type = lookup_cast_type_from_column(column)
default = type.deserialize(column.default)
if default.nil?
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 8a9c497918..1b4688a904 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -20,10 +20,6 @@ module ActiveRecord
include MySQL::ColumnDumper
include MySQL::SchemaStatements
- def update_table_definition(table_name, base) # :nodoc:
- MySQL::Table.new(table_name, base)
- end
-
##
# :singleton-method:
# By default, the Mysql2Adapter will consider all columns of type <tt>tinyint(1)</tt>
@@ -177,8 +173,7 @@ module ActiveRecord
#++
def explain(arel, binds = [])
- sql, binds = to_sql(arel, binds)
- sql = "EXPLAIN #{sql}"
+ sql = "EXPLAIN #{to_sql(arel, binds)}"
start = Time.now
result = exec_query(sql, "EXPLAIN", binds)
elapsed = Time.now - start
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb
index fbe3596dda..81f7dce562 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb
@@ -4,24 +4,23 @@ module ActiveRecord
module ConnectionAdapters
module MySQL
module ColumnDumper # :nodoc:
- def prepare_column_options(column)
- spec = super
- spec[:unsigned] = "true" if column.unsigned?
-
- if supports_virtual_columns? && column.virtual?
- spec[:as] = extract_expression_for_virtual_column(column)
- spec[:stored] = "true" if /\b(?:STORED|PERSISTENT)\b/.match?(column.extra)
- spec = { type: schema_type(column).inspect }.merge!(spec)
- end
-
- spec
- end
-
def migration_keys
super + [:unsigned]
end
private
+ def prepare_column_options(column)
+ spec = super
+ spec[:unsigned] = "true" if column.unsigned?
+
+ if supports_virtual_columns? && column.virtual?
+ spec[:as] = extract_expression_for_virtual_column(column)
+ spec[:stored] = "true" if /\b(?:STORED|PERSISTENT)\b/.match?(column.extra)
+ spec = { type: schema_type(column).inspect }.merge!(spec)
+ end
+
+ spec
+ end
def default_primary_key?(column)
super && column.auto_increment? && !column.unsigned?
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb
index 1d87d60ba9..7bebe82065 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb
@@ -62,6 +62,10 @@ module ActiveRecord
end
end
+ def update_table_definition(table_name, base)
+ MySQL::Table.new(table_name, base)
+ end
+
private
CHARSETS_OF_4BYTES_MAXLEN = ["utf8mb4", "utf16", "utf16le", "utf32"]
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
index 0dd4aac463..8db2a645af 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
@@ -5,8 +5,7 @@ module ActiveRecord
module PostgreSQL
module DatabaseStatements
def explain(arel, binds = [])
- sql, binds = to_sql(arel, binds)
- sql = "EXPLAIN #{sql}"
+ sql = "EXPLAIN #{to_sql(arel, binds)}"
PostgreSQL::ExplainPrettyPrinter.new.pp(exec_query(sql, "EXPLAIN", binds))
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb
index 12c6603081..5031605c2a 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb
@@ -4,19 +4,17 @@ module ActiveRecord
module ConnectionAdapters
module PostgreSQL
module ColumnDumper # :nodoc:
- # Adds +:array+ option to the default set
- def prepare_column_options(column)
- spec = super
- spec[:array] = "true" if column.array?
- spec
- end
-
# Adds +:array+ as a valid migration key
def migration_keys
super + [:array]
end
private
+ def prepare_column_options(column)
+ spec = super
+ spec[:array] = "true" if column.array?
+ spec
+ end
def default_primary_key?(column)
schema_type(column) == :bigserial
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
index 780e642f21..f258203a43 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -586,6 +586,10 @@ module ActiveRecord
[super, *order_columns].join(", ")
end
+ def update_table_definition(table_name, base) # :nodoc:
+ PostgreSQL::Table.new(table_name, base)
+ end
+
private
def schema_creation
PostgreSQL::SchemaCreation.new(self)
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 3b4439fc46..a2a9017bd9 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -76,7 +76,7 @@ module ActiveRecord
primary_key: "bigserial primary key",
string: { name: "character varying" },
text: { name: "text" },
- integer: { name: "integer" },
+ integer: { name: "integer", limit: 4 },
float: { name: "float" },
decimal: { name: "decimal" },
datetime: { name: "timestamp" },
@@ -368,10 +368,6 @@ module ActiveRecord
@use_insert_returning
end
- def update_table_definition(table_name, base) #:nodoc:
- PostgreSQL::Table.new(table_name, base)
- end
-
def column_name_for_operation(operation, node) # :nodoc:
OPERATION_ALIASES.fetch(operation) { operation.downcase }
end
@@ -439,9 +435,9 @@ module ActiveRecord
end
def initialize_type_map(m = type_map)
- register_class_with_limit m, "int2", Type::Integer
- register_class_with_limit m, "int4", Type::Integer
- register_class_with_limit m, "int8", Type::Integer
+ m.register_type "int2", Type::Integer.new(limit: 2)
+ m.register_type "int4", Type::Integer.new(limit: 4)
+ m.register_type "int8", Type::Integer.new(limit: 8)
m.register_type "oid", OID::Oid.new
m.register_type "float4", Type::Float.new
m.alias_type "float8", "float4"
@@ -508,17 +504,6 @@ module ActiveRecord
load_additional_types
end
- def extract_limit(sql_type)
- case sql_type
- when /^bigint/i, /^int8/i
- 8
- when /^smallint/i
- 2
- else
- super
- end
- end
-
# Extracts the value from a PostgreSQL column default definition.
def extract_value_from_default(default)
case default
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb
index c155e7f1ac..ee84646214 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb
@@ -39,6 +39,10 @@ module ActiveRecord
end
end
+ def update_table_definition(table_name, base)
+ SQLite3::Table.new(table_name, base)
+ end
+
private
def schema_creation
SQLite3::SchemaCreation.new(self)
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index 8c12cb09bd..327ca34e77 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -99,10 +99,6 @@ module ActiveRecord
end
end
- def update_table_definition(table_name, base) # :nodoc:
- SQLite3::Table.new(table_name, base)
- end
-
def initialize(connection, logger, connection_options, config)
super(connection, logger, config)
@@ -203,8 +199,7 @@ module ActiveRecord
#++
def explain(arel, binds = [])
- sql, binds = to_sql(arel, binds)
- sql = "EXPLAIN QUERY PLAN #{sql}"
+ sql = "EXPLAIN QUERY PLAN #{to_sql(arel, binds)}"
SQLite3::ExplainPrettyPrinter.new.pp(exec_query(sql, "EXPLAIN", []))
end
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index ef302fc0a0..4940e122f4 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -1065,6 +1065,10 @@ class ActiveRecord::FixtureSet::RenderContext # :nodoc:
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/reflection.rb b/activerecord/lib/active_record/reflection.rb
index d2b85e168b..b847933b2e 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -221,13 +221,8 @@ module ActiveRecord
end
def klass_join_scope(table, predicate_builder) # :nodoc:
- current_scope = klass.current_scope
-
- if current_scope && current_scope.empty_scope?
- build_scope(table, predicate_builder)
- else
- klass.default_scoped(build_scope(table, predicate_builder))
- end
+ relation = build_scope(table, predicate_builder)
+ klass.scope_for_association(relation)
end
def constraints
@@ -329,6 +324,10 @@ module ActiveRecord
def join_pk(_)
foreign_key
end
+
+ def primary_key(klass)
+ klass.primary_key || raise(UnknownPrimaryKey.new(klass))
+ end
end
# Base class for AggregateReflection and AssociationReflection. Objects of
@@ -512,7 +511,7 @@ module ActiveRecord
alias :check_eager_loadable! :check_preloadable!
def join_id_for(owner) # :nodoc:
- owner[active_record_primary_key]
+ owner[join_foreign_key]
end
def through_reflection
@@ -697,10 +696,6 @@ module ActiveRecord
def derive_join_table
ModelSchema.derive_join_table_name active_record.table_name, klass.table_name
end
-
- def primary_key(klass)
- klass.primary_key || raise(UnknownPrimaryKey.new(klass))
- end
end
class HasManyReflection < AssociationReflection # :nodoc:
@@ -715,6 +710,10 @@ module ActiveRecord
Associations::HasManyAssociation
end
end
+
+ def association_primary_key(klass = nil)
+ primary_key(klass || self.klass)
+ end
end
class HasOneReflection < AssociationReflection # :nodoc:
@@ -750,10 +749,6 @@ module ActiveRecord
end
end
- def join_id_for(owner) # :nodoc:
- owner[foreign_key]
- end
-
def join_foreign_key
foreign_key
end
@@ -780,7 +775,7 @@ module ActiveRecord
# Holds all the metadata about a :through association as it was specified
# in the Active Record class.
class ThroughReflection < AbstractReflection #:nodoc:
- delegate :foreign_key, :foreign_type, :association_foreign_key,
+ delegate :foreign_key, :foreign_type, :association_foreign_key, :join_id_for,
:active_record_primary_key, :type, :get_join_keys, to: :source_reflection
def initialize(delegate_reflection)
@@ -943,10 +938,6 @@ module ActiveRecord
through_reflection.options
end
- def join_id_for(owner) # :nodoc:
- source_reflection.join_id_for(owner)
- end
-
def check_validity!
if through_reflection.nil?
raise HasManyThroughAssociationNotFoundError.new(active_record.name, self)
@@ -1024,10 +1015,6 @@ module ActiveRecord
end
end
- def primary_key(klass)
- klass.primary_key || raise(UnknownPrimaryKey.new(klass))
- end
-
def inverse_name; delegate_reflection.send(:inverse_name); end
def derive_class_name
@@ -1085,15 +1072,16 @@ module ActiveRecord
@reflection.constraints + [source_type_info]
end
- def source_type_info
- type = @previous_reflection.foreign_type
- source_type = @previous_reflection.options[:source_type]
- lambda { |object| where(type => source_type) }
- end
-
def get_join_keys(association_klass)
@reflection.get_join_keys(association_klass)
end
+
+ private
+ def source_type_info
+ type = @previous_reflection.foreign_type
+ source_type = @previous_reflection.options[:source_type]
+ lambda { |object| where(type => source_type) }
+ end
end
class RuntimeReflection < PolymorphicReflection # :nodoc:
@@ -1112,10 +1100,6 @@ module ActiveRecord
@reflection.constraints
end
- def source_type_info
- @reflection.source_type_info
- end
-
def alias_candidate(name)
"#{plural_name}_#{name}_join"
end
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index caabad6055..d319978930 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -571,8 +571,7 @@ module ActiveRecord
conn = klass.connection
conn.unprepared_statement {
- sql, _ = conn.to_sql(relation.arel)
- sql
+ conn.to_sql(relation.arel)
}
end
end
diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb
index 141ad176ea..fa19c679cf 100644
--- a/activerecord/lib/active_record/relation/batches.rb
+++ b/activerecord/lib/active_record/relation/batches.rb
@@ -47,7 +47,12 @@ module ActiveRecord
# handle from 10000 and beyond by setting the +:start+ and +:finish+
# option on each worker.
#
- # # Let's process from record 10_000 on.
+ # # In worker 1, let's process until 9999 records.
+ # Person.find_each(finish: 9_999) do |person|
+ # person.party_all_night!
+ # end
+ #
+ # # In worker 2, let's process from record 10_000 and onwards.
# Person.find_each(start: 10_000) do |person|
# person.party_all_night!
# end
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index d3b5be6bce..42d43224fa 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -259,6 +259,9 @@ module ActiveRecord
column = aggregate_column(column_name)
select_value = operation_over_aggregate_column(column, operation, distinct)
+ if operation == "sum" && distinct
+ select_value.distinct = true
+ end
column_alias = select_value.alias
column_alias ||= @klass.connection.column_name_for_operation(operation, select_value)
diff --git a/activerecord/lib/active_record/schema.rb b/activerecord/lib/active_record/schema.rb
index f166c27fd9..1e121f2a09 100644
--- a/activerecord/lib/active_record/schema.rb
+++ b/activerecord/lib/active_record/schema.rb
@@ -39,7 +39,7 @@ module ActiveRecord
# The +info+ hash is optional, and if given is used to define metadata
# about the current schema (currently, only the schema's version):
#
- # ActiveRecord::Schema.define(version: 20380119000001) do
+ # ActiveRecord::Schema.define(version: 2038_01_19_000001) do
# ...
# end
def self.define(info = {}, &block)
diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb
index 27a1c89bd1..fd644225ca 100644
--- a/activerecord/lib/active_record/schema_dumper.rb
+++ b/activerecord/lib/active_record/schema_dumper.rb
@@ -243,7 +243,9 @@ HEADER
end
def remove_prefix_and_suffix(table)
- table.gsub(/^(#{@options[:table_name_prefix]})(.+)(#{@options[:table_name_suffix]})$/, "\\2")
+ prefix = Regexp.escape(@options[:table_name_prefix].to_s)
+ suffix = Regexp.escape(@options[:table_name_suffix].to_s)
+ table.sub(/\A#{prefix}(.+)#{suffix}\z/, "\\1")
end
def ignored?(table_name)
diff --git a/activerecord/lib/active_record/scoping/named.rb b/activerecord/lib/active_record/scoping/named.rb
index 43cce19c1f..6fa096c1fe 100644
--- a/activerecord/lib/active_record/scoping/named.rb
+++ b/activerecord/lib/active_record/scoping/named.rb
@@ -31,6 +31,16 @@ module ActiveRecord
end
end
+ def scope_for_association(scope = relation) # :nodoc:
+ current_scope = self.current_scope
+
+ if current_scope && current_scope.empty_scope?
+ scope
+ else
+ default_scoped(scope)
+ end
+ end
+
def default_scoped(scope = relation) # :nodoc:
build_default_scope(scope) || scope
end
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index b2f5e39e09..603dee8c0d 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -190,7 +190,7 @@ module ActiveRecord
#
# === Caveats
#
- # If you're on MySQL, then do not use Data Definition Language(DDL) operations in nested
+ # If you're on MySQL, then do not use Data Definition Language (DDL) operations in nested
# transactions blocks that are emulated with savepoints. That is, do not execute statements
# like 'CREATE TABLE' inside such blocks. This is because MySQL automatically
# releases all savepoints upon executing a DDL operation. When +transaction+
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index 2677fade18..baeb653c61 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -8,6 +8,10 @@ module ActiveRecord
raise ArgumentError, "#{options[:conditions]} was passed as :conditions but is not callable. " \
"Pass a callable instead: `conditions: -> { where(approved: true) }`"
end
+ unless Array(options[:scope]).all? { |scope| scope.respond_to?(:to_sym) }
+ raise ArgumentError, "#{options[:scope]} is not supported format for :scope option. " \
+ "Pass a symbol or an array of symbols instead: `scope: :user_id`"
+ end
super({ case_sensitive: true }.merge!(options))
@klass = options[:class]
end
diff --git a/activerecord/lib/rails/generators/active_record/application_record/application_record_generator.rb b/activerecord/lib/rails/generators/active_record/application_record/application_record_generator.rb
index d18330f5b2..35d5664400 100644
--- a/activerecord/lib/rails/generators/active_record/application_record/application_record_generator.rb
+++ b/activerecord/lib/rails/generators/active_record/application_record/application_record_generator.rb
@@ -15,11 +15,12 @@ module ActiveRecord
private
def application_record_file_name
- @application_record_file_name ||= if namespaced?
- "app/models/#{namespaced_path}/application_record.rb"
- else
- "app/models/application_record.rb"
- end
+ @application_record_file_name ||=
+ if namespaced?
+ "app/models/#{namespaced_path}/application_record.rb"
+ else
+ "app/models/application_record.rb"
+ end
end
end
end
diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb
index 6e04578576..9aaa2852d0 100644
--- a/activerecord/test/cases/adapter_test.rb
+++ b/activerecord/test/cases/adapter_test.rb
@@ -227,6 +227,38 @@ module ActiveRecord
result = @connection.select_all("SELECT * FROM posts WHERE id = #{Arel::Nodes::BindParam.new(nil).to_sql}", nil, [[nil, post.id]])
assert_equal expected.to_hash, result.to_hash
end
+
+ def test_insert_update_delete_with_legacy_binds
+ binds = [[nil, 1]]
+ bind_param = Arel::Nodes::BindParam.new(nil)
+
+ id = @connection.insert("INSERT INTO events(id) VALUES (#{bind_param.to_sql})", nil, nil, nil, nil, binds)
+ assert_equal 1, id
+
+ @connection.update("UPDATE events SET title = 'foo' WHERE id = #{bind_param.to_sql}", nil, binds)
+ result = @connection.select_all("SELECT * FROM events WHERE id = #{bind_param.to_sql}", nil, binds)
+ assert_equal({ "id" => 1, "title" => "foo" }, result.first)
+
+ @connection.delete("DELETE FROM events WHERE id = #{bind_param.to_sql}", nil, binds)
+ result = @connection.select_all("SELECT * FROM events WHERE id = #{bind_param.to_sql}", nil, binds)
+ assert_nil result.first
+ end
+
+ def test_insert_update_delete_with_binds
+ binds = [Relation::QueryAttribute.new("id", 1, Type.default_value)]
+ bind_param = Arel::Nodes::BindParam.new(nil)
+
+ id = @connection.insert("INSERT INTO events(id) VALUES (#{bind_param.to_sql})", nil, nil, nil, nil, binds)
+ assert_equal 1, id
+
+ @connection.update("UPDATE events SET title = 'foo' WHERE id = #{bind_param.to_sql}", nil, binds)
+ result = @connection.select_all("SELECT * FROM events WHERE id = #{bind_param.to_sql}", nil, binds)
+ assert_equal({ "id" => 1, "title" => "foo" }, result.first)
+
+ @connection.delete("DELETE FROM events WHERE id = #{bind_param.to_sql}", nil, binds)
+ result = @connection.select_all("SELECT * FROM events WHERE id = #{bind_param.to_sql}", nil, binds)
+ assert_nil result.first
+ end
end
def test_select_methods_passing_a_association_relation
@@ -256,11 +288,11 @@ module ActiveRecord
def test_log_invalid_encoding
error = assert_raises RuntimeError do
@connection.send :log, "SELECT 'ы' FROM DUAL" do
- raise "ы".force_encoding(Encoding::ASCII_8BIT)
+ raise "ы".dup.force_encoding(Encoding::ASCII_8BIT)
end
end
- assert_not_nil error.message
+ assert_equal "ы", error.message
end
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/array_test.rb b/activerecord/test/cases/adapters/postgresql/array_test.rb
index 08b17f37e2..0e9e86f425 100644
--- a/activerecord/test/cases/adapters/postgresql/array_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/array_test.rb
@@ -47,7 +47,7 @@ class PostgresqlArrayTest < ActiveRecord::PostgreSQLTestCase
assert ratings_column.array?
end
- def test_not_compatible_with_serialize
+ def test_not_compatible_with_serialize_array
new_klass = Class.new(PgArray) do
serialize :tags, Array
end
@@ -56,6 +56,30 @@ class PostgresqlArrayTest < ActiveRecord::PostgreSQLTestCase
end
end
+ class MyTags
+ def initialize(tags); @tags = tags end
+ def to_a; @tags end
+ def self.load(tags); new(tags) end
+ def self.dump(object); object.to_a end
+ end
+
+ def test_array_with_serialized_attributes
+ new_klass = Class.new(PgArray) do
+ serialize :tags, MyTags
+ end
+
+ new_klass.create!(tags: MyTags.new(["one", "two"]))
+ record = new_klass.first
+
+ assert_instance_of MyTags, record.tags
+ assert_equal ["one", "two"], record.tags.to_a
+
+ record.tags = MyTags.new(["three", "four"])
+ record.save!
+
+ assert_equal ["three", "four"], record.reload.tags.to_a
+ end
+
def test_default
@connection.add_column "pg_arrays", "score", :integer, array: true, default: [4, 4, 2]
PgArray.reset_column_information
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index cedb621b4f..6bd11a5d81 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -63,7 +63,7 @@ class HasManyAssociationsTestPrimaryKeys < ActiveRecord::TestCase
assert_equal 2, subscriber.subscriptions.size
end
- assert_equal subscriber.subscriptions, Subscription.where(subscriber_id: "webster132")
+ assert_equal Subscription.where(subscriber_id: "webster132"), subscriber.subscriptions
end
def test_association_primary_key_on_new_record_should_fetch_with_query
@@ -74,12 +74,23 @@ class HasManyAssociationsTestPrimaryKeys < ActiveRecord::TestCase
assert_equal 1, author.essays.size
end
- assert_equal author.essays, Essay.where(writer_id: "David")
+ assert_equal Essay.where(writer_id: "David"), author.essays
end
def test_has_many_custom_primary_key
david = authors(:david)
- assert_equal david.essays, Essay.where(writer_id: "David")
+ assert_equal Essay.where(writer_id: "David"), david.essays
+ end
+
+ def test_ids_on_unloaded_association_with_custom_primary_key
+ david = people(:david)
+ assert_equal Essay.where(writer_id: "David").pluck(:id), david.essay_ids
+ end
+
+ def test_ids_on_loaded_association_with_custom_primary_key
+ david = people(:david)
+ david.essays.load
+ assert_equal Essay.where(writer_id: "David").pluck(:id), david.essay_ids
end
def test_has_many_assignment_with_custom_primary_key
@@ -565,8 +576,16 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_taking_with_a_number
+ klass = Class.new(Author) do
+ has_many :posts, -> { order(:id) }
+
+ def self.name
+ "Author"
+ end
+ end
+
# taking from unloaded Relation
- bob = Author.find(authors(:bob).id)
+ bob = klass.find(authors(:bob).id)
new_post = bob.posts.build
assert_not bob.posts.loaded?
assert_equal [posts(:misc_by_bob)], bob.posts.take(1)
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 4c2723addc..f8ea51225a 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -880,13 +880,6 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
end
end
- def test_collection_singular_ids_setter_with_changed_primary_key
- company = companies(:first_firm)
- client = companies(:first_client)
- company.clients_using_primary_key_ids = [client.name]
- assert_equal [client], company.clients_using_primary_key
- end
-
def test_collection_singular_ids_setter_raises_exception_when_invalid_ids_set
company = companies(:rails_core)
ids = [Developer.first.id, -9999]
@@ -895,13 +888,6 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert_equal(msg, e.message)
end
- def test_collection_singular_ids_setter_raises_exception_when_invalid_ids_set_with_changed_primary_key
- company = companies(:first_firm)
- ids = [Client.first.name, "unknown client"]
- e = assert_raises(ActiveRecord::RecordNotFound) { company.clients_using_primary_key_ids = ids }
- assert_match(/Couldn't find all Clients with 'name'/, e.message)
- end
-
def test_collection_singular_ids_through_setter_raises_exception_when_invalid_ids_set
author = authors(:david)
ids = [categories(:general).name, "Unknown"]
@@ -1139,6 +1125,32 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert_equal ["parrot", "bulbul"], owner.toys.map { |r| r.pet.name }
end
+ def test_has_many_through_associations_sum_on_columns
+ post1 = Post.create(title: "active", body: "sample")
+ post2 = Post.create(title: "inactive", body: "sample")
+
+ person1 = Person.create(first_name: "aaron", followers_count: 1)
+ person2 = Person.create(first_name: "schmit", followers_count: 2)
+ person3 = Person.create(first_name: "bill", followers_count: 3)
+ person4 = Person.create(first_name: "cal", followers_count: 4)
+
+ Reader.create(post_id: post1.id, person_id: person1.id)
+ Reader.create(post_id: post1.id, person_id: person2.id)
+ Reader.create(post_id: post1.id, person_id: person3.id)
+ Reader.create(post_id: post1.id, person_id: person4.id)
+
+ Reader.create(post_id: post2.id, person_id: person1.id)
+ Reader.create(post_id: post2.id, person_id: person2.id)
+ Reader.create(post_id: post2.id, person_id: person3.id)
+ Reader.create(post_id: post2.id, person_id: person4.id)
+
+ active_persons = Person.joins(:readers).joins(:posts).distinct(true).where("posts.title" => "active")
+
+ assert_equal active_persons.map(&:followers_count).reduce(:+), 10
+ assert_equal active_persons.sum(:followers_count), 10
+ assert_equal active_persons.sum(:followers_count), active_persons.map(&:followers_count).reduce(:+)
+ end
+
def test_has_many_through_associations_on_new_records_use_null_relations
person = Person.new
@@ -1249,6 +1261,25 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert_equal 0, users[2].family_members.to_a.size
end
+ def test_through_scope_is_affected_by_unscoping
+ author = authors(:david)
+
+ expected = author.comments.to_a
+ FirstPost.unscoped do
+ assert_equal expected.sort_by(&:id), author.comments_on_first_posts.sort_by(&:id)
+ end
+ end
+
+ def test_through_scope_isnt_affected_by_scoping
+ author = authors(:david)
+
+ expected = author.comments_on_first_posts.to_a
+ FirstPost.where(id: 2).scoping do
+ author.comments_on_first_posts.reset
+ assert_equal expected.sort_by(&:id), author.comments_on_first_posts.sort_by(&:id)
+ end
+ end
+
def test_incorrectly_ordered_through_associations
assert_raises(ActiveRecord::HasManyThroughOrderError) do
DeveloperWithIncorrectlyOrderedHasManyThrough.create(
diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb
index 9d3d5353ff..3abdcf3564 100644
--- a/activerecord/test/cases/associations/join_model_test.rb
+++ b/activerecord/test/cases/associations/join_model_test.rb
@@ -99,11 +99,11 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
end
def test_polymorphic_has_many_create_model_with_inheritance_and_custom_base_class
- post = SubStiPost.create title: "SubStiPost", body: "SubStiPost body"
- assert_instance_of SubStiPost, post
+ post = SubAbstractStiPost.create title: "SubAbstractStiPost", body: "SubAbstractStiPost body"
+ assert_instance_of SubAbstractStiPost, post
tagging = tags(:misc).taggings.create(taggable: post)
- assert_equal "SubStiPost", tagging.taggable_type
+ assert_equal "SubAbstractStiPost", tagging.taggable_type
end
def test_polymorphic_has_many_going_through_join_model_with_inheritance
diff --git a/activerecord/test/cases/attribute_set_test.rb b/activerecord/test/cases/attribute_set_test.rb
index 006372a757..8be77ed88f 100644
--- a/activerecord/test/cases/attribute_set_test.rb
+++ b/activerecord/test/cases/attribute_set_test.rb
@@ -16,7 +16,7 @@ module ActiveRecord
test "building with custom types" do
builder = AttributeSet::Builder.new(foo: Type::Float.new)
- attributes = builder.build_from_database({ foo: "3.3", bar: "4.4" }, bar: Type::Integer.new)
+ attributes = builder.build_from_database({ foo: "3.3", bar: "4.4" }, { bar: Type::Integer.new })
assert_equal 3.3, attributes[:foo].value
assert_equal 4, attributes[:bar].value
diff --git a/activerecord/test/cases/enum_test.rb b/activerecord/test/cases/enum_test.rb
index abbc011b34..68a1190d68 100644
--- a/activerecord/test/cases/enum_test.rb
+++ b/activerecord/test/cases/enum_test.rb
@@ -255,12 +255,14 @@ class EnumTest < ActiveRecord::TestCase
assert Book.illustrator_visibility_invisible.create.illustrator_visibility_invisible?
end
- test "_before_type_cast returns the enum label (required for form fields)" do
- if @book.status_came_from_user?
- assert_equal "published", @book.status_before_type_cast
- else
- assert_equal "published", @book.status
- end
+ test "_before_type_cast" do
+ assert_equal 2, @book.status_before_type_cast
+ assert_equal "published", @book.status
+
+ @book.status = "published"
+
+ assert_equal "published", @book.status_before_type_cast
+ assert_equal "published", @book.status
end
test "reserved enum names" do
diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb
index 6b014bcb3d..b0b63f5203 100644
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -132,7 +132,7 @@ class FixturesTest < ActiveRecord::TestCase
def test_no_args_record_returns_all_without_array
all_binaries = binaries
assert_kind_of(Array, all_binaries)
- assert_equal 1, binaries.length
+ assert_equal 2, binaries.length
end
def test_nil_raises
@@ -313,6 +313,7 @@ class FixturesTest < ActiveRecord::TestCase
data.force_encoding("ASCII-8BIT")
data.freeze
assert_equal data, @flowers.data
+ assert_equal data, @binary_helper.data
end
def test_serialized_fixtures
diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb
index a263106f6d..c931f7d21c 100644
--- a/activerecord/test/cases/inheritance_test.rb
+++ b/activerecord/test/cases/inheritance_test.rb
@@ -147,12 +147,16 @@ class InheritanceTest < ActiveRecord::TestCase
# Concrete subclass of AR::Base.
assert Post.descends_from_active_record?
+ # Concrete subclasses of a concrete class which has a type column.
+ assert !StiPost.descends_from_active_record?
+ assert !SubStiPost.descends_from_active_record?
+
# Abstract subclass of a concrete class which has a type column.
# This is pathological, as you'll never have Sub < Abstract < Concrete.
- assert !StiPost.descends_from_active_record?
+ assert !AbstractStiPost.descends_from_active_record?
- # Concrete subclasses an abstract class which has a type column.
- assert !SubStiPost.descends_from_active_record?
+ # Concrete subclass of an abstract class which has a type column.
+ assert !SubAbstractStiPost.descends_from_active_record?
end
def test_company_descends_from_active_record
@@ -172,7 +176,8 @@ class InheritanceTest < ActiveRecord::TestCase
assert_equal Post, Post.base_class
assert_equal Post, SpecialPost.base_class
assert_equal Post, StiPost.base_class
- assert_equal SubStiPost, SubStiPost.base_class
+ assert_equal Post, SubStiPost.base_class
+ assert_equal SubAbstractStiPost, SubAbstractStiPost.base_class
end
def test_abstract_inheritance_base_class
diff --git a/activerecord/test/cases/json_shared_test_cases.rb b/activerecord/test/cases/json_shared_test_cases.rb
index 952194c6dc..56ec8c8a82 100644
--- a/activerecord/test/cases/json_shared_test_cases.rb
+++ b/activerecord/test/cases/json_shared_test_cases.rb
@@ -216,7 +216,7 @@ module JSONSharedTestCases
assert_equal true, json.payload
end
- def test_not_compatible_with_serialize_macro
+ def test_not_compatible_with_serialize_json
new_klass = Class.new(klass) do
serialize :payload, JSON
end
@@ -225,6 +225,30 @@ module JSONSharedTestCases
end
end
+ class MySettings
+ def initialize(hash); @hash = hash end
+ def to_hash; @hash end
+ def self.load(hash); new(hash) end
+ def self.dump(object); object.to_hash end
+ end
+
+ def test_json_with_serialized_attributes
+ new_klass = Class.new(klass) do
+ serialize :settings, MySettings
+ end
+
+ new_klass.create!(settings: MySettings.new("one" => "two"))
+ record = new_klass.first
+
+ assert_instance_of MySettings, record.settings
+ assert_equal({ "one" => "two" }, record.settings.to_hash)
+
+ record.settings = MySettings.new("three" => "four")
+ record.save!
+
+ assert_equal({ "three" => "four" }, record.reload.settings.to_hash)
+ end
+
private
def klass
JsonDataType
diff --git a/activerecord/test/cases/migration/column_attributes_test.rb b/activerecord/test/cases/migration/column_attributes_test.rb
index 9200d32caf..be6dc2acb1 100644
--- a/activerecord/test/cases/migration/column_attributes_test.rb
+++ b/activerecord/test/cases/migration/column_attributes_test.rb
@@ -45,11 +45,11 @@ module ActiveRecord
assert_nil TestModel.columns_hash["description"].limit
end
- if current_adapter?(:Mysql2Adapter)
+ if current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter)
def test_unabstracted_database_dependent_types
- add_column :test_models, :intelligence_quotient, :tinyint
+ add_column :test_models, :intelligence_quotient, :smallint
TestModel.reset_column_information
- assert_match(/tinyint/, TestModel.columns_hash["intelligence_quotient"].sql_type)
+ assert_match(/smallint/, TestModel.columns_hash["intelligence_quotient"].sql_type)
end
end
@@ -99,7 +99,21 @@ module ActiveRecord
assert_equal 7, wealth_column.scale
end
+ # Test SQLite3 adapter specifically for decimal types with precision and scale
+ # attributes, since these need to be maintained in schema but aren't actually
+ # used in SQLite3 itself
if current_adapter?(:SQLite3Adapter)
+ def test_change_column_with_new_precision_and_scale
+ connection.add_column "test_models", "wealth", :decimal, precision: 9, scale: 7
+
+ connection.change_column "test_models", "wealth", :decimal, precision: 12, scale: 8
+ TestModel.reset_column_information
+
+ wealth_column = TestModel.columns_hash["wealth"]
+ assert_equal 12, wealth_column.precision
+ assert_equal 8, wealth_column.scale
+ end
+
def test_change_column_preserve_other_column_precision_and_scale
connection.add_column "test_models", "last_name", :string
connection.add_column "test_models", "wealth", :decimal, precision: 9, scale: 7
diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb
index c887f54560..170fd02b6f 100644
--- a/activerecord/test/cases/persistence_test.rb
+++ b/activerecord/test/cases/persistence_test.rb
@@ -78,6 +78,16 @@ class PersistenceTest < ActiveRecord::TestCase
assert_equal "2 updated", Topic.find(2).content
end
+ def test_class_level_update_is_affected_by_scoping
+ topic_data = { 1 => { "content" => "1 updated" }, 2 => { "content" => "2 updated" } }
+
+ assert_raise(ActiveRecord::RecordNotFound) do
+ Topic.where("1=0").scoping { Topic.update(topic_data.keys, topic_data.values) }
+ end
+ assert_not_equal "1 updated", Topic.find(1).content
+ assert_not_equal "2 updated", Topic.find(2).content
+ end
+
def test_delete_all
assert Topic.count > 0
@@ -912,13 +922,33 @@ class PersistenceTest < ActiveRecord::TestCase
assert_raise(ActiveRecord::RecordNotFound) { Reply.find(should_be_destroyed_reply.id) }
end
+ def test_class_level_destroy_is_affected_by_scoping
+ should_not_be_destroyed_reply = Reply.create("title" => "hello", "content" => "world")
+ Topic.find(1).replies << should_not_be_destroyed_reply
+
+ assert_raise(ActiveRecord::RecordNotFound) do
+ Topic.where("1=0").scoping { Topic.destroy(1) }
+ end
+ assert_nothing_raised { Topic.find(1) }
+ assert_nothing_raised { Reply.find(should_not_be_destroyed_reply.id) }
+ end
+
def test_class_level_delete
- should_be_destroyed_reply = Reply.create("title" => "hello", "content" => "world")
- Topic.find(1).replies << should_be_destroyed_reply
+ should_not_be_destroyed_reply = Reply.create("title" => "hello", "content" => "world")
+ Topic.find(1).replies << should_not_be_destroyed_reply
Topic.delete(1)
assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1) }
- assert_nothing_raised { Reply.find(should_be_destroyed_reply.id) }
+ assert_nothing_raised { Reply.find(should_not_be_destroyed_reply.id) }
+ end
+
+ def test_class_level_delete_is_affected_by_scoping
+ should_not_be_destroyed_reply = Reply.create("title" => "hello", "content" => "world")
+ Topic.find(1).replies << should_not_be_destroyed_reply
+
+ Topic.where("1=0").scoping { Topic.delete(1) }
+ assert_nothing_raised { Topic.find(1) }
+ assert_nothing_raised { Reply.find(should_not_be_destroyed_reply.id) }
end
def test_create_with_custom_timestamps
diff --git a/activerecord/test/cases/relation/where_test.rb b/activerecord/test/cases/relation/where_test.rb
index 2fe2a67b1c..d95a54a2fe 100644
--- a/activerecord/test/cases/relation/where_test.rb
+++ b/activerecord/test/cases/relation/where_test.rb
@@ -109,6 +109,15 @@ module ActiveRecord
assert_equal expected.to_sql, actual.to_sql
end
+ def test_polymorphic_shallow_where_not
+ treasure = treasures(:sapphire)
+
+ expected = [price_estimates(:diamond), price_estimates(:honda)]
+ actual = PriceEstimate.where.not(estimate_of: treasure)
+
+ assert_equal expected.sort_by(&:id), actual.sort_by(&:id)
+ end
+
def test_polymorphic_nested_array_where
treasure = Treasure.new
treasure.id = 1
@@ -121,6 +130,16 @@ module ActiveRecord
assert_equal expected.to_sql, actual.to_sql
end
+ def test_polymorphic_nested_array_where_not
+ treasure = treasures(:diamond)
+ car = cars(:honda)
+
+ expected = [price_estimates(:sapphire_1), price_estimates(:sapphire_2)]
+ actual = PriceEstimate.where.not(estimate_of: [treasure, car])
+
+ assert_equal expected.sort_by(&:id), actual.sort_by(&:id)
+ end
+
def test_polymorphic_array_where_multiple_types
treasure_1 = treasures(:diamond)
treasure_2 = treasures(:sapphire)
diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb
index 01ec3e06ad..eb9b257da9 100644
--- a/activerecord/test/cases/schema_dumper_test.rb
+++ b/activerecord/test/cases/schema_dumper_test.rb
@@ -400,6 +400,31 @@ class SchemaDumperTest < ActiveRecord::TestCase
$stdout = original
end
+ def test_schema_dump_with_table_name_prefix_and_suffix_regexp_escape
+ original, $stdout = $stdout, StringIO.new
+ ActiveRecord::Base.table_name_prefix = "foo$"
+ ActiveRecord::Base.table_name_suffix = "$bar"
+
+ migration = CreateDogMigration.new
+ migration.migrate(:up)
+
+ output = perform_schema_dump
+ assert_no_match %r{create_table "foo\$.+\$bar"}, output
+ assert_no_match %r{add_index "foo\$.+\$bar"}, output
+ assert_no_match %r{create_table "schema_migrations"}, output
+ assert_no_match %r{create_table "ar_internal_metadata"}, output
+
+ if ActiveRecord::Base.connection.supports_foreign_keys?
+ assert_no_match %r{add_foreign_key "foo\$.+\$bar"}, output
+ assert_no_match %r{add_foreign_key "[^"]+", "foo\$.+\$bar"}, output
+ end
+ ensure
+ migration.migrate(:down)
+
+ ActiveRecord::Base.table_name_suffix = ActiveRecord::Base.table_name_prefix = ""
+ $stdout = original
+ end
+
def test_schema_dump_with_table_name_prefix_and_ignoring_tables
original, $stdout = $stdout, StringIO.new
diff --git a/activerecord/test/cases/timestamp_test.rb b/activerecord/test/cases/timestamp_test.rb
index ba0bc6c45d..5d3e2a175c 100644
--- a/activerecord/test/cases/timestamp_test.rb
+++ b/activerecord/test/cases/timestamp_test.rb
@@ -446,17 +446,6 @@ class TimestampTest < ActiveRecord::TestCase
toy = Toy.first
assert_equal ["created_at", "updated_at"], toy.send(:all_timestamp_attributes_in_model)
end
-
- def test_index_is_created_for_both_timestamps
- ActiveRecord::Base.connection.create_table(:foos, force: true) do |t|
- t.timestamps null: true, index: true
- end
-
- indexes = ActiveRecord::Base.connection.indexes("foos")
- assert_equal ["created_at", "updated_at"], indexes.flat_map(&:columns).sort
- ensure
- ActiveRecord::Base.connection.drop_table(:foos)
- end
end
class TimestampsWithoutTransactionTest < ActiveRecord::TestCase
@@ -475,4 +464,15 @@ class TimestampsWithoutTransactionTest < ActiveRecord::TestCase
assert_nil post.updated_at
end
end
+
+ def test_index_is_created_for_both_timestamps
+ ActiveRecord::Base.connection.create_table(:foos, force: true) do |t|
+ t.timestamps null: true, index: true
+ end
+
+ indexes = ActiveRecord::Base.connection.indexes("foos")
+ assert_equal ["created_at", "updated_at"], indexes.flat_map(&:columns).sort
+ ensure
+ ActiveRecord::Base.connection.drop_table(:foos)
+ end
end
diff --git a/activerecord/test/cases/validations/uniqueness_validation_test.rb b/activerecord/test/cases/validations/uniqueness_validation_test.rb
index fad55916c7..a10567f066 100644
--- a/activerecord/test/cases/validations/uniqueness_validation_test.rb
+++ b/activerecord/test/cases/validations/uniqueness_validation_test.rb
@@ -156,6 +156,13 @@ class UniquenessValidationTest < ActiveRecord::TestCase
assert r3.valid?, "Saving r3"
end
+ def test_validate_uniqueness_with_scope_invalid_syntax
+ error = assert_raises(ArgumentError) do
+ Reply.validates_uniqueness_of(:content, scope: { parent_id: false })
+ end
+ assert_match(/Pass a symbol or an array of symbols instead/, error.to_s)
+ end
+
def test_validate_uniqueness_with_object_scope
Reply.validates_uniqueness_of(:content, scope: :topic)
diff --git a/activerecord/test/fixtures/binaries.yml b/activerecord/test/fixtures/binaries.yml
index ec8f2facdc..53b7883369 100644
--- a/activerecord/test/fixtures/binaries.yml
+++ b/activerecord/test/fixtures/binaries.yml
@@ -131,3 +131,7 @@ flowers:
SgCUASgCUASgCUASgAC74PbXOTvE5/En7jpSoLE8/wBn7uPJjKyj46T9D/NT
pKsXyQzxNpdNP0/akB5484WkMKh4RfXG4UafNmH7b0UxWMrb7Nxg6rl9Z/Im
w+vWq0iscQwxQroiUIvkKsRZQBKAJQBKAJQB/9k=
+
+binary_helper:
+ id: 2
+ data: <%= binary(ASSETS_ROOT + "/flowers.jpg") %>
diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb
index 4b3576fce8..4c8e847354 100644
--- a/activerecord/test/models/post.rb
+++ b/activerecord/test/models/post.rb
@@ -184,14 +184,19 @@ end
class SpecialPost < Post; end
class StiPost < Post
- self.abstract_class = true
has_one :special_comment, class_name: "SpecialComment"
end
+class AbstractStiPost < Post
+ self.abstract_class = true
+end
+
class SubStiPost < StiPost
self.table_name = Post.table_name
end
+class SubAbstractStiPost < AbstractStiPost; end
+
class FirstPost < ActiveRecord::Base
self.inheritance_column = :disabled
self.table_name = "posts"