aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/CHANGELOG.md7
-rw-r--r--activerecord/lib/active_record/associations/association_scope.rb82
-rw-r--r--activerecord/lib/active_record/associations/belongs_to_association.rb3
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb3
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb20
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/join_dependency/join_association.rb8
-rw-r--r--activerecord/lib/active_record/associations/preloader.rb2
-rw-r--r--activerecord/lib/active_record/associations/preloader/association.rb6
-rw-r--r--activerecord/lib/active_record/attribute.rb7
-rw-r--r--activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/quoting.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb8
-rw-r--r--activerecord/lib/active_record/counter_cache.rb2
-rw-r--r--activerecord/lib/active_record/log_subscriber.rb20
-rw-r--r--activerecord/lib/active_record/model_schema.rb14
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb1
-rw-r--r--activerecord/lib/active_record/relation.rb32
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb16
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb6
-rw-r--r--activerecord/lib/active_record/relation/from_clause.rb32
-rw-r--r--activerecord/lib/active_record/relation/merger.rb5
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb4
-rw-r--r--activerecord/lib/active_record/relation/query_attribute.rb19
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb103
-rw-r--r--activerecord/lib/active_record/relation/where_clause.rb39
-rw-r--r--activerecord/lib/active_record/statement_cache.rb18
-rw-r--r--activerecord/lib/active_record/table_metadata.rb9
-rw-r--r--activerecord/lib/active_record/type_caster/connection.rb7
-rw-r--r--activerecord/test/cases/adapter_test.rb4
-rw-r--r--activerecord/test/cases/adapters/mysql/connection_test.rb6
-rw-r--r--activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb6
-rw-r--r--activerecord/test/cases/adapters/postgresql/connection_test.rb6
-rw-r--r--activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb16
-rw-r--r--activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb7
-rw-r--r--activerecord/test/cases/adapters/postgresql/schema_test.rb8
-rw-r--r--activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb8
-rw-r--r--activerecord/test/cases/associations/association_scope_test.rb7
-rw-r--r--activerecord/test/cases/associations/belongs_to_associations_test.rb4
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb8
-rw-r--r--activerecord/test/cases/associations/has_one_associations_test.rb8
-rw-r--r--activerecord/test/cases/associations_test.rb4
-rw-r--r--activerecord/test/cases/bind_parameter_test.rb24
-rw-r--r--activerecord/test/cases/counter_cache_test.rb18
-rw-r--r--activerecord/test/cases/explain_test.rb2
-rw-r--r--activerecord/test/cases/inheritance_test.rb4
-rw-r--r--activerecord/test/cases/log_subscriber_test.rb8
-rw-r--r--activerecord/test/cases/nested_attributes_test.rb10
-rw-r--r--activerecord/test/cases/relation/merging_test.rb15
-rw-r--r--activerecord/test/cases/relation/mutation_test.rb4
-rw-r--r--activerecord/test/cases/relation/where_chain_test.rb122
-rw-r--r--activerecord/test/cases/relation/where_clause_test.rb50
-rw-r--r--activerecord/test/cases/relation_test.rb4
-rw-r--r--activerecord/test/cases/relations_test.rb35
-rw-r--r--activerecord/test/cases/scoping/default_scoping_test.rb8
-rw-r--r--activerecord/test/cases/scoping/named_scoping_test.rb4
-rw-r--r--activerecord/test/cases/scoping/relation_scoping_test.rb2
-rw-r--r--activerecord/test/models/car.rb2
-rw-r--r--activerecord/test/models/ship.rb1
-rw-r--r--activerecord/test/models/treasure.rb1
-rw-r--r--activerecord/test/schema/schema.rb1
65 files changed, 447 insertions, 469 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 22b1c8e95b..c0ebb892aa 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,10 @@
+* Don't define autosave association callbacks twice from
+ `accepts_nested_attributes_for`.
+
+ Fixes #18704.
+
+ *Sean Griffin*
+
* Integer types will no longer raise a `RangeError` when assigning an
attribute, but will instead raise when going to the database.
diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb
index a2550fa382..2416167834 100644
--- a/activerecord/lib/active_record/associations/association_scope.rb
+++ b/activerecord/lib/active_record/associations/association_scope.rb
@@ -2,42 +2,30 @@ module ActiveRecord
module Associations
class AssociationScope #:nodoc:
def self.scope(association, connection)
- INSTANCE.scope association, connection
- end
-
- class BindSubstitution
- def initialize(block)
- @block = block
- end
-
- def bind_value(scope, column, value, connection)
- substitute = connection.substitute_at(column)
- scope.bind_values += [[column, @block.call(value)]]
- substitute
- end
+ INSTANCE.scope(association, connection)
end
def self.create(&block)
- block = block ? block : lambda { |val| val }
- new BindSubstitution.new(block)
+ block ||= lambda { |val| val }
+ new(block)
end
- def initialize(bind_substitution)
- @bind_substitution = bind_substitution
+ def initialize(value_transformation)
+ @value_transformation = value_transformation
end
INSTANCE = create
def scope(association, connection)
- klass = association.klass
- reflection = association.reflection
- scope = klass.unscoped
- owner = association.owner
+ klass = association.klass
+ reflection = association.reflection
+ scope = klass.unscoped
+ owner = association.owner
alias_tracker = AliasTracker.create connection, association.klass.table_name, klass.type_caster
chain_head, chain_tail = get_chain(reflection, association, alias_tracker)
scope.extending! Array(reflection.options[:extend])
- add_constraints(scope, owner, klass, reflection, connection, chain_head, chain_tail)
+ add_constraints(scope, owner, klass, reflection, chain_head, chain_tail)
end
def join_type
@@ -61,43 +49,36 @@ module ActiveRecord
binds
end
+ protected
+
+ attr_reader :value_transformation
+
private
def join(table, constraint)
table.create_join(table, table.create_on(constraint), join_type)
end
- def column_for(table_name, column_name, connection)
- columns = connection.schema_cache.columns_hash(table_name)
- columns[column_name]
- end
-
- def bind_value(scope, column, value, connection)
- @bind_substitution.bind_value scope, column, value, connection
- end
-
- def bind(scope, table_name, column_name, value, connection)
- column = column_for table_name, column_name, connection
- bind_value scope, column, value, connection
- end
-
- def last_chain_scope(scope, table, reflection, owner, connection, association_klass)
+ def last_chain_scope(scope, table, reflection, owner, association_klass)
join_keys = reflection.join_keys(association_klass)
key = join_keys.key
foreign_key = join_keys.foreign_key
- bind_val = bind scope, table.table_name, key.to_s, owner[foreign_key], connection
- scope = scope.where(table[key].eq(bind_val))
+ value = transform_value(owner[foreign_key])
+ scope = scope.where(table.name => { key => value })
if reflection.type
- value = owner.class.base_class.name
- bind_val = bind scope, table.table_name, reflection.type, value, connection
- scope = scope.where(table[reflection.type].eq(bind_val))
- else
- scope
+ polymorphic_type = transform_value(owner.class.base_class.name)
+ scope = scope.where(table.name => { reflection.type => polymorphic_type })
end
+
+ scope
+ end
+
+ def transform_value(value)
+ value_transformation.call(value)
end
- def next_chain_scope(scope, table, reflection, connection, association_klass, foreign_table, next_reflection)
+ def next_chain_scope(scope, table, reflection, association_klass, foreign_table, next_reflection)
join_keys = reflection.join_keys(association_klass)
key = join_keys.key
foreign_key = join_keys.foreign_key
@@ -105,9 +86,8 @@ module ActiveRecord
constraint = table[key].eq(foreign_table[foreign_key])
if reflection.type
- value = next_reflection.klass.base_class.name
- bind_val = bind scope, table.table_name, reflection.type, value, connection
- scope = scope.where(table[reflection.type].eq(bind_val))
+ value = transform_value(next_reflection.klass.base_class.name)
+ scope = scope.where(table.name => { reflection.type => value })
end
scope = scope.joins(join(foreign_table, constraint))
@@ -138,10 +118,10 @@ module ActiveRecord
[runtime_reflection, previous_reflection]
end
- def add_constraints(scope, owner, association_klass, refl, connection, chain_head, chain_tail)
+ def add_constraints(scope, owner, association_klass, refl, chain_head, chain_tail)
owner_reflection = chain_tail
table = owner_reflection.alias_name
- scope = last_chain_scope(scope, table, owner_reflection, owner, connection, association_klass)
+ scope = last_chain_scope(scope, table, owner_reflection, owner, association_klass)
reflection = chain_head
loop do
@@ -151,7 +131,7 @@ module ActiveRecord
unless reflection == chain_tail
next_reflection = reflection.next
foreign_table = next_reflection.alias_name
- scope = next_chain_scope(scope, table, reflection, connection, association_klass, foreign_table, next_reflection)
+ scope = next_chain_scope(scope, table, reflection, association_klass, foreign_table, next_reflection)
end
# Exclude the scope of the association itself, because that
diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb
index c63b42e2a0..265a65c4c1 100644
--- a/activerecord/lib/active_record/associations/belongs_to_association.rb
+++ b/activerecord/lib/active_record/associations/belongs_to_association.rb
@@ -68,6 +68,9 @@ module ActiveRecord
def increment_counter(counter_cache_name)
if foreign_key_present?
klass.increment_counter(counter_cache_name, target_id)
+ if target && !stale_target?
+ target.increment(counter_cache_name)
+ end
end
end
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index f2c96e9a2a..4b7591e15c 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -151,6 +151,7 @@ module ActiveRecord
# be chained. Since << flattens its argument list and inserts each record,
# +push+ and +concat+ behave identically.
def concat(*records)
+ records = records.flatten
if owner.new_record?
load_target
concat_records(records)
@@ -549,7 +550,7 @@ module ActiveRecord
def concat_records(records, should_raise = false)
result = true
- records.flatten.each do |record|
+ records.each do |record|
raise_on_type_mismatch!(record)
add_to_target(record) do |rec|
result &&= insert_record(rec, true, should_raise) unless owner.new_record?
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index 2a782c06d0..b574185561 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -101,7 +101,7 @@ module ActiveRecord
end
def update_counter_in_memory(difference, reflection = reflection())
- if has_cached_counter?(reflection)
+ if counter_must_be_updated_by_has_many?(reflection)
counter = cached_counter_attribute_name(reflection)
owner[counter] += difference
owner.send(:clear_attribute_changes, counter) # eww
@@ -118,18 +118,28 @@ module ActiveRecord
# it will be decremented twice.
#
# Hence this method.
- def inverse_updates_counter_cache?(reflection = reflection())
+ def inverse_which_updates_counter_cache(reflection = reflection())
counter_name = cached_counter_attribute_name(reflection)
- inverse_updates_counter_named?(counter_name, reflection)
+ inverse_which_updates_counter_named(counter_name, reflection)
end
+ alias inverse_updates_counter_cache? inverse_which_updates_counter_cache
- def inverse_updates_counter_named?(counter_name, reflection = reflection())
- reflection.klass._reflections.values.any? { |inverse_reflection|
+ def inverse_which_updates_counter_named(counter_name, reflection)
+ reflection.klass._reflections.values.find { |inverse_reflection|
inverse_reflection.belongs_to? &&
inverse_reflection.counter_cache_column == counter_name
}
end
+ def inverse_updates_counter_in_memory?(reflection)
+ inverse = inverse_which_updates_counter_cache(reflection)
+ inverse && inverse == reflection.inverse_of
+ end
+
+ def counter_must_be_updated_by_has_many?(reflection)
+ !inverse_updates_counter_in_memory?(reflection) && has_cached_counter?(reflection)
+ end
+
def delete_count(method, scope)
if method == :delete_all
scope.delete_all
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 f1e784d771..4fcbc98c62 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -143,7 +143,7 @@ module ActiveRecord
stmt.from scope.klass.arel_table
stmt.wheres = arel.constraints
- count = scope.klass.connection.delete(stmt, 'SQL', scope.bind_values)
+ count = scope.klass.connection.delete(stmt, 'SQL', scope.bound_attributes)
end
when :nullify
count = scope.update_all(source_reflection.foreign_key => nil)
diff --git a/activerecord/lib/active_record/associations/join_dependency/join_association.rb b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
index c1ef86a95b..a6ad09a38a 100644
--- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb
+++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
@@ -25,7 +25,7 @@ module ActiveRecord
def join_constraints(foreign_table, foreign_klass, node, join_type, tables, scope_chain, chain)
joins = []
- bind_values = []
+ binds = []
tables = tables.reverse
scope_chain_index = 0
@@ -66,7 +66,7 @@ module ActiveRecord
end
if rel && !rel.arel.constraints.empty?
- bind_values.concat rel.bind_values
+ binds += rel.bound_attributes
constraint = constraint.and rel.arel.constraints
end
@@ -75,7 +75,7 @@ module ActiveRecord
column = klass.columns_hash[reflection.type.to_s]
substitute = klass.connection.substitute_at(column)
- bind_values.push [column, value]
+ binds << Relation::QueryAttribute.new(column.name, value, klass.type_for_attribute(column.name))
constraint = constraint.and table[reflection.type].eq substitute
end
@@ -85,7 +85,7 @@ module ActiveRecord
foreign_table, foreign_klass = table, klass
end
- JoinInformation.new joins, bind_values
+ JoinInformation.new joins, binds
end
# Builds equality condition.
diff --git a/activerecord/lib/active_record/associations/preloader.rb b/activerecord/lib/active_record/associations/preloader.rb
index c3e49007ea..97f4bd3811 100644
--- a/activerecord/lib/active_record/associations/preloader.rb
+++ b/activerecord/lib/active_record/associations/preloader.rb
@@ -89,7 +89,7 @@ module ActiveRecord
# { author: :avatar }
# [ :books, { author: :avatar } ]
- NULL_RELATION = Struct.new(:values, :where_clause).new({}, Relation::WhereClause.empty)
+ NULL_RELATION = Struct.new(:values, :where_clause, :joins_values).new({}, Relation::WhereClause.empty, [])
def preload(records, associations, preload_scope = nil)
records = Array.wrap(records).compact.uniq
diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb
index 23848f5e54..9e4a2b925c 100644
--- a/activerecord/lib/active_record/associations/preloader/association.rb
+++ b/activerecord/lib/active_record/associations/preloader/association.rb
@@ -139,7 +139,11 @@ module ActiveRecord
scope._select! preload_values[:select] || values[:select] || table[Arel.star]
scope.includes! preload_values[:includes] || values[:includes]
- scope.joins! preload_values[:joins] || values[:joins]
+ if preload_scope.joins_values.any?
+ scope.joins!(preload_scope.joins_values)
+ else
+ scope.joins!(reflection_scope.joins_values)
+ end
scope.order! preload_values[:order] || values[:order]
if preload_values[:readonly] || values[:readonly]
diff --git a/activerecord/lib/active_record/attribute.rb b/activerecord/lib/active_record/attribute.rb
index 73bb059495..48b50d9017 100644
--- a/activerecord/lib/active_record/attribute.rb
+++ b/activerecord/lib/active_record/attribute.rb
@@ -88,6 +88,11 @@ module ActiveRecord
value_before_type_cast == other.value_before_type_cast &&
type == other.type
end
+ alias eql? ==
+
+ def hash
+ [self.class, name, value_before_type_cast, type].hash
+ end
protected
@@ -156,6 +161,6 @@ module ActiveRecord
false
end
end
- private_constant :FromDatabase, :FromUser, :Null, :Uninitialized
+ private_constant :FromDatabase, :FromUser, :Null, :Uninitialized, :WithCastValue
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
index 98671178cb..75cc88d4ee 100644
--- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
+++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
@@ -73,7 +73,7 @@ module ActiveRecord
time_zone_aware_types.include?(:not_explicitly_configured)
ActiveSupport::Deprecation.warn(<<-MESSAGE)
Time columns will become time zone aware in Rails 5.1. This
- still cause `String`s to be parsed as if they were in `Time.zone`,
+ still causes `String`s to be parsed as if they were in `Time.zone`,
and `Time`s to be converted to `Time.zone`.
To keep the old behavior, you must add the following to your initializer:
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 787d07c4c2..6022d0102b 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -286,10 +286,10 @@ module ActiveRecord
columns = schema_cache.columns_hash(table_name)
binds = fixture.map do |name, value|
- [columns[name], value]
+ Relation::QueryAttribute.new(name, value, columns[name].cast_type)
end
key_list = fixture.keys.map { |name| quote_column_name(name) }
- value_list = prepare_binds_for_database(binds).map do |_, value|
+ value_list = prepare_binds_for_database(binds).map do |value|
begin
quote(value)
rescue TypeError
@@ -381,7 +381,7 @@ module ActiveRecord
def binds_from_relation(relation, binds)
if relation.is_a?(Relation) && binds.empty?
- relation, binds = relation.arel, relation.bind_values
+ relation, binds = relation.arel, relation.bound_attributes
end
[relation, binds]
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
index c18caa2a2f..62123cdc1f 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
@@ -97,13 +97,7 @@ module ActiveRecord
end
def prepare_binds_for_database(binds) # :nodoc:
- binds.map do |column, value|
- if column
- column_name = column.name
- value = column.cast_type.type_cast_for_database(value)
- end
- [column_name, value]
- end
+ binds.map(&:value_for_database)
end
private
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index c941c9f1eb..436552d300 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -114,7 +114,7 @@ module ActiveRecord
class BindCollector < Arel::Collectors::Bind
def compile(bvs, conn)
casted_binds = conn.prepare_binds_for_database(bvs)
- super(casted_binds.map { |_, value| conn.quote(value) })
+ super(casted_binds.map { |value| conn.quote(value) })
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index 23d8389abb..16f50fc594 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -395,11 +395,9 @@ module ActiveRecord
def exec_stmt(sql, name, binds)
cache = {}
- type_casted_binds = binds.map { |col, val|
- [col, type_cast(val, col)]
- }
+ type_casted_binds = binds.map { |attr| type_cast(attr.value_for_database) }
- log(sql, name, type_casted_binds) do
+ log(sql, name, binds) do
if binds.empty?
stmt = @connection.prepare(sql)
else
@@ -410,7 +408,7 @@ module ActiveRecord
end
begin
- stmt.execute(*type_casted_binds.map { |_, val| val })
+ stmt.execute(*type_casted_binds)
rescue Mysql::Error => e
# Older versions of MySQL leave the prepared statement in a bad
# place when an error occurs. To support older MySQL versions, we
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index f4f9747359..d789069f0b 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -609,12 +609,10 @@ module ActiveRecord
def exec_cache(sql, name, binds)
stmt_key = prepare_statement(sql)
- type_casted_binds = binds.map { |col, val|
- [col, type_cast(val, col)]
- }
+ type_casted_binds = binds.map { |attr| type_cast(attr.value_for_database) }
- log(sql, name, type_casted_binds, stmt_key) do
- @connection.exec_prepared(stmt_key, type_casted_binds.map { |_, val| val })
+ log(sql, name, binds, stmt_key) do
+ @connection.exec_prepared(stmt_key, type_casted_binds)
end
rescue ActiveRecord::StatementInvalid => e
pgerror = e.original_exception
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index 52dce6291a..225c2a8587 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -280,11 +280,9 @@ module ActiveRecord
end
def exec_query(sql, name = nil, binds = [])
- type_casted_binds = binds.map { |col, val|
- [col, type_cast(val, col)]
- }
+ type_casted_binds = binds.map { |attr| type_cast(attr.value_for_database) }
- log(sql, name, type_casted_binds) do
+ log(sql, name, binds) do
# Don't cache statements if they are not prepared
if without_prepared_statement?(binds)
stmt = @connection.prepare(sql)
@@ -302,7 +300,7 @@ module ActiveRecord
stmt = cache[:stmt]
cols = cache[:cols] ||= stmt.columns
stmt.reset!
- stmt.bind_params type_casted_binds.map { |_, val| val }
+ stmt.bind_params type_casted_binds
end
ActiveRecord::Result.new(cols, stmt.to_a)
diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb
index 7d8e0a2063..82596b63df 100644
--- a/activerecord/lib/active_record/counter_cache.rb
+++ b/activerecord/lib/active_record/counter_cache.rb
@@ -156,7 +156,7 @@ module ActiveRecord
def each_counter_cached_associations
_reflections.each do |name, reflection|
- yield association(name) if reflection.belongs_to? && reflection.counter_cache_column
+ yield association(name.to_sym) if reflection.belongs_to? && reflection.counter_cache_column
end
end
diff --git a/activerecord/lib/active_record/log_subscriber.rb b/activerecord/lib/active_record/log_subscriber.rb
index a5c7279db9..6b26d7be78 100644
--- a/activerecord/lib/active_record/log_subscriber.rb
+++ b/activerecord/lib/active_record/log_subscriber.rb
@@ -20,18 +20,14 @@ module ActiveRecord
@odd = false
end
- def render_bind(column, value)
- if column
- if column.binary? && value
- # This specifically deals with the PG adapter that casts bytea columns into a Hash.
- value = value[:value] if value.is_a?(Hash)
- value = "<#{value.bytesize} bytes of binary data>"
- end
-
- [column.name, value]
+ def render_bind(attribute)
+ value = if attribute.type.binary? && attribute.value
+ "<#{attribute.value.bytesize} bytes of binary data>"
else
- [nil, value]
+ attribute.value_for_database
end
+
+ [attribute.name, value]
end
def sql(event)
@@ -47,9 +43,7 @@ module ActiveRecord
binds = nil
unless (payload[:binds] || []).empty?
- binds = " " + payload[:binds].map { |col,v|
- render_bind(col, v)
- }.inspect
+ binds = " " + payload[:binds].map { |attr| render_bind(attr) }.inspect
end
if odd?
diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb
index 641512d323..af0b667262 100644
--- a/activerecord/lib/active_record/model_schema.rb
+++ b/activerecord/lib/active_record/model_schema.rb
@@ -111,17 +111,6 @@ module ActiveRecord
# class Mouse < ActiveRecord::Base
# self.table_name = "mice"
# end
- #
- # Alternatively, you can override the table_name method to define your
- # own computation. (Possibly using <tt>super</tt> to manipulate the default
- # table name.) Example:
- #
- # class Post < ActiveRecord::Base
- # def self.table_name
- # "special_" + super
- # end
- # end
- # Post.table_name # => "special_posts"
def table_name
reset_table_name unless defined?(@table_name)
@table_name
@@ -132,9 +121,6 @@ module ActiveRecord
# class Project < ActiveRecord::Base
# self.table_name = "project"
# end
- #
- # You can also just define your own <tt>self.table_name</tt> method; see
- # the documentation for ActiveRecord::Base#table_name.
def table_name=(value)
value = value && value.to_s
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index 846e1162a9..919bbfa649 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -312,7 +312,6 @@ module ActiveRecord
attr_names.each do |association_name|
if reflection = _reflect_on_association(association_name)
reflection.autosave = true
- add_autosave_association_callbacks(reflection)
nested_attributes_options = self.nested_attributes_options.dup
nested_attributes_options[association_name.to_sym] = options
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 5aee74ae61..8073eb99dd 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -5,12 +5,12 @@ module ActiveRecord
# = Active Record Relation
class Relation
MULTI_VALUE_METHODS = [:includes, :eager_load, :preload, :select, :group,
- :order, :joins, :having, :references,
+ :order, :joins, :references,
:extending, :unscope]
- SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :from, :reordering,
+ SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :reordering,
:reverse_order, :distinct, :create_with, :uniq]
- CLAUSE_METHODS = [:where]
+ CLAUSE_METHODS = [:where, :having, :from]
INVALID_METHODS_FOR_DELETE_ALL = [:limit, :distinct, :offset, :group, :having]
VALUE_METHODS = MULTI_VALUE_METHODS + SINGLE_VALUE_METHODS + CLAUSE_METHODS
@@ -81,7 +81,7 @@ module ActiveRecord
end
relation = scope.where(@klass.primary_key => (id_was || id))
- bvs = binds + relation.bind_values
+ bvs = binds + relation.bound_attributes
um = relation
.arel
.compile_update(substitutes, @klass.primary_key)
@@ -95,11 +95,11 @@ module ActiveRecord
def substitute_values(values) # :nodoc:
binds = values.map do |arel_attr, value|
- [@klass.columns_hash[arel_attr.name], value]
+ QueryAttribute.new(arel_attr.name, value, klass.type_for_attribute(arel_attr.name))
end
- substitutes = values.each_with_index.map do |(arel_attr, _), i|
- [arel_attr, @klass.connection.substitute_at(binds[i][0])]
+ substitutes = values.map do |(arel_attr, _)|
+ [arel_attr, connection.substitute_at(klass.columns_hash[arel_attr.name])]
end
[substitutes, binds]
@@ -342,8 +342,7 @@ module ActiveRecord
stmt.wheres = arel.constraints
end
- bvs = arel.bind_values + bind_values
- @klass.connection.update stmt, 'SQL', bvs
+ @klass.connection.update stmt, 'SQL', bound_attributes
end
# Updates an object (or multiple objects) and saves it to the database, if validations pass.
@@ -467,8 +466,10 @@ module ActiveRecord
invalid_methods = INVALID_METHODS_FOR_DELETE_ALL.select { |method|
if MULTI_VALUE_METHODS.include?(method)
send("#{method}_values").any?
- else
+ elsif SINGLE_VALUE_METHODS.include?(method)
send("#{method}_value")
+ elsif CLAUSE_METHODS.include?(method)
+ send("#{method}_clause").any?
end
}
if invalid_methods.any?
@@ -487,7 +488,7 @@ module ActiveRecord
stmt.wheres = arel.constraints
end
- affected = @klass.connection.delete(stmt, 'SQL', bind_values)
+ affected = @klass.connection.delete(stmt, 'SQL', bound_attributes)
reset
affected
@@ -557,11 +558,10 @@ module ActiveRecord
find_with_associations { |rel| relation = rel }
end
- arel = relation.arel
- binds = arel.bind_values + relation.bind_values
+ binds = relation.bound_attributes
binds = connection.prepare_binds_for_database(binds)
- binds.map! { |_, value| connection.quote(value) }
- collect = visitor.accept(arel.ast, Arel::Collectors::Bind.new)
+ binds.map! { |value| connection.quote(value) }
+ collect = visitor.accept(relation.arel.ast, Arel::Collectors::Bind.new)
collect.substitute_binds(binds).join
end
end
@@ -634,7 +634,7 @@ module ActiveRecord
private
def exec_queries
- @records = eager_loading? ? find_with_associations : @klass.find_by_sql(arel, arel.bind_values + bind_values)
+ @records = eager_loading? ? find_with_associations : @klass.find_by_sql(arel, bound_attributes)
preload = preload_values
preload += includes_values unless eager_loading?
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index 1d4cb1a83b..83745bc169 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -166,7 +166,7 @@ module ActiveRecord
relation.select_values = column_names.map { |cn|
columns_hash.key?(cn) ? arel_table[cn] : cn
}
- result = klass.connection.select_all(relation.arel, nil, relation.arel.bind_values + bind_values)
+ result = klass.connection.select_all(relation.arel, nil, bound_attributes)
result.cast_values(klass.column_types)
end
end
@@ -227,14 +227,11 @@ module ActiveRecord
column_alias = column_name
- bind_values = nil
-
if operation == "count" && (relation.limit_value || relation.offset_value)
# Shortcut when limit is zero.
return 0 if relation.limit_value == 0
query_builder = build_count_subquery(relation, column_name, distinct)
- bind_values = query_builder.bind_values + relation.bind_values
else
column = aggregate_column(column_name)
@@ -245,10 +242,9 @@ module ActiveRecord
relation.select_values = [select_value]
query_builder = relation.arel
- bind_values = query_builder.bind_values + relation.bind_values
end
- result = @klass.connection.select_all(query_builder, nil, bind_values)
+ result = @klass.connection.select_all(query_builder, nil, bound_attributes)
row = result.first
value = row && row.values.first
column = result.column_types.fetch(column_alias) do
@@ -290,7 +286,7 @@ module ActiveRecord
operation,
distinct).as(aggregate_alias)
]
- select_values += select_values unless having_values.empty?
+ select_values += select_values unless having_clause.empty?
select_values.concat group_fields.zip(group_aliases).map { |field,aliaz|
if field.respond_to?(:as)
@@ -304,7 +300,7 @@ module ActiveRecord
relation.group_values = group
relation.select_values = select_values
- calculated_data = @klass.connection.select_all(relation, nil, relation.arel.bind_values + bind_values)
+ calculated_data = @klass.connection.select_all(relation, nil, relation.bound_attributes)
if association
key_ids = calculated_data.collect { |row| row[group_aliases.first] }
@@ -378,11 +374,9 @@ module ActiveRecord
aliased_column = aggregate_column(column_name == :all ? 1 : column_name).as(column_alias)
relation.select_values = [aliased_column]
- arel = relation.arel
- subquery = arel.as(subquery_alias)
+ subquery = relation.arel.as(subquery_alias)
sm = Arel::SelectManager.new relation.engine
- sm.bind_values = arel.bind_values
select_value = operation_over_aggregate_column(column_alias, 'count', distinct)
sm.project(select_value).from(subquery)
end
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index c83abfba06..fb47d915ff 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -311,7 +311,7 @@ module ActiveRecord
end
end
- connection.select_value(relation, "#{name} Exists", relation.arel.bind_values + relation.bind_values) ? true : false
+ connection.select_value(relation, "#{name} Exists", relation.bound_attributes) ? true : false
end
# This method is called whenever no records are found with either a single
@@ -365,7 +365,7 @@ module ActiveRecord
[]
else
arel = relation.arel
- rows = connection.select_all(arel, 'SQL', arel.bind_values + relation.bind_values)
+ rows = connection.select_all(arel, 'SQL', relation.bound_attributes)
join_dependency.instantiate(rows, aliases)
end
end
@@ -410,7 +410,7 @@ module ActiveRecord
relation = relation.except(:select).select(values).distinct!
arel = relation.arel
- id_rows = @klass.connection.select_all(arel, 'SQL', arel.bind_values + relation.bind_values)
+ id_rows = @klass.connection.select_all(arel, 'SQL', relation.bound_attributes)
id_rows.map {|row| row[primary_key]}
end
diff --git a/activerecord/lib/active_record/relation/from_clause.rb b/activerecord/lib/active_record/relation/from_clause.rb
new file mode 100644
index 0000000000..a93952fa30
--- /dev/null
+++ b/activerecord/lib/active_record/relation/from_clause.rb
@@ -0,0 +1,32 @@
+module ActiveRecord
+ class Relation
+ class FromClause
+ attr_reader :value, :name
+
+ def initialize(value, name)
+ @value = value
+ @name = name
+ end
+
+ def binds
+ if value.is_a?(Relation)
+ value.bound_attributes
+ else
+ []
+ end
+ end
+
+ def merge(other)
+ self
+ end
+
+ def empty?
+ value.nil?
+ end
+
+ def self.empty
+ new(nil, nil)
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb
index 82b38b6ec6..65b607ff1c 100644
--- a/activerecord/lib/active_record/relation/merger.rb
+++ b/activerecord/lib/active_record/relation/merger.rb
@@ -51,7 +51,7 @@ module ActiveRecord
NORMAL_VALUES = Relation::VALUE_METHODS -
Relation::CLAUSE_METHODS -
- [:joins, :order, :reverse_order, :lock, :create_with, :reordering, :from] # :nodoc:
+ [:joins, :order, :reverse_order, :lock, :create_with, :reordering] # :nodoc:
def normal_values
NORMAL_VALUES
@@ -120,8 +120,7 @@ module ActiveRecord
end
def merge_single_values
- relation.from_value = other.from_value unless relation.from_value
- relation.lock_value = other.lock_value unless relation.lock_value
+ relation.lock_value ||= other.lock_value
unless other.create_with_value.blank?
relation.create_with_value = (relation.create_with_value || {}).merge(other.create_with_value)
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index 490158f0d5..43e9afe853 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -104,11 +104,11 @@ module ActiveRecord
result[column_name] = attrs
binds += bvs
when Relation
- binds += value.arel.bind_values + value.bind_values
+ binds += value.bound_attributes
else
if can_be_bound?(column_name, value)
result[column_name] = Arel::Nodes::BindParam.new
- binds.push([table.column(column_name), value])
+ binds << Relation::QueryAttribute.new(column_name.to_s, value, table.type(column_name))
end
end
end
diff --git a/activerecord/lib/active_record/relation/query_attribute.rb b/activerecord/lib/active_record/relation/query_attribute.rb
new file mode 100644
index 0000000000..e69319b4de
--- /dev/null
+++ b/activerecord/lib/active_record/relation/query_attribute.rb
@@ -0,0 +1,19 @@
+require 'active_record/attribute'
+
+module ActiveRecord
+ class Relation
+ class QueryAttribute < Attribute
+ def type_cast(value)
+ value
+ end
+
+ def value_for_database
+ @value_for_database ||= super
+ end
+
+ def with_cast_value(value)
+ QueryAttribute.new(name, value, type)
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 31533b0939..0078b0f32e 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -1,8 +1,9 @@
-require 'active_support/core_ext/array/wrap'
-require 'active_support/core_ext/string/filters'
-require 'active_model/forbidden_attributes_protection'
+require "active_record/relation/from_clause"
+require "active_record/relation/query_attribute"
require "active_record/relation/where_clause"
require "active_record/relation/where_clause_factory"
+require 'active_model/forbidden_attributes_protection'
+require 'active_support/core_ext/string/filters'
module ActiveRecord
module QueryMethods
@@ -92,12 +93,8 @@ module ActiveRecord
CODE
end
- def bind_values
- where_clause.binds
- end
-
- def bind_values=(values)
- self.where_clause = Relation::WhereClause.new(where_clause.predicates, values || [])
+ def bound_attributes
+ from_clause.binds + arel.bind_values + where_clause.binds + having_clause.binds
end
def create_with_value # :nodoc:
@@ -434,15 +431,6 @@ module ActiveRecord
self
end
- def bind(value) # :nodoc:
- spawn.bind!(value)
- end
-
- def bind!(value) # :nodoc:
- self.bind_values += [value]
- self
- end
-
# Returns a new relation, which is the result of filtering the current relation
# according to the conditions in the arguments.
#
@@ -605,7 +593,7 @@ module ActiveRecord
def having!(opts, *rest) # :nodoc:
references!(PredicateBuilder.references(opts)) if Hash === opts
- self.having_values += build_where(opts, rest)
+ self.having_clause += having_clause_factory.build(opts, rest)
self
end
@@ -753,10 +741,7 @@ module ActiveRecord
end
def from!(value, subquery_name = nil) # :nodoc:
- self.from_value = [value, subquery_name]
- if value.is_a? Relation
- self.bind_values = value.arel.bind_values + value.bind_values + bind_values
- end
+ self.from_clause = Relation::FromClause.new(value, subquery_name)
self
end
@@ -867,13 +852,10 @@ module ActiveRecord
build_joins(arel, joins_values.flatten) unless joins_values.empty?
- collapse_wheres(arel, (where_clause.predicates - [''])) #TODO: Add uniq with real value comparison / ignore uniqs that have binds
-
- arel.having(*having_values.uniq.reject(&:blank?)) unless having_values.empty?
-
+ arel.where(where_clause.ast) unless where_clause.empty?
+ arel.having(having_clause.ast) unless having_clause.empty?
arel.take(connection.sanitize_limit(limit_value)) if limit_value
arel.skip(offset_value.to_i) if offset_value
-
arel.group(*group_values.uniq.reject(&:blank?)) unless group_values.empty?
build_order(arel)
@@ -881,7 +863,7 @@ module ActiveRecord
build_select(arel, select_values.uniq)
arel.distinct(distinct_value)
- arel.from(build_from) if from_value
+ arel.from(build_from) unless from_clause.empty?
arel.lock(lock_value) if lock_value
arel
@@ -910,38 +892,6 @@ module ActiveRecord
self.send(unscope_code, result)
end
- def custom_join_ast(table, joins)
- joins = joins.reject(&:blank?)
-
- return [] if joins.empty?
-
- joins.map! do |join|
- case join
- when Array
- join = Arel.sql(join.join(' ')) if array_of_strings?(join)
- when String
- join = Arel.sql(join)
- end
- table.create_string_join(join)
- end
- end
-
- def collapse_wheres(arel, wheres)
- predicates = wheres.map do |where|
- next where if ::Arel::Nodes::Equality === where
- where = Arel.sql(where) if String === where
- Arel::Nodes::Grouping.new(where)
- end
-
- arel.where(Arel::Nodes::And.new(predicates)) if predicates.present?
- end
-
- def build_where(opts, other = [])
- where_clause = where_clause_factory.build(opts, other)
- self.bind_values += where_clause.binds
- where_clause.predicates
- end
-
def association_for_table(table_name)
table_name = table_name.to_s
@klass._reflect_on_association(table_name) ||
@@ -949,7 +899,8 @@ module ActiveRecord
end
def build_from
- opts, name = from_value
+ opts = from_clause.value
+ name = from_clause.name
case opts
when Relation
name ||= 'subquery'
@@ -974,13 +925,14 @@ module ActiveRecord
raise 'unknown class: %s' % join.class.name
end
end
+ buckets.default = []
- association_joins = buckets[:association_join] || []
- stashed_association_joins = buckets[:stashed_join] || []
- join_nodes = (buckets[:join_node] || []).uniq
- string_joins = (buckets[:string_join] || []).map(&:strip).uniq
+ association_joins = buckets[:association_join]
+ stashed_association_joins = buckets[:stashed_join]
+ join_nodes = buckets[:join_node].uniq
+ string_joins = buckets[:string_join].map(&:strip).uniq
- join_list = join_nodes + custom_join_ast(manager, string_joins)
+ join_list = join_nodes + convert_join_strings_to_ast(manager, string_joins)
join_dependency = ActiveRecord::Associations::JoinDependency.new(
@klass,
@@ -1000,6 +952,13 @@ module ActiveRecord
manager
end
+ def convert_join_strings_to_ast(table, joins)
+ joins
+ .flatten
+ .reject(&:blank?)
+ .map { |join| table.create_string_join(Arel.sql(join)) }
+ end
+
def build_select(arel, selects)
if !selects.empty?
expanded_select = selects.map do |field|
@@ -1034,10 +993,6 @@ module ActiveRecord
end
end
- def array_of_strings?(o)
- o.is_a?(Array) && o.all? { |obj| obj.is_a?(String) }
- end
-
def build_order(arel)
orders = order_values.uniq
orders.reject!(&:blank?)
@@ -1108,9 +1063,15 @@ module ActiveRecord
def new_where_clause
Relation::WhereClause.empty
end
+ alias new_having_clause new_where_clause
def where_clause_factory
@where_clause_factory ||= Relation::WhereClauseFactory.new(klass, predicate_builder)
end
+ alias having_clause_factory where_clause_factory
+
+ def new_from_clause
+ Relation::FromClause.empty
+ end
end
end
diff --git a/activerecord/lib/active_record/relation/where_clause.rb b/activerecord/lib/active_record/relation/where_clause.rb
index 90fb85cbf1..ae5667dfd6 100644
--- a/activerecord/lib/active_record/relation/where_clause.rb
+++ b/activerecord/lib/active_record/relation/where_clause.rb
@@ -1,9 +1,9 @@
module ActiveRecord
class Relation
class WhereClause # :nodoc:
- attr_reader :predicates, :binds
+ attr_reader :binds
- delegate :empty?, to: :predicates
+ delegate :any?, :empty?, to: :predicates
def initialize(predicates, binds)
@predicates = predicates
@@ -39,7 +39,7 @@ module ActiveRecord
end
end
- binds = self.binds.select(&:first).to_h.transform_keys(&:name)
+ binds = self.binds.map { |attr| [attr.name, attr.value] }.to_h
equalities.map { |node|
name = node.left.name
@@ -53,6 +53,10 @@ module ActiveRecord
}.to_h
end
+ def ast
+ Arel::Nodes::And.new(predicates_with_wrapped_sql_literals)
+ end
+
def ==(other)
other.is_a?(WhereClause) &&
predicates == other.predicates &&
@@ -69,6 +73,8 @@ module ActiveRecord
protected
+ attr_reader :predicates
+
def referenced_columns
@referenced_columns ||= begin
equality_nodes = predicates.select { |n| equality_node?(n) }
@@ -91,7 +97,7 @@ module ActiveRecord
def non_conflicting_binds(other)
conflicts = referenced_columns & other.referenced_columns
conflicts.map! { |node| node.name.to_s }
- binds.reject { |col, _| conflicts.include?(col.name) }
+ binds.reject { |attr| conflicts.include?(attr.name) }
end
def inverted_predicates
@@ -124,9 +130,30 @@ module ActiveRecord
end
def binds_except(columns)
- binds.reject do |column, _|
- columns.include?(column.name)
+ binds.reject do |attr|
+ columns.include?(attr.name)
+ end
+ end
+
+ def predicates_with_wrapped_sql_literals
+ non_empty_predicates.map do |node|
+ if Arel::Nodes::Equality === node
+ node
+ else
+ wrap_sql_literal(node)
+ end
+ end
+ end
+
+ def non_empty_predicates
+ predicates - ['']
+ end
+
+ def wrap_sql_literal(node)
+ if ::String === node
+ node = Arel.sql(node)
end
+ Arel::Nodes::Grouping.new(node)
end
end
end
diff --git a/activerecord/lib/active_record/statement_cache.rb b/activerecord/lib/active_record/statement_cache.rb
index 3047a81ec4..95986c820c 100644
--- a/activerecord/lib/active_record/statement_cache.rb
+++ b/activerecord/lib/active_record/statement_cache.rb
@@ -48,7 +48,7 @@ module ActiveRecord
def sql_for(binds, connection)
val = @values.dup
binds = connection.prepare_binds_for_database(binds)
- @indexes.each { |i| val[i] = connection.quote(binds.shift.last) }
+ @indexes.each { |i| val[i] = connection.quote(binds.shift) }
val.join
end
end
@@ -67,21 +67,21 @@ module ActiveRecord
end
class BindMap # :nodoc:
- def initialize(bind_values)
+ def initialize(bound_attributes)
@indexes = []
- @bind_values = bind_values
+ @bound_attributes = bound_attributes
- bind_values.each_with_index do |(_, value), i|
- if Substitute === value
+ bound_attributes.each_with_index do |attr, i|
+ if Substitute === attr.value
@indexes << i
end
end
end
def bind(values)
- bvs = @bind_values.map(&:dup)
- @indexes.each_with_index { |offset,i| bvs[offset][1] = values[i] }
- bvs
+ bas = @bound_attributes.dup
+ @indexes.each_with_index { |offset,i| bas[offset] = bas[offset].with_cast_value(values[i]) }
+ bas
end
end
@@ -89,7 +89,7 @@ module ActiveRecord
def self.create(connection, block = Proc.new)
relation = block.call Params.new
- bind_map = BindMap.new relation.bind_values
+ bind_map = BindMap.new relation.bound_attributes
query_builder = connection.cacheable_query relation.arel
new query_builder, bind_map
end
diff --git a/activerecord/lib/active_record/table_metadata.rb b/activerecord/lib/active_record/table_metadata.rb
index 62a0c04a0f..6c8792ee80 100644
--- a/activerecord/lib/active_record/table_metadata.rb
+++ b/activerecord/lib/active_record/table_metadata.rb
@@ -22,12 +22,11 @@ module ActiveRecord
arel_table[column_name]
end
- def column(column_name)
+ def type(column_name)
if klass
- klass.columns_hash[column_name.to_s]
+ klass.type_for_attribute(column_name.to_s)
else
- # FIXME: We really shouldn't need to do this.
- ConnectionAdapters::Column.new(column_name.to_s, nil, Type::Value.new)
+ Type::Value.new
end
end
@@ -43,7 +42,7 @@ module ActiveRecord
association_klass = association.klass
arel_table = association_klass.arel_table
else
- type_caster = TypeCaster::Connection.new(klass.connection, table_name)
+ type_caster = TypeCaster::Connection.new(klass, table_name)
association_klass = nil
arel_table = Arel::Table.new(table_name, type_caster: type_caster)
end
diff --git a/activerecord/lib/active_record/type_caster/connection.rb b/activerecord/lib/active_record/type_caster/connection.rb
index 9e4a130b40..1d204edb76 100644
--- a/activerecord/lib/active_record/type_caster/connection.rb
+++ b/activerecord/lib/active_record/type_caster/connection.rb
@@ -1,8 +1,8 @@
module ActiveRecord
module TypeCaster
class Connection
- def initialize(connection, table_name)
- @connection = connection
+ def initialize(klass, table_name)
+ @klass = klass
@table_name = table_name
end
@@ -14,7 +14,8 @@ module ActiveRecord
protected
- attr_reader :connection, :table_name
+ attr_reader :table_name
+ delegate :connection, to: :@klass
private
diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb
index 99e3d7021d..ecf1368a91 100644
--- a/activerecord/test/cases/adapter_test.rb
+++ b/activerecord/test/cases/adapter_test.rb
@@ -193,7 +193,7 @@ module ActiveRecord
author = Author.create!(name: 'john')
Post.create!(author: author, title: 'foo', body: 'bar')
query = author.posts.where(title: 'foo').select(:title)
- assert_equal({"title" => "foo"}, @connection.select_one(query.arel, nil, query.bind_values))
+ assert_equal({"title" => "foo"}, @connection.select_one(query.arel, nil, query.bound_attributes))
assert_equal({"title" => "foo"}, @connection.select_one(query))
assert @connection.select_all(query).is_a?(ActiveRecord::Result)
assert_equal "foo", @connection.select_value(query)
@@ -203,7 +203,7 @@ module ActiveRecord
def test_select_methods_passing_a_relation
Post.create!(title: 'foo', body: 'bar')
query = Post.where(title: 'foo').select(:title)
- assert_equal({"title" => "foo"}, @connection.select_one(query.arel, nil, query.bind_values))
+ assert_equal({"title" => "foo"}, @connection.select_one(query.arel, nil, query.bound_attributes))
assert_equal({"title" => "foo"}, @connection.select_one(query))
assert @connection.select_all(query).is_a?(ActiveRecord::Result)
assert_equal "foo", @connection.select_value(query)
diff --git a/activerecord/test/cases/adapters/mysql/connection_test.rb b/activerecord/test/cases/adapters/mysql/connection_test.rb
index ce01b16362..4762ef43b5 100644
--- a/activerecord/test/cases/adapters/mysql/connection_test.rb
+++ b/activerecord/test/cases/adapters/mysql/connection_test.rb
@@ -94,7 +94,7 @@ class MysqlConnectionTest < ActiveRecord::TestCase
with_example_table do
@connection.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")')
result = @connection.exec_query(
- 'SELECT id, data FROM ex WHERE id = ?', nil, [[nil, 1]])
+ 'SELECT id, data FROM ex WHERE id = ?', nil, [ActiveRecord::Relation::QueryAttribute.new("id", 1, ActiveRecord::Type::Value.new)])
assert_equal 1, result.rows.length
assert_equal 2, result.columns.length
@@ -106,10 +106,10 @@ class MysqlConnectionTest < ActiveRecord::TestCase
def test_exec_typecasts_bind_vals
with_example_table do
@connection.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")')
- column = @connection.columns('ex').find { |col| col.name == 'id' }
+ bind = ActiveRecord::Relation::QueryAttribute.new("id", "1-fuu", ActiveRecord::Type::Integer.new)
result = @connection.exec_query(
- 'SELECT id, data FROM ex WHERE id = ?', nil, [[column, '1-fuu']])
+ 'SELECT id, data FROM ex WHERE id = ?', nil, [bind])
assert_equal 1, result.rows.length
assert_equal 2, result.columns.length
diff --git a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb
index 85db8f4614..1e6511620a 100644
--- a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb
@@ -129,10 +129,10 @@ module ActiveRecord
private
def insert(ctx, data, table='ex')
- binds = data.map { |name, value|
- [ctx.columns(table).find { |x| x.name == name }, value]
+ binds = data.map { |name, value|
+ Relation::QueryAttribute.new(name, value, Type::Value.new)
}
- columns = binds.map(&:first).map(&:name)
+ columns = binds.map(&:name)
sql = "INSERT INTO #{table} (#{columns.join(", ")})
VALUES (#{(['?'] * columns.length).join(', ')})"
diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb
index ab7fd3c6d5..7bf8d12eae 100644
--- a/activerecord/test/cases/adapters/postgresql/connection_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb
@@ -126,11 +126,11 @@ module ActiveRecord
end
def test_statement_key_is_logged
- bindval = 1
- @connection.exec_query('SELECT $1::integer', 'SQL', [[nil, bindval]])
+ bind = Relation::QueryAttribute.new(nil, 1, Type::Value.new)
+ @connection.exec_query('SELECT $1::integer', 'SQL', [bind])
name = @subscriber.payloads.last[:statement_name]
assert name
- res = @connection.exec_query("EXPLAIN (FORMAT JSON) EXECUTE #{name}(#{bindval})")
+ res = @connection.exec_query("EXPLAIN (FORMAT JSON) EXECUTE #{name}(1)")
plan = res.column_types['QUERY PLAN'].type_cast_from_database res.rows.first.first
assert_operator plan.length, :>, 0
end
diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
index 6bb2b26cd5..15c2f48ede 100644
--- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
@@ -284,7 +284,7 @@ module ActiveRecord
string = @connection.quote('foo')
@connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})")
result = @connection.exec_query(
- 'SELECT id, data FROM ex WHERE id = $1', nil, [[nil, 1]])
+ 'SELECT id, data FROM ex WHERE id = $1', nil, [bind_param(1)])
assert_equal 1, result.rows.length
assert_equal 2, result.columns.length
@@ -298,9 +298,9 @@ module ActiveRecord
string = @connection.quote('foo')
@connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})")
- column = @connection.columns('ex').find { |col| col.name == 'id' }
+ bind = ActiveRecord::Relation::QueryAttribute.new("id", "1-fuu", ActiveRecord::Type::Integer.new)
result = @connection.exec_query(
- 'SELECT id, data FROM ex WHERE id = $1', nil, [[column, '1-fuu']])
+ 'SELECT id, data FROM ex WHERE id = $1', nil, [bind])
assert_equal 1, result.rows.length
assert_equal 2, result.columns.length
@@ -437,10 +437,10 @@ module ActiveRecord
private
def insert(ctx, data)
- binds = data.map { |name, value|
- [ctx.columns('ex').find { |x| x.name == name }, value]
+ binds = data.map { |name, value|
+ bind_param(value, name)
}
- columns = binds.map(&:first).map(&:name)
+ columns = binds.map(&:name)
bind_subs = columns.length.times.map { |x| "$#{x + 1}" }
@@ -457,6 +457,10 @@ module ActiveRecord
def connection_without_insert_returning
ActiveRecord::Base.postgresql_connection(ActiveRecord::Base.configurations['arunit'].merge(:insert_returning => false))
end
+
+ def bind_param(value, name = nil)
+ ActiveRecord::Relation::QueryAttribute.new(name, value, ActiveRecord::Type::Value.new)
+ end
end
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb b/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb
index 99c26c4bf7..6937145439 100644
--- a/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb
@@ -55,7 +55,7 @@ class SchemaAuthorizationTest < ActiveRecord::TestCase
set_session_auth
USERS.each do |u|
set_session_auth u
- assert_equal u, @connection.exec_query("SELECT name FROM #{TABLE_NAME} WHERE id = $1", 'SQL', [[nil, 1]]).first['name']
+ assert_equal u, @connection.exec_query("SELECT name FROM #{TABLE_NAME} WHERE id = $1", 'SQL', [bind_param(1)]).first['name']
set_session_auth
end
end
@@ -67,7 +67,7 @@ class SchemaAuthorizationTest < ActiveRecord::TestCase
USERS.each do |u|
@connection.clear_cache!
set_session_auth u
- assert_equal u, @connection.exec_query("SELECT name FROM #{TABLE_NAME} WHERE id = $1", 'SQL', [[nil, 1]]).first['name']
+ assert_equal u, @connection.exec_query("SELECT name FROM #{TABLE_NAME} WHERE id = $1", 'SQL', [bind_param(1)]).first['name']
set_session_auth
end
end
@@ -111,4 +111,7 @@ class SchemaAuthorizationTest < ActiveRecord::TestCase
@connection.session_auth = auth || 'default'
end
+ def bind_param(value)
+ ActiveRecord::Relation::QueryAttribute.new(nil, value, ActiveRecord::Type::Value.new)
+ end
end
diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb
index 642b63357f..83e35ad1a1 100644
--- a/activerecord/test/cases/adapters/postgresql/schema_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb
@@ -151,10 +151,10 @@ class SchemaTest < ActiveRecord::TestCase
def test_schema_change_with_prepared_stmt
altered = false
- @connection.exec_query "select * from developers where id = $1", 'sql', [[nil, 1]]
+ @connection.exec_query "select * from developers where id = $1", 'sql', [bind_param(1)]
@connection.exec_query "alter table developers add column zomg int", 'sql', []
altered = true
- @connection.exec_query "select * from developers where id = $1", 'sql', [[nil, 1]]
+ @connection.exec_query "select * from developers where id = $1", 'sql', [bind_param(1)]
ensure
# We are not using DROP COLUMN IF EXISTS because that syntax is only
# supported by pg 9.X
@@ -435,6 +435,10 @@ class SchemaTest < ActiveRecord::TestCase
assert_equal this_index_column, this_index.columns[0]
assert_equal this_index_name, this_index.name
end
+
+ def bind_param(value)
+ ActiveRecord::Relation::QueryAttribute.new(nil, value, ActiveRecord::Type::Value.new)
+ end
end
class SchemaForeignKeyTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
index 029663e7f4..0527bf8b15 100644
--- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
@@ -83,8 +83,7 @@ module ActiveRecord
def test_exec_insert
with_example_table do
- column = @conn.columns('ex').find { |col| col.name == 'number' }
- vals = [[column, 10]]
+ vals = [Relation::QueryAttribute.new("number", 10, Type::Value.new)]
@conn.exec_insert('insert into ex (number) VALUES (?)', 'SQL', vals)
result = @conn.exec_query(
@@ -157,7 +156,7 @@ module ActiveRecord
with_example_table 'id int, data string' do
@conn.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")')
result = @conn.exec_query(
- 'SELECT id, data FROM ex WHERE id = ?', nil, [[nil, 1]])
+ 'SELECT id, data FROM ex WHERE id = ?', nil, [Relation::QueryAttribute.new(nil, 1, Type::Value.new)])
assert_equal 1, result.rows.length
assert_equal 2, result.columns.length
@@ -169,10 +168,9 @@ module ActiveRecord
def test_exec_query_typecasts_bind_vals
with_example_table 'id int, data string' do
@conn.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")')
- column = @conn.columns('ex').find { |col| col.name == 'id' }
result = @conn.exec_query(
- 'SELECT id, data FROM ex WHERE id = ?', nil, [[column, '1-fuu']])
+ 'SELECT id, data FROM ex WHERE id = ?', nil, [Relation::QueryAttribute.new("id", "1-fuu", Type::Integer.new)])
assert_equal 1, result.rows.length
assert_equal 2, result.columns.length
diff --git a/activerecord/test/cases/associations/association_scope_test.rb b/activerecord/test/cases/associations/association_scope_test.rb
index dd26a85e44..472e270f8c 100644
--- a/activerecord/test/cases/associations/association_scope_test.rb
+++ b/activerecord/test/cases/associations/association_scope_test.rb
@@ -8,12 +8,7 @@ module ActiveRecord
test 'does not duplicate conditions' do
scope = AssociationScope.scope(Author.new.association(:welcome_posts),
Author.connection)
- wheres = scope.where_clause.predicates.map(&:right)
- binds = scope.where_clause.binds.map(&:last)
- wheres.reject! { |node|
- Arel::Nodes::BindParam === node
- }
- assert_equal wheres.uniq, wheres
+ binds = scope.where_clause.binds.map(&:value)
assert_equal binds.uniq, binds
end
end
diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb
index 17394cb6f7..a425b3ed88 100644
--- a/activerecord/test/cases/associations/belongs_to_associations_test.rb
+++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -122,14 +122,14 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
Firm.create("name" => "Apple")
Client.create("name" => "Citibank", :firm_name => "Apple")
citibank_result = Client.all.merge!(:where => {:name => "Citibank"}, :includes => :firm_with_primary_key).first
- assert citibank_result.association_cache.key?(:firm_with_primary_key)
+ assert citibank_result.association(:firm_with_primary_key).loaded?
end
def test_eager_loading_with_primary_key_as_symbol
Firm.create("name" => "Apple")
Client.create("name" => "Citibank", :firm_name => "Apple")
citibank_result = Client.all.merge!(:where => {:name => "Citibank"}, :includes => :firm_with_primary_key_symbols).first
- assert citibank_result.association_cache.key?(:firm_with_primary_key_symbols)
+ assert citibank_result.association(:firm_with_primary_key_symbols).loaded?
end
def test_creating_the_belonging_object
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index 5ef84562ad..19f4384e83 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -316,16 +316,16 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
# would be convenient), because this would cause that scope to be applied to any callbacks etc.
def test_build_and_create_should_not_happen_within_scope
car = cars(:honda)
- scoped_count = car.foo_bulbs.where_clause.predicates.count
+ scope = car.foo_bulbs.where_values_hash
bulb = car.foo_bulbs.build
- assert_not_equal scoped_count, bulb.scope_after_initialize.where_clause.predicates.count
+ assert_not_equal scope, bulb.scope_after_initialize.where_values_hash
bulb = car.foo_bulbs.create
- assert_not_equal scoped_count, bulb.scope_after_initialize.where_clause.predicates.count
+ assert_not_equal scope, bulb.scope_after_initialize.where_values_hash
bulb = car.foo_bulbs.create!
- assert_not_equal scoped_count, bulb.scope_after_initialize.where_clause.predicates.count
+ assert_not_equal scope, bulb.scope_after_initialize.where_values_hash
end
def test_no_sql_should_be_fired_if_association_already_loaded
diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb
index c02509db46..3b6a4038cd 100644
--- a/activerecord/test/cases/associations/has_one_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_associations_test.rb
@@ -237,16 +237,16 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
def test_build_and_create_should_not_happen_within_scope
pirate = pirates(:blackbeard)
- scoped_count = pirate.association(:foo_bulb).scope.where_clause.predicates.count
+ scope = pirate.association(:foo_bulb).scope.where_values_hash
bulb = pirate.build_foo_bulb
- assert_not_equal scoped_count, bulb.scope_after_initialize.where_clause.predicates.count
+ assert_not_equal scope, bulb.scope_after_initialize.where_values_hash
bulb = pirate.create_foo_bulb
- assert_not_equal scoped_count, bulb.scope_after_initialize.where_clause.predicates.count
+ assert_not_equal scope, bulb.scope_after_initialize.where_values_hash
bulb = pirate.create_foo_bulb!
- assert_not_equal scoped_count, bulb.scope_after_initialize.where_clause.predicates.count
+ assert_not_equal scope, bulb.scope_after_initialize.where_values_hash
end
def test_create_association
diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb
index 705f86a699..de358114ab 100644
--- a/activerecord/test/cases/associations_test.rb
+++ b/activerecord/test/cases/associations_test.rb
@@ -141,7 +141,7 @@ class AssociationsTest < ActiveRecord::TestCase
def test_association_with_references
firm = companies(:first_firm)
- assert_equal ['foo'], firm.association_with_references.references_values
+ assert_includes firm.association_with_references.references_values, 'foo'
end
end
@@ -238,7 +238,7 @@ class AssociationProxyTest < ActiveRecord::TestCase
end
def test_scoped_allows_conditions
- assert developers(:david).projects.merge!(where: 'foo').where_clause.predicates.include?('foo')
+ assert developers(:david).projects.merge(where: 'foo').to_sql.include?('foo')
end
test "getting a scope from an association" do
diff --git a/activerecord/test/cases/bind_parameter_test.rb b/activerecord/test/cases/bind_parameter_test.rb
index 66663b3e0e..68d5a232ba 100644
--- a/activerecord/test/cases/bind_parameter_test.rb
+++ b/activerecord/test/cases/bind_parameter_test.rb
@@ -22,8 +22,8 @@ module ActiveRecord
def setup
super
@connection = ActiveRecord::Base.connection
- @subscriber = LogListener.new
- @pk = Topic.columns_hash[Topic.primary_key]
+ @subscriber = LogListener.new
+ @pk = ConnectionAdapters::Column.new(Topic.primary_key, nil, Topic.type_for_attribute(Topic.primary_key))
@subscription = ActiveSupport::Notifications.subscribe('sql.active_record', @subscriber)
end
@@ -40,7 +40,7 @@ module ActiveRecord
def test_binds_are_logged
sub = @connection.substitute_at(@pk)
- binds = [[@pk, 1]]
+ binds = [Relation::QueryAttribute.new("id", 1, Type::Value.new)]
sql = "select * from topics where id = #{sub.to_sql}"
@connection.exec_query(sql, 'SQL', binds)
@@ -49,29 +49,17 @@ module ActiveRecord
assert_equal binds, message[4][:binds]
end
- def test_binds_are_logged_after_type_cast
- sub = @connection.substitute_at(@pk)
- binds = [[@pk, "3"]]
- sql = "select * from topics where id = #{sub.to_sql}"
-
- @connection.exec_query(sql, 'SQL', binds)
-
- message = @subscriber.calls.find { |args| args[4][:sql] == sql }
- assert_equal [[@pk, 3]], message[4][:binds]
- end
-
def test_find_one_uses_binds
Topic.find(1)
- binds = [[@pk, 1]]
- message = @subscriber.calls.find { |args| args[4][:binds] == binds }
+ message = @subscriber.calls.find { |args| args[4][:binds].any? { |attr| attr.value == 1 } }
assert message, 'expected a message with binds'
end
- def test_logs_bind_vars
+ def test_logs_bind_vars_after_type_cast
payload = {
:name => 'SQL',
:sql => 'select * from topics where id = ?',
- :binds => [[@pk, 10]]
+ :binds => [Relation::QueryAttribute.new("id", "10", Type::Integer.new)]
}
event = ActiveSupport::Notifications::Event.new(
'foo',
diff --git a/activerecord/test/cases/counter_cache_test.rb b/activerecord/test/cases/counter_cache_test.rb
index 07a182070b..1f5055b2a2 100644
--- a/activerecord/test/cases/counter_cache_test.rb
+++ b/activerecord/test/cases/counter_cache_test.rb
@@ -180,4 +180,22 @@ class CounterCacheTest < ActiveRecord::TestCase
SpecialTopic.reset_counters(special.id, :lightweight_special_replies)
end
end
+
+ test "counters are updated both in memory and in the database on create" do
+ car = Car.new(engines_count: 0)
+ car.engines = [Engine.new, Engine.new]
+ car.save!
+
+ assert_equal 2, car.engines_count
+ assert_equal 2, car.reload.engines_count
+ end
+
+ test "counter caches are updated in memory when the default value is nil" do
+ car = Car.new(engines_count: nil)
+ car.engines = [Engine.new, Engine.new]
+ car.save!
+
+ assert_equal 2, car.engines_count
+ assert_equal 2, car.reload.engines_count
+ end
end
diff --git a/activerecord/test/cases/explain_test.rb b/activerecord/test/cases/explain_test.rb
index 9d25bdd82a..f1d5511bb8 100644
--- a/activerecord/test/cases/explain_test.rb
+++ b/activerecord/test/cases/explain_test.rb
@@ -28,7 +28,7 @@ if ActiveRecord::Base.connection.supports_explain?
assert_match "SELECT", sql
if binds.any?
assert_equal 1, binds.length
- assert_equal "honda", binds.flatten.last
+ assert_equal "honda", binds.last.value
else
assert_match 'honda', sql
end
diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb
index fe6323ab02..6b18bfe84f 100644
--- a/activerecord/test/cases/inheritance_test.rb
+++ b/activerecord/test/cases/inheritance_test.rb
@@ -294,12 +294,12 @@ class InheritanceTest < ActiveRecord::TestCase
def test_eager_load_belongs_to_something_inherited
account = Account.all.merge!(:includes => :firm).find(1)
- assert account.association_cache.key?(:firm), "nil proves eager load failed"
+ assert account.association(:firm).loaded?, "association was not eager loaded"
end
def test_alt_eager_loading
cabbage = RedCabbage.all.merge!(:includes => :seller).find(4)
- assert cabbage.association_cache.key?(:seller), "nil proves eager load failed"
+ assert cabbage.association(:seller).loaded?, "association was not eager loaded"
end
def test_eager_load_belongs_to_primary_key_quoting
diff --git a/activerecord/test/cases/log_subscriber_test.rb b/activerecord/test/cases/log_subscriber_test.rb
index 97c0350911..4192d12ff4 100644
--- a/activerecord/test/cases/log_subscriber_test.rb
+++ b/activerecord/test/cases/log_subscriber_test.rb
@@ -63,14 +63,6 @@ class LogSubscriberTest < ActiveRecord::TestCase
assert_match(/ruby rails/, logger.debugs.first)
end
- def test_ignore_binds_payload_with_nil_column
- event = Struct.new(:duration, :payload)
-
- logger = TestDebugLogSubscriber.new
- logger.sql(event.new(0, sql: 'hi mom!', binds: [[nil, 1]]))
- assert_equal 1, logger.debugs.length
- end
-
def test_basic_query_logging
Developer.all.load
wait
diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb
index 8bc42b8890..a9e93ad1b6 100644
--- a/activerecord/test/cases/nested_attributes_test.rb
+++ b/activerecord/test/cases/nested_attributes_test.rb
@@ -1037,4 +1037,14 @@ class TestHasManyAutosaveAssociationWhichItselfHasAutosaveAssociations < ActiveR
ShipPart.create!(:ship => @ship, :name => "Stern")
assert_no_queries { @ship.valid? }
end
+
+ test "circular references do not perform unnecessary queries" do
+ ship = Ship.new(name: "The Black Rock")
+ part = ship.parts.build(name: "Stern")
+ ship.treasures.build(looter: part)
+
+ assert_queries 3 do
+ ship.save!
+ end
+ end
end
diff --git a/activerecord/test/cases/relation/merging_test.rb b/activerecord/test/cases/relation/merging_test.rb
index eb76ef6328..0a2e874e4f 100644
--- a/activerecord/test/cases/relation/merging_test.rb
+++ b/activerecord/test/cases/relation/merging_test.rb
@@ -82,26 +82,15 @@ class RelationMergingTest < ActiveRecord::TestCase
left = Post.where(title: "omg").where(comments_count: 1)
right = Post.where(title: "wtf").where(title: "bbq")
- expected = [left.bind_values[1]] + right.bind_values
+ expected = [left.bound_attributes[1]] + right.bound_attributes
merged = left.merge(right)
- assert_equal expected, merged.bind_values
+ assert_equal expected, merged.bound_attributes
assert !merged.to_sql.include?("omg")
assert merged.to_sql.include?("wtf")
assert merged.to_sql.include?("bbq")
end
- def test_merging_keeps_lhs_bind_parameters
- column = Post.columns_hash['id']
- binds = [[column, 20]]
-
- right = Post.where(id: 20)
- left = Post.where(id: 10)
-
- merged = left.merge(right)
- assert_equal binds, merged.bind_values
- end
-
def test_merging_reorders_bind_params
post = Post.first
right = Post.where(id: 1)
diff --git a/activerecord/test/cases/relation/mutation_test.rb b/activerecord/test/cases/relation/mutation_test.rb
index 003d870045..45ead08bd5 100644
--- a/activerecord/test/cases/relation/mutation_test.rb
+++ b/activerecord/test/cases/relation/mutation_test.rb
@@ -81,7 +81,7 @@ module ActiveRecord
assert_equal [], relation.extending_values
end
- (Relation::SINGLE_VALUE_METHODS - [:from, :lock, :reordering, :reverse_order, :create_with]).each do |method|
+ (Relation::SINGLE_VALUE_METHODS - [:lock, :reordering, :reverse_order, :create_with]).each do |method|
test "##{method}!" do
assert relation.public_send("#{method}!", :foo).equal?(relation)
assert_equal :foo, relation.public_send("#{method}_value")
@@ -90,7 +90,7 @@ module ActiveRecord
test '#from!' do
assert relation.from!('foo').equal?(relation)
- assert_equal ['foo', nil], relation.from_value
+ assert_equal 'foo', relation.from_clause.value
end
test '#lock!' do
diff --git a/activerecord/test/cases/relation/where_chain_test.rb b/activerecord/test/cases/relation/where_chain_test.rb
index 00c9a001c0..27bbd80f79 100644
--- a/activerecord/test/cases/relation/where_chain_test.rb
+++ b/activerecord/test/cases/relation/where_chain_test.rb
@@ -11,22 +11,11 @@ module ActiveRecord
@name = 'title'
end
- def test_not_eq
+ def test_not_inverts_where_clause
relation = Post.where.not(title: 'hello')
+ expected_where_clause = Post.where(title: 'hello').where_clause.invert
- assert_equal 1, relation.where_clause.predicates.length
-
- value = relation.where_clause.predicates.first
- bind = relation.where_clause.binds.first
-
- assert_bound_ast value, Post.arel_table[@name], Arel::Nodes::NotEqual
- assert_equal 'hello', bind.last
- end
-
- def test_not_null
- expected = Post.arel_table[@name].not_eq(nil)
- relation = Post.where.not(title: nil)
- assert_equal([expected], relation.where_clause.predicates)
+ assert_equal expected_where_clause, relation.where_clause
end
def test_not_with_nil
@@ -35,146 +24,81 @@ module ActiveRecord
end
end
- def test_not_in
- expected = Post.arel_table[@name].not_in(%w[hello goodbye])
- relation = Post.where.not(title: %w[hello goodbye])
- assert_equal([expected], relation.where_clause.predicates)
- end
-
def test_association_not_eq
- expected = Comment.arel_table[@name].not_eq(Arel::Nodes::BindParam.new)
+ expected = Arel::Nodes::Grouping.new(Comment.arel_table[@name].not_eq(Arel::Nodes::BindParam.new))
relation = Post.joins(:comments).where.not(comments: {title: 'hello'})
- assert_equal(expected.to_sql, relation.where_clause.predicates.first.to_sql)
+ assert_equal(expected.to_sql, relation.where_clause.ast.to_sql)
end
def test_not_eq_with_preceding_where
relation = Post.where(title: 'hello').where.not(title: 'world')
+ expected_where_clause =
+ Post.where(title: 'hello').where_clause +
+ Post.where(title: 'world').where_clause.invert
- value = relation.where_clause.predicates.first
- bind = relation.where_clause.binds.first
- assert_bound_ast value, Post.arel_table[@name], Arel::Nodes::Equality
- assert_equal 'hello', bind.last
-
- value = relation.where_clause.predicates.last
- bind = relation.where_clause.binds.last
- assert_bound_ast value, Post.arel_table[@name], Arel::Nodes::NotEqual
- assert_equal 'world', bind.last
+ assert_equal expected_where_clause, relation.where_clause
end
def test_not_eq_with_succeeding_where
relation = Post.where.not(title: 'hello').where(title: 'world')
+ expected_where_clause =
+ Post.where(title: 'hello').where_clause.invert +
+ Post.where(title: 'world').where_clause
- value = relation.where_clause.predicates.first
- bind = relation.where_clause.binds.first
- assert_bound_ast value, Post.arel_table[@name], Arel::Nodes::NotEqual
- assert_equal 'hello', bind.last
-
- value = relation.where_clause.predicates.last
- bind = relation.where_clause.binds.last
- assert_bound_ast value, Post.arel_table[@name], Arel::Nodes::Equality
- assert_equal 'world', bind.last
- end
-
- def test_not_eq_with_string_parameter
- expected = Arel::Nodes::Not.new("title = 'hello'")
- relation = Post.where.not("title = 'hello'")
- assert_equal([expected], relation.where_clause.predicates)
- end
-
- def test_not_eq_with_array_parameter
- expected = Arel::Nodes::Not.new("title = 'hello'")
- relation = Post.where.not(['title = ?', 'hello'])
- assert_equal([expected], relation.where_clause.predicates)
+ assert_equal expected_where_clause, relation.where_clause
end
def test_chaining_multiple
relation = Post.where.not(author_id: [1, 2]).where.not(title: 'ruby on rails')
+ expected_where_clause =
+ Post.where(author_id: [1, 2]).where_clause.invert +
+ Post.where(title: 'ruby on rails').where_clause.invert
- expected = Post.arel_table['author_id'].not_in([1, 2])
- assert_equal(expected, relation.where_clause.predicates[0])
-
- value = relation.where_clause.predicates[1]
- bind = relation.where_clause.binds.first
-
- assert_bound_ast value, Post.arel_table[@name], Arel::Nodes::NotEqual
- assert_equal 'ruby on rails', bind.last
+ assert_equal expected_where_clause, relation.where_clause
end
def test_rewhere_with_one_condition
relation = Post.where(title: 'hello').where(title: 'world').rewhere(title: 'alone')
+ expected = Post.where(title: 'alone')
- assert_equal 1, relation.where_clause.predicates.size
- value = relation.where_clause.predicates.first
- bind = relation.where_clause.binds.first
- assert_bound_ast value, Post.arel_table[@name], Arel::Nodes::Equality
- assert_equal 'alone', bind.last
+ assert_equal expected.where_clause, relation.where_clause
end
def test_rewhere_with_multiple_overwriting_conditions
relation = Post.where(title: 'hello').where(body: 'world').rewhere(title: 'alone', body: 'again')
+ expected = Post.where(title: 'alone', body: 'again')
- assert_equal 2, relation.where_clause.predicates.size
-
- value = relation.where_clause.predicates.first
- bind = relation.where_clause.binds.first
- assert_bound_ast value, Post.arel_table['title'], Arel::Nodes::Equality
- assert_equal 'alone', bind.last
-
- value = relation.where_clause.predicates[1]
- bind = relation.where_clause.binds[1]
- assert_bound_ast value, Post.arel_table['body'], Arel::Nodes::Equality
- assert_equal 'again', bind.last
- end
-
- def assert_bound_ast value, table, type
- assert_equal table, value.left
- assert_kind_of type, value
- assert_kind_of Arel::Nodes::BindParam, value.right
+ assert_equal expected.where_clause, relation.where_clause
end
def test_rewhere_with_one_overwriting_condition_and_one_unrelated
relation = Post.where(title: 'hello').where(body: 'world').rewhere(title: 'alone')
+ expected = Post.where(body: 'world', title: 'alone')
- assert_equal 2, relation.where_clause.predicates.size
-
- value = relation.where_clause.predicates.first
- bind = relation.where_clause.binds.first
-
- assert_bound_ast value, Post.arel_table['body'], Arel::Nodes::Equality
- assert_equal 'world', bind.last
-
- value = relation.where_clause.predicates.second
- bind = relation.where_clause.binds.second
-
- assert_bound_ast value, Post.arel_table['title'], Arel::Nodes::Equality
- assert_equal 'alone', bind.last
+ assert_equal expected.where_clause, relation.where_clause
end
def test_rewhere_with_range
relation = Post.where(comments_count: 1..3).rewhere(comments_count: 3..5)
- assert_equal 1, relation.where_clause.predicates.size
assert_equal Post.where(comments_count: 3..5), relation
end
def test_rewhere_with_infinite_upper_bound_range
relation = Post.where(comments_count: 1..Float::INFINITY).rewhere(comments_count: 3..5)
- assert_equal 1, relation.where_clause.predicates.size
assert_equal Post.where(comments_count: 3..5), relation
end
def test_rewhere_with_infinite_lower_bound_range
relation = Post.where(comments_count: -Float::INFINITY..1).rewhere(comments_count: 3..5)
- assert_equal 1, relation.where_clause.predicates.size
assert_equal Post.where(comments_count: 3..5), relation
end
def test_rewhere_with_infinite_range
relation = Post.where(comments_count: -Float::INFINITY..Float::INFINITY).rewhere(comments_count: 3..5)
- assert_equal 1, relation.where_clause.predicates.size
assert_equal Post.where(comments_count: 3..5), relation
end
end
diff --git a/activerecord/test/cases/relation/where_clause_test.rb b/activerecord/test/cases/relation/where_clause_test.rb
index 6864be2608..db18980e0b 100644
--- a/activerecord/test/cases/relation/where_clause_test.rb
+++ b/activerecord/test/cases/relation/where_clause_test.rb
@@ -47,15 +47,15 @@ class ActiveRecord::Relation
test "merge removes bind parameters matching overlapping equality clauses" do
a = WhereClause.new(
[table["id"].eq(bind_param), table["name"].eq(bind_param)],
- [[column("id"), 1], [column("name"), "Sean"]],
+ [attribute("id", 1), attribute("name", "Sean")],
)
b = WhereClause.new(
[table["name"].eq(bind_param)],
- [[column("name"), "Jim"]]
+ [attribute("name", "Jim")]
)
expected = WhereClause.new(
[table["id"].eq(bind_param), table["name"].eq(bind_param)],
- [[column("id"), 1], [column("name"), "Jim"]],
+ [attribute("id", 1), attribute("name", "Jim")],
)
assert_equal expected, a.merge(b)
@@ -103,14 +103,48 @@ class ActiveRecord::Relation
table["name"].eq(bind_param),
table["age"].gteq(bind_param),
], [
- [column("name"), "Sean"],
- [column("age"), 30],
+ attribute("name", "Sean"),
+ attribute("age", 30),
])
- expected = WhereClause.new([table["age"].gteq(bind_param)], [[column("age"), 30]])
+ expected = WhereClause.new([table["age"].gteq(bind_param)], [attribute("age", 30)])
assert_equal expected, where_clause.except("id", "name")
end
+ test "ast groups its predicates with AND" do
+ predicates = [
+ table["id"].in([1, 2, 3]),
+ table["name"].eq(bind_param),
+ ]
+ where_clause = WhereClause.new(predicates, [])
+ expected = Arel::Nodes::And.new(predicates)
+
+ assert_equal expected, where_clause.ast
+ end
+
+ test "ast wraps any SQL literals in parenthesis" do
+ random_object = Object.new
+ where_clause = WhereClause.new([
+ table["id"].in([1, 2, 3]),
+ "foo = bar",
+ random_object,
+ ], [])
+ expected = Arel::Nodes::And.new([
+ table["id"].in([1, 2, 3]),
+ Arel::Nodes::Grouping.new(Arel.sql("foo = bar")),
+ Arel::Nodes::Grouping.new(random_object),
+ ])
+
+ assert_equal expected, where_clause.ast
+ end
+
+ test "ast removes any empty strings" do
+ where_clause = WhereClause.new([table["id"].in([1, 2, 3])], [])
+ where_clause_with_empty = WhereClause.new([table["id"].in([1, 2, 3]), ''], [])
+
+ assert_equal where_clause.ast, where_clause_with_empty.ast
+ end
+
private
def table
@@ -121,8 +155,8 @@ class ActiveRecord::Relation
Arel::Nodes::BindParam.new
end
- def column(name)
- ActiveRecord::ConnectionAdapters::Column.new(name, nil, nil)
+ def attribute(name, value)
+ ActiveRecord::Attribute.with_cast_value(name, value, ActiveRecord::Type::Value.new)
end
end
end
diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb
index d75c5435c6..194faa9473 100644
--- a/activerecord/test/cases/relation_test.rb
+++ b/activerecord/test/cases/relation_test.rb
@@ -156,7 +156,7 @@ module ActiveRecord
relation = Relation.new(FakeKlass, :b, nil)
relation = relation.merge where: :lol, readonly: true
- assert_equal [:lol], relation.where_clause.predicates
+ assert_equal Relation::WhereClause.new([:lol], []), relation.where_clause
assert_equal true, relation.readonly_value
end
@@ -191,7 +191,7 @@ module ActiveRecord
relation = Relation.new(klass, :b, nil)
relation.merge!(where: ['foo = ?', 'bar'])
- assert_equal ['foo = bar'], relation.where_clause.predicates
+ assert_equal Relation::WhereClause.new(['foo = bar'], []), relation.where_clause
end
def test_merging_readonly_false
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index d272b9b929..8eb65d39ca 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -39,15 +39,6 @@ class RelationTest < ActiveRecord::TestCase
assert_equal van, Minivan.where(:minivan_id => [van]).to_a.first
end
- def test_bind_values
- relation = Post.all
- assert_equal [], relation.bind_values
-
- relation2 = relation.bind 'foo'
- assert_equal %w{ foo }, relation2.bind_values
- assert_equal [], relation.bind_values
- end
-
def test_two_scopes_with_includes_should_not_drop_any_include
# heat habtm cache
car = Car.incl_engines.incl_tyres.first
@@ -1468,10 +1459,27 @@ class RelationTest < ActiveRecord::TestCase
def test_doesnt_add_having_values_if_options_are_blank
scope = Post.having('')
- assert_equal [], scope.having_values
+ assert scope.having_clause.empty?
scope = Post.having([])
- assert_equal [], scope.having_values
+ assert scope.having_clause.empty?
+ end
+
+ def test_having_with_binds_for_both_where_and_having
+ post = Post.first
+ having_then_where = Post.having(id: post.id).where(title: post.title).group(:id)
+ where_then_having = Post.where(title: post.title).having(id: post.id).group(:id)
+
+ assert_equal [post], having_then_where
+ assert_equal [post], where_then_having
+ end
+
+ def test_multiple_where_and_having_clauses
+ post = Post.first
+ having_then_where = Post.having(id: post.id).where(title: post.title)
+ .having(id: post.id).where(title: post.title).group(:id)
+
+ assert_equal [post], having_then_where
end
def test_references_triggers_eager_loading
@@ -1750,14 +1758,13 @@ class RelationTest < ActiveRecord::TestCase
end
def test_merging_keeps_lhs_bind_parameters
- column = Post.columns_hash['id']
- binds = [[column, 20]]
+ binds = [ActiveRecord::Relation::QueryAttribute.new("id", 20, Post.type_for_attribute("id"))]
right = Post.where(id: 20)
left = Post.where(id: 10)
merged = left.merge(right)
- assert_equal binds, merged.bind_values
+ assert_equal binds, merged.bound_attributes
end
def test_merging_reorders_bind_params
diff --git a/activerecord/test/cases/scoping/default_scoping_test.rb b/activerecord/test/cases/scoping/default_scoping_test.rb
index d0835ba3e5..4137b20c4a 100644
--- a/activerecord/test/cases/scoping/default_scoping_test.rb
+++ b/activerecord/test/cases/scoping/default_scoping_test.rb
@@ -426,19 +426,19 @@ class DefaultScopingTest < ActiveRecord::TestCase
test "additional conditions are ANDed with the default scope" do
scope = DeveloperCalledJamis.where(name: "David")
- assert_equal 2, scope.where_clause.predicates.length
+ assert_equal 2, scope.where_clause.ast.children.length
assert_equal [], scope.to_a
end
test "additional conditions in a scope are ANDed with the default scope" do
scope = DeveloperCalledJamis.david
- assert_equal 2, scope.where_clause.predicates.length
+ assert_equal 2, scope.where_clause.ast.children.length
assert_equal [], scope.to_a
end
test "a scope can remove the condition from the default scope" do
scope = DeveloperCalledJamis.david2
- assert_equal 1, scope.where_clause.predicates.length
- assert_equal Developer.where(name: "David").map(&:id), scope.map(&:id)
+ assert_equal 1, scope.where_clause.ast.children.length
+ assert_equal Developer.where(name: "David"), scope
end
end
diff --git a/activerecord/test/cases/scoping/named_scoping_test.rb b/activerecord/test/cases/scoping/named_scoping_test.rb
index 57c9ff6a8d..8cd94ebcc2 100644
--- a/activerecord/test/cases/scoping/named_scoping_test.rb
+++ b/activerecord/test/cases/scoping/named_scoping_test.rb
@@ -380,8 +380,8 @@ class NamedScopingTest < ActiveRecord::TestCase
end
def test_should_not_duplicates_where_values
- where_values = Topic.where("1=1").scope_with_lambda.where_clause.predicates
- assert_equal ["1=1"], where_values
+ relation = Topic.where("1=1")
+ assert_equal relation.where_clause, relation.scope_with_lambda.where_clause
end
def test_chaining_with_duplicate_joins
diff --git a/activerecord/test/cases/scoping/relation_scoping_test.rb b/activerecord/test/cases/scoping/relation_scoping_test.rb
index 73760e628b..02b32abebf 100644
--- a/activerecord/test/cases/scoping/relation_scoping_test.rb
+++ b/activerecord/test/cases/scoping/relation_scoping_test.rb
@@ -184,7 +184,7 @@ class RelationScopingTest < ActiveRecord::TestCase
rescue
end
- assert !Developer.all.where_clause.predicates.include?("name = 'Jamis'")
+ assert_not Developer.all.to_sql.include?("name = 'Jamis'"), "scope was not restored"
end
def test_default_scope_filters_on_joins
diff --git a/activerecord/test/models/car.rb b/activerecord/test/models/car.rb
index db0f93f63b..81263b79d1 100644
--- a/activerecord/test/models/car.rb
+++ b/activerecord/test/models/car.rb
@@ -8,7 +8,7 @@ class Car < ActiveRecord::Base
has_one :bulb
has_many :tyres
- has_many :engines, :dependent => :destroy
+ has_many :engines, :dependent => :destroy, inverse_of: :my_car
has_many :wheels, :as => :wheelable, :dependent => :destroy
scope :incl_tyres, -> { includes(:tyres) }
diff --git a/activerecord/test/models/ship.rb b/activerecord/test/models/ship.rb
index c2f6d492d8..312caef604 100644
--- a/activerecord/test/models/ship.rb
+++ b/activerecord/test/models/ship.rb
@@ -4,6 +4,7 @@ class Ship < ActiveRecord::Base
belongs_to :pirate
belongs_to :update_only_pirate, :class_name => 'Pirate'
has_many :parts, :class_name => 'ShipPart'
+ has_many :treasures
accepts_nested_attributes_for :parts, :allow_destroy => true
accepts_nested_attributes_for :pirate, :allow_destroy => true, :reject_if => proc(&:empty?)
diff --git a/activerecord/test/models/treasure.rb b/activerecord/test/models/treasure.rb
index a69d3fd3df..ffc65466d5 100644
--- a/activerecord/test/models/treasure.rb
+++ b/activerecord/test/models/treasure.rb
@@ -1,6 +1,7 @@
class Treasure < ActiveRecord::Base
has_and_belongs_to_many :parrots
belongs_to :looter, :polymorphic => true
+ belongs_to :ship
has_many :price_estimates, :as => :estimate_of
has_and_belongs_to_many :rich_people, join_table: 'peoples_treasures', validate: false
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index 21b23d8e0c..1add85cc05 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -772,6 +772,7 @@ ActiveRecord::Schema.define do
t.column :type, :string
t.column :looter_id, :integer
t.column :looter_type, :string
+ t.belongs_to :ship
end
create_table :tyres, force: true do |t|