Remove Relation#bind_params

`bound_attributes` is now used universally across the board, removing
the need for the conversion layer. These changes are mostly mechanical,
with the exception of the log subscriber. Additional, we had to
implement `hash` on the attribute objects, so they could be used as a
key for query caching.
This commit is contained in:
Sean Griffin 2015-01-27 13:42:02 -07:00
parent d66ffb656e
commit b06f64c348
30 changed files with 116 additions and 130 deletions

View File

@ -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)

View File

@ -75,7 +75,7 @@ module ActiveRecord
column = klass.columns_hash[reflection.type.to_s]
substitute = klass.connection.substitute_at(column)
binds << Attribute.with_cast_value(column.name, value, klass.type_for_attribute(column.name))
binds << Relation::QueryAttribute.new(column.name, value, klass.type_for_attribute(column.name))
constraint = constraint.and table[reflection.type].eq substitute
end

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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?

View File

@ -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,7 +342,7 @@ module ActiveRecord
stmt.wheres = arel.constraints
end
@klass.connection.update stmt, 'SQL', bind_values
@klass.connection.update stmt, 'SQL', bound_attributes
end
# Updates an object (or multiple objects) and saves it to the database, if validations pass.
@ -488,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
@ -558,9 +558,9 @@ module ActiveRecord
find_with_associations { |rel| relation = rel }
end
binds = relation.bind_values
binds = relation.bound_attributes
binds = connection.prepare_binds_for_database(binds)
binds.map! { |_, value| connection.quote(value) }
binds.map! { |value| connection.quote(value) }
collect = visitor.accept(relation.arel.ast, Arel::Collectors::Bind.new)
collect.substitute_binds(binds).join
end
@ -634,7 +634,7 @@ module ActiveRecord
private
def exec_queries
@records = eager_loading? ? find_with_associations : @klass.find_by_sql(arel, bind_values)
@records = eager_loading? ? find_with_associations : @klass.find_by_sql(arel, bound_attributes)
preload = preload_values
preload += includes_values unless eager_loading?

View File

@ -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, bind_values)
result = klass.connection.select_all(relation.arel, nil, bound_attributes)
result.cast_values(klass.column_types)
end
end
@ -244,7 +244,7 @@ module ActiveRecord
query_builder = relation.arel
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
@ -300,7 +300,7 @@ module ActiveRecord
relation.group_values = group
relation.select_values = select_values
calculated_data = @klass.connection.select_all(relation, nil, relation.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] }

View File

@ -311,7 +311,7 @@ module ActiveRecord
end
end
connection.select_value(relation, "#{name} Exists", 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', 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', relation.bind_values)
id_rows = @klass.connection.select_all(arel, 'SQL', relation.bound_attributes)
id_rows.map {|row| row[primary_key]}
end

View File

@ -108,7 +108,7 @@ module ActiveRecord
else
if can_be_bound?(column_name, value)
result[column_name] = Arel::Nodes::BindParam.new
binds << Attribute.with_cast_value(column_name.to_s, value, table.type(column_name))
binds << Relation::QueryAttribute.new(column_name.to_s, value, table.type(column_name))
end
end
end

View File

@ -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

View File

@ -1,4 +1,5 @@
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'
@ -96,16 +97,6 @@ module ActiveRecord
from_clause.binds + arel.bind_values + where_clause.binds + having_clause.binds
end
def bind_values
# convert to old style
bound_attributes.map do |attribute|
if attribute.name
column = ConnectionAdapters::Column.new(attribute.name, nil, attribute.type)
end
[column, attribute.value_before_type_cast]
end
end
def create_with_value # :nodoc:
@values[:create_with] || {}
end

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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(', ')})"

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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',

View File

@ -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

View File

@ -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

View File

@ -82,10 +82,10 @@ 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")

View File

@ -1758,7 +1758,7 @@ class RelationTest < ActiveRecord::TestCase
end
def test_merging_keeps_lhs_bind_parameters
binds = [ActiveRecord::Attribute.with_cast_value("id", 20, Post.type_for_attribute("id"))]
binds = [ActiveRecord::Relation::QueryAttribute.new("id", 20, Post.type_for_attribute("id"))]
right = Post.where(id: 20)
left = Post.where(id: 10)