ransack 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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