WIP: refactor type casting behavior to better support ranasckers
This commit is contained in:
parent
ecd42f4a83
commit
11dd0d63f4
|
@ -18,7 +18,8 @@ module Ransack
|
|||
end
|
||||
|
||||
opts[:type] ||= :string
|
||||
opts[:call] ||= block || lambda {|parent| parent.table[name]}
|
||||
opts[:args] ||= [:parent]
|
||||
opts[:callable] ||= block || (method(name) if method_defined?(name)) || proc {|parent| parent.table[name]}
|
||||
|
||||
_ransackers[name.to_s] = opts
|
||||
end
|
||||
|
|
|
@ -61,7 +61,6 @@ module Ransack
|
|||
@engine.connection_pool.columns_hash[table][name].type
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
def get_parent_and_attribute_name(str, parent = @base)
|
||||
|
|
|
@ -22,10 +22,6 @@ module Ransack
|
|||
attr
|
||||
end
|
||||
|
||||
def ransacker
|
||||
klass._ransackers[attr_name] if klass.respond_to?(:_ransackers)
|
||||
end
|
||||
|
||||
def type
|
||||
if ransacker
|
||||
return ransacker[:type]
|
||||
|
@ -34,6 +30,14 @@ module Ransack
|
|||
end
|
||||
end
|
||||
|
||||
def apply_predicate_with_values(predicate, values)
|
||||
attr.send(predicate.arel_predicate, predicate.format(values))
|
||||
end
|
||||
|
||||
def cast_value(value)
|
||||
value.cast_to_type(type)
|
||||
end
|
||||
|
||||
def eql?(other)
|
||||
self.class == other.class &&
|
||||
self.name == other.name
|
||||
|
@ -47,6 +51,7 @@ module Ransack
|
|||
def persisted?
|
||||
false
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -5,7 +5,11 @@ module Ransack
|
|||
attr_accessor :parent, :attr_name
|
||||
|
||||
def attr
|
||||
@attr ||= context.table_for(parent)[attr_name]
|
||||
@attr ||= ransacker ? ransacker[:callable].call(parent) : context.table_for(parent)[attr_name]
|
||||
end
|
||||
|
||||
def ransacker
|
||||
klass._ransackers[attr_name] if klass.respond_to?(:_ransackers)
|
||||
end
|
||||
|
||||
def klass
|
||||
|
|
|
@ -4,6 +4,8 @@ module Ransack
|
|||
i18n_word :attribute, :predicate, :combinator, :value
|
||||
i18n_alias :a => :attribute, :p => :predicate, :m => :combinator, :v => :value
|
||||
|
||||
delegate :cast_value, :to => :first_attribute
|
||||
|
||||
attr_reader :predicate
|
||||
|
||||
class << self
|
||||
|
@ -42,6 +44,10 @@ module Ransack
|
|||
values.size <= 1 || predicate.compound || %w(in not_in).include?(predicate.name)
|
||||
end
|
||||
|
||||
def first_attribute
|
||||
attributes.first
|
||||
end
|
||||
|
||||
def attributes
|
||||
@attributes ||= []
|
||||
end
|
||||
|
@ -74,12 +80,12 @@ module Ransack
|
|||
case args
|
||||
when Array
|
||||
args.each do |val|
|
||||
val = Value.new(@context, val, current_type)
|
||||
val = Value.new(@context, val)
|
||||
self.values << val
|
||||
end
|
||||
when Hash
|
||||
args.each do |index, attrs|
|
||||
val = Value.new(@context, attrs[:value], current_type)
|
||||
val = Value.new(@context, attrs[:value])
|
||||
self.values << val
|
||||
end
|
||||
else
|
||||
|
@ -105,13 +111,13 @@ module Ransack
|
|||
end
|
||||
|
||||
def build_value(val = nil)
|
||||
Value.new(@context, val, current_type).tap do |value|
|
||||
Value.new(@context, val).tap do |value|
|
||||
self.values << value
|
||||
end
|
||||
end
|
||||
|
||||
def value
|
||||
predicate.compound ? values.map(&:value) : values.first.value
|
||||
predicate.compound ? values.map {|v| cast_value(v)} : cast_value(values.first)
|
||||
end
|
||||
|
||||
def build(params)
|
||||
|
@ -121,8 +127,6 @@ module Ransack
|
|||
end
|
||||
end
|
||||
|
||||
set_value_types!
|
||||
|
||||
self
|
||||
end
|
||||
|
||||
|
@ -163,47 +167,29 @@ module Ransack
|
|||
alias :p :predicate_name
|
||||
|
||||
def apply_predicate
|
||||
attributes = arel_attributes.compact
|
||||
|
||||
if attributes.size > 1
|
||||
case combinator
|
||||
when 'and'
|
||||
Arel::Nodes::Grouping.new(Arel::Nodes::And.new(
|
||||
attributes.map {|a| a.send(predicate.arel_predicate, predicate.format(values))}
|
||||
attributes.map {|a| a.apply_predicate_with_values(predicate, values)}
|
||||
))
|
||||
when 'or'
|
||||
attributes.inject(attributes.shift.send(predicate.arel_predicate, predicate.format(values))) do |memo, a|
|
||||
memo.or(a.send(predicate.arel_predicate, predicate.format(values)))
|
||||
attributes.inject(attributes.shift.apply_predicate_with_values(predicate, values)) do |memo, a|
|
||||
memo.or(a.apply_predicate_with_values(predicate, values))
|
||||
end
|
||||
end
|
||||
else
|
||||
attributes.first.send(predicate.arel_predicate, predicate.format(values))
|
||||
attributes.first.apply_predicate_with_values(predicate, values)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_value_types!
|
||||
self.values.each {|v| v.type = current_type}
|
||||
end
|
||||
|
||||
def current_type
|
||||
if predicate && predicate.type
|
||||
predicate.type
|
||||
elsif attributes.size > 0
|
||||
attributes.first.type
|
||||
end
|
||||
end
|
||||
|
||||
def valid_combinator?
|
||||
attributes.size < 2 ||
|
||||
['and', 'or'].include?(combinator)
|
||||
end
|
||||
|
||||
def arel_attributes
|
||||
attributes.map(&:attr)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,22 +1,12 @@
|
|||
module Ransack
|
||||
module Nodes
|
||||
class Value < Node
|
||||
attr_reader :value_before_cast, :type
|
||||
delegate :blank?, :to => :value_before_cast
|
||||
attr_accessor :value
|
||||
delegate :blank?, :to => :value
|
||||
|
||||
def initialize(context, value = nil, type = nil)
|
||||
def initialize(context, value = nil)
|
||||
super(context)
|
||||
@value_before_cast = value
|
||||
self.type = type if type
|
||||
end
|
||||
|
||||
def value=(val)
|
||||
@value_before_cast = value
|
||||
@value = nil
|
||||
end
|
||||
|
||||
def value
|
||||
@value ||= cast_to_type(@value_before_cast, @type)
|
||||
@value = value
|
||||
end
|
||||
|
||||
def persisted?
|
||||
|
@ -25,96 +15,91 @@ module Ransack
|
|||
|
||||
def eql?(other)
|
||||
self.class == other.class &&
|
||||
self.value_before_cast == other.value_before_cast
|
||||
self.value == other.value
|
||||
end
|
||||
alias :== :eql?
|
||||
|
||||
def hash
|
||||
value_before_cast.hash
|
||||
value.hash
|
||||
end
|
||||
|
||||
def type=(type)
|
||||
@value = nil
|
||||
@type = type
|
||||
end
|
||||
def cast_to_type(type)
|
||||
case type
|
||||
when :date
|
||||
cast_to_date(value)
|
||||
when :datetime, :timestamp, :time
|
||||
cast_to_time(value)
|
||||
when :boolean
|
||||
cast_to_boolean(value)
|
||||
when :integer
|
||||
cast_to_integer(value)
|
||||
when :float
|
||||
cast_to_float(value)
|
||||
when :decimal
|
||||
cast_to_decimal(value)
|
||||
else
|
||||
cast_to_string(value)
|
||||
end
|
||||
end
|
||||
|
||||
def cast_to_type(val, type)
|
||||
case type
|
||||
when :date
|
||||
cast_to_date(val)
|
||||
when :datetime, :timestamp, :time
|
||||
cast_to_time(val)
|
||||
when :boolean
|
||||
cast_to_boolean(val)
|
||||
when :integer
|
||||
cast_to_integer(val)
|
||||
when :float
|
||||
cast_to_float(val)
|
||||
when :decimal
|
||||
cast_to_decimal(val)
|
||||
else
|
||||
cast_to_string(val)
|
||||
end
|
||||
end
|
||||
def cast_to_date(val)
|
||||
if val.respond_to?(:to_date)
|
||||
val.to_date rescue nil
|
||||
else
|
||||
y, m, d = *[val].flatten
|
||||
m ||= 1
|
||||
d ||= 1
|
||||
Date.new(y,m,d) rescue nil
|
||||
end
|
||||
end
|
||||
|
||||
def cast_to_date(val)
|
||||
if val.respond_to?(:to_date)
|
||||
val.to_date rescue nil
|
||||
else
|
||||
y, m, d = *[val].flatten
|
||||
m ||= 1
|
||||
d ||= 1
|
||||
Date.new(y,m,d) rescue nil
|
||||
end
|
||||
end
|
||||
# FIXME: doesn't seem to be casting, even with Time.zone.local
|
||||
def cast_to_time(val)
|
||||
if val.is_a?(Array)
|
||||
Time.zone.local(*val) rescue nil
|
||||
else
|
||||
unless val.acts_like?(:time)
|
||||
val = val.is_a?(String) ? Time.zone.parse(val) : val.to_time rescue val
|
||||
end
|
||||
val.in_time_zone
|
||||
end
|
||||
end
|
||||
|
||||
# FIXME: doesn't seem to be casting, even with Time.zone.local
|
||||
def cast_to_time(val)
|
||||
if val.is_a?(Array)
|
||||
Time.zone.local(*val) rescue nil
|
||||
else
|
||||
unless val.acts_like?(:time)
|
||||
val = val.is_a?(String) ? Time.zone.parse(val) : val.to_time rescue val
|
||||
end
|
||||
val.in_time_zone
|
||||
end
|
||||
end
|
||||
def cast_to_boolean(val)
|
||||
if val.is_a?(String) && val.blank?
|
||||
nil
|
||||
else
|
||||
Constants::TRUE_VALUES.include?(val)
|
||||
end
|
||||
end
|
||||
|
||||
def cast_to_boolean(val)
|
||||
if val.is_a?(String) && val.blank?
|
||||
nil
|
||||
else
|
||||
Constants::TRUE_VALUES.include?(val)
|
||||
end
|
||||
end
|
||||
def cast_to_string(val)
|
||||
val.respond_to?(:to_s) ? val.to_s : String.new(val)
|
||||
end
|
||||
|
||||
def cast_to_string(val)
|
||||
val.respond_to?(:to_s) ? val.to_s : String.new(val)
|
||||
end
|
||||
def cast_to_integer(val)
|
||||
val.blank? ? nil : val.to_i
|
||||
end
|
||||
|
||||
def cast_to_integer(val)
|
||||
val.blank? ? nil : val.to_i
|
||||
end
|
||||
def cast_to_float(val)
|
||||
val.blank? ? nil : val.to_f
|
||||
end
|
||||
|
||||
def cast_to_float(val)
|
||||
val.blank? ? nil : val.to_f
|
||||
end
|
||||
def cast_to_decimal(val)
|
||||
if val.blank?
|
||||
nil
|
||||
elsif val.class == BigDecimal
|
||||
val
|
||||
elsif val.respond_to?(:to_d)
|
||||
val.to_d
|
||||
else
|
||||
val.to_s.to_d
|
||||
end
|
||||
end
|
||||
|
||||
def cast_to_decimal(val)
|
||||
if val.blank?
|
||||
nil
|
||||
elsif val.class == BigDecimal
|
||||
val
|
||||
elsif val.respond_to?(:to_d)
|
||||
val.to_d
|
||||
else
|
||||
val.to_s.to_d
|
||||
end
|
||||
end
|
||||
|
||||
def array_of_arrays?(val)
|
||||
Array === val && Array === val.first
|
||||
end
|
||||
def array_of_arrays?(val)
|
||||
Array === val && Array === val.first
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -27,11 +27,11 @@ module Ransack
|
|||
|
||||
def format(vals)
|
||||
if formatter
|
||||
vals.select {|v| validator ? validator.call(v.value_before_cast) : !v.blank?}.
|
||||
map {|v| formatter.call(v.value)}
|
||||
vals.select {|v| validator ? validator.call(v.value) : !v.blank?}.
|
||||
map {|v| formatter.call(v.cast_to_type(type))}
|
||||
else
|
||||
vals.select {|v| validator ? validator.call(v.value_before_cast) : !v.blank?}.
|
||||
map {|v| v.value}
|
||||
vals.select {|v| validator ? validator.call(v.value) : !v.blank?}.
|
||||
map {|v| v.cast_to_type(type)}
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -47,7 +47,7 @@ module Ransack
|
|||
|
||||
def validate(vals)
|
||||
if validator
|
||||
vals.select {|v| validator.call(v.value_before_cast)}.any?
|
||||
vals.select {|v| validator.call(v.value)}.any?
|
||||
else
|
||||
vals.select {|v| !v.blank?}.any?
|
||||
end
|
||||
|
|
|
@ -26,9 +26,11 @@ module Ransack
|
|||
|
||||
describe '#ransacker' do
|
||||
it 'creates ransack attributes' do
|
||||
ancestors = Person.singleton_class.ancestors.size
|
||||
Person.ransacker :backwards_name
|
||||
Person.ransacker :backwards_name do |parent|
|
||||
parent.table[:backwards_name]
|
||||
end
|
||||
s = Person.search(:backwards_name_eq => 'blah')
|
||||
s.result.to_sql.should match /"people"."backwards_name" = 'blah'/
|
||||
end
|
||||
end
|
||||
|
||||
|
|
Loading…
Reference in New Issue