ransack 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +12 -4
  3. data/CONTRIBUTING.md +10 -4
  4. data/Gemfile +12 -9
  5. data/README.md +46 -11
  6. data/lib/ransack.rb +4 -2
  7. data/lib/ransack/adapters/active_record.rb +1 -1
  8. data/lib/ransack/adapters/active_record/3.0/compat.rb +16 -6
  9. data/lib/ransack/adapters/active_record/3.0/context.rb +32 -16
  10. data/lib/ransack/adapters/active_record/3.1/context.rb +32 -15
  11. data/lib/ransack/adapters/active_record/3.2/context.rb +1 -1
  12. data/lib/ransack/adapters/active_record/base.rb +9 -6
  13. data/lib/ransack/adapters/active_record/context.rb +193 -2
  14. data/lib/ransack/configuration.rb +4 -4
  15. data/lib/ransack/constants.rb +81 -18
  16. data/lib/ransack/context.rb +27 -12
  17. data/lib/ransack/helpers/form_builder.rb +126 -91
  18. data/lib/ransack/helpers/form_helper.rb +34 -12
  19. data/lib/ransack/naming.rb +2 -1
  20. data/lib/ransack/nodes/attribute.rb +6 -4
  21. data/lib/ransack/nodes/bindable.rb +3 -1
  22. data/lib/ransack/nodes/condition.rb +40 -27
  23. data/lib/ransack/nodes/grouping.rb +19 -13
  24. data/lib/ransack/nodes/node.rb +3 -3
  25. data/lib/ransack/nodes/sort.rb +5 -3
  26. data/lib/ransack/nodes/value.rb +2 -2
  27. data/lib/ransack/predicate.rb +18 -9
  28. data/lib/ransack/ransacker.rb +4 -4
  29. data/lib/ransack/search.rb +9 -12
  30. data/lib/ransack/translate.rb +42 -21
  31. data/lib/ransack/version.rb +1 -1
  32. data/lib/ransack/visitor.rb +4 -4
  33. data/ransack.gemspec +17 -7
  34. data/spec/blueprints/notes.rb +2 -0
  35. data/spec/blueprints/people.rb +4 -1
  36. data/spec/console.rb +3 -3
  37. data/spec/ransack/adapters/active_record/base_spec.rb +149 -22
  38. data/spec/ransack/adapters/active_record/context_spec.rb +5 -5
  39. data/spec/ransack/configuration_spec.rb +17 -8
  40. data/spec/ransack/dependencies_spec.rb +8 -0
  41. data/spec/ransack/helpers/form_builder_spec.rb +37 -14
  42. data/spec/ransack/helpers/form_helper_spec.rb +5 -5
  43. data/spec/ransack/predicate_spec.rb +6 -3
  44. data/spec/ransack/search_spec.rb +95 -73
  45. data/spec/ransack/translate_spec.rb +14 -0
  46. data/spec/spec_helper.rb +14 -8
  47. data/spec/support/en.yml +6 -0
  48. data/spec/support/schema.rb +76 -31
  49. metadata +48 -29
@@ -8,25 +8,35 @@ Gem::Specification.new do |s|
8
8
  s.platform = Gem::Platform::RUBY
9
9
  s.authors = ["Ernie Miller", "Ryan Bigg"]
10
10
  s.email = ["ernie@erniemiller.org", "radarlistener@gmail.com"]
11
- s.homepage = "https://github.com/ernie/ransack"
11
+ s.homepage = "https://github.com/activerecord-hackery/ransack"
12
12
  s.summary = %q{Object-based searching for ActiveRecord (currently).}
13
13
  s.description = %q{Ransack is the successor to the MetaSearch gem. It improves and expands upon MetaSearch's functionality, but does not have a 100%-compatible API.}
14
+ s.license = 'MIT'
14
15
 
15
16
  s.rubyforge_project = "ransack"
16
17
 
17
- s.add_dependency 'activerecord', '>= 3.0'
18
18
  s.add_dependency 'actionpack', '>= 3.0'
19
- s.add_dependency 'polyamorous', '~> 0.6.0'
19
+ s.add_dependency 'activerecord', '>= 3.0'
20
+ s.add_dependency 'activesupport', '>= 3.0'
21
+ s.add_dependency 'i18n'
22
+ # s.add_dependency 'polyamorous', '~> 0.6.0'
20
23
  s.add_development_dependency 'rspec', '~> 2.8.0'
21
24
  s.add_development_dependency 'machinist', '~> 1.0.6'
22
25
  s.add_development_dependency 'faker', '~> 0.9.5'
23
26
  s.add_development_dependency 'sqlite3', '~> 1.3.3'
24
27
  s.add_development_dependency 'pg', '0.17.0'
25
- s.add_development_dependency 'mysql2', '0.3.13'
28
+ s.add_development_dependency 'mysql2', '0.3.14'
26
29
  s.add_development_dependency 'pry', '0.9.12.2'
27
30
 
28
- s.files = `git ls-files`.split("\n")
29
- s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
30
- s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
31
+ s.files = `git ls-files`
32
+ .split("\n")
33
+
34
+ s.test_files = `git ls-files -- {test,spec,features}/*`
35
+ .split("\n")
36
+
37
+ s.executables = `git ls-files -- bin/*`
38
+ .split("\n")
39
+ .map { |f| File.basename(f) }
40
+
31
41
  s.require_paths = ["lib"]
32
42
  end
@@ -1,3 +1,5 @@
1
1
  Note.blueprint do
2
2
  note
3
+ notable_type { "Article" }
4
+ notable_id
3
5
  end
@@ -2,4 +2,7 @@ Person.blueprint do
2
2
  name
3
3
  email { "test@example.com" }
4
4
  salary
5
- end
5
+ only_sort
6
+ only_search
7
+ only_admin
8
+ end
@@ -4,7 +4,8 @@ require 'sham'
4
4
  require 'faker'
5
5
  require 'ransack'
6
6
 
7
- Dir[File.expand_path('../../spec/{helpers,support,blueprints}/*.rb', __FILE__)].each do |f|
7
+ Dir[File.expand_path('../../spec/{helpers,support,blueprints}/*.rb', __FILE__)]
8
+ .each do |f|
8
9
  require f
9
10
  end
10
11
 
@@ -12,10 +13,9 @@ Sham.define do
12
13
  name { Faker::Name.name }
13
14
  title { Faker::Lorem.sentence }
14
15
  body { Faker::Lorem.paragraph }
15
- salary {|index| 30000 + (index * 1000)}
16
+ salary { |index| 30000 + (index * 1000) }
16
17
  tag_name { Faker::Lorem.words(3).join(' ') }
17
18
  note { Faker::Lorem.words(7).join(' ') }
18
19
  end
19
20
 
20
21
  Schema.create
21
-
@@ -23,42 +23,69 @@ module Ransack
23
23
  # For infix tests
24
24
  def self.sane_adapter?
25
25
  case ::ActiveRecord::Base.connection.adapter_name
26
- when "SQLite3" || "PostgreSQL"
27
- true
28
- else
29
- false
26
+ when "SQLite3", "PostgreSQL"
27
+ true
28
+ else
29
+ false
30
30
  end
31
31
  end
32
32
  # in schema.rb, class Person:
33
- # ransacker :reversed_name, :formatter => proc {|v| v.reverse} do |parent|
33
+ # ransacker :reversed_name, formatter: proc { |v| v.reverse } do |parent|
34
34
  # parent.table[:name]
35
35
  # end
36
36
  #
37
37
  # ransacker :doubled_name do |parent|
38
38
  # Arel::Nodes::InfixOperation.new('||', parent.table[:name], parent.table[:name])
39
39
  # end
40
+
40
41
  it 'creates ransack attributes' do
41
- s = Person.search(:reversed_name_eq => 'htimS cirA')
42
+ s = Person.search(reversed_name_eq: 'htimS cirA')
42
43
  s.result.should have(1).person
43
- s.result.first.should eq Person.find_by_name('Aric Smith')
44
+
45
+ if ::ActiveRecord::VERSION::STRING >= "4"
46
+ s.result.first.should eq Person.find_by(name: 'Aric Smith')
47
+ else
48
+ s.result.first.should eq Person.find_by_name('Aric Smith')
49
+ end
44
50
  end
45
51
 
46
52
  it 'can be accessed through associations' do
47
- s = Person.search(:children_reversed_name_eq => 'htimS cirA')
48
-
49
- s.result.to_sql.should match /#{quote_table_name("children_people")}.#{quote_column_name("name")} = 'Aric Smith'/
53
+ s = Person.search(children_reversed_name_eq: 'htimS cirA')
54
+ s.result.to_sql.should match(
55
+ /#{quote_table_name("children_people")}.#{quote_column_name("name")} = 'Aric Smith'/
56
+ )
50
57
  end
51
58
 
52
59
  it 'allows an "attribute" to be an InfixOperation' do
53
- s = Person.search(:doubled_name_eq => 'Aric SmithAric Smith')
54
- s.result.first.should eq Person.find_by_name('Aric Smith')
60
+ s = Person.search(doubled_name_eq: 'Aric SmithAric Smith')
61
+ if ::ActiveRecord::VERSION::STRING >= "4"
62
+ s.result.first.should eq Person.find_by(name: 'Aric Smith')
63
+ else
64
+ s.result.first.should eq Person.find_by_name('Aric Smith')
65
+ end
55
66
  end if defined?(Arel::Nodes::InfixOperation) && sane_adapter?
56
67
 
57
68
  it "doesn't break #count if using InfixOperations" do
58
- s = Person.search(:doubled_name_eq => 'Aric SmithAric Smith')
69
+ s = Person.search(doubled_name_eq: 'Aric SmithAric Smith')
59
70
  s.result.count.should eq 1
60
71
  end if defined?(Arel::Nodes::InfixOperation) && sane_adapter?
61
72
 
73
+ if ::ActiveRecord::VERSION::STRING >= "4"
74
+ it "should remove empty key value pairs from the params hash" do
75
+ s = Person.search(children_reversed_name_eq: '')
76
+ s.result.to_sql.should_not match /LEFT OUTER JOIN/
77
+ end
78
+
79
+ it "should keep proper key value pairs in the params hash" do
80
+ s = Person.search(children_reversed_name_eq: 'Testing')
81
+ s.result.to_sql.should match /LEFT OUTER JOIN/
82
+ end
83
+
84
+ it "should function correctly when nil is passed in" do
85
+ s = Person.search(nil)
86
+ end
87
+ end
88
+
62
89
  it "should function correctly when using fields with dots in them" do
63
90
  s = Person.search(email_cont: "example.com")
64
91
  s.result.exists?.should be_true
@@ -75,22 +102,122 @@ module Ransack
75
102
  s = Person.search(name_cont: "\\WINNER\\")
76
103
  s.result.exists?.should be_true
77
104
  end
105
+
106
+ it 'allows sort by "only_sort" field' do
107
+ s = Person.search(
108
+ "s" => { "0" => { "dir" => "asc", "name" => "only_sort" } }
109
+ )
110
+ s.result.to_sql.should match(
111
+ /ORDER BY #{quote_table_name("people")}.#{quote_column_name("only_sort")} ASC/
112
+ )
113
+ end
114
+
115
+ it "doesn't sort by 'only_search' field" do
116
+ s = Person.search(
117
+ "s" => { "0" => { "dir" => "asc", "name" => "only_search" } }
118
+ )
119
+ s.result.to_sql.should_not match(
120
+ /ORDER BY #{quote_table_name("people")}.#{quote_column_name("only_search")} ASC/
121
+ )
122
+ end
123
+
124
+ it 'allows search by "only_search" field' do
125
+ s = Person.search(only_search_eq: 'htimS cirA')
126
+ s.result.to_sql.should match(
127
+ /WHERE #{quote_table_name("people")}.#{quote_column_name("only_search")} = 'htimS cirA'/
128
+ )
129
+ end
130
+
131
+ it "can't be searched by 'only_sort'" do
132
+ s = Person.search(only_sort_eq: 'htimS cirA')
133
+ s.result.to_sql.should_not match(
134
+ /WHERE #{quote_table_name("people")}.#{quote_column_name("only_sort")} = 'htimS cirA'/
135
+ )
136
+ end
137
+
138
+ it 'allows sort by "only_admin" field, if auth_object: :admin' do
139
+ s = Person.search(
140
+ { "s" => { "0" => { "dir" => "asc", "name" => "only_admin" } } },
141
+ { auth_object: :admin }
142
+ )
143
+ s.result.to_sql.should match(
144
+ /ORDER BY #{quote_table_name("people")}.#{quote_column_name("only_admin")} ASC/
145
+ )
146
+ end
147
+
148
+ it "doesn't sort by 'only_admin' field, if auth_object: nil" do
149
+ s = Person.search(
150
+ "s" => { "0" => { "dir" => "asc", "name" => "only_admin" } }
151
+ )
152
+ s.result.to_sql.should_not match(
153
+ /ORDER BY #{quote_table_name("people")}.#{quote_column_name("only_admin")} ASC/
154
+ )
155
+ end
156
+
157
+ it 'allows search by "only_admin" field, if auth_object: :admin' do
158
+ s = Person.search(
159
+ { only_admin_eq: 'htimS cirA' },
160
+ { auth_object: :admin }
161
+ )
162
+ s.result.to_sql.should match(
163
+ /WHERE #{quote_table_name("people")}.#{quote_column_name("only_admin")} = 'htimS cirA'/
164
+ )
165
+ end
166
+
167
+ it "can't be searched by 'only_admin', if auth_object: nil" do
168
+ s = Person.search(only_admin_eq: 'htimS cirA')
169
+ s.result.to_sql.should_not match(
170
+ /WHERE #{quote_table_name("people")}.#{quote_column_name("only_admin")} = 'htimS cirA'/
171
+ )
172
+ end
78
173
  end
79
174
 
80
175
  describe '#ransackable_attributes' do
81
- subject { Person.ransackable_attributes }
176
+ context 'when auth_object is nil' do
177
+ subject { Person.ransackable_attributes }
178
+
179
+ it { should include 'name' }
180
+ it { should include 'reversed_name' }
181
+ it { should include 'doubled_name' }
182
+ it { should include 'only_search' }
183
+ it { should_not include 'only_sort' }
184
+ it { should_not include 'only_admin' }
185
+ end
186
+
187
+ context 'with auth_object :admin' do
188
+ subject { Person.ransackable_attributes(:admin) }
82
189
 
83
- it { should include 'name' }
84
- it { should include 'reversed_name' }
85
- it { should include 'doubled_name' }
190
+ it { should include 'name' }
191
+ it { should include 'reversed_name' }
192
+ it { should include 'doubled_name' }
193
+ it { should include 'only_search' }
194
+ it { should_not include 'only_sort' }
195
+ it { should include 'only_admin' }
196
+ end
86
197
  end
87
198
 
88
199
  describe '#ransortable_attributes' do
89
- subject { Person.ransortable_attributes }
200
+ context 'when auth_object is nil' do
201
+ subject { Person.ransortable_attributes }
202
+
203
+ it { should include 'name' }
204
+ it { should include 'reversed_name' }
205
+ it { should include 'doubled_name' }
206
+ it { should include 'only_sort' }
207
+ it { should_not include 'only_search' }
208
+ it { should_not include 'only_admin' }
209
+ end
90
210
 
91
- it { should include 'name' }
92
- it { should include 'reversed_name' }
93
- it { should include 'doubled_name' }
211
+ context 'with auth_object :admin' do
212
+ subject { Person.ransortable_attributes(:admin) }
213
+
214
+ it { should include 'name' }
215
+ it { should include 'reversed_name' }
216
+ it { should include 'doubled_name' }
217
+ it { should include 'only_sort' }
218
+ it { should_not include 'only_search' }
219
+ it { should include 'only_admin' }
220
+ end
94
221
  end
95
222
 
96
223
  describe '#ransackable_associations' do
@@ -104,4 +231,4 @@ module Ransack
104
231
  end
105
232
  end
106
233
  end
107
- end
234
+ end
@@ -14,16 +14,16 @@ module Ransack
14
14
 
15
15
  describe '#evaluate' do
16
16
  it 'evaluates search objects' do
17
- search = Search.new(Person, :name_eq => 'Joe Blow')
17
+ search = Search.new(Person, name_eq: 'Joe Blow')
18
18
  result = subject.evaluate(search)
19
19
 
20
20
  result.should be_an ::ActiveRecord::Relation
21
21
  result.to_sql.should match /#{quote_column_name("name")} = 'Joe Blow'/
22
22
  end
23
23
 
24
- it 'SELECTs DISTINCT when :distinct => true' do
25
- search = Search.new(Person, :name_eq => 'Joe Blow')
26
- result = subject.evaluate(search, :distinct => true)
24
+ it 'SELECTs DISTINCT when distinct: true' do
25
+ search = Search.new(Person, name_eq: 'Joe Blow')
26
+ result = subject.evaluate(search, distinct: true)
27
27
 
28
28
  result.should be_an ::ActiveRecord::Relation
29
29
  result.to_sql.should match /SELECT DISTINCT/
@@ -48,4 +48,4 @@ module Ransack
48
48
  end
49
49
  end
50
50
  end
51
- end
51
+ end
@@ -18,14 +18,19 @@ module Ransack
18
18
  Ransack.predicates.should have_key 'test_predicate_all'
19
19
  end
20
20
 
21
- it 'avoids creating compound predicates if :compounds => false' do
21
+ it 'avoids creating compound predicates if compounds: false' do
22
22
  Ransack.configure do |config|
23
- config.add_predicate :test_predicate_without_compound, :compounds => false
23
+ config.add_predicate(
24
+ :test_predicate_without_compound,
25
+ compounds: false
26
+ )
24
27
  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'
28
+ Ransack.predicates
29
+ .should have_key 'test_predicate_without_compound'
30
+ Ransack.predicates
31
+ .should_not have_key 'test_predicate_without_compound_any'
32
+ Ransack.predicates
33
+ .should_not have_key 'test_predicate_without_compound_all'
29
34
  end
30
35
 
31
36
  it 'should have default value for search key' do
@@ -48,7 +53,11 @@ module Ransack
48
53
 
49
54
  it 'adds predicates that take arrays, overriding compounds' do
50
55
  Ransack.configure do |config|
51
- config.add_predicate :test_array_predicate, :wants_array => true, :compounds => true
56
+ config.add_predicate(
57
+ :test_array_predicate,
58
+ wants_array: true,
59
+ compounds: true
60
+ )
52
61
  end
53
62
 
54
63
  Ransack.predicates['test_array_predicate'].wants_array.should eq true
@@ -56,4 +65,4 @@ module Ransack
56
65
  Ransack.predicates.should_not have_key 'test_array_predicate_all'
57
66
  end
58
67
  end
59
- end
68
+ end
@@ -0,0 +1,8 @@
1
+ unless ::ActiveRecord::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
+ output.should be_empty
6
+ end
7
+ end
8
+ end
@@ -7,6 +7,7 @@ module Ransack
7
7
  router = ActionDispatch::Routing::RouteSet.new
8
8
  router.draw do
9
9
  resources :people
10
+ resources :notes
10
11
  get ':controller(/:action(/:id(.:format)))'
11
12
  end
12
13
 
@@ -32,7 +33,11 @@ module Ransack
32
33
 
33
34
  it 'selects previously-entered time values with datetime_select' do
34
35
  @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
+ html = @f.datetime_select(
37
+ :created_at_eq,
38
+ use_month_numbers: true,
39
+ include_seconds: true
40
+ )
36
41
  %w(2011 1 2 03 04 05).each do |val|
37
42
  html.should match /<option selected="selected" value="#{val}">#{val}<\/option>/
38
43
  end
@@ -49,7 +54,7 @@ module Ransack
49
54
 
50
55
  describe '#sort_link' do
51
56
  it 'sort_link for ransack attribute' do
52
- sort_link = @f.sort_link :name, :controller => 'people'
57
+ sort_link = @f.sort_link :name, controller: 'people'
53
58
  if ActiveRecord::VERSION::STRING =~ /^3\.[1-2]\./
54
59
  sort_link.should match /people\?q%5Bs%5D=name\+asc/
55
60
  else
@@ -60,7 +65,7 @@ module Ransack
60
65
  end
61
66
 
62
67
  it 'sort_link for common attribute' do
63
- sort_link = @f.sort_link :id, :controller => 'people'
68
+ sort_link = @f.sort_link :id, controller: 'people'
64
69
  sort_link.should match /id<\/a>/
65
70
  end
66
71
  end
@@ -78,15 +83,17 @@ module Ransack
78
83
 
79
84
  it 'returns ransackable attributes' do
80
85
  html = @f.attribute_select
81
- html.split(/\n/).should have(Person.ransackable_attributes.size + 1).lines
86
+ html.split(/\n/).
87
+ should have(Person.ransackable_attributes.size + 1).lines
82
88
  Person.ransackable_attributes.each do |attribute|
83
89
  html.should match /<option value="#{attribute}">/
84
90
  end
85
91
  end
86
92
 
87
93
  it 'returns ransackable attributes for associations with :associations' do
88
- attributes = Person.ransackable_attributes + Article.ransackable_attributes.map {|a| "articles_#{a}"}
89
- html = @f.attribute_select :associations => ['articles']
94
+ attributes = Person.ransackable_attributes + Article.
95
+ ransackable_attributes.map { |a| "articles_#{a}" }
96
+ html = @f.attribute_select(associations: ['articles'])
90
97
  html.split(/\n/).should have(attributes.size).lines
91
98
  attributes.each do |attribute|
92
99
  html.should match /<option value="#{attribute}">/
@@ -94,7 +101,7 @@ module Ransack
94
101
  end
95
102
 
96
103
  it 'returns option groups for base and associations with :associations' do
97
- html = @f.attribute_select :associations => ['articles']
104
+ html = @f.attribute_select(associations: ['articles'])
98
105
  [Person, Article].each do |model|
99
106
  html.should match /<optgroup label="#{model}">/
100
107
  end
@@ -112,26 +119,42 @@ module Ransack
112
119
  end
113
120
 
114
121
  it 'filters predicates with single-value :only' do
115
- html = @f.predicate_select :only => 'eq'
116
- Predicate.names.reject {|k| k =~ /^eq/}.each do |key|
122
+ html = @f.predicate_select only: 'eq'
123
+ Predicate.names.reject { |k| k =~ /^eq/ }.each do |key|
117
124
  html.should_not match /<option value="#{key}">/
118
125
  end
119
126
  end
120
127
 
121
128
  it 'filters predicates with multi-value :only' do
122
- html = @f.predicate_select :only => [:eq, :lt]
123
- Predicate.names.reject {|k| k =~ /^(eq|lt)/}.each do |key|
129
+ html = @f.predicate_select only: [:eq, :lt]
130
+ Predicate.names.reject { |k| k =~ /^(eq|lt)/ }.each do |key|
124
131
  html.should_not match /<option value="#{key}">/
125
132
  end
126
133
  end
127
134
 
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|
135
+ it 'excludes compounds when compounds: false' do
136
+ html = @f.predicate_select compounds: false
137
+ Predicate.names.select { |k| k =~ /_(any|all)$/ }.each do |key|
131
138
  html.should_not match /<option value="#{key}">/
132
139
  end
133
140
  end
134
141
  end
142
+
143
+ context 'fields used in polymorphic relations as search attributes in form' do
144
+ before do
145
+ @controller.view_context.search_form_for Note.search do |f|
146
+ @f = f
147
+ end
148
+ end
149
+ it 'accepts poly_id field' do
150
+ html = @f.text_field(:notable_id_eq)
151
+ html.should match /id=\"q_notable_id_eq\"/
152
+ end
153
+ it 'accepts poly_type field' do
154
+ html = @f.text_field(:notable_type_eq)
155
+ html.should match /id=\"q_notable_type_eq\"/
156
+ end
157
+ end
135
158
  end
136
159
  end
137
160
  end