Skip to content

Commit

Permalink
Merge branch 'fix-array-builder-wheres'
Browse files Browse the repository at this point in the history
  • Loading branch information
eileencodes committed May 5, 2020
2 parents 200058b + 272f2f0 commit 70ddb8a
Show file tree
Hide file tree
Showing 16 changed files with 159 additions and 10 deletions.
12 changes: 9 additions & 3 deletions activemodel/lib/active_model/type/helpers/numeric.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,21 @@ module Numeric
def serialize(value)
cast(value)
end
alias :unchecked_serialize :serialize

def cast(value)
value = \
# Checks whether the value is numeric. Spaceship operator
# will return nil if value is not numeric.
value = if value <=> 0
value
else
case value
when true then 1
when false then 0
when ::String then value.presence
else value
else value.presence
end
end

super(value)
end

Expand Down
11 changes: 10 additions & 1 deletion activemodel/lib/active_model/type/integer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,24 @@ def serialize(value)
ensure_in_range(super)
end

def serializable?(value)
cast_value = cast(value)
in_range?(cast_value) && super
end

private
attr_reader :range

def in_range?(value)
!value || range.member?(value)
end

def cast_value(value)
value.to_i rescue nil
end

def ensure_in_range(value)
if value && !range.cover?(value)
unless in_range?(value)
raise ActiveModel::RangeError, "#{value} is out of range for #{self.class} with limit #{_limit} bytes"
end
value
Expand Down
9 changes: 9 additions & 0 deletions activemodel/lib/active_model/type/value.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ def initialize(precision: nil, limit: nil, scale: nil)
@limit = limit
end

# Returns true if this type can convert +value+ to a type that is usable
# by the database. For example a boolean type can return +true+ if the
# value parameter is a Ruby boolean, but may return +false+ if the value
# parameter is some other object.
def serializable?(value)
true
end

def type # :nodoc:
end

Expand Down Expand Up @@ -45,6 +53,7 @@ def cast(value)
def serialize(value)
value
end
alias :unchecked_serialize :serialize

# Type casts a value for schema dumping. This method is private, as we are
# hoping to remove it entirely.
Expand Down
7 changes: 6 additions & 1 deletion activerecord/lib/active_record/enum.rb
Original file line number Diff line number Diff line change
Expand Up @@ -139,12 +139,17 @@ def deserialize(value)
mapping.key(subtype.deserialize(value))
end

def serializable?(value)
(value.blank? || mapping.has_key?(value) || mapping.has_value?(value)) && super
end

def serialize(value)
mapping.fetch(value, value)
end
alias :unchecked_serialize :serialize

def assert_valid_value(value)
unless value.blank? || mapping.has_key?(value) || mapping.has_value?(value)
unless serializable?(value)
raise ArgumentError, "'#{value}' is not a valid #{name}"
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,21 @@ def call(attribute, value)
when 0 then NullPredicate
when 1 then predicate_builder.build(attribute, values.first)
else
values.map! do |v|
predicate_builder.build_bind_attribute(attribute.name, v)
if nils.empty? && ranges.empty?
type = attribute.type_caster

casted_values = values.map do |raw_value|
type.unchecked_serialize(raw_value) if type.serializable?(raw_value)
end

casted_values.compact!

return Arel::Nodes::HomogeneousIn.new(casted_values, attribute, :in)
else
attribute.in values.map { |v|
predicate_builder.build_bind_attribute(attribute.name, v)
}
end
values.empty? ? NullPredicate : attribute.in(values)
end

unless nils.empty?
Expand Down
2 changes: 1 addition & 1 deletion activerecord/lib/active_record/relation/where_clause.rb
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ def predicates_unreferenced_by(other)
end

def equality_node?(node)
node.respond_to?(:operator) && node.operator == :==
!node.is_a?(String) && node.equality?
end

def invert_predicate(node)
Expand Down
6 changes: 5 additions & 1 deletion activerecord/lib/active_record/type_caster/map.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,14 @@ def initialize(types)

def type_cast_for_database(attr_name, value)
return value if value.is_a?(Arel::Nodes::BindParam)
type = types.type_for_attribute(attr_name)
type = type_for_attribute(attr_name)
type.serialize(value)
end

def type_for_attribute(name)
types.type_for_attribute(name)
end

private
attr_reader :types
end
Expand Down
4 changes: 4 additions & 0 deletions activerecord/lib/arel/attributes/attribute.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ class Attribute < Struct.new :relation, :name
include Arel::OrderPredications
include Arel::Math

def type_caster
relation.type_for_attribute(name)
end

###
# Create a node for lowering this attribute
def lower
Expand Down
1 change: 1 addition & 0 deletions activerecord/lib/arel/nodes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
# unary
require "arel/nodes/unary"
require "arel/nodes/grouping"
require "arel/nodes/homogeneous_in"
require "arel/nodes/ordering"
require "arel/nodes/ascending"
require "arel/nodes/descending"
Expand Down
2 changes: 2 additions & 0 deletions activerecord/lib/arel/nodes/equality.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ module Nodes
class Equality < Arel::Nodes::Binary
def operator; :== end

def equality?; true; end

def invert
Arel::Nodes::NotEqual.new(left, right)
end
Expand Down
57 changes: 57 additions & 0 deletions activerecord/lib/arel/nodes/homogeneous_in.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# frozen_string_literal: true

module Arel # :nodoc: all
module Nodes
class HomogeneousIn < Node
attr_reader :attribute, :values, :type

def initialize(values, attribute, type)
@values = values
@attribute = attribute
@type = type
end

def hash
ivars.hash
end

def eql?(other)
super || (self.class == other.class && self.ivars == other.ivars)
end
alias :== :eql?

def equality?
true
end

def invert
Arel::Nodes::HomogeneousIn.new(values, attribute, type == :in ? :notin : :in)
end

def left
attribute
end

def table_name
attribute.relation.table_alias || attribute.relation.name
end

def column_name
attribute.name
end

def fetch_attribute(&block)
if attribute
yield attribute
else
expr.fetch_attribute(&block)
end
end

protected
def ivars
[@attribute, @values, @type]
end
end
end
end
2 changes: 2 additions & 0 deletions activerecord/lib/arel/nodes/node.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ def to_sql(engine = Table.engine)

def fetch_attribute
end

def equality?; false; end
end
end
end
4 changes: 4 additions & 0 deletions activerecord/lib/arel/nodes/table_alias.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ def type_cast_for_database(*args)
relation.type_cast_for_database(*args)
end

def type_for_attribute(name)
relation.type_for_attribute(name)
end

def able_to_type_cast?
relation.respond_to?(:able_to_type_cast?) && relation.able_to_type_cast?
end
Expand Down
4 changes: 4 additions & 0 deletions activerecord/lib/arel/table.rb
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ def type_cast_for_database(attribute_name, value)
type_caster.type_cast_for_database(attribute_name, value)
end

def type_for_attribute(name)
type_caster.type_for_attribute(name)
end

def able_to_type_cast?
!type_caster.nil?
end
Expand Down
6 changes: 6 additions & 0 deletions activerecord/lib/arel/visitors/dot.rb
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,12 @@ def visit_Arel_Nodes_Casted(o)
visit_edge o, "attribute"
end

def visit_Arel_Nodes_HomogeneousIn(o)
visit_edge o, "values"
visit_edge o, "type"
visit_edge o, "attribute"
end

def visit_Arel_Attribute(o)
visit_edge o, "relation"
visit_edge o, "name"
Expand Down
25 changes: 25 additions & 0 deletions activerecord/lib/arel/visitors/to_sql.rb
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,31 @@ def visit_Arel_Nodes_Grouping(o, collector)
end
end

def visit_Arel_Nodes_HomogeneousIn(o, collector)
collector.preparable = false
collector << "("

collector << quote_table_name(o.table_name) << "." << quote_column_name(o.column_name)

if o.type == :in
collector << "IN ("
else
collector << "NOT IN ("
end

values = o.values.map { |v| @connection.quote v }

expr = if values.empty?
@connection.quote(nil)
else
values.join(",")
end

collector << expr
collector << "))"
collector
end

def visit_Arel_SelectManager(o, collector)
collector << "("
visit(o.ast, collector) << ")"
Expand Down

0 comments on commit 70ddb8a

Please sign in to comment.