ransack 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. data/lib/ransack.rb +1 -1
  2. data/lib/ransack/adapters/active_record.rb +18 -2
  3. data/lib/ransack/adapters/active_record/3.0/base.rb +34 -0
  4. data/lib/ransack/adapters/active_record/3.0/compat.rb +23 -0
  5. data/lib/ransack/adapters/active_record/3.0/context.rb +168 -0
  6. data/lib/ransack/adapters/active_record/3.0/join_association.rb +44 -0
  7. data/lib/ransack/adapters/active_record/3.0/join_dependency.rb +63 -0
  8. data/lib/ransack/adapters/active_record/base.rb +19 -2
  9. data/lib/ransack/adapters/active_record/context.rb +45 -33
  10. data/lib/ransack/adapters/active_record/join_association.rb +44 -0
  11. data/lib/ransack/adapters/active_record/join_dependency.rb +63 -0
  12. data/lib/ransack/context.rb +25 -65
  13. data/lib/ransack/helpers/form_builder.rb +10 -4
  14. data/lib/ransack/helpers/form_helper.rb +1 -0
  15. data/lib/ransack/locale/en.yml +1 -0
  16. data/lib/ransack/nodes.rb +1 -0
  17. data/lib/ransack/nodes/attribute.rb +21 -4
  18. data/lib/ransack/nodes/bindable.rb +29 -0
  19. data/lib/ransack/nodes/condition.rb +30 -28
  20. data/lib/ransack/nodes/sort.rb +5 -3
  21. data/lib/ransack/nodes/value.rb +84 -100
  22. data/lib/ransack/predicate.rb +1 -11
  23. data/lib/ransack/ransacker.rb +26 -0
  24. data/lib/ransack/search.rb +3 -2
  25. data/lib/ransack/version.rb +1 -1
  26. data/lib/ransack/visitor.rb +64 -0
  27. data/ransack.gemspec +3 -3
  28. data/spec/console.rb +1 -2
  29. data/spec/ransack/adapters/active_record/base_spec.rb +18 -0
  30. data/spec/ransack/adapters/active_record/context_spec.rb +2 -2
  31. data/spec/ransack/helpers/form_builder_spec.rb +4 -0
  32. data/spec/ransack/search_spec.rb +25 -2
  33. data/spec/spec_helper.rb +2 -3
  34. data/spec/support/schema.rb +8 -0
  35. metadata +16 -8
@@ -1,7 +1,9 @@
1
1
  module Ransack
2
2
  module Nodes
3
3
  class Sort < Node
4
- attr_reader :name, :attr, :dir
4
+ include Bindable
5
+
6
+ attr_reader :name, :dir
5
7
  i18n_word :asc, :desc
6
8
 
7
9
  class << self
@@ -22,12 +24,12 @@ module Ransack
22
24
  end
23
25
 
24
26
  def valid?
25
- @attr
27
+ bound? && attr
26
28
  end
27
29
 
28
30
  def name=(name)
29
31
  @name = name
30
- @attr = contextualize(name) unless name.blank?
32
+ context.bind(self, name) unless name.blank?
31
33
  end
32
34
 
33
35
  def dir=(dir)
@@ -1,22 +1,12 @@
1
1
  module Ransack
2
2
  module Nodes
3
3
  class Value < Node
4
- attr_reader :value_before_cast, :type
5
- delegate :blank?, :to => :value_before_cast
4
+ attr_accessor :value
5
+ delegate :blank?, :present?, :to => :value
6
6
 
7
- def initialize(context, value = nil, type = nil)
7
+ def initialize(context, value = nil)
8
8
  super(context)
9
- @value_before_cast = value
10
- self.type = type if type
11
- end
12
-
13
- def value=(val)
14
- @value_before_cast = value
15
- @value = nil
16
- end
17
-
18
- def value
19
- @value ||= cast_to_type(@value_before_cast, @type)
9
+ @value = value
20
10
  end
21
11
 
22
12
  def persisted?
@@ -25,96 +15,90 @@ module Ransack
25
15
 
26
16
  def eql?(other)
27
17
  self.class == other.class &&
28
- self.value_before_cast == other.value_before_cast
18
+ self.value == other.value
29
19
  end
30
20
  alias :== :eql?
31
21
 
32
22
  def hash
33
- value_before_cast.hash
34
- end
35
-
36
- def type=(type)
37
- @value = nil
38
- @type = type
39
- end
40
-
41
- def cast_to_type(val, type)
42
- case type
43
- when :date
44
- cast_to_date(val)
45
- when :datetime, :timestamp, :time
46
- cast_to_time(val)
47
- when :boolean
48
- cast_to_boolean(val)
49
- when :integer
50
- cast_to_integer(val)
51
- when :float
52
- cast_to_float(val)
53
- when :decimal
54
- cast_to_decimal(val)
55
- else
56
- cast_to_string(val)
57
- end
58
- end
59
-
60
- def cast_to_date(val)
61
- if val.respond_to?(:to_date)
62
- val.to_date rescue nil
63
- else
64
- y, m, d = *[val].flatten
65
- m ||= 1
66
- d ||= 1
67
- Date.new(y,m,d) rescue nil
68
- end
69
- end
70
-
71
- # FIXME: doesn't seem to be casting, even with Time.zone.local
72
- def cast_to_time(val)
73
- if val.is_a?(Array)
74
- Time.zone.local(*val) rescue nil
75
- else
76
- unless val.acts_like?(:time)
77
- val = val.is_a?(String) ? Time.zone.parse(val) : val.to_time rescue val
78
- end
79
- val.in_time_zone
80
- end
81
- end
82
-
83
- def cast_to_boolean(val)
84
- if val.is_a?(String) && val.blank?
85
- nil
86
- else
87
- Constants::TRUE_VALUES.include?(val)
88
- end
89
- end
90
-
91
- def cast_to_string(val)
92
- val.respond_to?(:to_s) ? val.to_s : String.new(val)
93
- end
94
-
95
- def cast_to_integer(val)
96
- val.blank? ? nil : val.to_i
97
- end
98
-
99
- def cast_to_float(val)
100
- val.blank? ? nil : val.to_f
101
- end
102
-
103
- def cast_to_decimal(val)
104
- if val.blank?
105
- nil
106
- elsif val.class == BigDecimal
107
- val
108
- elsif val.respond_to?(:to_d)
109
- val.to_d
110
- else
111
- val.to_s.to_d
112
- end
113
- end
114
-
115
- def array_of_arrays?(val)
116
- Array === val && Array === val.first
117
- end
23
+ value.hash
24
+ end
25
+
26
+ def cast_to_type(type)
27
+ case type
28
+ when :date
29
+ cast_to_date(value)
30
+ when :datetime, :timestamp, :time
31
+ cast_to_time(value)
32
+ when :boolean
33
+ cast_to_boolean(value)
34
+ when :integer
35
+ cast_to_integer(value)
36
+ when :float
37
+ cast_to_float(value)
38
+ when :decimal
39
+ cast_to_decimal(value)
40
+ else
41
+ cast_to_string(value)
42
+ end
43
+ end
44
+
45
+ def cast_to_date(val)
46
+ if val.respond_to?(:to_date)
47
+ val.to_date rescue nil
48
+ else
49
+ y, m, d = *[val].flatten
50
+ m ||= 1
51
+ d ||= 1
52
+ Date.new(y,m,d) rescue nil
53
+ end
54
+ end
55
+
56
+ def cast_to_time(val)
57
+ if val.is_a?(Array)
58
+ Time.zone.local(*val) rescue nil
59
+ else
60
+ unless val.acts_like?(:time)
61
+ val = val.is_a?(String) ? Time.zone.parse(val) : val.to_time rescue val
62
+ end
63
+ val.in_time_zone rescue nil
64
+ end
65
+ end
66
+
67
+ def cast_to_boolean(val)
68
+ if val.is_a?(String) && val.blank?
69
+ nil
70
+ else
71
+ Constants::TRUE_VALUES.include?(val)
72
+ end
73
+ end
74
+
75
+ def cast_to_string(val)
76
+ val.respond_to?(:to_s) ? val.to_s : String.new(val)
77
+ end
78
+
79
+ def cast_to_integer(val)
80
+ val.blank? ? nil : val.to_i
81
+ end
82
+
83
+ def cast_to_float(val)
84
+ val.blank? ? nil : val.to_f
85
+ end
86
+
87
+ def cast_to_decimal(val)
88
+ if val.blank?
89
+ nil
90
+ elsif val.class == BigDecimal
91
+ val
92
+ elsif val.respond_to?(:to_d)
93
+ val.to_d
94
+ else
95
+ val.to_s.to_d
96
+ end
97
+ end
98
+
99
+ def array_of_arrays?(val)
100
+ Array === val && Array === val.first
101
+ end
118
102
  end
119
103
  end
120
104
  end
@@ -25,16 +25,6 @@ module Ransack
25
25
  @compound = opts[:compound]
26
26
  end
27
27
 
28
- def format(vals)
29
- if formatter
30
- vals.select {|v| validator ? validator.call(v.value_before_cast) : !v.blank?}.
31
- map {|v| formatter.call(v.value)}
32
- else
33
- vals.select {|v| validator ? validator.call(v.value_before_cast) : !v.blank?}.
34
- map {|v| v.value}
35
- end
36
- end
37
-
38
28
  def eql?(other)
39
29
  self.class == other.class &&
40
30
  self.name == other.name
@@ -47,7 +37,7 @@ module Ransack
47
37
 
48
38
  def validate(vals)
49
39
  if validator
50
- vals.select {|v| validator.call(v.value_before_cast)}.any?
40
+ vals.select {|v| validator.call(v.value)}.any?
51
41
  else
52
42
  vals.select {|v| !v.blank?}.any?
53
43
  end
@@ -0,0 +1,26 @@
1
+ module Ransack
2
+ class Ransacker
3
+
4
+ attr_reader :name, :type, :formatter, :args
5
+
6
+ delegate :call, :to => :@callable
7
+
8
+ def initialize(klass, name, opts = {}, &block)
9
+ @klass, @name = klass, name
10
+
11
+ @type = opts[:type] || :string
12
+ @args = opts[:args] || [:parent]
13
+ @formatter = opts[:formatter]
14
+ @callable = opts[:callable] || block ||
15
+ (@klass.method(name) if @klass.respond_to?(name)) ||
16
+ proc {|parent| parent.table[name]}
17
+
18
+ @klass._ransackers[name.to_s] = self
19
+ end
20
+
21
+ def attr_from(bindable)
22
+ call(*args.map {|arg| bindable.send(arg)})
23
+ end
24
+
25
+ end
26
+ end
@@ -13,15 +13,16 @@ module Ransack
13
13
  :build_and, :build_or, :build_condition,
14
14
  :translate, :to => :base
15
15
 
16
- def initialize(object, params = {})
16
+ def initialize(object, params = {}, options = {})
17
17
  params ||= {}
18
18
  @context = Context.for(object)
19
+ @context.auth_object = options[:auth_object]
19
20
  @base = Nodes::And.new(@context)
20
21
  build(params.with_indifferent_access)
21
22
  end
22
23
 
23
24
  def result(opts = {})
24
- @result ||= @context.evaluate(self, opts)
25
+ @context.evaluate(self, opts)
25
26
  end
26
27
 
27
28
  def build(params)
@@ -1,3 +1,3 @@
1
1
  module Ransack
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -0,0 +1,64 @@
1
+ module Ransack
2
+ class Visitor
3
+
4
+ def accept(object)
5
+ visit(object)
6
+ end
7
+
8
+ def can_accept?(object)
9
+ respond_to? DISPATCH[object.class]
10
+ end
11
+
12
+ def visit_Array(object)
13
+ object.map {|o| accept(o)}.compact
14
+ end
15
+
16
+ def visit_Ransack_Nodes_Condition(object)
17
+ object.arel_predicate if object.valid?
18
+ end
19
+
20
+ def visit_Ransack_Nodes_And(object)
21
+ nodes = object.values.map {|o| accept(o)}.compact
22
+ return nil unless nodes.size > 0
23
+
24
+ if nodes.size > 1
25
+ Arel::Nodes::Grouping.new(Arel::Nodes::And.new(nodes))
26
+ else
27
+ nodes.first
28
+ end
29
+ end
30
+
31
+ def visit_Ransack_Nodes_Sort(object)
32
+ object.attr.send(object.dir) if object.valid?
33
+ end
34
+
35
+ def visit_Ransack_Nodes_Or(object)
36
+ nodes = object.values.map {|o| accept(o)}.compact
37
+ return nil unless nodes.size > 0
38
+
39
+ if nodes.size > 1
40
+ nodes.inject(&:or)
41
+ else
42
+ nodes.first
43
+ end
44
+ end
45
+
46
+ def quoted?(object)
47
+ case object
48
+ when Arel::Nodes::SqlLiteral, Bignum, Fixnum
49
+ false
50
+ else
51
+ true
52
+ end
53
+ end
54
+
55
+ def visit(object)
56
+ send(DISPATCH[object.class], object)
57
+ end
58
+
59
+ DISPATCH = Hash.new do |hash, klass|
60
+ hash[klass] = "visit_#{klass.name.gsub('::', '_')}"
61
+ end
62
+
63
+ end
64
+ end
@@ -14,9 +14,9 @@ Gem::Specification.new do |s|
14
14
 
15
15
  s.rubyforge_project = "ransack"
16
16
 
17
- s.add_dependency 'activerecord', '~> 3.1.0.alpha'
18
- s.add_dependency 'activesupport', '~> 3.1.0.alpha'
19
- s.add_dependency 'actionpack', '~> 3.1.0.alpha'
17
+ s.add_dependency 'activerecord', '~> 3.0'
18
+ s.add_dependency 'activesupport', '~> 3.0'
19
+ s.add_dependency 'actionpack', '~> 3.0'
20
20
  s.add_development_dependency 'rspec', '~> 2.5.0'
21
21
  s.add_development_dependency 'machinist', '~> 1.0.6'
22
22
  s.add_development_dependency 'faker', '~> 0.9.5'
@@ -2,6 +2,7 @@ Bundler.setup
2
2
  require 'machinist/active_record'
3
3
  require 'sham'
4
4
  require 'faker'
5
+ require 'ransack'
5
6
 
6
7
  Dir[File.expand_path('../../spec/{helpers,support,blueprints}/*.rb', __FILE__)].each do |f|
7
8
  require f
@@ -18,5 +19,3 @@ end
18
19
 
19
20
  Schema.create
20
21
 
21
- require 'ransack'
22
-
@@ -24,6 +24,24 @@ module Ransack
24
24
  end
25
25
  end
26
26
 
27
+ describe '#ransacker' do
28
+ it 'creates ransack attributes' do
29
+ s = Person.search(:reversed_name_eq => 'htimS cirA')
30
+ s.result.should have(1).person
31
+ s.result.first.should eq Person.find_by_name('Aric Smith')
32
+ end
33
+
34
+ it 'allows access of attributes through associations' do
35
+ s = Person.search(:children_reversed_name_eq => 'htimS cirA')
36
+ s.result.to_sql.should match /"children_people"."name" = 'Aric Smith'/
37
+ end
38
+
39
+ it 'allows an "attribute" to be an InfixOperation' do
40
+ s = Person.search(:doubled_name_eq => 'Aric SmithAric Smith')
41
+ s.result.first.should eq Person.find_by_name('Aric Smith')
42
+ end if defined?(Arel::Nodes::InfixOperation)
43
+ end
44
+
27
45
  end
28
46
  end
29
47
  end
@@ -11,14 +11,14 @@ module Ransack
11
11
  it 'contextualizes strings to attributes' do
12
12
  attribute = @c.contextualize 'children_children_parent_name'
13
13
  attribute.should be_a Arel::Attributes::Attribute
14
- attribute.name.should eq 'name'
14
+ attribute.name.to_s.should eq 'name'
15
15
  attribute.relation.table_alias.should eq 'parents_people'
16
16
  end
17
17
 
18
18
  it 'builds new associations if not yet built' do
19
19
  attribute = @c.contextualize 'children_articles_title'
20
20
  attribute.should be_a Arel::Attributes::Attribute
21
- attribute.name.should eq 'title'
21
+ attribute.name.to_s.should eq 'title'
22
22
  attribute.relation.name.should eq 'articles'
23
23
  attribute.relation.table_alias.should be_nil
24
24
  end
@@ -20,6 +20,10 @@ module Ransack
20
20
  include router.url_helpers
21
21
  end
22
22
 
23
+ @controller.view_context_class.class_eval do
24
+ include router.url_helpers
25
+ end
26
+
23
27
  @s = Person.search
24
28
  @controller.view_context.search_form_for @s do |f|
25
29
  @f = f