ransack_ffcrm 0.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.
- data/.gitignore +4 -0
- data/.travis.yml +9 -0
- data/Gemfile +40 -0
- data/LICENSE +20 -0
- data/README.md +137 -0
- data/Rakefile +19 -0
- data/lib/ransack/adapters/active_record/3.0/compat.rb +166 -0
- data/lib/ransack/adapters/active_record/3.0/context.rb +161 -0
- data/lib/ransack/adapters/active_record/3.1/context.rb +166 -0
- data/lib/ransack/adapters/active_record/base.rb +33 -0
- data/lib/ransack/adapters/active_record/context.rb +41 -0
- data/lib/ransack/adapters/active_record.rb +12 -0
- data/lib/ransack/configuration.rb +35 -0
- data/lib/ransack/constants.rb +23 -0
- data/lib/ransack/context.rb +124 -0
- data/lib/ransack/helpers/form_builder.rb +203 -0
- data/lib/ransack/helpers/form_helper.rb +75 -0
- data/lib/ransack/helpers.rb +2 -0
- data/lib/ransack/locale/en.yml +70 -0
- data/lib/ransack/naming.rb +53 -0
- data/lib/ransack/nodes/attribute.rb +49 -0
- data/lib/ransack/nodes/bindable.rb +30 -0
- data/lib/ransack/nodes/condition.rb +212 -0
- data/lib/ransack/nodes/grouping.rb +183 -0
- data/lib/ransack/nodes/node.rb +34 -0
- data/lib/ransack/nodes/sort.rb +41 -0
- data/lib/ransack/nodes/value.rb +108 -0
- data/lib/ransack/nodes.rb +7 -0
- data/lib/ransack/predicate.rb +70 -0
- data/lib/ransack/ransacker.rb +24 -0
- data/lib/ransack/search.rb +123 -0
- data/lib/ransack/translate.rb +92 -0
- data/lib/ransack/version.rb +3 -0
- data/lib/ransack/visitor.rb +68 -0
- data/lib/ransack.rb +27 -0
- data/ransack_ffcrm.gemspec +30 -0
- data/spec/blueprints/articles.rb +5 -0
- data/spec/blueprints/comments.rb +5 -0
- data/spec/blueprints/notes.rb +3 -0
- data/spec/blueprints/people.rb +4 -0
- data/spec/blueprints/tags.rb +3 -0
- data/spec/console.rb +21 -0
- data/spec/helpers/ransack_helper.rb +2 -0
- data/spec/ransack/adapters/active_record/base_spec.rb +67 -0
- data/spec/ransack/adapters/active_record/context_spec.rb +45 -0
- data/spec/ransack/configuration_spec.rb +31 -0
- data/spec/ransack/helpers/form_builder_spec.rb +137 -0
- data/spec/ransack/helpers/form_helper_spec.rb +38 -0
- data/spec/ransack/nodes/condition_spec.rb +15 -0
- data/spec/ransack/nodes/grouping_spec.rb +13 -0
- data/spec/ransack/predicate_spec.rb +55 -0
- data/spec/ransack/search_spec.rb +225 -0
- data/spec/spec_helper.rb +47 -0
- data/spec/support/en.yml +5 -0
- data/spec/support/schema.rb +111 -0
- metadata +229 -0
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Ransack
|
4
|
+
module Adapters
|
5
|
+
module ActiveRecord
|
6
|
+
describe Base do
|
7
|
+
|
8
|
+
subject { ::ActiveRecord::Base }
|
9
|
+
|
10
|
+
it { should respond_to :ransack }
|
11
|
+
it { should respond_to :search }
|
12
|
+
|
13
|
+
describe '#search' do
|
14
|
+
subject { Person.search }
|
15
|
+
|
16
|
+
it { should be_a Search }
|
17
|
+
it 'has a Relation as its object' do
|
18
|
+
subject.object.should be_an ::ActiveRecord::Relation
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe '#ransacker' do
|
23
|
+
# in schema.rb, class Person:
|
24
|
+
# ransacker :reversed_name, :formatter => proc {|v| v.reverse} do |parent|
|
25
|
+
# parent.table[:name]
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# ransacker :doubled_name do |parent|
|
29
|
+
# Arel::Nodes::InfixOperation.new('||', parent.table[:name], parent.table[:name])
|
30
|
+
# end
|
31
|
+
it 'creates ransack attributes' do
|
32
|
+
s = Person.search(:reversed_name_eq => 'htimS cirA')
|
33
|
+
s.result.should have(1).person
|
34
|
+
s.result.first.should eq Person.find_by_name('Aric Smith')
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'can be accessed through associations' do
|
38
|
+
s = Person.search(:children_reversed_name_eq => 'htimS cirA')
|
39
|
+
s.result.to_sql.should match /"children_people"."name" = 'Aric Smith'/
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'allows an "attribute" to be an InfixOperation' do
|
43
|
+
s = Person.search(:doubled_name_eq => 'Aric SmithAric Smith')
|
44
|
+
s.result.first.should eq Person.find_by_name('Aric Smith')
|
45
|
+
end if defined?(Arel::Nodes::InfixOperation)
|
46
|
+
end
|
47
|
+
|
48
|
+
describe '#ransackable_attributes' do
|
49
|
+
subject { Person.ransackable_attributes }
|
50
|
+
|
51
|
+
it { should include 'name' }
|
52
|
+
it { should include 'reversed_name' }
|
53
|
+
it { should include 'doubled_name' }
|
54
|
+
end
|
55
|
+
|
56
|
+
describe '#ransackable_associations' do
|
57
|
+
subject { Person.ransackable_associations }
|
58
|
+
|
59
|
+
it { should include 'parent' }
|
60
|
+
it { should include 'children' }
|
61
|
+
it { should include 'articles' }
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Ransack
|
4
|
+
module Adapters
|
5
|
+
module ActiveRecord
|
6
|
+
describe Context do
|
7
|
+
subject { Context.new(Person) }
|
8
|
+
|
9
|
+
describe '#evaluate' do
|
10
|
+
it 'evaluates search obects' do
|
11
|
+
search = Search.new(Person, :name_eq => 'Joe Blow')
|
12
|
+
result = subject.evaluate(search)
|
13
|
+
|
14
|
+
result.should be_an ::ActiveRecord::Relation
|
15
|
+
result.to_sql.should match /"name" = 'Joe Blow'/
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'SELECTs DISTINCT when :distinct => true' do
|
19
|
+
search = Search.new(Person, :name_eq => 'Joe Blow')
|
20
|
+
result = subject.evaluate(search, :distinct => true)
|
21
|
+
|
22
|
+
result.should be_an ::ActiveRecord::Relation
|
23
|
+
result.to_sql.should match /SELECT DISTINCT/
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'contextualizes strings to attributes' do
|
28
|
+
attribute = subject.contextualize 'children_children_parent_name'
|
29
|
+
attribute.should be_a Arel::Attributes::Attribute
|
30
|
+
attribute.name.to_s.should eq 'name'
|
31
|
+
attribute.relation.table_alias.should eq 'parents_people'
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'builds new associations if not yet built' do
|
35
|
+
attribute = subject.contextualize 'children_articles_title'
|
36
|
+
attribute.should be_a Arel::Attributes::Attribute
|
37
|
+
attribute.name.to_s.should eq 'title'
|
38
|
+
attribute.relation.name.should eq 'articles'
|
39
|
+
attribute.relation.table_alias.should be_nil
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Ransack
|
4
|
+
describe Configuration do
|
5
|
+
it 'yields Ransack on configure' do
|
6
|
+
Ransack.configure do |config|
|
7
|
+
config.should eq Ransack
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'adds predicates' do
|
12
|
+
Ransack.configure do |config|
|
13
|
+
config.add_predicate :test_predicate
|
14
|
+
end
|
15
|
+
|
16
|
+
Ransack.predicates.should have_key 'test_predicate'
|
17
|
+
Ransack.predicates.should have_key 'test_predicate_any'
|
18
|
+
Ransack.predicates.should have_key 'test_predicate_all'
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'avoids creating compound predicates if :compounds => false' do
|
22
|
+
Ransack.configure do |config|
|
23
|
+
config.add_predicate :test_predicate_without_compound, :compounds => false
|
24
|
+
end
|
25
|
+
|
26
|
+
Ransack.predicates.should have_key 'test_predicate_without_compound'
|
27
|
+
Ransack.predicates.should_not have_key 'test_predicate_without_compound_any'
|
28
|
+
Ransack.predicates.should_not have_key 'test_predicate_without_compound_all'
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Ransack
|
4
|
+
module Helpers
|
5
|
+
describe FormBuilder do
|
6
|
+
|
7
|
+
router = ActionDispatch::Routing::RouteSet.new
|
8
|
+
router.draw do
|
9
|
+
resources :people
|
10
|
+
match ':controller(/:action(/:id(.:format)))'
|
11
|
+
end
|
12
|
+
|
13
|
+
include router.url_helpers
|
14
|
+
|
15
|
+
# FIXME: figure out a cleaner way to get this behavior
|
16
|
+
before do
|
17
|
+
@controller = ActionView::TestCase::TestController.new
|
18
|
+
@controller.instance_variable_set(:@_routes, router)
|
19
|
+
@controller.class_eval do
|
20
|
+
include router.url_helpers
|
21
|
+
end
|
22
|
+
|
23
|
+
@controller.view_context_class.class_eval do
|
24
|
+
include router.url_helpers
|
25
|
+
end
|
26
|
+
|
27
|
+
@s = Person.search
|
28
|
+
@controller.view_context.search_form_for @s do |f|
|
29
|
+
@f = f
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'selects previously-entered time values with datetime_select' do
|
34
|
+
@s.created_at_eq = [2011, 1, 2, 3, 4, 5]
|
35
|
+
html = @f.datetime_select :created_at_eq, :use_month_numbers => true, :include_seconds => true
|
36
|
+
%w(2011 1 2 03 04 05).each do |val|
|
37
|
+
html.should match /<option selected="selected" value="#{val}">#{val}<\/option>/
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe '#label' do
|
42
|
+
|
43
|
+
it 'localizes attribute names' do
|
44
|
+
html = @f.label :name_cont
|
45
|
+
html.should match /Full Name contains/
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
describe '#sort_link' do
|
51
|
+
subject { @f.sort_link :name, :controller => 'people' }
|
52
|
+
|
53
|
+
it { should match /people\?q%5Bs%5D=name\+asc/}
|
54
|
+
it { should match /sort_link/}
|
55
|
+
it { should match /Full Name<\/a>/}
|
56
|
+
end
|
57
|
+
|
58
|
+
describe '#submit' do
|
59
|
+
|
60
|
+
it 'localizes :search when no default value given' do
|
61
|
+
html = @f.submit
|
62
|
+
html.should match /"Search"/
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
describe '#attribute_select' do
|
68
|
+
|
69
|
+
it 'returns ransackable attributes' do
|
70
|
+
html = @f.attribute_select
|
71
|
+
html.split(/\n/).should have(Person.ransackable_attributes.size + 1).lines
|
72
|
+
Person.ransackable_attributes.each do |attribute|
|
73
|
+
html.should match /<option value="#{attribute}">/
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'returns ransackable attributes for associations with :associations' do
|
78
|
+
attributes = Person.ransackable_attributes + Article.ransackable_attributes.map {|a| "articles_#{a}"}
|
79
|
+
html = @f.attribute_select :associations => ['articles']
|
80
|
+
html.split(/\n/).should have(attributes.size).lines
|
81
|
+
attributes.each do |attribute|
|
82
|
+
html.should match /<option value="#{attribute}">/
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'returns option groups for base and associations with :associations' do
|
87
|
+
html = @f.attribute_select :associations => ['articles']
|
88
|
+
[Person, Article].each do |model|
|
89
|
+
html.should match /<optgroup label="#{model}">/
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
describe '#predicate_select' do
|
96
|
+
|
97
|
+
it 'returns predicates with predicate_select' do
|
98
|
+
html = @f.predicate_select
|
99
|
+
Predicate.names.each do |key|
|
100
|
+
html.should match /<option value="#{key}">/
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'filters predicates with single-value :only' do
|
105
|
+
html = @f.predicate_select :only => 'eq'
|
106
|
+
Predicate.names.reject {|k| k =~ /^eq/}.each do |key|
|
107
|
+
html.should_not match /<option value="#{key}">/
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'filters predicates with multi-value :only' do
|
112
|
+
html = @f.predicate_select :only => [:eq, :lt]
|
113
|
+
Predicate.names.reject {|k| k =~ /^(eq|lt)/}.each do |key|
|
114
|
+
html.should_not match /<option value="#{key}">/
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
it 'sorts predicate keys based on order of :only array, with :compounds => false' do
|
119
|
+
ordered_keys = @f.send(:predicate_keys, :compounds => false, :only => [:cont, :blank, :eq, :unknown])
|
120
|
+
ordered_keys.should == %w(cont blank eq)
|
121
|
+
end
|
122
|
+
|
123
|
+
it 'sorts predicate keys based on order of :only array, with :compounds => true' do
|
124
|
+
ordered_keys = @f.send(:predicate_keys, :compounds => true, :only => [:cont, :blank, :eq, :unknown])
|
125
|
+
ordered_keys.should == %w(cont cont_any cont_all blank eq eq_any eq_all)
|
126
|
+
end
|
127
|
+
|
128
|
+
it 'excludes compounds when :compounds => false' do
|
129
|
+
html = @f.predicate_select :compounds => false
|
130
|
+
Predicate.names.select {|k| k =~ /_(any|all)$/}.each do |key|
|
131
|
+
html.should_not match /<option value="#{key}">/
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Ransack
|
4
|
+
module Helpers
|
5
|
+
describe FormHelper do
|
6
|
+
|
7
|
+
router = ActionDispatch::Routing::RouteSet.new
|
8
|
+
router.draw do
|
9
|
+
resources :people
|
10
|
+
match ':controller(/:action(/:id(.:format)))'
|
11
|
+
end
|
12
|
+
|
13
|
+
include router.url_helpers
|
14
|
+
|
15
|
+
# FIXME: figure out a cleaner way to get this behavior
|
16
|
+
before do
|
17
|
+
@controller = ActionView::TestCase::TestController.new
|
18
|
+
@controller.instance_variable_set(:@_routes, router)
|
19
|
+
@controller.class_eval do
|
20
|
+
include router.url_helpers
|
21
|
+
end
|
22
|
+
|
23
|
+
@controller.view_context_class.class_eval do
|
24
|
+
include router.url_helpers
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe '#sort_link' do
|
29
|
+
subject { @controller.view_context.sort_link(Person.search(:sorts => ['name desc']), :name, :controller => 'people') }
|
30
|
+
|
31
|
+
it { should match /people\?q%5Bs%5D=name\+asc/}
|
32
|
+
it { should match /sort_link desc/}
|
33
|
+
it { should match /Full Name ▼/}
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require '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 { subject.values.should have(2).values }
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Ransack
|
4
|
+
describe Predicate do
|
5
|
+
|
6
|
+
before do
|
7
|
+
@s = Search.new(Person)
|
8
|
+
end
|
9
|
+
|
10
|
+
describe 'eq' do
|
11
|
+
it 'generates an equality condition for boolean true' do
|
12
|
+
@s.awesome_eq = true
|
13
|
+
@s.result.to_sql.should match /"people"."awesome" = 't'/
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'generates an equality condition for boolean false' do
|
17
|
+
@s.awesome_eq = false
|
18
|
+
@s.result.to_sql.should match /"people"."awesome" = 'f'/
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'does not generate a condition for nil' do
|
22
|
+
@s.awesome_eq = nil
|
23
|
+
@s.result.to_sql.should_not match /WHERE/
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe 'cont' do
|
28
|
+
it 'generates a LIKE query with value surrounded by %' do
|
29
|
+
@s.name_cont = 'ric'
|
30
|
+
@s.result.to_sql.should match /"people"."name" LIKE '%ric%'/
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe 'not_cont' do
|
35
|
+
it 'generates a NOT LIKE query with value surrounded by %' do
|
36
|
+
@s.name_not_cont = 'ric'
|
37
|
+
@s.result.to_sql.should match /"people"."name" NOT LIKE '%ric%'/
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe 'null' do
|
42
|
+
it 'generates a value IS NULL query' do
|
43
|
+
@s.name_null = true
|
44
|
+
@s.result.to_sql.should match /"people"."name" IS NULL/
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe 'not_null' do
|
49
|
+
it 'generates a value IS NOT NULL query' do
|
50
|
+
@s.name_not_null = true
|
51
|
+
@s.result.to_sql.should match /"people"."name" IS NOT NULL/
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,225 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Ransack
|
4
|
+
describe Search do
|
5
|
+
|
6
|
+
describe '#build' do
|
7
|
+
it 'creates Conditions for top-level attributes' do
|
8
|
+
search = Search.new(Person, :name_eq => 'Ernie')
|
9
|
+
condition = search.base[:name_eq]
|
10
|
+
condition.should be_a Nodes::Condition
|
11
|
+
condition.predicate.name.should eq 'eq'
|
12
|
+
condition.attributes.first.name.should eq 'name'
|
13
|
+
condition.value.should eq 'Ernie'
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'creates Conditions for association attributes' do
|
17
|
+
search = Search.new(Person, :children_name_eq => 'Ernie')
|
18
|
+
condition = search.base[:children_name_eq]
|
19
|
+
condition.should be_a Nodes::Condition
|
20
|
+
condition.predicate.name.should eq 'eq'
|
21
|
+
condition.attributes.first.name.should eq 'children_name'
|
22
|
+
condition.value.should eq 'Ernie'
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'creates Conditions for polymorphic belongs_to association attributes' do
|
26
|
+
search = Search.new(Note, :notable_of_Person_type_name_eq => 'Ernie')
|
27
|
+
condition = search.base[:notable_of_Person_type_name_eq]
|
28
|
+
condition.should be_a Nodes::Condition
|
29
|
+
condition.predicate.name.should eq 'eq'
|
30
|
+
condition.attributes.first.name.should eq 'notable_of_Person_type_name'
|
31
|
+
condition.value.should eq 'Ernie'
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'creates Conditions for multiple polymorphic belongs_to association attributes' do
|
35
|
+
search = Search.new(Note, :notable_of_Person_type_name_or_notable_of_Article_type_title_eq => 'Ernie')
|
36
|
+
condition = search.base[:notable_of_Person_type_name_or_notable_of_Article_type_title_eq]
|
37
|
+
condition.should be_a Nodes::Condition
|
38
|
+
condition.predicate.name.should eq 'eq'
|
39
|
+
condition.attributes.first.name.should eq 'notable_of_Person_type_name'
|
40
|
+
condition.attributes.last.name.should eq 'notable_of_Article_type_title'
|
41
|
+
condition.value.should eq 'Ernie'
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'discards empty conditions' do
|
45
|
+
search = Search.new(Person, :children_name_eq => '')
|
46
|
+
condition = search.base[:children_name_eq]
|
47
|
+
condition.should be_nil
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'accepts arrays of groupings' do
|
51
|
+
search = Search.new(Person,
|
52
|
+
:g => [
|
53
|
+
{:m => 'or', :name_eq => 'Ernie', :children_name_eq => 'Ernie'},
|
54
|
+
{:m => 'or', :name_eq => 'Bert', :children_name_eq => 'Bert'},
|
55
|
+
]
|
56
|
+
)
|
57
|
+
ors = search.groupings
|
58
|
+
ors.should have(2).items
|
59
|
+
or1, or2 = ors
|
60
|
+
or1.should be_a Nodes::Grouping
|
61
|
+
or1.combinator.should eq 'or'
|
62
|
+
or2.should be_a Nodes::Grouping
|
63
|
+
or2.combinator.should eq 'or'
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'accepts "attributes" hashes for groupings' do
|
67
|
+
search = Search.new(Person,
|
68
|
+
:g => {
|
69
|
+
'0' => {:m => 'or', :name_eq => 'Ernie', :children_name_eq => 'Ernie'},
|
70
|
+
'1' => {:m => 'or', :name_eq => 'Bert', :children_name_eq => 'Bert'},
|
71
|
+
}
|
72
|
+
)
|
73
|
+
ors = search.groupings
|
74
|
+
ors.should have(2).items
|
75
|
+
or1, or2 = ors
|
76
|
+
or1.should be_a Nodes::Grouping
|
77
|
+
or1.combinator.should eq 'or'
|
78
|
+
or2.should be_a Nodes::Grouping
|
79
|
+
or2.combinator.should eq 'or'
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'accepts "attributes" hashes for conditions' do
|
83
|
+
search = Search.new(Person,
|
84
|
+
:c => {
|
85
|
+
'0' => {:a => ['name'], :p => 'eq', :v => ['Ernie']},
|
86
|
+
'1' => {:a => ['children_name', 'parent_name'], :p => 'eq', :v => ['Ernie'], :m => 'or'}
|
87
|
+
}
|
88
|
+
)
|
89
|
+
conditions = search.base.conditions
|
90
|
+
conditions.should have(2).items
|
91
|
+
conditions.map {|c| c.class}.should eq [Nodes::Condition, Nodes::Condition]
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
describe '#result' do
|
96
|
+
it 'evaluates conditions contextually' do
|
97
|
+
search = Search.new(Person, :children_name_eq => 'Ernie')
|
98
|
+
search.result.should be_an ActiveRecord::Relation
|
99
|
+
where = search.result.where_values.first
|
100
|
+
where.to_sql.should match /"children_people"\."name" = 'Ernie'/
|
101
|
+
end
|
102
|
+
|
103
|
+
it 'evaluates compound conditions contextually' do
|
104
|
+
search = Search.new(Person, :children_name_or_name_eq => 'Ernie')
|
105
|
+
search.result.should be_an ActiveRecord::Relation
|
106
|
+
where = search.result.where_values.first
|
107
|
+
where.to_sql.should match /"children_people"\."name" = 'Ernie' OR "people"\."name" = 'Ernie'/
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'evaluates polymorphic belongs_to association conditions contextually' do
|
111
|
+
search = Search.new(Note, :notable_of_Person_type_name_eq => 'Ernie')
|
112
|
+
search.result.should be_an ActiveRecord::Relation
|
113
|
+
where = search.result.where_values.first
|
114
|
+
where.to_sql.should match /"people"."name" = 'Ernie'/
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'evaluates nested conditions' do
|
118
|
+
search = Search.new(Person, :children_name_eq => 'Ernie',
|
119
|
+
:g => [{
|
120
|
+
:m => 'or',
|
121
|
+
:name_eq => 'Ernie',
|
122
|
+
:children_children_name_eq => 'Ernie'
|
123
|
+
}]
|
124
|
+
)
|
125
|
+
search.result.should be_an ActiveRecord::Relation
|
126
|
+
where = search.result.where_values.first
|
127
|
+
where.to_sql.should match /"children_people"."name" = 'Ernie'/
|
128
|
+
where.to_sql.should match /"people"."name" = 'Ernie'/
|
129
|
+
where.to_sql.should match /"children_people_2"."name" = 'Ernie'/
|
130
|
+
end
|
131
|
+
|
132
|
+
it 'evaluates arrays of groupings' do
|
133
|
+
search = Search.new(Person,
|
134
|
+
:g => [
|
135
|
+
{:m => 'or', :name_eq => 'Ernie', :children_name_eq => 'Ernie'},
|
136
|
+
{:m => 'or', :name_eq => 'Bert', :children_name_eq => 'Bert'},
|
137
|
+
]
|
138
|
+
)
|
139
|
+
search.result.should be_an ActiveRecord::Relation
|
140
|
+
where = search.result.where_values.first
|
141
|
+
sql = where.to_sql
|
142
|
+
first, second = sql.split(/ AND /)
|
143
|
+
first.should match /"people"."name" = 'Ernie'/
|
144
|
+
first.should match /"children_people"."name" = 'Ernie'/
|
145
|
+
second.should match /"people"."name" = 'Bert'/
|
146
|
+
second.should match /"children_people"."name" = 'Bert'/
|
147
|
+
end
|
148
|
+
|
149
|
+
it 'returns distinct records when passed :distinct => true' do
|
150
|
+
search = Search.new(Person, :g => [{:m => 'or', :comments_body_cont => 'e', :articles_comments_body_cont => 'e'}])
|
151
|
+
search.result.all.should have(920).items
|
152
|
+
search.result(:distinct => true).should have(330).items
|
153
|
+
search.result.all.uniq.should eq search.result(:distinct => true).all
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
describe '#sorts=' do
|
158
|
+
before do
|
159
|
+
@s = Search.new(Person)
|
160
|
+
end
|
161
|
+
|
162
|
+
it 'creates sorts based on a single attribute/direction' do
|
163
|
+
@s.sorts = 'id desc'
|
164
|
+
@s.sorts.should have(1).item
|
165
|
+
sort = @s.sorts.first
|
166
|
+
sort.should be_a Nodes::Sort
|
167
|
+
sort.name.should eq 'id'
|
168
|
+
sort.dir.should eq 'desc'
|
169
|
+
end
|
170
|
+
|
171
|
+
it 'creates sorts based on multiple attributes/directions in array format' do
|
172
|
+
@s.sorts = ['id desc', 'name asc']
|
173
|
+
@s.sorts.should have(2).items
|
174
|
+
sort1, sort2 = @s.sorts
|
175
|
+
sort1.should be_a Nodes::Sort
|
176
|
+
sort1.name.should eq 'id'
|
177
|
+
sort1.dir.should eq 'desc'
|
178
|
+
sort2.should be_a Nodes::Sort
|
179
|
+
sort2.name.should eq 'name'
|
180
|
+
sort2.dir.should eq 'asc'
|
181
|
+
end
|
182
|
+
|
183
|
+
it 'creates sorts based on multiple attributes/directions in hash format' do
|
184
|
+
@s.sorts = {
|
185
|
+
'0' => {
|
186
|
+
:name => 'id',
|
187
|
+
:dir => 'desc'
|
188
|
+
},
|
189
|
+
'1' => {
|
190
|
+
:name => 'name',
|
191
|
+
:dir => 'asc'
|
192
|
+
}
|
193
|
+
}
|
194
|
+
@s.sorts.should have(2).items
|
195
|
+
@s.sorts.should be_all {|s| Nodes::Sort === s}
|
196
|
+
id_sort = @s.sorts.detect {|s| s.name == 'id'}
|
197
|
+
name_sort = @s.sorts.detect {|s| s.name == 'name'}
|
198
|
+
id_sort.dir.should eq 'desc'
|
199
|
+
name_sort.dir.should eq 'asc'
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
describe '#method_missing' do
|
204
|
+
before do
|
205
|
+
@s = Search.new(Person)
|
206
|
+
end
|
207
|
+
|
208
|
+
it 'raises NoMethodError when sent an invalid attribute' do
|
209
|
+
expect {@s.blah}.to raise_error NoMethodError
|
210
|
+
end
|
211
|
+
|
212
|
+
it 'sets condition attributes when sent valid attributes' do
|
213
|
+
@s.name_eq = 'Ernie'
|
214
|
+
@s.name_eq.should eq 'Ernie'
|
215
|
+
end
|
216
|
+
|
217
|
+
it 'allows chaining to access nested conditions' do
|
218
|
+
@s.groupings = [{:m => 'or', :name_eq => 'Ernie', :children_name_eq => 'Ernie'}]
|
219
|
+
@s.groupings.first.name_eq.should eq 'Ernie'
|
220
|
+
@s.groupings.first.children_name_eq.should eq 'Ernie'
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
end
|
225
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'machinist/active_record'
|
2
|
+
require 'sham'
|
3
|
+
require 'faker'
|
4
|
+
require 'ransack'
|
5
|
+
|
6
|
+
Time.zone = 'Eastern Time (US & Canada)'
|
7
|
+
I18n.load_path += Dir[File.join(File.dirname(__FILE__), 'support', '*.yml')]
|
8
|
+
|
9
|
+
Dir[File.expand_path('../{helpers,support,blueprints}/*.rb', __FILE__)].each do |f|
|
10
|
+
require f
|
11
|
+
end
|
12
|
+
|
13
|
+
Sham.define do
|
14
|
+
name { Faker::Name.name }
|
15
|
+
title { Faker::Lorem.sentence }
|
16
|
+
body { Faker::Lorem.paragraph }
|
17
|
+
salary {|index| 30000 + (index * 1000)}
|
18
|
+
tag_name { Faker::Lorem.words(3).join(' ') }
|
19
|
+
note { Faker::Lorem.words(7).join(' ') }
|
20
|
+
end
|
21
|
+
|
22
|
+
RSpec.configure do |config|
|
23
|
+
config.before(:suite) do
|
24
|
+
puts '=' * 80
|
25
|
+
puts "Running specs against ActiveRecord #{ActiveRecord::VERSION::STRING} and ARel #{Arel::VERSION}..."
|
26
|
+
puts '=' * 80
|
27
|
+
Schema.create
|
28
|
+
end
|
29
|
+
|
30
|
+
config.before(:all) { Sham.reset(:before_all) }
|
31
|
+
config.before(:each) { Sham.reset(:before_each) }
|
32
|
+
|
33
|
+
config.include RansackHelper
|
34
|
+
end
|
35
|
+
|
36
|
+
RSpec::Matchers.define :be_like do |expected|
|
37
|
+
match do |actual|
|
38
|
+
actual.gsub(/^\s+|\s+$/, '').gsub(/\s+/, ' ').strip ==
|
39
|
+
expected.gsub(/^\s+|\s+$/, '').gsub(/\s+/, ' ').strip
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
RSpec::Matchers.define :have_attribute_method do |expected|
|
44
|
+
match do |actual|
|
45
|
+
actual.attribute_method?(expected)
|
46
|
+
end
|
47
|
+
end
|