ransack 1.5.1 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +47 -3
  3. data/CHANGELOG.md +106 -18
  4. data/CONTRIBUTING.md +56 -23
  5. data/Gemfile +16 -5
  6. data/README.md +114 -38
  7. data/Rakefile +30 -2
  8. data/lib/ransack.rb +9 -0
  9. data/lib/ransack/adapters/active_record/3.0/compat.rb +11 -8
  10. data/lib/ransack/adapters/active_record/3.0/context.rb +14 -22
  11. data/lib/ransack/adapters/active_record/3.1/context.rb +14 -22
  12. data/lib/ransack/adapters/active_record/context.rb +36 -31
  13. data/lib/ransack/adapters/active_record/ransack/constants.rb +113 -0
  14. data/lib/ransack/adapters/active_record/ransack/context.rb +64 -0
  15. data/lib/ransack/adapters/active_record/ransack/nodes/condition.rb +48 -0
  16. data/lib/ransack/adapters/active_record/ransack/translate.rb +12 -0
  17. data/lib/ransack/adapters/active_record/ransack/visitor.rb +24 -0
  18. data/lib/ransack/adapters/mongoid.rb +13 -0
  19. data/lib/ransack/adapters/mongoid/3.2/.gitkeep +0 -0
  20. data/lib/ransack/adapters/mongoid/attributes/attribute.rb +37 -0
  21. data/lib/ransack/adapters/mongoid/attributes/order_predications.rb +17 -0
  22. data/lib/ransack/adapters/mongoid/attributes/predications.rb +141 -0
  23. data/lib/ransack/adapters/mongoid/base.rb +126 -0
  24. data/lib/ransack/adapters/mongoid/context.rb +208 -0
  25. data/lib/ransack/adapters/mongoid/inquiry_hash.rb +23 -0
  26. data/lib/ransack/adapters/mongoid/ransack/constants.rb +88 -0
  27. data/lib/ransack/adapters/mongoid/ransack/context.rb +60 -0
  28. data/lib/ransack/adapters/mongoid/ransack/nodes/condition.rb +27 -0
  29. data/lib/ransack/adapters/mongoid/ransack/translate.rb +13 -0
  30. data/lib/ransack/adapters/mongoid/ransack/visitor.rb +24 -0
  31. data/lib/ransack/adapters/mongoid/table.rb +35 -0
  32. data/lib/ransack/configuration.rb +22 -4
  33. data/lib/ransack/constants.rb +26 -120
  34. data/lib/ransack/context.rb +32 -60
  35. data/lib/ransack/helpers/form_builder.rb +50 -36
  36. data/lib/ransack/helpers/form_helper.rb +148 -104
  37. data/lib/ransack/naming.rb +11 -11
  38. data/lib/ransack/nodes.rb +2 -0
  39. data/lib/ransack/nodes/bindable.rb +12 -4
  40. data/lib/ransack/nodes/condition.rb +5 -22
  41. data/lib/ransack/nodes/grouping.rb +9 -10
  42. data/lib/ransack/nodes/sort.rb +3 -2
  43. data/lib/ransack/nodes/value.rb +1 -2
  44. data/lib/ransack/predicate.rb +3 -3
  45. data/lib/ransack/search.rb +46 -13
  46. data/lib/ransack/translate.rb +8 -8
  47. data/lib/ransack/version.rb +1 -1
  48. data/lib/ransack/visitor.rb +4 -16
  49. data/ransack.gemspec +1 -0
  50. data/spec/mongoid/adapters/mongoid/base_spec.rb +276 -0
  51. data/spec/mongoid/adapters/mongoid/context_spec.rb +56 -0
  52. data/spec/mongoid/configuration_spec.rb +66 -0
  53. data/spec/mongoid/dependencies_spec.rb +8 -0
  54. data/spec/mongoid/helpers/ransack_helper.rb +11 -0
  55. data/spec/mongoid/nodes/condition_spec.rb +34 -0
  56. data/spec/mongoid/nodes/grouping_spec.rb +13 -0
  57. data/spec/mongoid/predicate_spec.rb +155 -0
  58. data/spec/mongoid/search_spec.rb +446 -0
  59. data/spec/mongoid/support/mongoid.yml +6 -0
  60. data/spec/mongoid/support/schema.rb +128 -0
  61. data/spec/mongoid/translate_spec.rb +14 -0
  62. data/spec/mongoid_spec_helper.rb +59 -0
  63. data/spec/ransack/adapters/active_record/base_spec.rb +68 -35
  64. data/spec/ransack/dependencies_spec.rb +3 -1
  65. data/spec/ransack/helpers/form_builder_spec.rb +6 -6
  66. data/spec/ransack/helpers/form_helper_spec.rb +114 -47
  67. data/spec/ransack/nodes/condition_spec.rb +2 -2
  68. data/spec/ransack/search_spec.rb +2 -6
  69. data/spec/ransack/translate_spec.rb +1 -1
  70. data/spec/spec_helper.rb +2 -3
  71. data/spec/support/schema.rb +9 -0
  72. metadata +49 -4
@@ -0,0 +1,6 @@
1
+ test:
2
+ sessions:
3
+ default:
4
+ database: ransack_mongoid_test
5
+ hosts:
6
+ - localhost:27017
@@ -0,0 +1,128 @@
1
+ require 'mongoid'
2
+
3
+ Mongoid.load!(File.expand_path("../mongoid.yml", __FILE__), :test)
4
+
5
+ class Person
6
+ include Mongoid::Document
7
+ include Mongoid::Timestamps
8
+
9
+ field :name, type: String
10
+ field :email, type: String
11
+ field :only_search, type: String
12
+ field :only_sort, type: String
13
+ field :only_admin, type: String
14
+ field :salary, type: Integer
15
+ field :awesome, type: Boolean, default: false
16
+
17
+ belongs_to :parent, :class_name => 'Person', inverse_of: :children
18
+ has_many :children, :class_name => 'Person', inverse_of: :parent
19
+
20
+ has_many :articles
21
+ has_many :comments
22
+
23
+ # has_many :authored_article_comments, :through => :articles,
24
+ # :source => :comments, :foreign_key => :person_id
25
+
26
+ has_many :notes, :as => :notable
27
+
28
+ default_scope -> { order(id: :desc) }
29
+
30
+ scope :restricted, lambda { where(restricted: 1) }
31
+ scope :active, lambda { where(active: 1) }
32
+ scope :over_age, lambda { |y| where(:age.gt => y) }
33
+
34
+ ransacker :reversed_name, :formatter => proc { |v| v.reverse } do |parent|
35
+ parent.table[:name]
36
+ end
37
+
38
+ ransacker :doubled_name do |parent|
39
+ # Arel::Nodes::InfixOperation.new(
40
+ # '||', parent.table[:name], parent.table[:name]
41
+ # )
42
+ parent.table[:name]
43
+ end
44
+
45
+ def self.ransackable_attributes(auth_object = nil)
46
+ if auth_object == :admin
47
+ all_ransackable_attributes - ['only_sort']
48
+ else
49
+ all_ransackable_attributes - ['only_sort', 'only_admin']
50
+ end
51
+ end
52
+
53
+ def self.ransortable_attributes(auth_object = nil)
54
+ if auth_object == :admin
55
+ all_ransackable_attributes - ['only_search']
56
+ else
57
+ all_ransackable_attributes - ['only_search', 'only_admin']
58
+ end
59
+ end
60
+ end
61
+
62
+ class Article
63
+ include Mongoid::Document
64
+
65
+ field :title, type: String
66
+ field :body, type: String
67
+
68
+ belongs_to :person
69
+ has_many :comments
70
+ # has_and_belongs_to_many :tags
71
+ has_many :notes, :as => :notable
72
+ end
73
+
74
+ module Namespace
75
+ class Article < ::Article
76
+
77
+ end
78
+ end
79
+
80
+ class Comment
81
+ include Mongoid::Document
82
+
83
+ field :body, type: String
84
+
85
+
86
+ belongs_to :article
87
+ belongs_to :person
88
+ end
89
+
90
+ class Tag
91
+ include Mongoid::Document
92
+
93
+ field :name, type: String
94
+
95
+ # has_and_belongs_to_many :articles
96
+ end
97
+
98
+ class Note
99
+ include Mongoid::Document
100
+
101
+ field :note, type: String
102
+
103
+ belongs_to :notable, :polymorphic => true
104
+ end
105
+
106
+ module Schema
107
+ def self.create
108
+ 10.times do
109
+ person = Person.make.save!
110
+ Note.make.save!(:notable => person)
111
+ 3.times do
112
+ article = Article.create!(:person => person)
113
+ 3.times do
114
+ # article.tags = [Tag.make.save!, Tag.make.save!, Tag.make.save!]
115
+ end
116
+ Note.create.save!(:notable => article)
117
+ 10.times do
118
+ Comment.create.save!(:article => article, :person => person)
119
+ end
120
+ end
121
+ end
122
+
123
+ Comment.create!(
124
+ :body => 'First post!',
125
+ :article => Article.create!(:title => 'Hello, world!')
126
+ )
127
+ end
128
+ end
@@ -0,0 +1,14 @@
1
+ require 'mongoid_spec_helper'
2
+
3
+ module Ransack
4
+ describe Translate do
5
+
6
+ describe '.attribute' do
7
+ it 'translate namespaced attribute like AR does' do
8
+ ar_translation = ::Namespace::Article.human_attribute_name(:title)
9
+ ransack_translation = Ransack::Translate.attribute(:title, :context => ::Namespace::Article.search.context)
10
+ expect(ransack_translation).to eq ar_translation
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,59 @@
1
+ require 'machinist/object'
2
+ require 'sham'
3
+ require 'faker'
4
+ require 'pry'
5
+ require 'mongoid'
6
+ require 'ransack'
7
+
8
+ I18n.enforce_available_locales = false
9
+ Time.zone = 'Eastern Time (US & Canada)'
10
+ I18n.load_path += Dir[File.join(File.dirname(__FILE__), 'support', '*.yml')]
11
+
12
+ Dir[File.expand_path('../{mongoid/helpers,mongoid/support,blueprints}/*.rb', __FILE__)]
13
+ .each do |f|
14
+ require f
15
+ end
16
+
17
+ Sham.define do
18
+ name { Faker::Name.name }
19
+ title { Faker::Lorem.sentence }
20
+ body { Faker::Lorem.paragraph }
21
+ salary { |index| 30000 + (index * 1000) }
22
+ tag_name { Faker::Lorem.words(3).join(' ') }
23
+ note { Faker::Lorem.words(7).join(' ') }
24
+ only_admin { Faker::Lorem.words(3).join(' ') }
25
+ only_search { Faker::Lorem.words(3).join(' ') }
26
+ only_sort { Faker::Lorem.words(3).join(' ') }
27
+ notable_id { |id| id }
28
+ end
29
+
30
+ RSpec.configure do |config|
31
+ config.alias_it_should_behave_like_to :it_has_behavior, 'has behavior'
32
+
33
+ config.before(:suite) do
34
+ puts '=' * 80
35
+ connection_name = Mongoid.default_session.inspect
36
+ puts "Running specs against #{connection_name}, Mongoid #{
37
+ Mongoid::VERSION}, Moped #{Moped::VERSION} and Origin #{Origin::VERSION}..."
38
+ puts '=' * 80
39
+ Schema.create
40
+ end
41
+
42
+ config.before(:all) { Sham.reset(:before_all) }
43
+ config.before(:each) { Sham.reset(:before_each) }
44
+
45
+ config.include RansackHelper
46
+ end
47
+
48
+ RSpec::Matchers.define :be_like do |expected|
49
+ match do |actual|
50
+ actual.gsub(/^\s+|\s+$/, '').gsub(/\s+/, ' ').strip ==
51
+ expected.gsub(/^\s+|\s+$/, '').gsub(/\s+/, ' ').strip
52
+ end
53
+ end
54
+
55
+ RSpec::Matchers.define :have_attribute_method do |expected|
56
+ match do |actual|
57
+ actual.attribute_method?(expected)
58
+ end
59
+ end
@@ -11,7 +11,7 @@ module Ransack
11
11
  it { should respond_to :search }
12
12
 
13
13
  describe '#search' do
14
- subject { Person.search }
14
+ subject { Person.ransack }
15
15
 
16
16
  it { should be_a Search }
17
17
  it 'has a Relation as its object' do
@@ -20,43 +20,63 @@ module Ransack
20
20
 
21
21
  context 'with scopes' do
22
22
  before do
23
- Person.stub :ransackable_scopes => [:active, :over_age]
23
+ Person.stub :ransackable_scopes => [:active, :over_age, :of_age]
24
24
  end
25
25
 
26
26
  it "applies true scopes" do
27
- search = Person.search('active' => true)
27
+ search = Person.ransack('active' => true)
28
28
  search.result.to_sql.should include "active = 1"
29
29
  end
30
30
 
31
+ it "applies stringy true scopes" do
32
+ search = Person.ransack('active' => 'true')
33
+ search.result.to_sql.should include "active = 1"
34
+ end
35
+
36
+ it "applies stringy boolean scopes with true value in an array" do
37
+ search = Person.ransack('of_age' => ['true'])
38
+ search.result.to_sql.should include "age >= 18"
39
+ end
40
+
41
+ it "applies stringy boolean scopes with false value in an array" do
42
+ search = Person.ransack('of_age' => ['false'])
43
+ search.result.to_sql.should include "age < 18"
44
+ end
45
+
31
46
  it "ignores unlisted scopes" do
32
- search = Person.search('restricted' => true)
47
+ search = Person.ransack('restricted' => true)
33
48
  search.result.to_sql.should_not include "restricted"
34
49
  end
35
50
 
36
51
  it "ignores false scopes" do
37
- search = Person.search('active' => false)
52
+ search = Person.ransack('active' => false)
53
+ search.result.to_sql.should_not include "active"
54
+ end
55
+
56
+ it "ignores stringy false scopes" do
57
+ search = Person.ransack('active' => 'false')
38
58
  search.result.to_sql.should_not include "active"
39
59
  end
40
60
 
41
61
  it "passes values to scopes" do
42
- search = Person.search('over_age' => 18)
62
+ search = Person.ransack('over_age' => 18)
43
63
  search.result.to_sql.should include "age > 18"
44
64
  end
45
65
 
46
66
  it "chains scopes" do
47
- search = Person.search('over_age' => 18, 'active' => true)
67
+ search = Person.ransack('over_age' => 18, 'active' => true)
48
68
  search.result.to_sql.should include "age > 18"
49
69
  search.result.to_sql.should include "active = 1"
50
70
  end
51
71
  end
52
72
 
53
73
  it 'does not raise exception for string :params argument' do
54
- lambda { Person.search('') }.should_not raise_error
74
+ lambda { Person.ransack('') }.should_not raise_error
55
75
  end
56
76
 
57
77
  it 'does not modify the parameters' do
58
78
  params = { :name_eq => '' }
59
- expect { Person.search(params) }.not_to change { params }
79
+ expect { Person.ransack(params) }.not_to change { params }
60
80
  end
61
81
  end
62
82
 
@@ -82,14 +102,14 @@ module Ransack
82
102
  # end
83
103
 
84
104
  it 'creates ransack attributes' do
85
- s = Person.search(:reversed_name_eq => 'htimS cirA')
105
+ s = Person.ransack(:reversed_name_eq => 'htimS cirA')
86
106
  expect(s.result.size).to eq(1)
87
107
 
88
108
  expect(s.result.first).to eq Person.where(name: 'Aric Smith').first
89
109
  end
90
110
 
91
111
  it 'can be accessed through associations' do
92
- s = Person.search(:children_reversed_name_eq => 'htimS cirA')
112
+ s = Person.ransack(:children_reversed_name_eq => 'htimS cirA')
93
113
  expect(s.result.to_sql).to match(
94
114
  /#{quote_table_name("children_people")}.#{
95
115
  quote_column_name("name")} = 'Aric Smith'/
@@ -97,80 +117,93 @@ module Ransack
97
117
  end
98
118
 
99
119
  it 'allows an "attribute" to be an InfixOperation' do
100
- s = Person.search(:doubled_name_eq => 'Aric SmithAric Smith')
120
+ s = Person.ransack(:doubled_name_eq => 'Aric SmithAric Smith')
101
121
  expect(s.result.first).to eq Person.where(name: 'Aric Smith').first
102
122
  end if defined?(Arel::Nodes::InfixOperation) && sane_adapter?
103
123
 
104
124
  it "doesn't break #count if using InfixOperations" do
105
- s = Person.search(:doubled_name_eq => 'Aric SmithAric Smith')
125
+ s = Person.ransack(:doubled_name_eq => 'Aric SmithAric Smith')
106
126
  expect(s.result.count).to eq 1
107
127
  end if defined?(Arel::Nodes::InfixOperation) && sane_adapter?
108
128
 
109
129
  it "should remove empty key value pairs from the params hash" do
110
- s = Person.search(:children_reversed_name_eq => '')
130
+ s = Person.ransack(:children_reversed_name_eq => '')
111
131
  expect(s.result.to_sql).not_to match /LEFT OUTER JOIN/
112
132
  end
113
133
 
114
134
  it "should keep proper key value pairs in the params hash" do
115
- s = Person.search(:children_reversed_name_eq => 'Testing')
135
+ s = Person.ransack(:children_reversed_name_eq => 'Testing')
116
136
  expect(s.result.to_sql).to match /LEFT OUTER JOIN/
117
137
  end
118
138
 
119
139
  it "should function correctly when nil is passed in" do
120
- s = Person.search(nil)
140
+ s = Person.ransack(nil)
121
141
  end
122
142
 
123
143
  it "should function correctly when a blank string is passed in" do
124
- s = Person.search('')
144
+ s = Person.ransack('')
125
145
  end
126
146
 
127
147
  it "should function correctly when using fields with dots in them" do
128
- s = Person.search(:email_cont => "example.com")
148
+ s = Person.ransack(:email_cont => "example.com")
129
149
  expect(s.result.exists?).to be true
130
150
  end
131
151
 
132
152
  it "should function correctly when using fields with % in them" do
133
153
  p = Person.create!(:name => "110%-er")
134
- s = Person.search(:name_cont => "10%")
154
+ s = Person.ransack(:name_cont => "10%")
135
155
  expect(s.result.to_a).to eq [p]
136
156
  end
137
157
 
138
158
  it "should function correctly when using fields with backslashes in them" do
139
159
  p = Person.create!(:name => "\\WINNER\\")
140
- s = Person.search(:name_cont => "\\WINNER\\")
160
+ s = Person.ransack(:name_cont => "\\WINNER\\")
141
161
  expect(s.result.to_a).to eq [p]
142
162
  end
143
163
 
164
+ context "search using an `in` predicate and an array passed to a ransacker" do
165
+ it "should function correctly when passing an array of ids" do
166
+ s = Person.ransack(array_users_in: true)
167
+ expect(s.result.length).to be > 0
168
+ end
169
+
170
+ it "should function correctly when passing an array of strings" do
171
+ p = Person.create!(name: Person.first.id.to_s)
172
+ s = Person.ransack(array_names_in: true)
173
+ expect(s.result.length).to be > 0
174
+ end
175
+ end
176
+
144
177
  it "should function correctly when an attribute name ends with '_start'" do
145
178
  p = Person.create!(:new_start => 'Bar and foo', :name => 'Xiang')
146
179
 
147
- s = Person.search(:new_start_end => ' and foo')
180
+ s = Person.ransack(:new_start_end => ' and foo')
148
181
  expect(s.result.to_a).to eq [p]
149
182
 
150
- s = Person.search(:name_or_new_start_start => 'Xia')
183
+ s = Person.ransack(:name_or_new_start_start => 'Xia')
151
184
  expect(s.result.to_a).to eq [p]
152
185
 
153
- s = Person.search(:new_start_or_name_end => 'iang')
186
+ s = Person.ransack(:new_start_or_name_end => 'iang')
154
187
  expect(s.result.to_a).to eq [p]
155
188
  end
156
189
 
157
190
  it "should function correctly when an attribute name ends with '_end'" do
158
191
  p = Person.create!(:stop_end => 'Foo and bar', :name => 'Marianne')
159
192
 
160
- s = Person.search(:stop_end_start => 'Foo and')
193
+ s = Person.ransack(:stop_end_start => 'Foo and')
161
194
  expect(s.result.to_a).to eq [p]
162
195
 
163
- s = Person.search(:stop_end_or_name_end => 'anne')
196
+ s = Person.ransack(:stop_end_or_name_end => 'anne')
164
197
  expect(s.result.to_a).to eq [p]
165
198
 
166
- s = Person.search(:name_or_stop_end_end => ' bar')
199
+ s = Person.ransack(:name_or_stop_end_end => ' bar')
167
200
  expect(s.result.to_a).to eq [p]
168
201
  end
169
202
 
170
203
  it "should function correctly when an attribute name has 'and' in it" do
171
204
  # FIXME: this test does not pass!
172
205
  p = Person.create!(:terms_and_conditions => true)
173
- s = Person.search(:terms_and_conditions_eq => true)
206
+ s = Person.ransack(:terms_and_conditions_eq => true)
174
207
  # search is not detecting the attribute
175
208
  puts "
176
209
  FIXME: Search not detecting the `terms_and_conditions` attribute in
@@ -179,7 +212,7 @@ module Ransack
179
212
  end
180
213
 
181
214
  it 'allows sort by "only_sort" field' do
182
- s = Person.search(
215
+ s = Person.ransack(
183
216
  "s" => { "0" => { "dir" => "asc", "name" => "only_sort" } }
184
217
  )
185
218
  expect(s.result.to_sql).to match(
@@ -189,7 +222,7 @@ module Ransack
189
222
  end
190
223
 
191
224
  it "doesn't sort by 'only_search' field" do
192
- s = Person.search(
225
+ s = Person.ransack(
193
226
  "s" => { "0" => { "dir" => "asc", "name" => "only_search" } }
194
227
  )
195
228
  expect(s.result.to_sql).not_to match(
@@ -199,7 +232,7 @@ module Ransack
199
232
  end
200
233
 
201
234
  it 'allows search by "only_search" field' do
202
- s = Person.search(:only_search_eq => 'htimS cirA')
235
+ s = Person.ransack(:only_search_eq => 'htimS cirA')
203
236
  expect(s.result.to_sql).to match(
204
237
  /WHERE #{quote_table_name("people")}.#{
205
238
  quote_column_name("only_search")} = 'htimS cirA'/
@@ -207,7 +240,7 @@ module Ransack
207
240
  end
208
241
 
209
242
  it "can't be searched by 'only_sort'" do
210
- s = Person.search(:only_sort_eq => 'htimS cirA')
243
+ s = Person.ransack(:only_sort_eq => 'htimS cirA')
211
244
  expect(s.result.to_sql).not_to match(
212
245
  /WHERE #{quote_table_name("people")}.#{
213
246
  quote_column_name("only_sort")} = 'htimS cirA'/
@@ -215,7 +248,7 @@ module Ransack
215
248
  end
216
249
 
217
250
  it 'allows sort by "only_admin" field, if auth_object: :admin' do
218
- s = Person.search(
251
+ s = Person.ransack(
219
252
  { "s" => { "0" => { "dir" => "asc", "name" => "only_admin" } } },
220
253
  { auth_object: :admin }
221
254
  )
@@ -226,7 +259,7 @@ module Ransack
226
259
  end
227
260
 
228
261
  it "doesn't sort by 'only_admin' field, if auth_object: nil" do
229
- s = Person.search(
262
+ s = Person.ransack(
230
263
  "s" => { "0" => { "dir" => "asc", "name" => "only_admin" } }
231
264
  )
232
265
  expect(s.result.to_sql).not_to match(
@@ -236,7 +269,7 @@ module Ransack
236
269
  end
237
270
 
238
271
  it 'allows search by "only_admin" field, if auth_object: :admin' do
239
- s = Person.search(
272
+ s = Person.ransack(
240
273
  { :only_admin_eq => 'htimS cirA' },
241
274
  { :auth_object => :admin }
242
275
  )
@@ -247,7 +280,7 @@ module Ransack
247
280
  end
248
281
 
249
282
  it "can't be searched by 'only_admin', if auth_object: nil" do
250
- s = Person.search(:only_admin_eq => 'htimS cirA')
283
+ s = Person.ransack(:only_admin_eq => 'htimS cirA')
251
284
  expect(s.result.to_sql).not_to match(
252
285
  /WHERE #{quote_table_name("people")}.#{
253
286
  quote_column_name("only_admin")} = 'htimS cirA'/