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.
Files changed (117) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +6 -4
  3. data/lib/polyamorous/polyamorous.rb +1 -1
  4. data/lib/ransack/adapters/active_record/context.rb +30 -3
  5. data/lib/ransack/context.rb +3 -0
  6. data/lib/ransack/helpers/form_builder.rb +6 -7
  7. data/lib/ransack/helpers/form_helper.rb +86 -20
  8. data/lib/ransack/locale/ja.yml +51 -51
  9. data/lib/ransack/locale/ko.yml +6 -6
  10. data/lib/ransack/locale/uk.yml +72 -0
  11. data/lib/ransack/nodes/condition.rb +35 -5
  12. data/lib/ransack/nodes/grouping.rb +1 -1
  13. data/lib/ransack/nodes/sort.rb +1 -1
  14. data/lib/ransack/nodes/value.rb +1 -1
  15. data/lib/ransack/search.rb +1 -1
  16. data/lib/ransack/version.rb +1 -1
  17. data/lib/ransack.rb +4 -0
  18. data/spec/console.rb +3 -15
  19. data/spec/factories/articles.rb +7 -0
  20. data/spec/factories/comments.rb +7 -0
  21. data/spec/factories/notes.rb +13 -0
  22. data/spec/factories/people.rb +10 -0
  23. data/spec/factories/tags.rb +5 -0
  24. data/spec/polyamorous/join_association_spec.rb +0 -1
  25. data/spec/polyamorous/join_dependency_spec.rb +0 -1
  26. data/spec/ransack/adapters/active_record/base_spec.rb +139 -2
  27. data/spec/ransack/adapters/active_record/context_spec.rb +72 -0
  28. data/spec/ransack/helpers/form_builder_spec.rb +0 -2
  29. data/spec/ransack/helpers/form_helper_spec.rb +219 -5
  30. data/spec/ransack/invalid_search_error_spec.rb +27 -0
  31. data/spec/ransack/nodes/condition_spec.rb +229 -0
  32. data/spec/ransack/nodes/grouping_spec.rb +2 -2
  33. data/spec/ransack/nodes/value_spec.rb +12 -1
  34. data/spec/ransack/predicate_spec.rb +0 -1
  35. data/spec/ransack/ransacker_spec.rb +69 -0
  36. data/spec/ransack/search_spec.rb +115 -2
  37. data/spec/ransack/translate_spec.rb +0 -1
  38. data/spec/spec_helper.rb +7 -21
  39. data/spec/support/schema.rb +36 -9
  40. metadata +51 -93
  41. data/.github/FUNDING.yml +0 -3
  42. data/.github/SECURITY.md +0 -12
  43. data/.github/workflows/codeql.yml +0 -72
  44. data/.github/workflows/cronjob.yml +0 -141
  45. data/.github/workflows/deploy.yml +0 -35
  46. data/.github/workflows/rubocop.yml +0 -20
  47. data/.github/workflows/test-deploy.yml +0 -29
  48. data/.github/workflows/test.yml +0 -183
  49. data/.gitignore +0 -7
  50. data/.nojekyll +0 -0
  51. data/.rubocop.yml +0 -50
  52. data/CHANGELOG.md +0 -1193
  53. data/CONTRIBUTING.md +0 -171
  54. data/Gemfile +0 -58
  55. data/Rakefile +0 -24
  56. data/bug_report_templates/test-ransack-scope-and-column-same-name.rb +0 -78
  57. data/bug_report_templates/test-ransacker-arel-present-predicate.rb +0 -75
  58. data/docs/.gitignore +0 -19
  59. data/docs/.nojekyll +0 -0
  60. data/docs/babel.config.js +0 -3
  61. data/docs/blog/2022-03-27-ransack-3.0.0.md +0 -20
  62. data/docs/docs/getting-started/_category_.json +0 -4
  63. data/docs/docs/getting-started/advanced-mode.md +0 -46
  64. data/docs/docs/getting-started/configuration.md +0 -47
  65. data/docs/docs/getting-started/search-matches.md +0 -67
  66. data/docs/docs/getting-started/simple-mode.md +0 -289
  67. data/docs/docs/getting-started/sorting.md +0 -71
  68. data/docs/docs/getting-started/using-predicates.md +0 -282
  69. data/docs/docs/going-further/_category_.json +0 -4
  70. data/docs/docs/going-further/acts-as-taggable-on.md +0 -114
  71. data/docs/docs/going-further/associations.md +0 -70
  72. data/docs/docs/going-further/custom-predicates.md +0 -52
  73. data/docs/docs/going-further/documentation.md +0 -43
  74. data/docs/docs/going-further/exporting-to-csv.md +0 -49
  75. data/docs/docs/going-further/external-guides.md +0 -57
  76. data/docs/docs/going-further/form-customisation.md +0 -63
  77. data/docs/docs/going-further/i18n.md +0 -53
  78. data/docs/docs/going-further/img/create_release.png +0 -0
  79. data/docs/docs/going-further/merging-searches.md +0 -41
  80. data/docs/docs/going-further/other-notes.md +0 -425
  81. data/docs/docs/going-further/polymorphic-search.md +0 -46
  82. data/docs/docs/going-further/ransackers.md +0 -331
  83. data/docs/docs/going-further/release_process.md +0 -36
  84. data/docs/docs/going-further/saving-queries.md +0 -82
  85. data/docs/docs/going-further/searching-postgres.md +0 -57
  86. data/docs/docs/going-further/wiki-contributors.md +0 -82
  87. data/docs/docs/intro.md +0 -99
  88. data/docs/docusaurus.config.js +0 -120
  89. data/docs/package.json +0 -42
  90. data/docs/sidebars.js +0 -31
  91. data/docs/src/components/HomepageFeatures/index.js +0 -64
  92. data/docs/src/components/HomepageFeatures/styles.module.css +0 -11
  93. data/docs/src/css/custom.css +0 -39
  94. data/docs/src/pages/index.module.css +0 -23
  95. data/docs/src/pages/markdown-page.md +0 -7
  96. data/docs/static/.nojekyll +0 -0
  97. data/docs/static/img/docusaurus.png +0 -0
  98. data/docs/static/img/favicon.ico +0 -0
  99. data/docs/static/img/logo.svg +0 -1
  100. data/docs/static/img/tutorial/docsVersionDropdown.png +0 -0
  101. data/docs/static/img/tutorial/localeDropdown.png +0 -0
  102. data/docs/static/img/undraw_docusaurus_mountain.svg +0 -171
  103. data/docs/static/img/undraw_docusaurus_react.svg +0 -170
  104. data/docs/static/img/undraw_docusaurus_tree.svg +0 -40
  105. data/docs/static/logo/ransack-h.png +0 -0
  106. data/docs/static/logo/ransack-h.svg +0 -34
  107. data/docs/static/logo/ransack-v.png +0 -0
  108. data/docs/static/logo/ransack-v.svg +0 -34
  109. data/docs/static/logo/ransack.png +0 -0
  110. data/docs/static/logo/ransack.svg +0 -21
  111. data/docs/yarn.lock +0 -8884
  112. data/ransack.gemspec +0 -26
  113. data/spec/blueprints/articles.rb +0 -5
  114. data/spec/blueprints/comments.rb +0 -5
  115. data/spec/blueprints/notes.rb +0 -5
  116. data/spec/blueprints/people.rb +0 -8
  117. 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
@@ -5,7 +5,6 @@ module Ransack
5
5
  FALSE_VALUES = [false, 0, '0', 'f', 'F', 'false', 'FALSE'].to_set
6
6
 
7
7
  describe Predicate do
8
-
9
8
  before do
10
9
  @s = Search.new(Person)
11
10
  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
@@ -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(9000)
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)
@@ -2,7 +2,6 @@ require 'spec_helper'
2
2
 
3
3
  module Ransack
4
4
  describe Translate do
5
-
6
5
  describe '.attribute' do
7
6
  it 'translate namespaced attribute like AR does' do
8
7
  ar_translation = ::Namespace::Article.human_attribute_name(:title)
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,blueprints}/*.rb', __FILE__)]
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