提交 43187f9c 编写于 作者: R Rafael Mendonça França

Merge pull request #10673 from sgrif/master

Add ability to specify how a class is converted to Arel predicate when passed to where
* Add ability to define how a class is converted to Arel predicates.
For example, adding a very vendor specific regex implementation:
regex_handler = proc do |column, value|
Arel::Nodes::InfixOperation.new('~', column, value.source)
end
ActiveRecord::PredicateBuilder.register_handler(Regexp, regex_handler)
*Sean Griffin & @joannecheng*
* Don't allow `quote_value` to be called without a column.
Some adapters require column information to do their job properly.
......
module ActiveRecord
class PredicateBuilder # :nodoc:
@handlers = []
autoload :RelationHandler, 'active_record/relation/predicate_builder/relation_handler'
autoload :ArrayHandler, 'active_record/relation/predicate_builder/array_handler'
def self.resolve_column_aliases(klass, hash)
hash = hash.dup
hash.keys.grep(Symbol) do |key|
......@@ -73,44 +78,36 @@ def self.references(attributes)
end.compact
end
# Define how a class is converted to Arel nodes when passed to +where+.
# The handler can be any object that responds to +call+, and will be used
# for any value that +===+ the class given. For example:
#
# MyCustomDateRange = Struct.new(:start, :end)
# handler = proc do |column, range|
# Arel::Nodes::Between.new(column,
# Arel::Nodes::And.new([range.start, range.end])
# )
# end
# ActiveRecord::PredicateBuilder.register_handler(MyCustomDateRange, handler)
def self.register_handler(klass, handler)
@handlers.unshift([klass, handler])
end
register_handler(BasicObject, ->(attribute, value) { attribute.eq(value) })
# FIXME: I think we need to deprecate this behavior
register_handler(Class, ->(attribute, value) { attribute.eq(value.name) })
register_handler(Base, ->(attribute, value) { attribute.eq(value.id) })
register_handler(Range, ->(attribute, value) { attribute.in(value) })
register_handler(Relation, RelationHandler.new)
register_handler(Array, ArrayHandler.new)
private
def self.build(attribute, value)
case value
when Array
values = value.to_a.map {|x| x.is_a?(Base) ? x.id : x}
ranges, values = values.partition {|v| v.is_a?(Range)}
values_predicate = if values.include?(nil)
values = values.compact
case values.length
when 0
attribute.eq(nil)
when 1
attribute.eq(values.first).or(attribute.eq(nil))
else
attribute.in(values).or(attribute.eq(nil))
end
else
attribute.in(values)
end
handler_for(value).call(attribute, value)
end
array_predicates = ranges.map { |range| attribute.in(range) }
array_predicates << values_predicate
array_predicates.inject { |composite, predicate| composite.or(predicate) }
when ActiveRecord::Relation
value = value.select(value.klass.arel_table[value.klass.primary_key]) if value.select_values.empty?
attribute.in(value.arel.ast)
when Range
attribute.in(value)
when ActiveRecord::Base
attribute.eq(value.id)
when Class
# FIXME: I think we need to deprecate this behavior
attribute.eq(value.name)
else
attribute.eq(value)
end
def self.handler_for(object)
@handlers.detect { |klass, _| klass === object }.last
end
end
end
module ActiveRecord
class PredicateBuilder
class ArrayHandler # :nodoc:
def call(attribute, value)
values = value.map { |x| x.is_a?(Base) ? x.id : x }
ranges, values = values.partition { |v| v.is_a?(Range) }
values_predicate = if values.include?(nil)
values = values.compact
case values.length
when 0
attribute.eq(nil)
when 1
attribute.eq(values.first).or(attribute.eq(nil))
else
attribute.in(values).or(attribute.eq(nil))
end
else
attribute.in(values)
end
array_predicates = ranges.map { |range| attribute.in(range) }
array_predicates << values_predicate
array_predicates.inject { |composite, predicate| composite.or(predicate) }
end
end
end
end
module ActiveRecord
class PredicateBuilder
class RelationHandler # :nodoc:
def call(attribute, value)
if value.select_values.empty?
value = value.select(value.klass.arel_table[value.klass.primary_key])
end
attribute.in(value.arel.ast)
end
end
end
end
require "cases/helper"
require 'models/topic'
module ActiveRecord
class PredicateBuilderTest < ActiveRecord::TestCase
def test_registering_new_handlers
PredicateBuilder.register_handler(Regexp, proc do |column, value|
Arel::Nodes::InfixOperation.new('~', column, value.source)
end)
assert_match %r{["`]topics["`].["`]title["`] ~ 'rails'}, Topic.where(title: /rails/).to_sql
end
end
end
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册