ransack 1.5.1 → 1.6.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 (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