1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00

Use bind values for joined tables in where statements

In practical terms, this allows serialized columns and tz aware columns
to be used in wheres that go through joins, where they previously would
not behave correctly. Internally, this removes 1/3 of the cases where we
rely on Arel to perform type casting for us.

There were two non-obvious changes required for this. `update_all` on
relation was merging its bind values with arel's in the wrong order.
Additionally, through associations were assuming there would be no bind
parameters in the preloader (presumably because the where would always
be part of a join)

[Melanie Gilman & Sean Griffin]
This commit is contained in:
Sean Griffin 2014-10-31 09:43:38 -06:00
parent a431df84b5
commit 10f75af933
8 changed files with 43 additions and 1 deletions

View file

@ -1,3 +1,8 @@
* Queries now properly type cast values that are part of a join statement,
even when using type decorators such as `serialize`.
*Melanie Gilman & Sean Griffin*
* MySQL enum type lookups, with values matching another type, no longer result
in an endless loop.

View file

@ -81,6 +81,7 @@ module ActiveRecord
unless reflection_scope.where_values.empty?
scope.includes_values = Array(reflection_scope.values[:includes] || options[:source])
scope.where_values = reflection_scope.values[:where]
scope.bind_values = reflection_scope.bind_values
end
scope.references! reflection_scope.values[:references]

View file

@ -336,7 +336,7 @@ module ActiveRecord
stmt.wheres = arel.constraints
end
bvs = bind_values + arel.bind_values
bvs = arel.bind_values + bind_values
@klass.connection.update stmt, 'SQL', bvs
end

View file

@ -980,6 +980,10 @@ module ActiveRecord
end
end
association_binds, non_binds = non_binds.partition do |column, value|
value.is_a?(Hash) && association_for_table(column)
end
new_opts = {}
binds = []
@ -988,11 +992,24 @@ module ActiveRecord
new_opts[column] = connection.substitute_at(column)
end
association_binds.each do |(column, value)|
association_relation = association_for_table(column).klass.send(:relation)
association_new_opts, association_bind = association_relation.send(:create_binds, value)
new_opts[column] = association_new_opts
binds += association_bind
end
non_binds.each { |column,value| new_opts[column] = value }
[new_opts, binds]
end
def association_for_table(table_name)
table_name = table_name.to_s
@klass._reflect_on_association(table_name) ||
@klass._reflect_on_association(table_name.singularize)
end
def build_from
opts, name = from_value
case opts

View file

@ -2,6 +2,8 @@ require "cases/helper"
require 'models/contact'
require 'models/topic'
require 'models/book'
require 'models/author'
require 'models/post'
class SerializationTest < ActiveRecord::TestCase
fixtures :books
@ -92,4 +94,11 @@ class SerializationTest < ActiveRecord::TestCase
book = klazz.find(books(:awdr).id)
assert_equal 'paperback', book.read_attribute_for_serialization(:format)
end
def test_find_records_by_serialized_attributes_through_join
author = Author.create!(name: "David")
author.serialized_posts.create!(title: "Hello")
assert_equal 1, Author.joins(:serialized_posts).where(name: "David", serialized_posts: { title: "Hello" }).length
end
end

View file

@ -1,5 +1,6 @@
class Author < ActiveRecord::Base
has_many :posts
has_many :serialized_posts
has_one :post
has_many :very_special_comments, :through => :posts
has_many :posts_with_comments, -> { includes(:comments) }, :class_name => "Post"

View file

@ -233,3 +233,7 @@ class PostWithCommentWithDefaultScopeReferencesAssociation < ActiveRecord::Base
has_many :comment_with_default_scope_references_associations, foreign_key: :post_id
has_one :first_comment, class_name: "CommentWithDefaultScopeReferencesAssociation", foreign_key: :post_id
end
class SerializedPost < ActiveRecord::Base
serialize :title
end

View file

@ -585,6 +585,11 @@ ActiveRecord::Schema.define do
t.integer :tags_with_nullify_count, default: 0
end
create_table :serialized_posts, force: true do |t|
t.integer :author_id
t.string :title, null: false
end
create_table :price_estimates, force: true do |t|
t.string :estimate_of_type
t.integer :estimate_of_id