From 11dd0d63f4a3e52cd3a0b2830553f2b20121a61e Mon Sep 17 00:00:00 2001 From: Ernie Miller Date: Sun, 10 Apr 2011 21:11:28 -0400 Subject: [PATCH] WIP: refactor type casting behavior to better support ranasckers --- lib/ransack/adapters/active_record/base.rb | 3 +- lib/ransack/adapters/active_record/context.rb | 1 - lib/ransack/nodes/attribute.rb | 13 +- lib/ransack/nodes/bindable.rb | 6 +- lib/ransack/nodes/condition.rb | 42 ++--- lib/ransack/nodes/value.rb | 165 ++++++++---------- lib/ransack/predicate.rb | 10 +- .../adapters/active_record/base_spec.rb | 6 +- 8 files changed, 114 insertions(+), 132 deletions(-) diff --git a/lib/ransack/adapters/active_record/base.rb b/lib/ransack/adapters/active_record/base.rb index d5c2044..bda8ccb 100644 --- a/lib/ransack/adapters/active_record/base.rb +++ b/lib/ransack/adapters/active_record/base.rb @@ -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 diff --git a/lib/ransack/adapters/active_record/context.rb b/lib/ransack/adapters/active_record/context.rb index 9c628b5..ec27749 100644 --- a/lib/ransack/adapters/active_record/context.rb +++ b/lib/ransack/adapters/active_record/context.rb @@ -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) diff --git a/lib/ransack/nodes/attribute.rb b/lib/ransack/nodes/attribute.rb index 547c2d8..55f6267 100644 --- a/lib/ransack/nodes/attribute.rb +++ b/lib/ransack/nodes/attribute.rb @@ -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 \ No newline at end of file diff --git a/lib/ransack/nodes/bindable.rb b/lib/ransack/nodes/bindable.rb index ae683e6..4484f52 100644 --- a/lib/ransack/nodes/bindable.rb +++ b/lib/ransack/nodes/bindable.rb @@ -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 diff --git a/lib/ransack/nodes/condition.rb b/lib/ransack/nodes/condition.rb index 078c7fd..e1194e2 100644 --- a/lib/ransack/nodes/condition.rb +++ b/lib/ransack/nodes/condition.rb @@ -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 \ No newline at end of file diff --git a/lib/ransack/nodes/value.rb b/lib/ransack/nodes/value.rb index 73ff871..396f91b 100644 --- a/lib/ransack/nodes/value.rb +++ b/lib/ransack/nodes/value.rb @@ -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 \ No newline at end of file diff --git a/lib/ransack/predicate.rb b/lib/ransack/predicate.rb index fa7c02d..c135bd6 100644 --- a/lib/ransack/predicate.rb +++ b/lib/ransack/predicate.rb @@ -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 diff --git a/spec/ransack/adapters/active_record/base_spec.rb b/spec/ransack/adapters/active_record/base_spec.rb index 8e7044d..d678259 100644 --- a/spec/ransack/adapters/active_record/base_spec.rb +++ b/spec/ransack/adapters/active_record/base_spec.rb @@ -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