ransack 2.4.2 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/cronjob.yml +6 -9
- data/.github/workflows/rubocop.yml +1 -1
- data/.github/workflows/test.yml +25 -34
- data/.nojekyll +0 -0
- data/CHANGELOG.md +90 -11
- data/CONTRIBUTING.md +4 -3
- data/README.md +45 -973
- data/docs/.gitignore +20 -0
- data/docs/.nojekyll +0 -0
- data/docs/babel.config.js +3 -0
- data/docs/blog/2022-03-27-ransack-3.0.0.md +20 -0
- data/docs/docs/getting-started/_category_.json +4 -0
- data/docs/docs/getting-started/advanced-mode.md +46 -0
- data/docs/docs/getting-started/configuration.md +47 -0
- data/docs/docs/getting-started/search-matches.md +67 -0
- data/docs/docs/getting-started/simple-mode.md +284 -0
- data/docs/docs/getting-started/sorting.md +79 -0
- data/docs/docs/getting-started/using-predicates.md +282 -0
- data/docs/docs/going-further/_category_.json +4 -0
- data/docs/docs/going-further/associations.md +70 -0
- data/docs/docs/going-further/custom-predicates.md +52 -0
- data/docs/docs/going-further/documentation.md +31 -0
- data/docs/docs/going-further/exporting-to-csv.md +49 -0
- data/docs/docs/going-further/external-guides.md +57 -0
- data/docs/docs/going-further/form-customisation.md +63 -0
- data/docs/docs/going-further/i18n.md +53 -0
- data/docs/{img → docs/going-further/img}/create_release.png +0 -0
- data/docs/docs/going-further/merging-searches.md +41 -0
- data/docs/docs/going-further/other-notes.md +428 -0
- data/docs/docs/going-further/ransackers.md +331 -0
- data/docs/docs/going-further/release_process.md +36 -0
- data/docs/docs/going-further/saving-queries.md +82 -0
- data/docs/docs/going-further/searching-postgres.md +57 -0
- data/docs/docs/intro.md +99 -0
- data/docs/docusaurus.config.js +108 -0
- data/docs/package-lock.json +9207 -0
- data/docs/package.json +37 -0
- data/docs/sidebars.js +31 -0
- data/docs/src/components/HomepageFeatures/index.js +64 -0
- data/docs/src/components/HomepageFeatures/styles.module.css +11 -0
- data/docs/src/css/custom.css +39 -0
- data/docs/src/pages/index.module.css +23 -0
- data/docs/src/pages/markdown-page.md +7 -0
- 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 +1 -0
- 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 +171 -0
- data/docs/static/img/undraw_docusaurus_react.svg +170 -0
- data/docs/static/img/undraw_docusaurus_tree.svg +40 -0
- data/{logo → docs/static/logo}/ransack-h.png +0 -0
- data/{logo → docs/static/logo}/ransack-h.svg +0 -0
- data/{logo → docs/static/logo}/ransack-v.png +0 -0
- data/{logo → docs/static/logo}/ransack-v.svg +0 -0
- data/{logo → docs/static/logo}/ransack.png +0 -0
- data/{logo → docs/static/logo}/ransack.svg +0 -0
- data/docs/yarn.lock +7671 -0
- data/lib/polyamorous/activerecord_6.0_ruby_2/join_association.rb +20 -1
- data/lib/polyamorous/activerecord_6.0_ruby_2/join_dependency.rb +0 -1
- data/lib/polyamorous/activerecord_6.0_ruby_2/reflection.rb +11 -1
- data/lib/polyamorous/activerecord_6.1_ruby_2/join_association.rb +0 -4
- data/lib/polyamorous/activerecord_6.1_ruby_2/join_dependency.rb +0 -1
- data/lib/polyamorous/{activerecord_6.2_ruby_2 → activerecord_7.0_ruby_2}/join_association.rb +0 -0
- data/lib/polyamorous/{activerecord_6.2_ruby_2 → activerecord_7.0_ruby_2}/join_dependency.rb +0 -0
- data/lib/polyamorous/{activerecord_6.2_ruby_2 → activerecord_7.0_ruby_2}/reflection.rb +0 -0
- data/lib/polyamorous.rb +1 -0
- data/lib/ransack/adapters/active_record/base.rb +1 -3
- data/lib/ransack/adapters/active_record/context.rb +7 -2
- data/lib/ransack/adapters/active_record/ransack/nodes/condition.rb +10 -9
- data/lib/ransack/configuration.rb +16 -2
- data/lib/ransack/helpers/form_helper.rb +11 -3
- data/lib/ransack/locale/sv.yml +70 -0
- data/lib/ransack/nodes/sort.rb +2 -2
- data/lib/ransack/search.rb +4 -3
- data/lib/ransack/translate.rb +1 -1
- data/lib/ransack/version.rb +1 -1
- data/ransack.gemspec +4 -4
- data/spec/polyamorous/activerecord_compatibility_spec.rb +15 -0
- data/spec/polyamorous/join_association_spec.rb +1 -6
- data/spec/polyamorous/join_dependency_spec.rb +0 -16
- data/spec/ransack/adapters/active_record/base_spec.rb +28 -11
- data/spec/ransack/configuration_spec.rb +14 -0
- data/spec/ransack/helpers/form_helper_spec.rb +33 -2
- data/spec/ransack/nodes/condition_spec.rb +13 -0
- data/spec/ransack/search_spec.rb +140 -27
- data/spec/support/schema.rb +49 -0
- metadata +72 -25
- data/docs/release_process.md +0 -20
- data/lib/polyamorous/activerecord_5.2_ruby_2/join_association.rb +0 -24
- data/lib/polyamorous/activerecord_5.2_ruby_2/join_dependency.rb +0 -79
- data/lib/polyamorous/activerecord_5.2_ruby_2/reflection.rb +0 -11
@@ -0,0 +1,41 @@
|
|
1
|
+
---
|
2
|
+
sidebar_position: 5
|
3
|
+
title: Merging searches
|
4
|
+
---
|
5
|
+
|
6
|
+
To find records that match multiple searches, it's possible to merge all the ransack search conditions into an ActiveRecord relation to perform a single query. In order to avoid conflicts between joined table names it's necessary to set up a shared context to track table aliases used across all the conditions before initializing the searches:
|
7
|
+
|
8
|
+
```ruby
|
9
|
+
shared_context = Ransack::Context.for(Person)
|
10
|
+
|
11
|
+
search_parents = Person.ransack(
|
12
|
+
{ parent_name_eq: "A" }, context: shared_context
|
13
|
+
)
|
14
|
+
|
15
|
+
search_children = Person.ransack(
|
16
|
+
{ children_name_eq: "B" }, context: shared_context
|
17
|
+
)
|
18
|
+
|
19
|
+
shared_conditions = [search_parents, search_children].map { |search|
|
20
|
+
Ransack::Visitor.new.accept(search.base)
|
21
|
+
}
|
22
|
+
|
23
|
+
Person.joins(shared_context.join_sources)
|
24
|
+
.where(shared_conditions.reduce(&:or))
|
25
|
+
.to_sql
|
26
|
+
```
|
27
|
+
Produces:
|
28
|
+
```sql
|
29
|
+
SELECT "people".*
|
30
|
+
FROM "people"
|
31
|
+
LEFT OUTER JOIN "people" "parents_people"
|
32
|
+
ON "parents_people"."id" = "people"."parent_id"
|
33
|
+
LEFT OUTER JOIN "people" "children_people"
|
34
|
+
ON "children_people"."parent_id" = "people"."id"
|
35
|
+
WHERE (
|
36
|
+
("parents_people"."name" = 'A' OR "children_people"."name" = 'B')
|
37
|
+
)
|
38
|
+
ORDER BY "people"."id" DESC
|
39
|
+
```
|
40
|
+
|
41
|
+
Admittedly this is not as simple as it should be, but it's workable for now. (Implementing #417 could make this more straightforward.)
|
@@ -0,0 +1,428 @@
|
|
1
|
+
---
|
2
|
+
sidebar_position: 8
|
3
|
+
title: Other notes
|
4
|
+
---
|
5
|
+
|
6
|
+
### Ransack Aliases
|
7
|
+
|
8
|
+
You can customize the attribute names for your Ransack searches by using a
|
9
|
+
`ransack_alias`. This is particularly useful for long attribute names that are
|
10
|
+
necessary when querying associations or multiple columns.
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
class Post < ActiveRecord::Base
|
14
|
+
belongs_to :author
|
15
|
+
|
16
|
+
# Abbreviate :author_first_name_or_author_last_name to :author
|
17
|
+
ransack_alias :author, :author_first_name_or_author_last_name
|
18
|
+
end
|
19
|
+
```
|
20
|
+
|
21
|
+
Now, rather than using `:author_first_name_or_author_last_name_cont` in your
|
22
|
+
form, you can simply use `:author_cont`. This serves to produce more expressive
|
23
|
+
query parameters in your URLs.
|
24
|
+
|
25
|
+
```erb
|
26
|
+
<%= search_form_for @q do |f| %>
|
27
|
+
<%= f.label :author_cont %>
|
28
|
+
<%= f.search_field :author_cont %>
|
29
|
+
<% end %>
|
30
|
+
```
|
31
|
+
|
32
|
+
You can also use `ransack_alias` for sorting.
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
class Post < ActiveRecord::Base
|
36
|
+
belongs_to :author
|
37
|
+
|
38
|
+
# Abbreviate :author_first_name to :author
|
39
|
+
ransack_alias :author, :author_first_name
|
40
|
+
end
|
41
|
+
```
|
42
|
+
|
43
|
+
Now, you can use `:author` instead of `:author_first_name` in a `sort_link`.
|
44
|
+
|
45
|
+
```erb
|
46
|
+
<%= sort_link(@q, :author) %>
|
47
|
+
```
|
48
|
+
|
49
|
+
Note that using `:author_first_name_or_author_last_name_cont` would produce an invalid sql query. In those cases, Ransack ignores the sorting clause.
|
50
|
+
|
51
|
+
|
52
|
+
|
53
|
+
### Problem with DISTINCT selects
|
54
|
+
|
55
|
+
If passed `distinct: true`, `result` will generate a `SELECT DISTINCT` to
|
56
|
+
avoid returning duplicate rows, even if conditions on a join would otherwise
|
57
|
+
result in some. It generates the same SQL as calling `uniq` on the relation.
|
58
|
+
|
59
|
+
Please note that for many databases, a sort on an associated table's columns
|
60
|
+
may result in invalid SQL with `distinct: true` -- in those cases, you
|
61
|
+
will need to modify the result as needed to allow these queries to work.
|
62
|
+
|
63
|
+
For example, you could call joins and includes on the result which has the
|
64
|
+
effect of adding those tables columns to the select statement, overcoming
|
65
|
+
the issue, like so:
|
66
|
+
|
67
|
+
```ruby
|
68
|
+
def index
|
69
|
+
@q = Person.ransack(params[:q])
|
70
|
+
@people = @q.result(distinct: true)
|
71
|
+
.includes(:articles)
|
72
|
+
.joins(:articles)
|
73
|
+
.page(params[:page])
|
74
|
+
end
|
75
|
+
```
|
76
|
+
|
77
|
+
If the above doesn't help, you can also use ActiveRecord's `select` query
|
78
|
+
to explicitly add the columns you need, which brute force's adding the
|
79
|
+
columns you need that your SQL engine is complaining about, you need to
|
80
|
+
make sure you give all of the columns you care about, for example:
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
def index
|
84
|
+
@q = Person.ransack(params[:q])
|
85
|
+
@people = @q.result(distinct: true)
|
86
|
+
.select('people.*, articles.name, articles.description')
|
87
|
+
.page(params[:page])
|
88
|
+
end
|
89
|
+
```
|
90
|
+
|
91
|
+
Another method to approach this when using Postgresql is to use ActiveRecords's `.includes` in combination with `.group` instead of `distinct: true`.
|
92
|
+
|
93
|
+
For example:
|
94
|
+
```ruby
|
95
|
+
def index
|
96
|
+
@q = Person.ransack(params[:q])
|
97
|
+
@people = @q.result
|
98
|
+
.group('persons.id')
|
99
|
+
.includes(:articles)
|
100
|
+
.page(params[:page])
|
101
|
+
end
|
102
|
+
|
103
|
+
```
|
104
|
+
|
105
|
+
A final way of last resort is to call `to_a.uniq` on the collection at the end
|
106
|
+
with the caveat that the de-duping is taking place in Ruby instead of in SQL,
|
107
|
+
which is potentially slower and uses more memory, and that it may display
|
108
|
+
awkwardly with pagination if the number of results is greater than the page size.
|
109
|
+
|
110
|
+
For example:
|
111
|
+
|
112
|
+
```ruby
|
113
|
+
def index
|
114
|
+
@q = Person.ransack(params[:q])
|
115
|
+
@people = @q.result.includes(:articles).page(params[:page]).to_a.uniq
|
116
|
+
end
|
117
|
+
```
|
118
|
+
|
119
|
+
#### `PG::UndefinedFunction: ERROR: could not identify an equality operator for type json`
|
120
|
+
|
121
|
+
If you get the above error while using `distinct: true` that means that
|
122
|
+
one of the columns that Ransack is selecting is a `json` column.
|
123
|
+
PostgreSQL does not provide comparison operators for the `json` type. While
|
124
|
+
it is possible to work around this, in practice it's much better to convert those
|
125
|
+
to `jsonb`, as [recommended by the PostgreSQL documentation](https://www.postgresql.org/docs/9.6/static/datatype-json.html).
|
126
|
+
|
127
|
+
### Authorization (allowlisting/denylisting)
|
128
|
+
|
129
|
+
By default, searching and sorting are authorized on any column of your model
|
130
|
+
and no class methods/scopes are whitelisted.
|
131
|
+
|
132
|
+
Ransack adds four methods to `ActiveRecord::Base` that you can redefine as
|
133
|
+
class methods in your models to apply selective authorization:
|
134
|
+
|
135
|
+
- `ransackable_attributes`
|
136
|
+
- `ransackable_associations`
|
137
|
+
- `ransackable_scopes`
|
138
|
+
- `ransortable_attributes`
|
139
|
+
|
140
|
+
Here is how these four methods are implemented in Ransack:
|
141
|
+
|
142
|
+
```ruby
|
143
|
+
# `ransackable_attributes` by default returns all column names
|
144
|
+
# and any defined ransackers as an array of strings.
|
145
|
+
# For overriding with a whitelist array of strings.
|
146
|
+
#
|
147
|
+
def ransackable_attributes(auth_object = nil)
|
148
|
+
column_names + _ransackers.keys
|
149
|
+
end
|
150
|
+
|
151
|
+
# `ransackable_associations` by default returns the names
|
152
|
+
# of all associations as an array of strings.
|
153
|
+
# For overriding with a whitelist array of strings.
|
154
|
+
#
|
155
|
+
def ransackable_associations(auth_object = nil)
|
156
|
+
reflect_on_all_associations.map { |a| a.name.to_s }
|
157
|
+
end
|
158
|
+
|
159
|
+
# `ransortable_attributes` by default returns the names
|
160
|
+
# of all attributes available for sorting as an array of strings.
|
161
|
+
# For overriding with a whitelist array of strings.
|
162
|
+
#
|
163
|
+
def ransortable_attributes(auth_object = nil)
|
164
|
+
ransackable_attributes(auth_object)
|
165
|
+
end
|
166
|
+
|
167
|
+
# `ransackable_scopes` by default returns an empty array
|
168
|
+
# i.e. no class methods/scopes are authorized.
|
169
|
+
# For overriding with a whitelist array of *symbols*.
|
170
|
+
#
|
171
|
+
def ransackable_scopes(auth_object = nil)
|
172
|
+
[]
|
173
|
+
end
|
174
|
+
```
|
175
|
+
|
176
|
+
Any values not returned from these methods will be ignored by Ransack, i.e.
|
177
|
+
they are not authorized.
|
178
|
+
|
179
|
+
All four methods can receive a single optional parameter, `auth_object`. When
|
180
|
+
you call the search or ransack method on your model, you can provide a value
|
181
|
+
for an `auth_object` key in the options hash which can be used by your own
|
182
|
+
overridden methods.
|
183
|
+
|
184
|
+
Here is an example that puts all this together, adapted from
|
185
|
+
[this blog post by Ernie Miller](http://erniemiller.org/2012/05/11/why-your-ruby-class-macros-might-suck-mine-did/).
|
186
|
+
In an `Article` model, add the following `ransackable_attributes` class method
|
187
|
+
(preferably private):
|
188
|
+
|
189
|
+
```ruby
|
190
|
+
class Article < ActiveRecord::Base
|
191
|
+
def self.ransackable_attributes(auth_object = nil)
|
192
|
+
if auth_object == :admin
|
193
|
+
# whitelist all attributes for admin
|
194
|
+
super
|
195
|
+
else
|
196
|
+
# whitelist only the title and body attributes for other users
|
197
|
+
super & %w(title body)
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
private_class_method :ransackable_attributes
|
202
|
+
end
|
203
|
+
```
|
204
|
+
|
205
|
+
Here is example code for the `articles_controller`:
|
206
|
+
|
207
|
+
```ruby
|
208
|
+
class ArticlesController < ApplicationController
|
209
|
+
def index
|
210
|
+
@q = Article.ransack(params[:q], auth_object: set_ransack_auth_object)
|
211
|
+
@articles = @q.result
|
212
|
+
end
|
213
|
+
|
214
|
+
private
|
215
|
+
|
216
|
+
def set_ransack_auth_object
|
217
|
+
current_user.admin? ? :admin : nil
|
218
|
+
end
|
219
|
+
end
|
220
|
+
```
|
221
|
+
|
222
|
+
Trying it out in `rails console`:
|
223
|
+
|
224
|
+
```ruby
|
225
|
+
> Article
|
226
|
+
=> Article(id: integer, person_id: integer, title: string, body: text)
|
227
|
+
|
228
|
+
> Article.ransackable_attributes
|
229
|
+
=> ["title", "body"]
|
230
|
+
|
231
|
+
> Article.ransackable_attributes(:admin)
|
232
|
+
=> ["id", "person_id", "title", "body"]
|
233
|
+
|
234
|
+
> Article.ransack(id_eq: 1).result.to_sql
|
235
|
+
=> SELECT "articles".* FROM "articles" # Note that search param was ignored!
|
236
|
+
|
237
|
+
> Article.ransack({ id_eq: 1 }, { auth_object: nil }).result.to_sql
|
238
|
+
=> SELECT "articles".* FROM "articles" # Search param still ignored!
|
239
|
+
|
240
|
+
> Article.ransack({ id_eq: 1 }, { auth_object: :admin }).result.to_sql
|
241
|
+
=> SELECT "articles".* FROM "articles" WHERE "articles"."id" = 1
|
242
|
+
```
|
243
|
+
|
244
|
+
That's it! Now you know how to whitelist/blacklist various elements in Ransack.
|
245
|
+
|
246
|
+
### Handling unknown predicates or attributes
|
247
|
+
|
248
|
+
By default, Ransack will ignore any unknown predicates or attributes:
|
249
|
+
|
250
|
+
```ruby
|
251
|
+
Article.ransack(unknown_attr_eq: 'Ernie').result.to_sql
|
252
|
+
=> SELECT "articles".* FROM "articles"
|
253
|
+
```
|
254
|
+
|
255
|
+
Ransack may be configured to raise an error if passed an unknown predicate or
|
256
|
+
attributes, by setting the `ignore_unknown_conditions` option to `false` in your
|
257
|
+
Ransack initializer file at `config/initializers/ransack.rb`:
|
258
|
+
|
259
|
+
```ruby
|
260
|
+
Ransack.configure do |c|
|
261
|
+
# Raise errors if a query contains an unknown predicate or attribute.
|
262
|
+
# Default is true (do not raise error on unknown conditions).
|
263
|
+
c.ignore_unknown_conditions = false
|
264
|
+
end
|
265
|
+
```
|
266
|
+
|
267
|
+
```ruby
|
268
|
+
Article.ransack(unknown_attr_eq: 'Ernie')
|
269
|
+
# ArgumentError (Invalid search term unknown_attr_eq)
|
270
|
+
```
|
271
|
+
|
272
|
+
As an alternative to setting a global configuration option, the `.ransack!`
|
273
|
+
class method also raises an error if passed an unknown condition:
|
274
|
+
|
275
|
+
```ruby
|
276
|
+
Article.ransack!(unknown_attr_eq: 'Ernie')
|
277
|
+
# ArgumentError: Invalid search term unknown_attr_eq
|
278
|
+
```
|
279
|
+
|
280
|
+
This is equivalent to the `ignore_unknown_conditions` configuration option,
|
281
|
+
except it may be applied on a case-by-case basis.
|
282
|
+
|
283
|
+
### Using Scopes/Class Methods
|
284
|
+
|
285
|
+
Continuing on from the preceding section, searching by scopes requires defining
|
286
|
+
a whitelist of `ransackable_scopes` on the model class. The whitelist should be
|
287
|
+
an array of *symbols*. By default, all class methods (e.g. scopes) are ignored.
|
288
|
+
Scopes will be applied for matching `true` values, or for given values if the
|
289
|
+
scope accepts a value:
|
290
|
+
|
291
|
+
```ruby
|
292
|
+
class Employee < ActiveRecord::Base
|
293
|
+
scope :activated, ->(boolean = true) { where(active: boolean) }
|
294
|
+
scope :salary_gt, ->(amount) { where('salary > ?', amount) }
|
295
|
+
|
296
|
+
# Scopes are just syntactical sugar for class methods, which may also be used:
|
297
|
+
|
298
|
+
def self.hired_since(date)
|
299
|
+
where('start_date >= ?', date)
|
300
|
+
end
|
301
|
+
|
302
|
+
def self.ransackable_scopes(auth_object = nil)
|
303
|
+
if auth_object.try(:admin?)
|
304
|
+
# allow admin users access to all three methods
|
305
|
+
%i(activated hired_since salary_gt)
|
306
|
+
else
|
307
|
+
# allow other users to search on `activated` and `hired_since` only
|
308
|
+
%i(activated hired_since)
|
309
|
+
end
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
Employee.ransack({ activated: true, hired_since: '2013-01-01' })
|
314
|
+
|
315
|
+
Employee.ransack({ salary_gt: 100_000 }, { auth_object: current_user })
|
316
|
+
```
|
317
|
+
|
318
|
+
In Rails 3 and 4, if the `true` value is being passed via url params or some
|
319
|
+
other mechanism that will convert it to a string, the true value may not be
|
320
|
+
passed to the ransackable scope unless you wrap it in an array
|
321
|
+
(i.e. `activated: ['true']`). Ransack will take care of changing 'true' into a
|
322
|
+
boolean. This is currently resolved in Rails 5 :smiley:
|
323
|
+
|
324
|
+
However, perhaps you have `user_id: [1]` and you do not want Ransack to convert
|
325
|
+
1 into a boolean. (Values sanitized to booleans can be found in the
|
326
|
+
[constants.rb](https://github.com/activerecord-hackery/ransack/blob/master/lib/ransack/constants.rb#L28)).
|
327
|
+
To turn this off globally, and handle type conversions yourself, set
|
328
|
+
`sanitize_custom_scope_booleans` to false in an initializer file like
|
329
|
+
config/initializers/ransack.rb:
|
330
|
+
|
331
|
+
```ruby
|
332
|
+
Ransack.configure do |c|
|
333
|
+
c.sanitize_custom_scope_booleans = false
|
334
|
+
end
|
335
|
+
```
|
336
|
+
|
337
|
+
To turn this off on a per-scope basis Ransack adds the following method to
|
338
|
+
`ActiveRecord::Base` that you can redefine to selectively override sanitization:
|
339
|
+
|
340
|
+
`ransackable_scopes_skip_sanitize_args`
|
341
|
+
|
342
|
+
Add the scope you wish to bypass this behavior to ransackable_scopes_skip_sanitize_args:
|
343
|
+
|
344
|
+
```ruby
|
345
|
+
def self.ransackable_scopes_skip_sanitize_args
|
346
|
+
[:scope_to_skip_sanitize_args]
|
347
|
+
end
|
348
|
+
```
|
349
|
+
|
350
|
+
Scopes are a recent addition to Ransack and currently have a few caveats:
|
351
|
+
First, a scope involving child associations needs to be defined in the parent
|
352
|
+
table model, not in the child model. Second, scopes with an array as an
|
353
|
+
argument are not easily usable yet, because the array currently needs to be
|
354
|
+
wrapped in an array to function (see
|
355
|
+
[this issue](https://github.com/activerecord-hackery/ransack/issues/404)),
|
356
|
+
which is not compatible with Ransack form helpers. For this use case, it may be
|
357
|
+
better for now to use [ransackers](https://github.com/activerecord-hackery/ransack/wiki/Using-Ransackers) instead,
|
358
|
+
where feasible. Pull requests with solutions and tests are welcome!
|
359
|
+
|
360
|
+
### Grouping queries by OR instead of AND
|
361
|
+
|
362
|
+
The default `AND` grouping can be changed to `OR` by adding `m: 'or'` to the
|
363
|
+
query hash.
|
364
|
+
|
365
|
+
You can easily try it in your controller code by changing `params[:q]` in the
|
366
|
+
`index` action to `params[:q].try(:merge, m: 'or')` as follows:
|
367
|
+
|
368
|
+
```ruby
|
369
|
+
def index
|
370
|
+
@q = Artist.ransack(params[:q].try(:merge, m: 'or'))
|
371
|
+
@artists = @q.result
|
372
|
+
end
|
373
|
+
```
|
374
|
+
Normally, if you wanted users to be able to toggle between `AND` and `OR`
|
375
|
+
query grouping, you would probably set up your search form so that `m` was in
|
376
|
+
the URL params hash, but here we assigned `m` manually just to try it out
|
377
|
+
quickly.
|
378
|
+
|
379
|
+
Alternatively, trying it in the Rails console:
|
380
|
+
|
381
|
+
```ruby
|
382
|
+
artists = Artist.ransack(name_cont: 'foo', style_cont: 'bar', m: 'or')
|
383
|
+
=> Ransack::Search<class: Artist, base: Grouping <conditions: [
|
384
|
+
Condition <attributes: ["name"], predicate: cont, values: ["foo"]>,
|
385
|
+
Condition <attributes: ["style"], predicate: cont, values: ["bar"]>
|
386
|
+
], combinator: or>>
|
387
|
+
|
388
|
+
artists.result.to_sql
|
389
|
+
=> "SELECT \"artists\".* FROM \"artists\"
|
390
|
+
WHERE ((\"artists\".\"name\" ILIKE '%foo%'
|
391
|
+
OR \"artists\".\"style\" ILIKE '%bar%'))"
|
392
|
+
```
|
393
|
+
|
394
|
+
The combinator becomes `or` instead of the default `and`, and the SQL query
|
395
|
+
becomes `WHERE...OR` instead of `WHERE...AND`.
|
396
|
+
|
397
|
+
This works with associations as well. Imagine an Artist model that has many
|
398
|
+
Memberships, and many Musicians through Memberships:
|
399
|
+
|
400
|
+
```ruby
|
401
|
+
artists = Artist.ransack(name_cont: 'foo', musicians_email_cont: 'bar', m: 'or')
|
402
|
+
=> Ransack::Search<class: Artist, base: Grouping <conditions: [
|
403
|
+
Condition <attributes: ["name"], predicate: cont, values: ["foo"]>,
|
404
|
+
Condition <attributes: ["musicians_email"], predicate: cont, values: ["bar"]>
|
405
|
+
], combinator: or>>
|
406
|
+
|
407
|
+
artists.result.to_sql
|
408
|
+
=> "SELECT \"artists\".* FROM \"artists\"
|
409
|
+
LEFT OUTER JOIN \"memberships\"
|
410
|
+
ON \"memberships\".\"artist_id\" = \"artists\".\"id\"
|
411
|
+
LEFT OUTER JOIN \"musicians\"
|
412
|
+
ON \"musicians\".\"id\" = \"memberships\".\"musician_id\"
|
413
|
+
WHERE ((\"artists\".\"name\" ILIKE '%foo%'
|
414
|
+
OR \"musicians\".\"email\" ILIKE '%bar%'))"
|
415
|
+
```
|
416
|
+
|
417
|
+
### Using SimpleForm
|
418
|
+
|
419
|
+
If you would like to combine the Ransack and SimpleForm form builders, set the
|
420
|
+
`RANSACK_FORM_BUILDER` environment variable before Rails boots up, e.g. in
|
421
|
+
`config/application.rb` before `require 'rails/all'` as shown below (and add
|
422
|
+
`gem 'simple_form'` in your Gemfile).
|
423
|
+
|
424
|
+
```ruby
|
425
|
+
require File.expand_path('../boot', __FILE__)
|
426
|
+
ENV['RANSACK_FORM_BUILDER'] = '::SimpleForm::FormBuilder'
|
427
|
+
require 'rails/all'
|
428
|
+
```
|