ransack 4.2.1 → 4.4.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.
- checksums.yaml +4 -4
- data/README.md +5 -3
- data/lib/polyamorous/polyamorous.rb +2 -2
- data/lib/ransack/adapters/active_record/context.rb +30 -3
- data/lib/ransack/constants.rb +1 -1
- 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/invalid_search_error.rb +3 -0
- data/lib/ransack/locale/ja.yml +51 -51
- data/lib/ransack/locale/ko.yml +70 -0
- data/lib/ransack/locale/uk.yml +72 -0
- data/lib/ransack/nodes/condition.rb +39 -7
- data/lib/ransack/nodes/grouping.rb +1 -1
- data/lib/ransack/nodes/sort.rb +1 -1
- data/lib/ransack/nodes/value.rb +9 -1
- data/lib/ransack/search.rb +4 -3
- data/lib/ransack/version.rb +1 -1
- data/lib/ransack.rb +8 -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 +106 -3
- 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/nodes/condition_spec.rb +230 -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 +16 -9
- data/spec/ransack/search_spec.rb +121 -1
- data/spec/ransack/translate_spec.rb +0 -1
- data/spec/spec_helper.rb +2 -3
- data/spec/support/schema.rb +42 -0
- metadata +43 -87
- 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 -99
- 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 -131
- data/.gitignore +0 -7
- data/.nojekyll +0 -0
- data/.rubocop.yml +0 -50
- data/CHANGELOG.md +0 -1186
- data/CONTRIBUTING.md +0 -171
- data/Gemfile +0 -53
- 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 -288
- 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 -428
- 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
@@ -64,6 +64,7 @@ module Ransack
|
|
64
64
|
end
|
65
65
|
|
66
66
|
specify { expect { subject }.to raise_error ArgumentError }
|
67
|
+
specify { expect { subject }.to raise_error InvalidSearchError }
|
67
68
|
end
|
68
69
|
|
69
70
|
context "when ignore_unknown_conditions is true" do
|
@@ -98,6 +99,235 @@ module Ransack
|
|
98
99
|
specify { expect(subject).to eq Condition.extract(Context.for(Person), 'full_name_eq', Person.first.name) }
|
99
100
|
end
|
100
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
|
101
331
|
end
|
102
332
|
end
|
103
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
|
@@ -158,9 +157,10 @@ module Ransack
|
|
158
157
|
|
159
158
|
describe 'cont' do
|
160
159
|
it_has_behavior 'wildcard escaping', :name_cont,
|
161
|
-
(
|
160
|
+
(case ActiveRecord::Base.connection.adapter_name
|
161
|
+
when "PostGIS", "PostgreSQL"
|
162
162
|
/"people"."name" ILIKE '%\\%\\.\\_\\\\%'/
|
163
|
-
|
163
|
+
when "Mysql2"
|
164
164
|
/`people`.`name` LIKE '%\\\\%.\\\\_\\\\\\\\%'/
|
165
165
|
else
|
166
166
|
/"people"."name" LIKE '%%._\\%'/
|
@@ -177,9 +177,10 @@ module Ransack
|
|
177
177
|
|
178
178
|
describe 'not_cont' do
|
179
179
|
it_has_behavior 'wildcard escaping', :name_not_cont,
|
180
|
-
(
|
180
|
+
(case ActiveRecord::Base.connection.adapter_name
|
181
|
+
when "PostGIS", "PostgreSQL"
|
181
182
|
/"people"."name" NOT ILIKE '%\\%\\.\\_\\\\%'/
|
182
|
-
|
183
|
+
when "Mysql2"
|
183
184
|
/`people`.`name` NOT LIKE '%\\\\%.\\\\_\\\\\\\\%'/
|
184
185
|
else
|
185
186
|
/"people"."name" NOT LIKE '%%._\\%'/
|
@@ -196,9 +197,12 @@ module Ransack
|
|
196
197
|
|
197
198
|
describe 'i_cont' do
|
198
199
|
it_has_behavior 'wildcard escaping', :name_i_cont,
|
199
|
-
(
|
200
|
+
(case ActiveRecord::Base.connection.adapter_name
|
201
|
+
when "PostGIS"
|
202
|
+
/LOWER\("people"."name"\) ILIKE '%\\%\\.\\_\\\\%'/
|
203
|
+
when "PostgreSQL"
|
200
204
|
/"people"."name" ILIKE '%\\%\\.\\_\\\\%'/
|
201
|
-
|
205
|
+
when "Mysql2"
|
202
206
|
/LOWER\(`people`.`name`\) LIKE '%\\\\%.\\\\_\\\\\\\\%'/
|
203
207
|
else
|
204
208
|
/LOWER\("people"."name"\) LIKE '%%._\\%'/
|
@@ -215,9 +219,12 @@ module Ransack
|
|
215
219
|
|
216
220
|
describe 'not_i_cont' do
|
217
221
|
it_has_behavior 'wildcard escaping', :name_not_i_cont,
|
218
|
-
(
|
222
|
+
(case ActiveRecord::Base.connection.adapter_name
|
223
|
+
when "PostGIS"
|
224
|
+
/LOWER\("people"."name"\) NOT ILIKE '%\\%\\.\\_\\\\%'/
|
225
|
+
when "PostgreSQL"
|
219
226
|
/"people"."name" NOT ILIKE '%\\%\\.\\_\\\\%'/
|
220
|
-
|
227
|
+
when "Mysql2"
|
221
228
|
/LOWER\(`people`.`name`\) NOT LIKE '%\\\\%.\\\\_\\\\\\\\%'/
|
222
229
|
else
|
223
230
|
/LOWER\("people"."name"\) NOT LIKE '%%._\\%'/
|
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'
|
@@ -270,6 +269,7 @@ module Ransack
|
|
270
269
|
end
|
271
270
|
|
272
271
|
specify { expect { subject }.to raise_error ArgumentError }
|
272
|
+
specify { expect { subject }.to raise_error InvalidSearchError }
|
273
273
|
end
|
274
274
|
|
275
275
|
context 'when ignore_unknown_conditions configuration option is true' do
|
@@ -300,6 +300,7 @@ module Ransack
|
|
300
300
|
|
301
301
|
context 'when ignore_unknown_conditions search parameter is false' do
|
302
302
|
specify { expect { with_ignore_unknown_conditions_false }.to raise_error ArgumentError }
|
303
|
+
specify { expect { with_ignore_unknown_conditions_false }.to raise_error InvalidSearchError }
|
303
304
|
end
|
304
305
|
|
305
306
|
context 'when ignore_unknown_conditions search parameter is true' do
|
@@ -335,18 +336,90 @@ module Ransack
|
|
335
336
|
end
|
336
337
|
end
|
337
338
|
|
339
|
+
context "ransackable_scope with array arguments" do
|
340
|
+
around(:each) do |example|
|
341
|
+
Person.define_singleton_method(:domestic) do |countries|
|
342
|
+
self.where(name: countries)
|
343
|
+
end
|
344
|
+
|
345
|
+
Person.define_singleton_method(:flexible_scope) do |*args|
|
346
|
+
self.where(id: args)
|
347
|
+
end
|
348
|
+
|
349
|
+
Person.define_singleton_method(:two_param_scope) do |param1, param2|
|
350
|
+
self.where(name: param1, id: param2)
|
351
|
+
end
|
352
|
+
|
353
|
+
begin
|
354
|
+
example.run
|
355
|
+
ensure
|
356
|
+
Person.singleton_class.undef_method :domestic
|
357
|
+
Person.singleton_class.undef_method :flexible_scope
|
358
|
+
Person.singleton_class.undef_method :two_param_scope
|
359
|
+
end
|
360
|
+
end
|
361
|
+
|
362
|
+
it "handles scopes that take arrays as single arguments (arity 1)" do
|
363
|
+
allow(Person).to receive(:ransackable_scopes)
|
364
|
+
.and_return(Person.ransackable_scopes + [:domestic])
|
365
|
+
|
366
|
+
# This should not raise ArgumentError
|
367
|
+
expect {
|
368
|
+
s = Search.new(Person, domestic: ['US', 'JP'])
|
369
|
+
s.result # This triggers the actual scope call
|
370
|
+
}.not_to raise_error
|
371
|
+
|
372
|
+
s = Search.new(Person, domestic: ['US', 'JP'])
|
373
|
+
expect(s.instance_variable_get(:@scope_args)["domestic"]).to eq("US")
|
374
|
+
end
|
375
|
+
|
376
|
+
it "handles scopes with flexible arity (negative arity)" do
|
377
|
+
allow(Person).to receive(:ransackable_scopes)
|
378
|
+
.and_return(Person.ransackable_scopes + [:flexible_scope])
|
379
|
+
|
380
|
+
expect {
|
381
|
+
s = Search.new(Person, flexible_scope: ['US', 'JP'])
|
382
|
+
s.result
|
383
|
+
}.not_to raise_error
|
384
|
+
end
|
385
|
+
|
386
|
+
it "handles scopes with arity > 1" do
|
387
|
+
allow(Person).to receive(:ransackable_scopes)
|
388
|
+
.and_return(Person.ransackable_scopes + [:two_param_scope])
|
389
|
+
|
390
|
+
expect {
|
391
|
+
s = Search.new(Person, two_param_scope: ['param1', 'param2'])
|
392
|
+
s.result
|
393
|
+
}.not_to raise_error
|
394
|
+
end
|
395
|
+
|
396
|
+
it "still supports the workaround with nested arrays" do
|
397
|
+
allow(Person).to receive(:ransackable_scopes)
|
398
|
+
.and_return(Person.ransackable_scopes + [:domestic])
|
399
|
+
|
400
|
+
# The workaround from the issue should still work
|
401
|
+
expect {
|
402
|
+
s = Search.new(Person, domestic: [['US', 'JP']])
|
403
|
+
s.result
|
404
|
+
}.not_to raise_error
|
405
|
+
end
|
406
|
+
end
|
338
407
|
end
|
339
408
|
|
340
409
|
describe '#result' do
|
341
410
|
let(:people_name_field) {
|
342
411
|
"#{quote_table_name("people")}.#{quote_column_name("name")}"
|
343
412
|
}
|
413
|
+
let(:people_temperament_field) {
|
414
|
+
"#{quote_table_name("people")}.#{quote_column_name("temperament")}"
|
415
|
+
}
|
344
416
|
let(:children_people_name_field) {
|
345
417
|
"#{quote_table_name("children_people")}.#{quote_column_name("name")}"
|
346
418
|
}
|
347
419
|
let(:notable_type_field) {
|
348
420
|
"#{quote_table_name("notes")}.#{quote_column_name("notable_type")}"
|
349
421
|
}
|
422
|
+
|
350
423
|
it 'evaluates conditions contextually' do
|
351
424
|
s = Search.new(Person, children_name_eq: 'Ernie')
|
352
425
|
expect(s.result).to be_an ActiveRecord::Relation
|
@@ -354,6 +427,36 @@ module Ransack
|
|
354
427
|
children_people_name_field} = 'Ernie'/
|
355
428
|
end
|
356
429
|
|
430
|
+
context 'when evaluating enums' do
|
431
|
+
before do
|
432
|
+
Person.take.update_attribute(:temperament, 'choleric')
|
433
|
+
end
|
434
|
+
|
435
|
+
it 'evaluates enum key correctly' do
|
436
|
+
s = Search.new(Person, temperament_eq: 'choleric')
|
437
|
+
|
438
|
+
expect(s.result.to_sql).not_to match /#{
|
439
|
+
people_temperament_field} = 0/
|
440
|
+
|
441
|
+
expect(s.result.to_sql).to match /#{
|
442
|
+
people_temperament_field} = #{Person.temperaments[:choleric]}/
|
443
|
+
|
444
|
+
expect(s.result).not_to be_empty
|
445
|
+
end
|
446
|
+
|
447
|
+
it 'evaluates enum value correctly' do
|
448
|
+
s = Search.new(Person, temperament_eq: Person.temperaments[:choleric])
|
449
|
+
|
450
|
+
expect(s.result.to_sql).not_to match /#{
|
451
|
+
people_temperament_field} = 0/
|
452
|
+
|
453
|
+
expect(s.result.to_sql).to match /#{
|
454
|
+
people_temperament_field} = #{Person.temperaments[:choleric]}/
|
455
|
+
|
456
|
+
expect(s.result).not_to be_empty
|
457
|
+
end
|
458
|
+
end
|
459
|
+
|
357
460
|
it 'use appropriate table alias' do
|
358
461
|
s = Search.new(Person, {
|
359
462
|
name_eq: "person_name_query",
|
@@ -477,6 +580,11 @@ module Ransack
|
|
477
580
|
@s = Search.new(Person)
|
478
581
|
end
|
479
582
|
|
583
|
+
it 'doesn\'t creates sorts' do
|
584
|
+
@s.sorts = ''
|
585
|
+
expect(@s.sorts.size).to eq(0)
|
586
|
+
end
|
587
|
+
|
480
588
|
it 'creates sorts based on a single attribute/direction' do
|
481
589
|
@s.sorts = 'id desc'
|
482
590
|
expect(@s.sorts.size).to eq(1)
|
@@ -614,6 +722,18 @@ module Ransack
|
|
614
722
|
expect(@s.result.first.id).to eq 1
|
615
723
|
end
|
616
724
|
|
725
|
+
it 'raises ArgumentError when an invalid argument is sent' do
|
726
|
+
expect do
|
727
|
+
@s.sorts = 1234
|
728
|
+
end.to raise_error(ArgumentError, "Invalid argument (Integer) supplied to sorts=")
|
729
|
+
end
|
730
|
+
|
731
|
+
it 'raises InvalidSearchError when an invalid argument is sent' do
|
732
|
+
expect do
|
733
|
+
@s.sorts = 1234
|
734
|
+
end.to raise_error(Ransack::InvalidSearchError, "Invalid argument (Integer) supplied to sorts=")
|
735
|
+
end
|
736
|
+
|
617
737
|
it "PG's sort option", if: ::ActiveRecord::Base.connection.adapter_name == "PostgreSQL" do
|
618
738
|
default = Ransack.options.clone
|
619
739
|
|
data/spec/spec_helper.rb
CHANGED
@@ -1,13 +1,12 @@
|
|
1
|
-
require '
|
2
|
-
require 'polyamorous/polyamorous'
|
1
|
+
require 'ransack'
|
3
2
|
require 'sham'
|
4
3
|
require 'faker'
|
5
|
-
require 'ransack'
|
6
4
|
require 'action_controller'
|
7
5
|
require 'ransack/helpers'
|
8
6
|
require 'pry'
|
9
7
|
require 'simplecov'
|
10
8
|
require 'byebug'
|
9
|
+
require 'machinist/active_record'
|
11
10
|
|
12
11
|
SimpleCov.start
|
13
12
|
I18n.enforce_available_locales = false
|
data/spec/support/schema.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'active_record'
|
2
|
+
require 'activerecord-postgis-adapter'
|
2
3
|
|
3
4
|
case ENV['DB'].try(:downcase)
|
4
5
|
when 'mysql', 'mysql2'
|
@@ -20,6 +21,17 @@ when 'pg', 'postgres', 'postgresql'
|
|
20
21
|
host: ENV.fetch("DATABASE_HOST") { "localhost" },
|
21
22
|
min_messages: 'warning'
|
22
23
|
)
|
24
|
+
when 'postgis'
|
25
|
+
# To test with PostGIS: `DB=postgis bundle exec rake spec`
|
26
|
+
ActiveRecord::Base.establish_connection(
|
27
|
+
adapter: 'postgis',
|
28
|
+
postgis_extension: 'postgis',
|
29
|
+
database: 'ransack',
|
30
|
+
username: ENV.fetch("DATABASE_USERNAME") { "postgres" },
|
31
|
+
password: ENV.fetch("DATABASE_PASSWORD") { "" },
|
32
|
+
host: ENV.fetch("DATABASE_HOST") { "localhost" },
|
33
|
+
min_messages: 'warning'
|
34
|
+
)
|
23
35
|
else
|
24
36
|
# Otherwise, assume SQLite3: `bundle exec rake spec`
|
25
37
|
ActiveRecord::Base.establish_connection(
|
@@ -69,6 +81,8 @@ class Person < ApplicationRecord
|
|
69
81
|
scope :sort_by_reverse_name_asc, lambda { order(Arel.sql("REVERSE(name) ASC")) }
|
70
82
|
scope :sort_by_reverse_name_desc, lambda { order("REVERSE(name) DESC") }
|
71
83
|
|
84
|
+
enum :temperament, { sanguine: 1, choleric: 2, melancholic: 3, phlegmatic: 4 }
|
85
|
+
|
72
86
|
alias_attribute :full_name, :name
|
73
87
|
|
74
88
|
ransack_alias :term, :name_or_email
|
@@ -126,6 +140,17 @@ class Person < ApplicationRecord
|
|
126
140
|
Arel.sql(query)
|
127
141
|
end
|
128
142
|
|
143
|
+
ransacker :article_tags, formatter: proc { |id|
|
144
|
+
if Tag.exists?(id)
|
145
|
+
joins(articles: :tags)
|
146
|
+
.where(tags: { id: id })
|
147
|
+
.distinct
|
148
|
+
.select(:id).arel
|
149
|
+
end
|
150
|
+
} do |parent|
|
151
|
+
parent.table[:id]
|
152
|
+
end
|
153
|
+
|
129
154
|
def self.ransackable_attributes(auth_object = nil)
|
130
155
|
if auth_object == :admin
|
131
156
|
authorizable_ransackable_attributes - ['only_sort']
|
@@ -151,6 +176,7 @@ class Article < ApplicationRecord
|
|
151
176
|
has_many :comments
|
152
177
|
has_and_belongs_to_many :tags
|
153
178
|
has_many :notes, as: :notable
|
179
|
+
has_many :recent_notes, as: :notable
|
154
180
|
|
155
181
|
alias_attribute :content, :body
|
156
182
|
|
@@ -220,18 +246,28 @@ end
|
|
220
246
|
class Comment < ApplicationRecord
|
221
247
|
belongs_to :article
|
222
248
|
belongs_to :person
|
249
|
+
has_and_belongs_to_many :tags
|
223
250
|
|
224
251
|
default_scope { where(disabled: false) }
|
225
252
|
end
|
226
253
|
|
227
254
|
class Tag < ApplicationRecord
|
228
255
|
has_and_belongs_to_many :articles
|
256
|
+
has_and_belongs_to_many :comments
|
229
257
|
end
|
230
258
|
|
231
259
|
class Note < ApplicationRecord
|
232
260
|
belongs_to :notable, polymorphic: true
|
233
261
|
end
|
234
262
|
|
263
|
+
class RecentNote < ApplicationRecord
|
264
|
+
DEFAULT_NOTABLE_ID = 1
|
265
|
+
self.table_name = "notes"
|
266
|
+
default_scope { where(notable_id: DEFAULT_NOTABLE_ID) }
|
267
|
+
|
268
|
+
belongs_to :notable, polymorphic: true
|
269
|
+
end
|
270
|
+
|
235
271
|
class Account < ApplicationRecord
|
236
272
|
belongs_to :agent_account, class_name: "Account"
|
237
273
|
belongs_to :trade_account, class_name: "Account"
|
@@ -266,6 +302,7 @@ module Schema
|
|
266
302
|
t.string :new_start
|
267
303
|
t.string :stop_end
|
268
304
|
t.integer :salary
|
305
|
+
t.integer :temperament
|
269
306
|
t.date :life_start
|
270
307
|
t.boolean :awesome, default: false
|
271
308
|
t.boolean :terms_and_conditions, default: false
|
@@ -298,6 +335,11 @@ module Schema
|
|
298
335
|
t.integer :tag_id
|
299
336
|
end
|
300
337
|
|
338
|
+
create_table :comments_tags, force: true, id: false do |t|
|
339
|
+
t.integer :comment_id
|
340
|
+
t.integer :tag_id
|
341
|
+
end
|
342
|
+
|
301
343
|
create_table :notes, force: true do |t|
|
302
344
|
t.integer :notable_id
|
303
345
|
t.string :notable_type
|