ransack 4.3.0 → 4.4.1
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/README.md +6 -4
- data/lib/polyamorous/polyamorous.rb +1 -1
- data/lib/ransack/adapters/active_record/context.rb +30 -3
- data/lib/ransack/context.rb +3 -0
- data/lib/ransack/helpers/form_builder.rb +6 -7
- data/lib/ransack/helpers/form_helper.rb +86 -20
- data/lib/ransack/locale/ja.yml +51 -51
- data/lib/ransack/locale/ko.yml +6 -6
- data/lib/ransack/locale/uk.yml +72 -0
- data/lib/ransack/nodes/condition.rb +35 -5
- data/lib/ransack/nodes/grouping.rb +1 -1
- data/lib/ransack/nodes/sort.rb +1 -1
- data/lib/ransack/nodes/value.rb +1 -1
- data/lib/ransack/search.rb +1 -1
- data/lib/ransack/version.rb +1 -1
- data/lib/ransack.rb +4 -0
- data/spec/console.rb +3 -15
- data/spec/factories/articles.rb +7 -0
- data/spec/factories/comments.rb +7 -0
- data/spec/factories/notes.rb +13 -0
- data/spec/factories/people.rb +10 -0
- data/spec/factories/tags.rb +5 -0
- data/spec/polyamorous/join_association_spec.rb +0 -1
- data/spec/polyamorous/join_dependency_spec.rb +0 -1
- data/spec/ransack/adapters/active_record/base_spec.rb +139 -2
- data/spec/ransack/adapters/active_record/context_spec.rb +72 -0
- data/spec/ransack/helpers/form_builder_spec.rb +0 -2
- data/spec/ransack/helpers/form_helper_spec.rb +219 -5
- data/spec/ransack/invalid_search_error_spec.rb +27 -0
- data/spec/ransack/nodes/condition_spec.rb +229 -0
- data/spec/ransack/nodes/grouping_spec.rb +2 -2
- data/spec/ransack/nodes/value_spec.rb +12 -1
- data/spec/ransack/predicate_spec.rb +0 -1
- data/spec/ransack/ransacker_spec.rb +69 -0
- data/spec/ransack/search_spec.rb +115 -2
- data/spec/ransack/translate_spec.rb +0 -1
- data/spec/spec_helper.rb +7 -21
- data/spec/support/schema.rb +36 -9
- metadata +51 -93
- data/.github/FUNDING.yml +0 -3
- data/.github/SECURITY.md +0 -12
- data/.github/workflows/codeql.yml +0 -72
- data/.github/workflows/cronjob.yml +0 -141
- data/.github/workflows/deploy.yml +0 -35
- data/.github/workflows/rubocop.yml +0 -20
- data/.github/workflows/test-deploy.yml +0 -29
- data/.github/workflows/test.yml +0 -183
- data/.gitignore +0 -7
- data/.nojekyll +0 -0
- data/.rubocop.yml +0 -50
- data/CHANGELOG.md +0 -1193
- data/CONTRIBUTING.md +0 -171
- data/Gemfile +0 -58
- data/Rakefile +0 -24
- data/bug_report_templates/test-ransack-scope-and-column-same-name.rb +0 -78
- data/bug_report_templates/test-ransacker-arel-present-predicate.rb +0 -75
- data/docs/.gitignore +0 -19
- data/docs/.nojekyll +0 -0
- data/docs/babel.config.js +0 -3
- data/docs/blog/2022-03-27-ransack-3.0.0.md +0 -20
- data/docs/docs/getting-started/_category_.json +0 -4
- data/docs/docs/getting-started/advanced-mode.md +0 -46
- data/docs/docs/getting-started/configuration.md +0 -47
- data/docs/docs/getting-started/search-matches.md +0 -67
- data/docs/docs/getting-started/simple-mode.md +0 -289
- data/docs/docs/getting-started/sorting.md +0 -71
- data/docs/docs/getting-started/using-predicates.md +0 -282
- data/docs/docs/going-further/_category_.json +0 -4
- data/docs/docs/going-further/acts-as-taggable-on.md +0 -114
- data/docs/docs/going-further/associations.md +0 -70
- data/docs/docs/going-further/custom-predicates.md +0 -52
- data/docs/docs/going-further/documentation.md +0 -43
- data/docs/docs/going-further/exporting-to-csv.md +0 -49
- data/docs/docs/going-further/external-guides.md +0 -57
- data/docs/docs/going-further/form-customisation.md +0 -63
- data/docs/docs/going-further/i18n.md +0 -53
- data/docs/docs/going-further/img/create_release.png +0 -0
- data/docs/docs/going-further/merging-searches.md +0 -41
- data/docs/docs/going-further/other-notes.md +0 -425
- data/docs/docs/going-further/polymorphic-search.md +0 -46
- data/docs/docs/going-further/ransackers.md +0 -331
- data/docs/docs/going-further/release_process.md +0 -36
- data/docs/docs/going-further/saving-queries.md +0 -82
- data/docs/docs/going-further/searching-postgres.md +0 -57
- data/docs/docs/going-further/wiki-contributors.md +0 -82
- data/docs/docs/intro.md +0 -99
- data/docs/docusaurus.config.js +0 -120
- data/docs/package.json +0 -42
- data/docs/sidebars.js +0 -31
- data/docs/src/components/HomepageFeatures/index.js +0 -64
- data/docs/src/components/HomepageFeatures/styles.module.css +0 -11
- data/docs/src/css/custom.css +0 -39
- data/docs/src/pages/index.module.css +0 -23
- data/docs/src/pages/markdown-page.md +0 -7
- data/docs/static/.nojekyll +0 -0
- data/docs/static/img/docusaurus.png +0 -0
- data/docs/static/img/favicon.ico +0 -0
- data/docs/static/img/logo.svg +0 -1
- data/docs/static/img/tutorial/docsVersionDropdown.png +0 -0
- data/docs/static/img/tutorial/localeDropdown.png +0 -0
- data/docs/static/img/undraw_docusaurus_mountain.svg +0 -171
- data/docs/static/img/undraw_docusaurus_react.svg +0 -170
- data/docs/static/img/undraw_docusaurus_tree.svg +0 -40
- data/docs/static/logo/ransack-h.png +0 -0
- data/docs/static/logo/ransack-h.svg +0 -34
- data/docs/static/logo/ransack-v.png +0 -0
- data/docs/static/logo/ransack-v.svg +0 -34
- data/docs/static/logo/ransack.png +0 -0
- data/docs/static/logo/ransack.svg +0 -21
- data/docs/yarn.lock +0 -8884
- data/ransack.gemspec +0 -26
- data/spec/blueprints/articles.rb +0 -5
- data/spec/blueprints/comments.rb +0 -5
- data/spec/blueprints/notes.rb +0 -5
- data/spec/blueprints/people.rb +0 -8
- data/spec/blueprints/tags.rb +0 -3
@@ -99,6 +99,235 @@ module Ransack
|
|
99
99
|
specify { expect(subject).to eq Condition.extract(Context.for(Person), 'full_name_eq', Person.first.name) }
|
100
100
|
end
|
101
101
|
end
|
102
|
+
|
103
|
+
context 'with wildcard string values' do
|
104
|
+
it 'properly quotes values with wildcards for LIKE predicates' do
|
105
|
+
ransack_hash = { name_cont: 'test%' }
|
106
|
+
sql = Person.ransack(ransack_hash).result.to_sql
|
107
|
+
|
108
|
+
# The % should be properly quoted in the SQL
|
109
|
+
case ActiveRecord::Base.connection.adapter_name
|
110
|
+
when "Mysql2"
|
111
|
+
expect(sql).to include("LIKE '%test\\\\%%'")
|
112
|
+
expect(sql).not_to include("NOT LIKE '%test\\\\%%'")
|
113
|
+
when "PostGIS", "PostgreSQL"
|
114
|
+
expect(sql).to include("ILIKE '%test\\%%'")
|
115
|
+
expect(sql).not_to include("NOT ILIKE '%test\\%%'")
|
116
|
+
else
|
117
|
+
expect(sql).to include("LIKE '%test%%'")
|
118
|
+
expect(sql).not_to include("NOT LIKE '%test%%'")
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
it 'properly quotes values with wildcards for NOT LIKE predicates' do
|
123
|
+
ransack_hash = { name_not_cont: 'test%' }
|
124
|
+
sql = Person.ransack(ransack_hash).result.to_sql
|
125
|
+
|
126
|
+
# The % should be properly quoted in the SQL
|
127
|
+
case ActiveRecord::Base.connection.adapter_name
|
128
|
+
when "Mysql2"
|
129
|
+
expect(sql).to include("NOT LIKE '%test\\\\%%'")
|
130
|
+
when "PostGIS", "PostgreSQL"
|
131
|
+
expect(sql).to include("NOT ILIKE '%test\\%%'")
|
132
|
+
else
|
133
|
+
expect(sql).to include("NOT LIKE '%test%%'")
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
context 'with negative conditions on associations' do
|
139
|
+
it 'handles not_null predicate with true value correctly' do
|
140
|
+
ransack_hash = { comments_id_not_null: true }
|
141
|
+
sql = Person.ransack(ransack_hash).result.to_sql
|
142
|
+
|
143
|
+
# Should generate an IN query with IS NOT NULL condition
|
144
|
+
expect(sql).to include('IN (')
|
145
|
+
expect(sql).to include('IS NOT NULL')
|
146
|
+
expect(sql).not_to include('IS NULL')
|
147
|
+
end
|
148
|
+
|
149
|
+
it 'handles not_null predicate with false value correctly' do
|
150
|
+
ransack_hash = { comments_id_not_null: false }
|
151
|
+
sql = Person.ransack(ransack_hash).result.to_sql
|
152
|
+
|
153
|
+
# Should generate a NOT IN query with IS NULL condition
|
154
|
+
expect(sql).to include('NOT IN (')
|
155
|
+
expect(sql).to include('IS NULL')
|
156
|
+
expect(sql).not_to include('IS NOT NULL')
|
157
|
+
end
|
158
|
+
|
159
|
+
it 'handles not_cont predicate correctly' do
|
160
|
+
ransack_hash = { comments_body_not_cont: 'test' }
|
161
|
+
sql = Person.ransack(ransack_hash).result.to_sql
|
162
|
+
|
163
|
+
# Should generate a NOT IN query with LIKE condition (not NOT LIKE)
|
164
|
+
expect(sql).to include('NOT IN (')
|
165
|
+
expect(sql).to include("LIKE '%test%'")
|
166
|
+
expect(sql).not_to include("NOT LIKE '%test%'")
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
context 'with nested conditions' do
|
171
|
+
it 'correctly identifies non-nested conditions' do
|
172
|
+
condition = Condition.extract(
|
173
|
+
Context.for(Person), 'name_eq', 'Test'
|
174
|
+
)
|
175
|
+
|
176
|
+
# Create a mock parent table
|
177
|
+
parent_table = Person.arel_table
|
178
|
+
|
179
|
+
# Get the attribute name and make sure it starts with the table name
|
180
|
+
attribute = condition.attributes.first
|
181
|
+
expect(attribute.name).to eq('name')
|
182
|
+
expect(parent_table.name).to eq('people')
|
183
|
+
|
184
|
+
# The method should return false because 'name' doesn't start with 'people'
|
185
|
+
result = condition.send(:not_nested_condition, attribute, parent_table)
|
186
|
+
expect(result).to be false
|
187
|
+
end
|
188
|
+
|
189
|
+
it 'correctly identifies truly non-nested conditions when attribute name starts with table name' do
|
190
|
+
# Create a condition with an attribute that starts with the table name
|
191
|
+
condition = Condition.extract(
|
192
|
+
Context.for(Person), 'name_eq', 'Test'
|
193
|
+
)
|
194
|
+
|
195
|
+
# Modify the attribute name to start with the table name for testing purposes
|
196
|
+
attribute = condition.attributes.first
|
197
|
+
allow(attribute).to receive(:name).and_return('people_name')
|
198
|
+
|
199
|
+
# Create a parent table
|
200
|
+
parent_table = Person.arel_table
|
201
|
+
|
202
|
+
# Now the method should return true because 'people_name' starts with 'people'
|
203
|
+
result = condition.send(:not_nested_condition, attribute, parent_table)
|
204
|
+
expect(result).to be true
|
205
|
+
end
|
206
|
+
|
207
|
+
it 'correctly identifies nested conditions' do
|
208
|
+
condition = Condition.extract(
|
209
|
+
Context.for(Person), 'articles_title_eq', 'Test'
|
210
|
+
)
|
211
|
+
|
212
|
+
# Create a mock table alias
|
213
|
+
parent_table = Arel::Nodes::TableAlias.new(
|
214
|
+
Article.arel_table,
|
215
|
+
Article.arel_table
|
216
|
+
)
|
217
|
+
|
218
|
+
# Access the private method using send
|
219
|
+
result = condition.send(:not_nested_condition, condition.attributes.first, parent_table)
|
220
|
+
|
221
|
+
# Should return false for nested condition
|
222
|
+
expect(result).to be false
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
context 'with polymorphic associations and not_in predicate' do
|
227
|
+
before do
|
228
|
+
# Define test models for polymorphic associations
|
229
|
+
class ::TestTask < ActiveRecord::Base
|
230
|
+
self.table_name = 'tasks'
|
231
|
+
has_many :follows, primary_key: :uid, inverse_of: :followed, foreign_key: :followed_uid, class_name: 'TestFollow'
|
232
|
+
has_many :users, through: :follows, source: :follower, source_type: 'TestUser'
|
233
|
+
|
234
|
+
# Add ransackable_attributes method
|
235
|
+
def self.ransackable_attributes(auth_object = nil)
|
236
|
+
["created_at", "id", "name", "uid", "updated_at"]
|
237
|
+
end
|
238
|
+
|
239
|
+
# Add ransackable_associations method
|
240
|
+
def self.ransackable_associations(auth_object = nil)
|
241
|
+
["follows", "users"]
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
class ::TestFollow < ActiveRecord::Base
|
246
|
+
self.table_name = 'follows'
|
247
|
+
belongs_to :follower, polymorphic: true, foreign_key: :follower_uid, primary_key: :uid
|
248
|
+
belongs_to :followed, polymorphic: true, foreign_key: :followed_uid, primary_key: :uid
|
249
|
+
|
250
|
+
# Add ransackable_attributes method
|
251
|
+
def self.ransackable_attributes(auth_object = nil)
|
252
|
+
["created_at", "followed_type", "followed_uid", "follower_type", "follower_uid", "id", "updated_at"]
|
253
|
+
end
|
254
|
+
|
255
|
+
# Add ransackable_associations method
|
256
|
+
def self.ransackable_associations(auth_object = nil)
|
257
|
+
["followed", "follower"]
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
class ::TestUser < ActiveRecord::Base
|
262
|
+
self.table_name = 'users'
|
263
|
+
has_many :follows, primary_key: :uid, inverse_of: :follower, foreign_key: :follower_uid, class_name: 'TestFollow'
|
264
|
+
has_many :tasks, through: :follows, source: :followed, source_type: 'TestTask'
|
265
|
+
|
266
|
+
# Add ransackable_attributes method
|
267
|
+
def self.ransackable_attributes(auth_object = nil)
|
268
|
+
["created_at", "id", "name", "uid", "updated_at"]
|
269
|
+
end
|
270
|
+
|
271
|
+
# Add ransackable_associations method
|
272
|
+
def self.ransackable_associations(auth_object = nil)
|
273
|
+
["follows", "tasks"]
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
# Create tables if they don't exist
|
278
|
+
ActiveRecord::Base.connection.create_table(:tasks, force: true) do |t|
|
279
|
+
t.string :uid
|
280
|
+
t.string :name
|
281
|
+
t.timestamps null: false
|
282
|
+
end
|
283
|
+
|
284
|
+
ActiveRecord::Base.connection.create_table(:follows, force: true) do |t|
|
285
|
+
t.string :followed_uid, null: false
|
286
|
+
t.string :followed_type, null: false
|
287
|
+
t.string :follower_uid, null: false
|
288
|
+
t.string :follower_type, null: false
|
289
|
+
t.timestamps null: false
|
290
|
+
t.index [:followed_uid, :followed_type]
|
291
|
+
t.index [:follower_uid, :follower_type]
|
292
|
+
end
|
293
|
+
|
294
|
+
ActiveRecord::Base.connection.create_table(:users, force: true) do |t|
|
295
|
+
t.string :uid
|
296
|
+
t.string :name
|
297
|
+
t.timestamps null: false
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
after do
|
302
|
+
# Clean up test models and tables
|
303
|
+
Object.send(:remove_const, :TestTask)
|
304
|
+
Object.send(:remove_const, :TestFollow)
|
305
|
+
Object.send(:remove_const, :TestUser)
|
306
|
+
|
307
|
+
ActiveRecord::Base.connection.drop_table(:tasks, if_exists: true)
|
308
|
+
ActiveRecord::Base.connection.drop_table(:follows, if_exists: true)
|
309
|
+
ActiveRecord::Base.connection.drop_table(:users, if_exists: true)
|
310
|
+
end
|
311
|
+
|
312
|
+
it 'correctly handles not_in predicate with polymorphic associations' do
|
313
|
+
# Create the search
|
314
|
+
search = TestTask.ransack(users_uid_not_in: ['uid_example'])
|
315
|
+
sql = search.result.to_sql
|
316
|
+
|
317
|
+
# Verify the SQL contains the expected NOT IN clause
|
318
|
+
expect(sql).to include('NOT IN')
|
319
|
+
expect(sql).to include("follower_uid")
|
320
|
+
expect(sql).to include("followed_uid")
|
321
|
+
expect(sql).to include("'uid_example'")
|
322
|
+
|
323
|
+
# The SQL should include a reference to tasks.uid
|
324
|
+
expect(sql).to include("tasks")
|
325
|
+
expect(sql).to include("uid")
|
326
|
+
|
327
|
+
# The SQL should include a reference to follows table
|
328
|
+
expect(sql).to include("follows")
|
329
|
+
end
|
330
|
+
end
|
102
331
|
end
|
103
332
|
end
|
104
333
|
end
|
@@ -3,7 +3,6 @@ require 'spec_helper'
|
|
3
3
|
module Ransack
|
4
4
|
module Nodes
|
5
5
|
describe Grouping do
|
6
|
-
|
7
6
|
before do
|
8
7
|
@g = 1
|
9
8
|
end
|
@@ -66,6 +65,7 @@ module Ransack
|
|
66
65
|
}
|
67
66
|
}
|
68
67
|
end
|
68
|
+
|
69
69
|
before { subject.conditions = conditions }
|
70
70
|
|
71
71
|
it 'expect duplicates to be removed' do
|
@@ -98,6 +98,7 @@ module Ransack
|
|
98
98
|
}
|
99
99
|
}
|
100
100
|
end
|
101
|
+
|
101
102
|
before { subject.conditions = conditions }
|
102
103
|
|
103
104
|
it 'expect them to be parsed as different and not as duplicates' do
|
@@ -105,7 +106,6 @@ module Ransack
|
|
105
106
|
end
|
106
107
|
end
|
107
108
|
end
|
108
|
-
|
109
109
|
end
|
110
110
|
end
|
111
111
|
end
|
@@ -71,6 +71,18 @@ module Ransack
|
|
71
71
|
end
|
72
72
|
end
|
73
73
|
|
74
|
+
[[], ["12"], ["101.5"]].each do |value|
|
75
|
+
context "with an array value (#{value.inspect})" do
|
76
|
+
let(:raw_value) { value }
|
77
|
+
|
78
|
+
it "should cast to integer as nil" do
|
79
|
+
result = subject.cast(:integer)
|
80
|
+
|
81
|
+
expect(result).to be nil
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
74
86
|
["12", "101.5"].each do |value|
|
75
87
|
context "with a float value (#{value})" do
|
76
88
|
let(:raw_value) { value }
|
@@ -109,7 +121,6 @@ module Ransack
|
|
109
121
|
end
|
110
122
|
end
|
111
123
|
end
|
112
|
-
|
113
124
|
end
|
114
125
|
end
|
115
126
|
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Ransack
|
4
|
+
describe Ransacker do
|
5
|
+
let(:klass) { Person }
|
6
|
+
let(:name) { :test_ransacker }
|
7
|
+
let(:opts) { {} }
|
8
|
+
|
9
|
+
describe '#initialize' do
|
10
|
+
context 'with minimal options' do
|
11
|
+
subject { Ransacker.new(klass, name, opts) }
|
12
|
+
|
13
|
+
it 'sets the name' do
|
14
|
+
expect(subject.name).to eq(name)
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'sets default type to string' do
|
18
|
+
expect(subject.type).to eq(:string)
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'sets default args to [:parent]' do
|
22
|
+
expect(subject.args).to eq([:parent])
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context 'with custom options' do
|
27
|
+
let(:opts) { { type: :integer, args: [:parent, :custom_arg], formatter: proc { |v| v.to_i } } }
|
28
|
+
|
29
|
+
subject { Ransacker.new(klass, name, opts) }
|
30
|
+
|
31
|
+
it 'sets the custom type' do
|
32
|
+
expect(subject.type).to eq(:integer)
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'sets the custom args' do
|
36
|
+
expect(subject.args).to eq([:parent, :custom_arg])
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'sets the formatter' do
|
40
|
+
expect(subject.formatter).to eq(opts[:formatter])
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context 'with callable option' do
|
45
|
+
let(:callable) { proc { |parent| parent.table[:id] } }
|
46
|
+
let(:opts) { { callable: callable } }
|
47
|
+
|
48
|
+
subject { Ransacker.new(klass, name, opts) }
|
49
|
+
|
50
|
+
it 'initializes successfully' do
|
51
|
+
expect(subject).to be_a(Ransacker)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe 'basic functionality' do
|
57
|
+
subject { Ransacker.new(klass, name, opts) }
|
58
|
+
|
59
|
+
it 'responds to required methods' do
|
60
|
+
expect(subject).to respond_to(:name)
|
61
|
+
expect(subject).to respond_to(:type)
|
62
|
+
expect(subject).to respond_to(:args)
|
63
|
+
expect(subject).to respond_to(:formatter)
|
64
|
+
expect(subject).to respond_to(:attr_from)
|
65
|
+
expect(subject).to respond_to(:call)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
data/spec/ransack/search_spec.rb
CHANGED
@@ -178,7 +178,6 @@ module Ransack
|
|
178
178
|
# AND "articles"."title" = 'Test' AND "articles"."published" = 't' AND ('default_scope' = 'default_scope')
|
179
179
|
# ) ORDER BY "people"."id" DESC
|
180
180
|
|
181
|
-
pending("spec should pass, but I do not know how/where to fix lib code")
|
182
181
|
s = Search.new(Person, published_articles_title_not_eq: 'Test')
|
183
182
|
expect(s.result.to_sql).to include 'default_scope'
|
184
183
|
expect(s.result.to_sql).to include 'published'
|
@@ -314,6 +313,46 @@ module Ransack
|
|
314
313
|
expect { Search.new(Person, params) }.not_to change { params }
|
315
314
|
end
|
316
315
|
|
316
|
+
context 'with empty search parameters' do
|
317
|
+
it 'handles completely empty parameters' do
|
318
|
+
search = Search.new(Person, {})
|
319
|
+
expect(search.result.to_sql).not_to match(/WHERE/)
|
320
|
+
end
|
321
|
+
|
322
|
+
it 'handles nil parameters' do
|
323
|
+
search = Search.new(Person, nil)
|
324
|
+
expect(search.result.to_sql).not_to match(/WHERE/)
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
context 'with whitespace-only values' do
|
329
|
+
before do
|
330
|
+
Ransack.configure { |c| c.strip_whitespace = true }
|
331
|
+
end
|
332
|
+
|
333
|
+
it 'removes whitespace-only values' do
|
334
|
+
expect_any_instance_of(Search).to receive(:build).with({})
|
335
|
+
Search.new(Person, name_eq: ' ')
|
336
|
+
end
|
337
|
+
|
338
|
+
it 'keeps values with content after whitespace stripping' do
|
339
|
+
expect_any_instance_of(Search).to receive(:build).with({ 'name_eq' => 'test' })
|
340
|
+
Search.new(Person, name_eq: ' test ')
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
344
|
+
context 'with special characters in values' do
|
345
|
+
it 'handles values with special regex characters' do
|
346
|
+
search = Search.new(Person, name_cont: 'test[(){}^$|?*+.\\')
|
347
|
+
expect { search.result }.not_to raise_error
|
348
|
+
end
|
349
|
+
|
350
|
+
it 'handles values with SQL injection attempts' do
|
351
|
+
search = Search.new(Person, name_cont: "'; DROP TABLE people; --")
|
352
|
+
expect { search.result }.not_to raise_error
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
317
356
|
context "ransackable_scope" do
|
318
357
|
around(:each) do |example|
|
319
358
|
Person.define_singleton_method(:name_eq) do |name|
|
@@ -337,6 +376,74 @@ module Ransack
|
|
337
376
|
end
|
338
377
|
end
|
339
378
|
|
379
|
+
context "ransackable_scope with array arguments" do
|
380
|
+
around(:each) do |example|
|
381
|
+
Person.define_singleton_method(:domestic) do |countries|
|
382
|
+
self.where(name: countries)
|
383
|
+
end
|
384
|
+
|
385
|
+
Person.define_singleton_method(:flexible_scope) do |*args|
|
386
|
+
self.where(id: args)
|
387
|
+
end
|
388
|
+
|
389
|
+
Person.define_singleton_method(:two_param_scope) do |param1, param2|
|
390
|
+
self.where(name: param1, id: param2)
|
391
|
+
end
|
392
|
+
|
393
|
+
begin
|
394
|
+
example.run
|
395
|
+
ensure
|
396
|
+
Person.singleton_class.undef_method :domestic
|
397
|
+
Person.singleton_class.undef_method :flexible_scope
|
398
|
+
Person.singleton_class.undef_method :two_param_scope
|
399
|
+
end
|
400
|
+
end
|
401
|
+
|
402
|
+
it "handles scopes that take arrays as single arguments (arity 1)" do
|
403
|
+
allow(Person).to receive(:ransackable_scopes)
|
404
|
+
.and_return(Person.ransackable_scopes + [:domestic])
|
405
|
+
|
406
|
+
# This should not raise ArgumentError
|
407
|
+
expect {
|
408
|
+
s = Search.new(Person, domestic: ['US', 'JP'])
|
409
|
+
s.result # This triggers the actual scope call
|
410
|
+
}.not_to raise_error
|
411
|
+
|
412
|
+
s = Search.new(Person, domestic: ['US', 'JP'])
|
413
|
+
expect(s.instance_variable_get(:@scope_args)["domestic"]).to eq("US")
|
414
|
+
end
|
415
|
+
|
416
|
+
it "handles scopes with flexible arity (negative arity)" do
|
417
|
+
allow(Person).to receive(:ransackable_scopes)
|
418
|
+
.and_return(Person.ransackable_scopes + [:flexible_scope])
|
419
|
+
|
420
|
+
expect {
|
421
|
+
s = Search.new(Person, flexible_scope: ['US', 'JP'])
|
422
|
+
s.result
|
423
|
+
}.not_to raise_error
|
424
|
+
end
|
425
|
+
|
426
|
+
it "handles scopes with arity > 1" do
|
427
|
+
allow(Person).to receive(:ransackable_scopes)
|
428
|
+
.and_return(Person.ransackable_scopes + [:two_param_scope])
|
429
|
+
|
430
|
+
expect {
|
431
|
+
s = Search.new(Person, two_param_scope: ['param1', 'param2'])
|
432
|
+
s.result
|
433
|
+
}.not_to raise_error
|
434
|
+
end
|
435
|
+
|
436
|
+
it "still supports the workaround with nested arrays" do
|
437
|
+
allow(Person).to receive(:ransackable_scopes)
|
438
|
+
.and_return(Person.ransackable_scopes + [:domestic])
|
439
|
+
|
440
|
+
# The workaround from the issue should still work
|
441
|
+
expect {
|
442
|
+
s = Search.new(Person, domestic: [['US', 'JP']])
|
443
|
+
s.result
|
444
|
+
}.not_to raise_error
|
445
|
+
end
|
446
|
+
end
|
340
447
|
end
|
341
448
|
|
342
449
|
describe '#result' do
|
@@ -349,6 +456,7 @@ module Ransack
|
|
349
456
|
let(:notable_type_field) {
|
350
457
|
"#{quote_table_name("notes")}.#{quote_column_name("notable_type")}"
|
351
458
|
}
|
459
|
+
|
352
460
|
it 'evaluates conditions contextually' do
|
353
461
|
s = Search.new(Person, children_name_eq: 'Ernie')
|
354
462
|
expect(s.result).to be_an ActiveRecord::Relation
|
@@ -455,7 +563,7 @@ module Ransack
|
|
455
563
|
|
456
564
|
all_or_load, uniq_or_distinct = :load, :distinct
|
457
565
|
expect(s.result.send(all_or_load).size)
|
458
|
-
.to eq(
|
566
|
+
.to eq(8998)
|
459
567
|
expect(s.result(distinct: true).size)
|
460
568
|
.to eq(10)
|
461
569
|
expect(s.result.send(all_or_load).send(uniq_or_distinct))
|
@@ -479,6 +587,11 @@ module Ransack
|
|
479
587
|
@s = Search.new(Person)
|
480
588
|
end
|
481
589
|
|
590
|
+
it 'doesn\'t creates sorts' do
|
591
|
+
@s.sorts = ''
|
592
|
+
expect(@s.sorts.size).to eq(0)
|
593
|
+
end
|
594
|
+
|
482
595
|
it 'creates sorts based on a single attribute/direction' do
|
483
596
|
@s.sorts = 'id desc'
|
484
597
|
expect(@s.sorts.size).to eq(1)
|
data/spec/spec_helper.rb
CHANGED
@@ -1,38 +1,27 @@
|
|
1
|
-
require 'machinist/active_record'
|
2
|
-
require 'polyamorous/polyamorous'
|
3
|
-
require 'sham'
|
4
|
-
require 'faker'
|
5
1
|
require 'ransack'
|
2
|
+
require 'factory_bot'
|
3
|
+
require 'faker'
|
6
4
|
require 'action_controller'
|
7
5
|
require 'ransack/helpers'
|
8
6
|
require 'pry'
|
9
7
|
require 'simplecov'
|
10
8
|
require 'byebug'
|
9
|
+
require 'rspec'
|
11
10
|
|
12
11
|
SimpleCov.start
|
13
12
|
I18n.enforce_available_locales = false
|
14
13
|
Time.zone = 'Eastern Time (US & Canada)'
|
15
14
|
I18n.load_path += Dir[File.join(File.dirname(__FILE__), 'support', '*.yml')]
|
16
15
|
|
17
|
-
Dir[File.expand_path('../{helpers,support,
|
16
|
+
Dir[File.expand_path('../{helpers,support,factories}/*.rb', __FILE__)]
|
18
17
|
.each { |f| require f }
|
19
18
|
|
20
19
|
Faker::Config.random = Random.new(0)
|
21
|
-
Sham.define do
|
22
|
-
name { Faker::Name.name }
|
23
|
-
title { Faker::Lorem.sentence }
|
24
|
-
body { Faker::Lorem.paragraph }
|
25
|
-
salary { |index| 30000 + (index * 1000) }
|
26
|
-
tag_name { Faker::Lorem.words(number: 3).join(' ') }
|
27
|
-
note { Faker::Lorem.words(number: 7).join(' ') }
|
28
|
-
only_admin { Faker::Lorem.words(number: 3).join(' ') }
|
29
|
-
only_search { Faker::Lorem.words(number: 3).join(' ') }
|
30
|
-
only_sort { Faker::Lorem.words(number: 3).join(' ') }
|
31
|
-
notable_id { |id| id }
|
32
|
-
end
|
33
20
|
|
34
21
|
RSpec.configure do |config|
|
35
22
|
config.alias_it_should_behave_like_to :it_has_behavior, 'has behavior'
|
23
|
+
|
24
|
+
config.include FactoryBot::Syntax::Methods
|
36
25
|
|
37
26
|
config.before(:suite) do
|
38
27
|
message = "Running Ransack specs with #{
|
@@ -42,12 +31,9 @@ RSpec.configure do |config|
|
|
42
31
|
line = '=' * message.length
|
43
32
|
puts line, message, line
|
44
33
|
Schema.create
|
45
|
-
SubDB::Schema.create
|
34
|
+
SubDB::Schema.create if defined?(SubDB)
|
46
35
|
end
|
47
36
|
|
48
|
-
config.before(:all) { Sham.reset(:before_all) }
|
49
|
-
config.before(:each) { Sham.reset(:before_each) }
|
50
|
-
|
51
37
|
config.include RansackHelper
|
52
38
|
config.include PolyamorousHelper
|
53
39
|
end
|