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.
- checksums.yaml +4 -4
- data/.travis.yml +47 -3
- data/CHANGELOG.md +106 -18
- data/CONTRIBUTING.md +56 -23
- data/Gemfile +16 -5
- data/README.md +114 -38
- data/Rakefile +30 -2
- data/lib/ransack.rb +9 -0
- data/lib/ransack/adapters/active_record/3.0/compat.rb +11 -8
- data/lib/ransack/adapters/active_record/3.0/context.rb +14 -22
- data/lib/ransack/adapters/active_record/3.1/context.rb +14 -22
- data/lib/ransack/adapters/active_record/context.rb +36 -31
- data/lib/ransack/adapters/active_record/ransack/constants.rb +113 -0
- data/lib/ransack/adapters/active_record/ransack/context.rb +64 -0
- data/lib/ransack/adapters/active_record/ransack/nodes/condition.rb +48 -0
- data/lib/ransack/adapters/active_record/ransack/translate.rb +12 -0
- data/lib/ransack/adapters/active_record/ransack/visitor.rb +24 -0
- data/lib/ransack/adapters/mongoid.rb +13 -0
- data/lib/ransack/adapters/mongoid/3.2/.gitkeep +0 -0
- data/lib/ransack/adapters/mongoid/attributes/attribute.rb +37 -0
- data/lib/ransack/adapters/mongoid/attributes/order_predications.rb +17 -0
- data/lib/ransack/adapters/mongoid/attributes/predications.rb +141 -0
- data/lib/ransack/adapters/mongoid/base.rb +126 -0
- data/lib/ransack/adapters/mongoid/context.rb +208 -0
- data/lib/ransack/adapters/mongoid/inquiry_hash.rb +23 -0
- data/lib/ransack/adapters/mongoid/ransack/constants.rb +88 -0
- data/lib/ransack/adapters/mongoid/ransack/context.rb +60 -0
- data/lib/ransack/adapters/mongoid/ransack/nodes/condition.rb +27 -0
- data/lib/ransack/adapters/mongoid/ransack/translate.rb +13 -0
- data/lib/ransack/adapters/mongoid/ransack/visitor.rb +24 -0
- data/lib/ransack/adapters/mongoid/table.rb +35 -0
- data/lib/ransack/configuration.rb +22 -4
- data/lib/ransack/constants.rb +26 -120
- data/lib/ransack/context.rb +32 -60
- data/lib/ransack/helpers/form_builder.rb +50 -36
- data/lib/ransack/helpers/form_helper.rb +148 -104
- data/lib/ransack/naming.rb +11 -11
- data/lib/ransack/nodes.rb +2 -0
- data/lib/ransack/nodes/bindable.rb +12 -4
- data/lib/ransack/nodes/condition.rb +5 -22
- data/lib/ransack/nodes/grouping.rb +9 -10
- data/lib/ransack/nodes/sort.rb +3 -2
- data/lib/ransack/nodes/value.rb +1 -2
- data/lib/ransack/predicate.rb +3 -3
- data/lib/ransack/search.rb +46 -13
- data/lib/ransack/translate.rb +8 -8
- data/lib/ransack/version.rb +1 -1
- data/lib/ransack/visitor.rb +4 -16
- data/ransack.gemspec +1 -0
- data/spec/mongoid/adapters/mongoid/base_spec.rb +276 -0
- data/spec/mongoid/adapters/mongoid/context_spec.rb +56 -0
- data/spec/mongoid/configuration_spec.rb +66 -0
- data/spec/mongoid/dependencies_spec.rb +8 -0
- data/spec/mongoid/helpers/ransack_helper.rb +11 -0
- data/spec/mongoid/nodes/condition_spec.rb +34 -0
- data/spec/mongoid/nodes/grouping_spec.rb +13 -0
- data/spec/mongoid/predicate_spec.rb +155 -0
- data/spec/mongoid/search_spec.rb +446 -0
- data/spec/mongoid/support/mongoid.yml +6 -0
- data/spec/mongoid/support/schema.rb +128 -0
- data/spec/mongoid/translate_spec.rb +14 -0
- data/spec/mongoid_spec_helper.rb +59 -0
- data/spec/ransack/adapters/active_record/base_spec.rb +68 -35
- data/spec/ransack/dependencies_spec.rb +3 -1
- data/spec/ransack/helpers/form_builder_spec.rb +6 -6
- data/spec/ransack/helpers/form_helper_spec.rb +114 -47
- data/spec/ransack/nodes/condition_spec.rb +2 -2
- data/spec/ransack/search_spec.rb +2 -6
- data/spec/ransack/translate_spec.rb +1 -1
- data/spec/spec_helper.rb +2 -3
- data/spec/support/schema.rb +9 -0
- 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,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,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
|