ransack 1.5.1 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +47 -3
  3. data/CHANGELOG.md +106 -18
  4. data/CONTRIBUTING.md +56 -23
  5. data/Gemfile +16 -5
  6. data/README.md +114 -38
  7. data/Rakefile +30 -2
  8. data/lib/ransack.rb +9 -0
  9. data/lib/ransack/adapters/active_record/3.0/compat.rb +11 -8
  10. data/lib/ransack/adapters/active_record/3.0/context.rb +14 -22
  11. data/lib/ransack/adapters/active_record/3.1/context.rb +14 -22
  12. data/lib/ransack/adapters/active_record/context.rb +36 -31
  13. data/lib/ransack/adapters/active_record/ransack/constants.rb +113 -0
  14. data/lib/ransack/adapters/active_record/ransack/context.rb +64 -0
  15. data/lib/ransack/adapters/active_record/ransack/nodes/condition.rb +48 -0
  16. data/lib/ransack/adapters/active_record/ransack/translate.rb +12 -0
  17. data/lib/ransack/adapters/active_record/ransack/visitor.rb +24 -0
  18. data/lib/ransack/adapters/mongoid.rb +13 -0
  19. data/lib/ransack/adapters/mongoid/3.2/.gitkeep +0 -0
  20. data/lib/ransack/adapters/mongoid/attributes/attribute.rb +37 -0
  21. data/lib/ransack/adapters/mongoid/attributes/order_predications.rb +17 -0
  22. data/lib/ransack/adapters/mongoid/attributes/predications.rb +141 -0
  23. data/lib/ransack/adapters/mongoid/base.rb +126 -0
  24. data/lib/ransack/adapters/mongoid/context.rb +208 -0
  25. data/lib/ransack/adapters/mongoid/inquiry_hash.rb +23 -0
  26. data/lib/ransack/adapters/mongoid/ransack/constants.rb +88 -0
  27. data/lib/ransack/adapters/mongoid/ransack/context.rb +60 -0
  28. data/lib/ransack/adapters/mongoid/ransack/nodes/condition.rb +27 -0
  29. data/lib/ransack/adapters/mongoid/ransack/translate.rb +13 -0
  30. data/lib/ransack/adapters/mongoid/ransack/visitor.rb +24 -0
  31. data/lib/ransack/adapters/mongoid/table.rb +35 -0
  32. data/lib/ransack/configuration.rb +22 -4
  33. data/lib/ransack/constants.rb +26 -120
  34. data/lib/ransack/context.rb +32 -60
  35. data/lib/ransack/helpers/form_builder.rb +50 -36
  36. data/lib/ransack/helpers/form_helper.rb +148 -104
  37. data/lib/ransack/naming.rb +11 -11
  38. data/lib/ransack/nodes.rb +2 -0
  39. data/lib/ransack/nodes/bindable.rb +12 -4
  40. data/lib/ransack/nodes/condition.rb +5 -22
  41. data/lib/ransack/nodes/grouping.rb +9 -10
  42. data/lib/ransack/nodes/sort.rb +3 -2
  43. data/lib/ransack/nodes/value.rb +1 -2
  44. data/lib/ransack/predicate.rb +3 -3
  45. data/lib/ransack/search.rb +46 -13
  46. data/lib/ransack/translate.rb +8 -8
  47. data/lib/ransack/version.rb +1 -1
  48. data/lib/ransack/visitor.rb +4 -16
  49. data/ransack.gemspec +1 -0
  50. data/spec/mongoid/adapters/mongoid/base_spec.rb +276 -0
  51. data/spec/mongoid/adapters/mongoid/context_spec.rb +56 -0
  52. data/spec/mongoid/configuration_spec.rb +66 -0
  53. data/spec/mongoid/dependencies_spec.rb +8 -0
  54. data/spec/mongoid/helpers/ransack_helper.rb +11 -0
  55. data/spec/mongoid/nodes/condition_spec.rb +34 -0
  56. data/spec/mongoid/nodes/grouping_spec.rb +13 -0
  57. data/spec/mongoid/predicate_spec.rb +155 -0
  58. data/spec/mongoid/search_spec.rb +446 -0
  59. data/spec/mongoid/support/mongoid.yml +6 -0
  60. data/spec/mongoid/support/schema.rb +128 -0
  61. data/spec/mongoid/translate_spec.rb +14 -0
  62. data/spec/mongoid_spec_helper.rb +59 -0
  63. data/spec/ransack/adapters/active_record/base_spec.rb +68 -35
  64. data/spec/ransack/dependencies_spec.rb +3 -1
  65. data/spec/ransack/helpers/form_builder_spec.rb +6 -6
  66. data/spec/ransack/helpers/form_helper_spec.rb +114 -47
  67. data/spec/ransack/nodes/condition_spec.rb +2 -2
  68. data/spec/ransack/search_spec.rb +2 -6
  69. data/spec/ransack/translate_spec.rb +1 -1
  70. data/spec/spec_helper.rb +2 -3
  71. data/spec/support/schema.rb +9 -0
  72. metadata +49 -4
@@ -0,0 +1,56 @@
1
+ require 'mongoid_spec_helper'
2
+
3
+ module Ransack
4
+ module Adapters
5
+ module Mongoid
6
+ describe Context do
7
+ subject { Context.new(Person) }
8
+
9
+ describe '#relation_for' do
10
+ before { pending "not implemented for mongoid" }
11
+ it 'returns relation for given object' do
12
+ expect(subject.object).to be_an ::ActiveRecord::Relation
13
+ end
14
+ end
15
+
16
+ describe '#evaluate' do
17
+ it 'evaluates search objects' do
18
+ search = Search.new(Person, :name_eq => 'Joe Blow')
19
+ result = subject.evaluate(search)
20
+
21
+ expect(result).to be_an ::Mongoid::Criteria
22
+ expect(result.selector).to eq({ 'name' => 'Joe Blow' })
23
+ end
24
+
25
+ it 'SELECTs DISTINCT when distinct: true' do
26
+ pending "distinct doesn't work"
27
+
28
+ search = Search.new(Person, :name_eq => 'Joe Blow')
29
+ result = subject.evaluate(search, :distinct => true)
30
+
31
+ expect(result).to be_an ::ActiveRecord::Relation
32
+ expect(result.to_sql).to match /SELECT DISTINCT/
33
+ end
34
+ end
35
+
36
+ it 'contextualizes strings to attributes' do
37
+ attribute = subject.contextualize 'name'
38
+ expect(attribute).to be_a ::Ransack::Adapters::Mongoid::Attributes::Attribute
39
+ expect(attribute.name.to_s).to eq 'name'
40
+ # expect(attribute.relation.table_alias).to eq 'parents_people'
41
+ end
42
+
43
+ it 'builds new associations if not yet built' do
44
+ pending "not implemented for mongoid"
45
+
46
+ attribute = subject.contextualize 'children_articles_title'
47
+ expect(attribute).to be_a Arel::Attributes::Attribute
48
+ expect(attribute.name.to_s).to eq 'title'
49
+ expect(attribute.relation.name).to eq 'articles'
50
+ expect(attribute.relation.table_alias).to be_nil
51
+ end
52
+
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,66 @@
1
+ require 'mongoid_spec_helper'
2
+
3
+ module Ransack
4
+ describe Configuration do
5
+ it 'yields Ransack on configure' do
6
+ Ransack.configure { |config| expect(config).to eq Ransack }
7
+ end
8
+
9
+ it 'adds predicates' do
10
+ Ransack.configure do |config|
11
+ config.add_predicate :test_predicate
12
+ end
13
+
14
+ expect(Ransack.predicates).to have_key 'test_predicate'
15
+ expect(Ransack.predicates).to have_key 'test_predicate_any'
16
+ expect(Ransack.predicates).to have_key 'test_predicate_all'
17
+ end
18
+
19
+ it 'avoids creating compound predicates if compounds: false' do
20
+ Ransack.configure do |config|
21
+ config.add_predicate(
22
+ :test_predicate_without_compound,
23
+ :compounds => false
24
+ )
25
+ end
26
+ expect(Ransack.predicates)
27
+ .to have_key 'test_predicate_without_compound'
28
+ expect(Ransack.predicates)
29
+ .not_to have_key 'test_predicate_without_compound_any'
30
+ expect(Ransack.predicates)
31
+ .not_to have_key 'test_predicate_without_compound_all'
32
+ end
33
+
34
+ it 'should have default value for search key' do
35
+ expect(Ransack.options[:search_key]).to eq :q
36
+ end
37
+
38
+ it 'changes default search key parameter' do
39
+ # store original state so we can restore it later
40
+ before = Ransack.options.clone
41
+
42
+ Ransack.configure do |config|
43
+ config.search_key = :query
44
+ end
45
+
46
+ expect(Ransack.options[:search_key]).to eq :query
47
+
48
+ # restore original state so we don't break other tests
49
+ Ransack.options = before
50
+ end
51
+
52
+ it 'adds predicates that take arrays, overriding compounds' do
53
+ Ransack.configure do |config|
54
+ config.add_predicate(
55
+ :test_array_predicate,
56
+ :wants_array => true,
57
+ :compounds => true
58
+ )
59
+ end
60
+
61
+ expect(Ransack.predicates['test_array_predicate'].wants_array).to eq true
62
+ expect(Ransack.predicates).not_to have_key 'test_array_predicate_any'
63
+ expect(Ransack.predicates).not_to have_key 'test_array_predicate_all'
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,8 @@
1
+ unless ::ActiveSupport::VERSION::STRING >= '4'
2
+ describe 'Ransack' do
3
+ it 'can be required without errors' do
4
+ output = `bundle exec ruby -e "require 'ransack'" 2>&1`
5
+ expect(output).to be_empty
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,11 @@
1
+ module RansackHelper
2
+ def quote_table_name(table)
3
+ # ActiveRecord::Base.connection.quote_table_name(table)
4
+ table
5
+ end
6
+
7
+ def quote_column_name(column)
8
+ # ActiveRecord::Base.connection.quote_column_name(column)
9
+ column
10
+ end
11
+ end
@@ -0,0 +1,34 @@
1
+ require 'mongoid_spec_helper'
2
+
3
+ module Ransack
4
+ module Nodes
5
+ describe Condition do
6
+
7
+ context 'with multiple values and an _any predicate' do
8
+ subject { Condition.extract(Context.for(Person), 'name_eq_any', Person.first(2).map(&:name)) }
9
+
10
+ specify { expect(subject.values.size).to eq(2) }
11
+ end
12
+
13
+ context 'with an invalid predicate' do
14
+ subject { Condition.extract(Context.for(Person), 'name_invalid', Person.first.name) }
15
+
16
+ context "when ignore_unknown_conditions is false" do
17
+ before do
18
+ Ransack.configure { |config| config.ignore_unknown_conditions = false }
19
+ end
20
+
21
+ specify { expect { subject }.to raise_error ArgumentError }
22
+ end
23
+
24
+ context "when ignore_unknown_conditions is true" do
25
+ before do
26
+ Ransack.configure { |config| config.ignore_unknown_conditions = true }
27
+ end
28
+
29
+ specify { subject.should be_nil }
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,13 @@
1
+ require 'mongoid_spec_helper'
2
+
3
+ module Ransack
4
+ module Nodes
5
+ describe Grouping do
6
+ before do
7
+ @g = 1
8
+ end
9
+
10
+
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,155 @@
1
+ require 'mongoid_spec_helper'
2
+
3
+ module Ransack
4
+ describe Predicate do
5
+
6
+ before do
7
+ @s = Search.new(Person)
8
+ end
9
+
10
+ shared_examples 'wildcard escaping' do |method, value|
11
+ it 'automatically converts integers to strings' do
12
+ subject.parent_id_cont = 1
13
+ expect { subject.result }.to_not raise_error
14
+ end
15
+
16
+ it "escapes '%', '.' and '\\\\' in value" do
17
+ subject.send(:"#{method}=", '%._\\')
18
+ expect(subject.result.selector).to eq(value)
19
+ end
20
+ end
21
+
22
+ describe 'eq' do
23
+ it 'generates an equality condition for boolean true' do
24
+ @s.awesome_eq = true
25
+ expect(@s.result.selector).to eq({ "awesome" => true })
26
+ end
27
+
28
+ it 'generates an equality condition for boolean false' do
29
+ @s.awesome_eq = false
30
+ expect(@s.result.selector).to eq({ "awesome" => false })
31
+ end
32
+
33
+ it 'does not generate a condition for nil' do
34
+ @s.awesome_eq = nil
35
+ expect(@s.result.selector).to eq({ })
36
+ end
37
+ end
38
+
39
+ describe 'cont' do
40
+ it_has_behavior 'wildcard escaping', :name_cont, { 'name' => /%\._\\/i } do
41
+ subject { @s }
42
+ end
43
+
44
+ it 'generates a regex query' do
45
+ @s.name_cont = 'ric'
46
+ expect(@s.result.selector).to eq({ 'name' => /ric/i })
47
+ end
48
+ end
49
+
50
+ describe 'not_cont' do
51
+ it_has_behavior 'wildcard escaping', :name_not_cont, { "$not" => { 'name' => /%\._\\/i } } do
52
+ subject { @s }
53
+ end
54
+
55
+ it 'generates a regex query' do
56
+ @s.name_not_cont = 'ric'
57
+ expect(@s.result.selector).to eq({ "$not" => { 'name' => /ric/i } })
58
+ end
59
+ end
60
+
61
+ describe 'null' do
62
+ it 'generates a value IS NULL query' do
63
+ @s.name_null = true
64
+ expect(@s.result.selector).to eq({ 'name' => nil })
65
+ end
66
+
67
+ it 'generates a value IS NOT NULL query when assigned false' do
68
+ @s.name_null = false
69
+ expect(@s.result.selector).to eq( { 'name' => { '$ne' => nil } })
70
+ end
71
+ end
72
+
73
+ describe 'not_null' do
74
+ it 'generates a value IS NOT NULL query' do
75
+ @s.name_not_null = true
76
+ expect(@s.result.selector).to eq({ 'name' => { '$ne' => nil } })
77
+ end
78
+
79
+ it 'generates a value IS NULL query when assigned false' do
80
+ @s.name_not_null = false
81
+ expect(@s.result.selector).to eq({ 'name' => nil })
82
+ end
83
+ end
84
+
85
+ describe 'present' do
86
+ it %q[generates a value IS NOT NULL AND value != '' query] do
87
+ @s.name_present = true
88
+ expect(@s.result.selector).to eq({ '$and' => [ { 'name' => { '$ne' => nil } }, { 'name' => { '$ne' => '' } } ] })
89
+ end
90
+
91
+ it %q[generates a value IS NULL OR value = '' query when assigned false] do
92
+ @s.name_present = false
93
+ expect(@s.result.selector).to eq({ '$or' => [ { 'name' => nil }, { 'name' => '' } ] })
94
+ end
95
+ end
96
+
97
+ describe 'blank' do
98
+ it %q[generates a value IS NULL OR value = '' query] do
99
+ @s.name_blank = true
100
+ expect(@s.result.selector).to eq({ '$or' => [ { 'name' => nil}, { 'name' => '' } ] })
101
+ end
102
+
103
+ it %q[generates a value IS NOT NULL AND value != '' query when assigned false] do
104
+ @s.name_blank = false
105
+ expect(@s.result.selector).to eq({ '$and' => [ { 'name' => { '$ne' => nil}}, { 'name' => { '$ne' => '' }} ] })
106
+ end
107
+ end
108
+
109
+ describe 'gt' do
110
+ it 'generates an greater than for time' do
111
+ time = Time.now
112
+ @s.created_at_gt = time
113
+ expect(@s.result.selector).to eq({ "created_at" => { '$gt' => time } })
114
+ end
115
+ end
116
+
117
+ describe 'lt' do
118
+ it 'generates an greater than for time' do
119
+ time = Time.now
120
+ @s.created_at_lt = time
121
+ expect(@s.result.selector).to eq({ "created_at" => { '$lt' => time } })
122
+ end
123
+ end
124
+
125
+ describe 'gteq' do
126
+ it 'generates an greater than for time' do
127
+ time = Time.now
128
+ @s.created_at_gteq = time
129
+ expect(@s.result.selector).to eq({ "created_at" => { '$gte' => time } })
130
+ end
131
+ end
132
+
133
+ describe 'lteq' do
134
+ it 'generates an greater than for time' do
135
+ time = Time.now
136
+ @s.created_at_lteq = time
137
+ expect(@s.result.selector).to eq({ "created_at" => { '$lte' => time } })
138
+ end
139
+ end
140
+
141
+ describe 'starts_with' do
142
+ it 'generates an starts_with' do
143
+ @s.name_start = 'ric'
144
+ expect(@s.result.selector).to eq({ "name" => /\Aric/i })
145
+ end
146
+ end
147
+
148
+ describe 'ends_with' do
149
+ it 'generates an ends_with' do
150
+ @s.name_end = 'ric'
151
+ expect(@s.result.selector).to eq({ "name" => /ric\Z/i })
152
+ end
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,446 @@
1
+ require 'mongoid_spec_helper'
2
+
3
+ module Ransack
4
+ describe Search do
5
+ describe '#initialize' do
6
+ it "removes empty conditions before building" do
7
+ expect_any_instance_of(Search).to receive(:build).with({})
8
+ Search.new(Person, :name_eq => '')
9
+ end
10
+
11
+ it "keeps conditions with a false value before building" do
12
+ expect_any_instance_of(Search).to receive(:build).with({"name_eq" => false})
13
+ Search.new(Person, :name_eq => false)
14
+ end
15
+
16
+ it "keeps conditions with a value before building" do
17
+ expect_any_instance_of(Search).to receive(:build).with({"name_eq" => 'foobar'})
18
+ Search.new(Person, :name_eq => 'foobar')
19
+ end
20
+
21
+ it "removes empty suffixed conditions before building" do
22
+ expect_any_instance_of(Search).to receive(:build).with({})
23
+ Search.new(Person, :name_eq_any => [''])
24
+ end
25
+
26
+ it "keeps suffixed conditions with a false value before building" do
27
+ expect_any_instance_of(Search).to receive(:build).with({"name_eq_any" => [false]})
28
+ Search.new(Person, :name_eq_any => [false])
29
+ end
30
+
31
+ it "keeps suffixed conditions with a value before building" do
32
+ expect_any_instance_of(Search).to receive(:build).with({"name_eq_any" => ['foobar']})
33
+ Search.new(Person, :name_eq_any => ['foobar'])
34
+ end
35
+
36
+ it 'does not raise exception for string :params argument' do
37
+ expect { Search.new(Person, '') }.not_to raise_error
38
+ end
39
+ end
40
+
41
+ describe '#build' do
42
+ it 'creates conditions for top-level attributes' do
43
+ search = Search.new(Person, :name_eq => 'Ernie')
44
+ condition = search.base[:name_eq]
45
+ expect(condition).to be_a Nodes::Condition
46
+ expect(condition.predicate.name).to eq 'eq'
47
+ expect(condition.attributes.first.name).to eq 'name'
48
+ expect(condition.value).to eq 'Ernie'
49
+ end
50
+
51
+ context 'joins' do
52
+ before { pending 'not implemented for mongoid' }
53
+
54
+ it 'creates conditions for association attributes' do
55
+ search = Search.new(Person, :children_name_eq => 'Ernie')
56
+ condition = search.base[:children_name_eq]
57
+ expect(condition).to be_a Nodes::Condition
58
+ expect(condition.predicate.name).to eq 'eq'
59
+ expect(condition.attributes.first.name).to eq 'children_name'
60
+ expect(condition.value).to eq 'Ernie'
61
+ end
62
+
63
+ it 'creates conditions for polymorphic belongs_to association attributes' do
64
+ search = Search.new(Note, :notable_of_Person_type_name_eq => 'Ernie')
65
+ condition = search.base[:notable_of_Person_type_name_eq]
66
+ expect(condition).to be_a Nodes::Condition
67
+ expect(condition.predicate.name).to eq 'eq'
68
+ expect(condition.attributes.first.name).to eq 'notable_of_Person_type_name'
69
+ expect(condition.value).to eq 'Ernie'
70
+ end
71
+
72
+ it 'creates conditions for multiple polymorphic belongs_to association attributes' do
73
+ search = Search.new(Note,
74
+ :notable_of_Person_type_name_or_notable_of_Article_type_title_eq => 'Ernie')
75
+ condition = search.
76
+ base[:notable_of_Person_type_name_or_notable_of_Article_type_title_eq]
77
+ expect(condition).to be_a Nodes::Condition
78
+ expect(condition.predicate.name).to eq 'eq'
79
+ expect(condition.attributes.first.name).to eq 'notable_of_Person_type_name'
80
+ expect(condition.attributes.last.name).to eq 'notable_of_Article_type_title'
81
+ expect(condition.value).to eq 'Ernie'
82
+ end
83
+ before { skip }
84
+ it 'accepts arrays of groupings with joins' do
85
+ search = Search.new(Person,
86
+ g: [
87
+ { :m => 'or', :name_eq => 'Ernie', :children_name_eq => 'Ernie' },
88
+ { :m => 'or', :name_eq => 'Bert', :children_name_eq => 'Bert' },
89
+ ]
90
+ )
91
+ ors = search.groupings
92
+ expect(ors.size).to eq(2)
93
+ or1, or2 = ors
94
+ expect(or1).to be_a Nodes::Grouping
95
+ expect(or1.combinator).to eq 'or'
96
+ expect(or2).to be_a Nodes::Grouping
97
+ expect(or2.combinator).to eq 'or'
98
+ end
99
+
100
+ it 'accepts "attributes" hashes for groupings' do
101
+ search = Search.new(Person,
102
+ g: {
103
+ '0' => { m: 'or', name_eq: 'Ernie', children_name_eq: 'Ernie' },
104
+ '1' => { m: 'or', name_eq: 'Bert', children_name_eq: 'Bert' },
105
+ }
106
+ )
107
+ ors = search.groupings
108
+ expect(ors.size).to eq(2)
109
+ or1, or2 = ors
110
+ expect(or1).to be_a Nodes::Grouping
111
+ expect(or1.combinator).to eq 'or'
112
+ expect(or2).to be_a Nodes::Grouping
113
+ expect(or2.combinator).to eq 'or'
114
+ end
115
+
116
+ it 'accepts "attributes" hashes for conditions' do
117
+ search = Search.new(Person,
118
+ :c => {
119
+ '0' => { :a => ['name'], :p => 'eq', :v => ['Ernie'] },
120
+ '1' => { :a => ['children_name', 'parent_name'],
121
+ :p => 'eq', :v => ['Ernie'], :m => 'or' }
122
+ }
123
+ )
124
+ conditions = search.base.conditions
125
+ expect(conditions.size).to eq(2)
126
+ expect(conditions.map { |c| c.class })
127
+ .to eq [Nodes::Condition, Nodes::Condition]
128
+ end
129
+
130
+ before { skip }
131
+ it 'does not evaluate the query on #inspect' do
132
+ search = Search.new(Person, :children_id_in => [1, 2, 3])
133
+ expect(search.inspect).not_to match /ActiveRecord/
134
+ end
135
+ end
136
+
137
+ it 'discards empty conditions' do
138
+ search = Search.new(Person, :children_name_eq => '')
139
+ condition = search.base[:children_name_eq]
140
+ expect(condition).to be_nil
141
+ end
142
+
143
+ it 'accepts arrays of groupings' do
144
+ search = Search.new(Person,
145
+ g: [
146
+ { :m => 'or', :name_eq => 'Ernie', :email_eq => 'ernie@example.org' },
147
+ { :m => 'or', :name_eq => 'Bert', :email_eq => 'bert@example.org' },
148
+ ]
149
+ )
150
+ ors = search.groupings
151
+ expect(ors.size).to eq(2)
152
+ or1, or2 = ors
153
+ expect(or1).to be_a Nodes::Grouping
154
+ expect(or1.combinator).to eq 'or'
155
+ expect(or2).to be_a Nodes::Grouping
156
+ expect(or2.combinator).to eq 'or'
157
+ end
158
+
159
+ it 'creates conditions for custom predicates that take arrays' do
160
+ Ransack.configure do |config|
161
+ config.add_predicate 'ary_pred', :wants_array => true
162
+ end
163
+
164
+ search = Search.new(Person, :name_ary_pred => ['Ernie', 'Bert'])
165
+ condition = search.base[:name_ary_pred]
166
+ expect(condition).to be_a Nodes::Condition
167
+ expect(condition.predicate.name).to eq 'ary_pred'
168
+ expect(condition.attributes.first.name).to eq 'name'
169
+ expect(condition.value).to eq ['Ernie', 'Bert']
170
+ end
171
+
172
+ context 'with an invalid condition' do
173
+ subject { Search.new(Person, :unknown_attr_eq => 'Ernie') }
174
+
175
+ context "when ignore_unknown_conditions is false" do
176
+ before do
177
+ Ransack.configure { |c| c.ignore_unknown_conditions = false }
178
+ end
179
+
180
+ specify { expect { subject }.to raise_error ArgumentError }
181
+ end
182
+
183
+ context "when ignore_unknown_conditions is true" do
184
+ before do
185
+ Ransack.configure { |c| c.ignore_unknown_conditions = true }
186
+ end
187
+
188
+ specify { expect { subject }.not_to raise_error }
189
+ end
190
+ end
191
+ end
192
+
193
+ describe '#result' do
194
+ let(:people_name_field) {
195
+ "#{quote_table_name("people")}.#{quote_column_name("name")}"
196
+ }
197
+ # let(:children_people_name_field) {
198
+ # "#{quote_table_name("children_people")}.#{quote_column_name("name")}"
199
+ # }
200
+
201
+ it 'evaluates arrays of groupings' do
202
+ search = Search.new(Person,
203
+ :g => [
204
+ { :m => 'or', :name_eq => 'Ernie', :email_eq => 'ernie@example.org' },
205
+ { :m => 'or', :name_eq => 'Bert', :email_eq => 'bert@example.org' }
206
+ ]
207
+ )
208
+ expect(search.result).to be_an Mongoid::Criteria
209
+ selector = search.result.selector
210
+ expect(selector.keys).to eq ['$and']
211
+ first, second = selector.values.first
212
+ expect(first).to eq({ '$or' => [ { 'name' => 'Ernie' }, { 'email' => 'ernie@example.org' } ] })
213
+ expect(second).to eq({ '$or' => [ { 'name' => 'Bert' }, { 'email' => 'bert@example.org' } ] })
214
+ end
215
+
216
+ context 'with joins' do
217
+ before { pending 'not implemented for mongoid' }
218
+
219
+ it 'evaluates conditions contextually' do
220
+ search = Search.new(Person, :children_name_eq => 'Ernie')
221
+ expect(search.result).to be_an ActiveRecord::Relation
222
+ where = search.result.where_values.first
223
+ expect(where.to_sql).to match /#{children_people_name_field} = 'Ernie'/
224
+ end
225
+
226
+ it 'evaluates compound conditions contextually' do
227
+ search = Search.new(Person, :children_name_or_name_eq => 'Ernie')
228
+ expect(search.result).to be_an ActiveRecord::Relation
229
+ where = search.result.where_values.first
230
+ expect(where.to_sql).to match /#{children_people_name_field
231
+ } = 'Ernie' OR #{people_name_field} = 'Ernie'/
232
+ end
233
+
234
+ it 'evaluates polymorphic belongs_to association conditions contextually' do
235
+ search = Search.new(Note, :notable_of_Person_type_name_eq => 'Ernie')
236
+ expect(search.result).to be_an ActiveRecord::Relation
237
+ where = search.result.where_values.first
238
+ expect(where.to_sql).to match /#{people_name_field} = 'Ernie'/
239
+ end
240
+
241
+ it 'evaluates nested conditions' do
242
+ search = Search.new(Person, :children_name_eq => 'Ernie',
243
+ :g => [
244
+ { :m => 'or',
245
+ :name_eq => 'Ernie',
246
+ :children_children_name_eq => 'Ernie'
247
+ }
248
+ ]
249
+ )
250
+ expect(search.result).to be_an ActiveRecord::Relation
251
+ where = search.result.where_values.first
252
+ expect(where.to_sql).to match /#{children_people_name_field} = 'Ernie'/
253
+ expect(where.to_sql).to match /#{people_name_field} = 'Ernie'/
254
+ expect(where.to_sql).to match /#{quote_table_name("children_people_2")
255
+ }.#{quote_column_name("name")} = 'Ernie'/
256
+ end
257
+
258
+ it 'evaluates arrays of groupings' do
259
+ search = Search.new(Person,
260
+ :g => [
261
+ { :m => 'or', :name_eq => 'Ernie', :children_name_eq => 'Ernie' },
262
+ { :m => 'or', :name_eq => 'Bert', :children_name_eq => 'Bert' }
263
+ ]
264
+ )
265
+ expect(search.result).to be_an ActiveRecord::Relation
266
+ where = search.result.where_values.first
267
+ sql = where.to_sql
268
+ first, second = sql.split(/ AND /)
269
+ expect(first).to match /#{people_name_field} = 'Ernie'/
270
+ expect(first).to match /#{children_people_name_field} = 'Ernie'/
271
+ expect(second).to match /#{people_name_field} = 'Bert'/
272
+ expect(second).to match /#{children_people_name_field} = 'Bert'/
273
+ end
274
+
275
+ it 'returns distinct records when passed :distinct => true' do
276
+ search = Search.new(
277
+ Person, :g => [
278
+ { :m => 'or',
279
+ :comments_body_cont => 'e',
280
+ :articles_comments_body_cont => 'e'
281
+ }
282
+ ]
283
+ )
284
+ if ActiveRecord::VERSION::MAJOR == 3
285
+ all_or_load, uniq_or_distinct = :all, :uniq
286
+ else
287
+ all_or_load, uniq_or_distinct = :load, :distinct
288
+ end
289
+ expect(search.result.send(all_or_load).size).
290
+ to eq(9000)
291
+ expect(search.result(:distinct => true).size).
292
+ to eq(10)
293
+ expect(search.result.send(all_or_load).send(uniq_or_distinct)).
294
+ to eq search.result(:distinct => true).send(all_or_load)
295
+ end
296
+ end
297
+ end
298
+
299
+ describe '#sorts=' do
300
+ before do
301
+ @s = Search.new(Person)
302
+ end
303
+
304
+ it 'creates sorts based on a single attribute/direction' do
305
+ @s.sorts = 'id desc'
306
+ expect(@s.sorts.size).to eq(1)
307
+ sort = @s.sorts.first
308
+ expect(sort).to be_a Nodes::Sort
309
+ expect(sort.name).to eq 'id'
310
+ expect(sort.dir).to eq 'desc'
311
+ expect(@s.result.options).to eq({ :sort => { '_id' => -1 } })
312
+ end
313
+
314
+ it 'creates sorts based on a single attribute and uppercase direction' do
315
+ @s.sorts = 'id DESC'
316
+ expect(@s.sorts.size).to eq(1)
317
+ sort = @s.sorts.first
318
+ expect(sort).to be_a Nodes::Sort
319
+ expect(sort.name).to eq 'id'
320
+ expect(sort.dir).to eq 'desc'
321
+ expect(@s.result.options).to eq({ :sort => { '_id' => -1 } })
322
+ end
323
+
324
+ it 'creates sorts based on a single attribute and without direction' do
325
+ @s.sorts = 'id'
326
+ expect(@s.sorts.size).to eq(1)
327
+ sort = @s.sorts.first
328
+ expect(sort).to be_a Nodes::Sort
329
+ expect(sort.name).to eq 'id'
330
+ expect(sort.dir).to eq 'asc'
331
+ expect(@s.result.options).to eq({ :sort => { '_id' => 1 } })
332
+ end
333
+
334
+ it 'creates sorts based on multiple attributes/directions in array format' do
335
+ @s.sorts = ['id desc', { :name => 'name', :dir => 'asc' }]
336
+ expect(@s.sorts.size).to eq(2)
337
+ sort1, sort2 = @s.sorts
338
+ expect(sort1).to be_a Nodes::Sort
339
+ expect(sort1.name).to eq 'id'
340
+ expect(sort1.dir).to eq 'desc'
341
+ expect(sort2).to be_a Nodes::Sort
342
+ expect(sort2.name).to eq 'name'
343
+ expect(sort2.dir).to eq 'asc'
344
+ expect(@s.result.options).to eq({ :sort=>{"_id"=>-1, "name"=>1} })
345
+ end
346
+
347
+ it 'creates sorts based on multiple attributes and uppercase directions in array format' do
348
+ @s.sorts = ['id DESC', { :name => 'name', :dir => 'ASC' }]
349
+ expect(@s.sorts.size).to eq(2)
350
+ sort1, sort2 = @s.sorts
351
+ expect(sort1).to be_a Nodes::Sort
352
+ expect(sort1.name).to eq 'id'
353
+ expect(sort1.dir).to eq 'desc'
354
+ expect(sort2).to be_a Nodes::Sort
355
+ expect(sort2.name).to eq 'name'
356
+ expect(sort2.dir).to eq 'asc'
357
+ expect(@s.result.options).to eq({ :sort=>{"_id"=>-1, "name"=>1} })
358
+ end
359
+
360
+ it 'creates sorts based on multiple attributes and different directions in array format' do
361
+ @s.sorts = ['id DESC', { name: 'name', dir: nil }]
362
+ expect(@s.sorts.size).to eq(2)
363
+ sort1, sort2 = @s.sorts
364
+ expect(sort1).to be_a Nodes::Sort
365
+ expect(sort1.name).to eq 'id'
366
+ expect(sort1.dir).to eq 'desc'
367
+ expect(sort2).to be_a Nodes::Sort
368
+ expect(sort2.name).to eq 'name'
369
+ expect(sort2.dir).to eq 'asc'
370
+ expect(@s.result.options).to eq({ :sort=>{"_id"=>-1, "name"=>1} })
371
+ end
372
+
373
+ it 'creates sorts based on multiple attributes/directions in hash format' do
374
+ @s.sorts = {
375
+ '0' => { :name => 'id', :dir => 'desc' },
376
+ '1' => { :name => 'name', :dir => 'asc' }
377
+ }
378
+ expect(@s.sorts.size).to eq(2)
379
+ expect(@s.sorts).to be_all { |s| Nodes::Sort === s }
380
+ id_sort = @s.sorts.detect { |s| s.name == 'id' }
381
+ name_sort = @s.sorts.detect { |s| s.name == 'name' }
382
+ expect(id_sort.dir).to eq 'desc'
383
+ expect(name_sort.dir).to eq 'asc'
384
+ expect(@s.result.options).to eq({ :sort=>{"_id"=>-1, "name"=>1} })
385
+ end
386
+
387
+ it 'creates sorts based on multiple attributes and uppercase directions in hash format' do
388
+ @s.sorts = {
389
+ '0' => { :name => 'id', :dir => 'DESC' },
390
+ '1' => { :name => 'name', :dir => 'ASC' }
391
+ }
392
+ expect(@s.sorts.size).to eq(2)
393
+ expect(@s.sorts).to be_all { |s| Nodes::Sort === s }
394
+ id_sort = @s.sorts.detect { |s| s.name == 'id' }
395
+ name_sort = @s.sorts.detect { |s| s.name == 'name' }
396
+ expect(id_sort.dir).to eq 'desc'
397
+ expect(name_sort.dir).to eq 'asc'
398
+ expect(@s.result.options).to eq({ :sort=>{"_id"=>-1, "name"=>1} })
399
+ end
400
+
401
+ it 'creates sorts based on multiple attributes and different directions in hash format' do
402
+ @s.sorts = {
403
+ '0' => { :name => 'id', :dir => 'DESC' },
404
+ '1' => { :name => 'name', :dir => nil }
405
+ }
406
+ expect(@s.sorts.size).to eq(2)
407
+ expect(@s.sorts).to be_all { |s| Nodes::Sort === s }
408
+ id_sort = @s.sorts.detect { |s| s.name == 'id' }
409
+ name_sort = @s.sorts.detect { |s| s.name == 'name' }
410
+ expect(id_sort.dir).to eq 'desc'
411
+ expect(name_sort.dir).to eq 'asc'
412
+ expect(@s.result.options).to eq({ :sort=>{"_id"=>-1, "name"=>1} })
413
+ end
414
+
415
+ it 'overrides existing sort' do
416
+ @s.sorts = 'id asc'
417
+ expect(@s.result.first.id.to_s).to eq Person.min(:id).to_s
418
+ end
419
+ end
420
+
421
+ describe '#method_missing' do
422
+ before do
423
+ @s = Search.new(Person)
424
+ end
425
+
426
+ it 'raises NoMethodError when sent an invalid attribute' do
427
+ expect { @s.blah }.to raise_error NoMethodError
428
+ end
429
+
430
+ it 'sets condition attributes when sent valid attributes' do
431
+ @s.name_eq = 'Ernie'
432
+ expect(@s.name_eq).to eq 'Ernie'
433
+ end
434
+
435
+ context 'with joins' do
436
+ before { pending 'not implemented for mongoid' }
437
+ it 'allows chaining to access nested conditions' do
438
+ @s.groupings = [
439
+ { :m => 'or', :name_eq => 'Ernie', :children_name_eq => 'Ernie' }
440
+ ]
441
+ expect(@s.groupings.first.children_name_eq).to eq 'Ernie'
442
+ end
443
+ end
444
+ end
445
+ end
446
+ end