ransack 1.5.1 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +47 -3
  3. data/CHANGELOG.md +106 -18
  4. data/CONTRIBUTING.md +56 -23
  5. data/Gemfile +16 -5
  6. data/README.md +114 -38
  7. data/Rakefile +30 -2
  8. data/lib/ransack.rb +9 -0
  9. data/lib/ransack/adapters/active_record/3.0/compat.rb +11 -8
  10. data/lib/ransack/adapters/active_record/3.0/context.rb +14 -22
  11. data/lib/ransack/adapters/active_record/3.1/context.rb +14 -22
  12. data/lib/ransack/adapters/active_record/context.rb +36 -31
  13. data/lib/ransack/adapters/active_record/ransack/constants.rb +113 -0
  14. data/lib/ransack/adapters/active_record/ransack/context.rb +64 -0
  15. data/lib/ransack/adapters/active_record/ransack/nodes/condition.rb +48 -0
  16. data/lib/ransack/adapters/active_record/ransack/translate.rb +12 -0
  17. data/lib/ransack/adapters/active_record/ransack/visitor.rb +24 -0
  18. data/lib/ransack/adapters/mongoid.rb +13 -0
  19. data/lib/ransack/adapters/mongoid/3.2/.gitkeep +0 -0
  20. data/lib/ransack/adapters/mongoid/attributes/attribute.rb +37 -0
  21. data/lib/ransack/adapters/mongoid/attributes/order_predications.rb +17 -0
  22. data/lib/ransack/adapters/mongoid/attributes/predications.rb +141 -0
  23. data/lib/ransack/adapters/mongoid/base.rb +126 -0
  24. data/lib/ransack/adapters/mongoid/context.rb +208 -0
  25. data/lib/ransack/adapters/mongoid/inquiry_hash.rb +23 -0
  26. data/lib/ransack/adapters/mongoid/ransack/constants.rb +88 -0
  27. data/lib/ransack/adapters/mongoid/ransack/context.rb +60 -0
  28. data/lib/ransack/adapters/mongoid/ransack/nodes/condition.rb +27 -0
  29. data/lib/ransack/adapters/mongoid/ransack/translate.rb +13 -0
  30. data/lib/ransack/adapters/mongoid/ransack/visitor.rb +24 -0
  31. data/lib/ransack/adapters/mongoid/table.rb +35 -0
  32. data/lib/ransack/configuration.rb +22 -4
  33. data/lib/ransack/constants.rb +26 -120
  34. data/lib/ransack/context.rb +32 -60
  35. data/lib/ransack/helpers/form_builder.rb +50 -36
  36. data/lib/ransack/helpers/form_helper.rb +148 -104
  37. data/lib/ransack/naming.rb +11 -11
  38. data/lib/ransack/nodes.rb +2 -0
  39. data/lib/ransack/nodes/bindable.rb +12 -4
  40. data/lib/ransack/nodes/condition.rb +5 -22
  41. data/lib/ransack/nodes/grouping.rb +9 -10
  42. data/lib/ransack/nodes/sort.rb +3 -2
  43. data/lib/ransack/nodes/value.rb +1 -2
  44. data/lib/ransack/predicate.rb +3 -3
  45. data/lib/ransack/search.rb +46 -13
  46. data/lib/ransack/translate.rb +8 -8
  47. data/lib/ransack/version.rb +1 -1
  48. data/lib/ransack/visitor.rb +4 -16
  49. data/ransack.gemspec +1 -0
  50. data/spec/mongoid/adapters/mongoid/base_spec.rb +276 -0
  51. data/spec/mongoid/adapters/mongoid/context_spec.rb +56 -0
  52. data/spec/mongoid/configuration_spec.rb +66 -0
  53. data/spec/mongoid/dependencies_spec.rb +8 -0
  54. data/spec/mongoid/helpers/ransack_helper.rb +11 -0
  55. data/spec/mongoid/nodes/condition_spec.rb +34 -0
  56. data/spec/mongoid/nodes/grouping_spec.rb +13 -0
  57. data/spec/mongoid/predicate_spec.rb +155 -0
  58. data/spec/mongoid/search_spec.rb +446 -0
  59. data/spec/mongoid/support/mongoid.yml +6 -0
  60. data/spec/mongoid/support/schema.rb +128 -0
  61. data/spec/mongoid/translate_spec.rb +14 -0
  62. data/spec/mongoid_spec_helper.rb +59 -0
  63. data/spec/ransack/adapters/active_record/base_spec.rb +68 -35
  64. data/spec/ransack/dependencies_spec.rb +3 -1
  65. data/spec/ransack/helpers/form_builder_spec.rb +6 -6
  66. data/spec/ransack/helpers/form_helper_spec.rb +114 -47
  67. data/spec/ransack/nodes/condition_spec.rb +2 -2
  68. data/spec/ransack/search_spec.rb +2 -6
  69. data/spec/ransack/translate_spec.rb +1 -1
  70. data/spec/spec_helper.rb +2 -3
  71. data/spec/support/schema.rb +9 -0
  72. metadata +49 -4
@@ -0,0 +1,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'/