ransack 2.6.0 → 3.1.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/.github/workflows/deploy.yml +35 -0
- data/.github/workflows/test-deploy.yml +29 -0
- data/.github/workflows/test.yml +10 -22
- data/.nojekyll +0 -0
- data/CHANGELOG.md +114 -13
- data/README.md +45 -1039
- data/docs/.gitignore +19 -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/acts-as-taggable-on.md +114 -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 +43 -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/polymorphic-search.md +40 -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/going-further/wiki-contributors.md +82 -0
- data/docs/docs/intro.md +99 -0
- data/docs/docusaurus.config.js +107 -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_7.1_ruby_2/join_association.rb +1 -0
- data/lib/polyamorous/activerecord_7.1_ruby_2/join_dependency.rb +1 -0
- data/lib/polyamorous/activerecord_7.1_ruby_2/reflection.rb +1 -0
- data/lib/ransack/adapters/active_record/base.rb +0 -2
- data/lib/ransack/adapters/active_record/context.rb +1 -0
- data/lib/ransack/helpers/form_helper.rb +10 -2
- data/lib/ransack/search.rb +2 -2
- data/lib/ransack/version.rb +1 -1
- data/ransack.gemspec +3 -3
- data/spec/ransack/adapters/active_record/base_spec.rb +10 -1
- data/spec/ransack/helpers/form_helper_spec.rb +44 -0
- data/spec/ransack/search_spec.rb +23 -0
- data/spec/support/schema.rb +16 -0
- metadata +66 -13
- data/docs/release_process.md +0 -17
@@ -0,0 +1,282 @@
|
|
1
|
+
---
|
2
|
+
title: Using Predicates
|
3
|
+
---
|
4
|
+
|
5
|
+
The primary method of searching in Ransack is by using what is known as *predicates*.
|
6
|
+
|
7
|
+
Predicates are used within Ransack search queries to determine what information to
|
8
|
+
match. For instance, the `cont` predicate will check to see if an attribute called
|
9
|
+
"first_name" contains a value using a wildcard query:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
>> User.ransack(first_name_cont: 'Rya').result.to_sql
|
13
|
+
=> SELECT "users".* FROM "users" WHERE ("users"."first_name" LIKE '%Rya%')
|
14
|
+
```
|
15
|
+
|
16
|
+
You can also combine predicates for OR queries:
|
17
|
+
```ruby
|
18
|
+
>> User.ransack(first_name_or_last_name_cont: 'Rya').result.to_sql
|
19
|
+
=> SELECT "users".* FROM "users" WHERE ("users"."first_name" LIKE '%Rya%'
|
20
|
+
OR "users"."last_name" LIKE '%Rya%')
|
21
|
+
```
|
22
|
+
|
23
|
+
The syntax for `OR` queries on an associated model is not immediately obvious, but makes sense. Assuming a `User` `has_one` `Account` and the `Account` has `attributes` `foo` and `bar`:
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
>> User.ransack(account_foo_or_account_bar_cont: 'val').result.to_sql
|
27
|
+
=> SELECT "users".* FROM "users" INNER JOIN accounts ON accounts.user_id = users.id WHERE ("accounts.foo LIKE '%val%' OR accounts.bar LIKE '%val%')
|
28
|
+
```
|
29
|
+
|
30
|
+
Below is a list of the built-in predicates of Ransack and their opposites. You may already
|
31
|
+
be familiar with some of the predicates, as they also exist in the ARel library.
|
32
|
+
|
33
|
+
If you want to add your own, please
|
34
|
+
see the [[Custom-Predicates|Custom Predicates]] page.
|
35
|
+
|
36
|
+
**Please note:** any attempt to use a predicate for an attribute that does not exist will
|
37
|
+
*silently fail*. For instance, this will not work when there is no `name` attribute:
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
>> User.ransack(name_cont: 'Rya').result.to_sql
|
41
|
+
=> "SELECT "users".* FROM "users"
|
42
|
+
```
|
43
|
+
|
44
|
+
## eq (equals)
|
45
|
+
|
46
|
+
The `eq` predicate returns all records where a field is *exactly* equal to a given value:
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
>> User.ransack(first_name_eq: 'Ryan').result.to_sql
|
50
|
+
=> SELECT "users".* FROM "users" WHERE "users"."first_name" = 'Ryan'
|
51
|
+
```
|
52
|
+
|
53
|
+
**Opposite: `not_eq`**
|
54
|
+
|
55
|
+
## matches
|
56
|
+
|
57
|
+
The `matches` predicate returns all records where a field is like a given value:
|
58
|
+
|
59
|
+
```ruby
|
60
|
+
>> User.ransack(first_name_matches: 'Ryan').result.to_sql
|
61
|
+
=> SELECT "users".* FROM "users" WHERE ("users"."first_name" LIKE 'Ryan')
|
62
|
+
```
|
63
|
+
|
64
|
+
On Postgres, the case-insensitive ILIKE will be used.
|
65
|
+
|
66
|
+
**Opposite: `does_not_match`**
|
67
|
+
|
68
|
+
*Note: If you want to do wildcard matching, you may be looking for the `cont`/`not_cont`
|
69
|
+
predicates instead.*
|
70
|
+
|
71
|
+
## lt (less than)
|
72
|
+
|
73
|
+
The `lt` predicate returns all records where a field is less than a given value:
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
>> User.ransack(age_lt: 25).result.to_sql
|
77
|
+
=> SELECT "users".* FROM "users" WHERE ("users"."age" < 25)
|
78
|
+
```
|
79
|
+
|
80
|
+
**Opposite: `gteq` (greater than or equal to)**
|
81
|
+
|
82
|
+
## lteq (less than or equal to)
|
83
|
+
|
84
|
+
The `lteq` predicate returns all records where a field is less than *or equal to* a given value:
|
85
|
+
|
86
|
+
```ruby
|
87
|
+
>> User.ransack(age_lteq: 25).result.to_sql
|
88
|
+
=> SELECT "users".* FROM "users" WHERE ("users"."age" <= 25)
|
89
|
+
```
|
90
|
+
|
91
|
+
**Opposite: `gt` (greater than)**
|
92
|
+
|
93
|
+
## in
|
94
|
+
|
95
|
+
The `in` predicate returns all records where a field is within a specified list:
|
96
|
+
|
97
|
+
```ruby
|
98
|
+
>> User.ransack(age_in: 20..25).result.to_sql
|
99
|
+
=> SELECT "users".* FROM "users" WHERE "users"."age" IN (20, 21, 22, 23, 24, 25)
|
100
|
+
```
|
101
|
+
|
102
|
+
It can also take an array:
|
103
|
+
|
104
|
+
```ruby
|
105
|
+
>> User.ransack(age_in: [20, 21, 22, 23, 24, 25]).result.to_sql
|
106
|
+
=> SELECT "users".* FROM "users" WHERE "users"."age" IN (20, 21, 22, 23, 24, 25)
|
107
|
+
```
|
108
|
+
|
109
|
+
**Opposite: `not_in`**
|
110
|
+
|
111
|
+
## cont
|
112
|
+
|
113
|
+
The `cont` predicate returns all records where a field contains a given value:
|
114
|
+
|
115
|
+
```ruby
|
116
|
+
>> User.ransack(first_name_cont: 'Rya').result.to_sql
|
117
|
+
=> SELECT "users".* FROM "users" WHERE ("users"."first_name" LIKE '%Rya%')
|
118
|
+
```
|
119
|
+
|
120
|
+
**Opposite: `not_cont`**
|
121
|
+
|
122
|
+
## cont_any (contains any)
|
123
|
+
|
124
|
+
The `cont_any` predicate returns all records where a field contains any of the given values:
|
125
|
+
|
126
|
+
```ruby
|
127
|
+
>> User.ransack(first_name_cont_any: %w(Rya Lis)).result.to_sql
|
128
|
+
=> SELECT "users".* FROM "users" WHERE (("users"."first_name" LIKE '%Rya%' OR "users"."first_name" LIKE '%Lis%'))
|
129
|
+
```
|
130
|
+
|
131
|
+
**Opposite: `not_cont_any`**
|
132
|
+
|
133
|
+
|
134
|
+
## cont_all (contains all)
|
135
|
+
|
136
|
+
The `cont_all` predicate returns all records where a field contains all of the given values:
|
137
|
+
|
138
|
+
```ruby
|
139
|
+
>> User.ransack(city_cont_all: %w(Grand Rapids)).result.to_sql
|
140
|
+
=> SELECT "users".* FROM "users" WHERE (("users"."city" LIKE '%Grand%' AND "users"."city" LIKE '%Rapids%'))
|
141
|
+
```
|
142
|
+
|
143
|
+
**Opposite: `not_cont_all`**
|
144
|
+
|
145
|
+
|
146
|
+
## i_cont
|
147
|
+
|
148
|
+
The `i_cont` case-insensitive predicate returns all records where a field contains a given value and ignores case:
|
149
|
+
|
150
|
+
```ruby
|
151
|
+
>> User.ransack(first_name_i_cont: 'Rya').result.to_sql
|
152
|
+
=> SELECT "users".* FROM "users" WHERE (LOWER("users"."first_name") LIKE '%rya%')
|
153
|
+
```
|
154
|
+
|
155
|
+
**Opposite: `not_i_cont`**
|
156
|
+
|
157
|
+
## i_cont_any
|
158
|
+
|
159
|
+
The `i_cont_any` case-insensitive predicate returns all records where a field contains any of the given values and ignores case:
|
160
|
+
|
161
|
+
```ruby
|
162
|
+
>> User.ransack(first_name_i_cont_any: %w(Rya Lis)).result.to_sql
|
163
|
+
=> SELECT "users".* FROM "users" WHERE ((LOWER("users"."first_name") LIKE '%rya%' OR LOWER("users"."first_name") LIKE '%lis%'))
|
164
|
+
```
|
165
|
+
|
166
|
+
**Opposite: `not_i_cont_any`**
|
167
|
+
|
168
|
+
|
169
|
+
## i_cont_all
|
170
|
+
|
171
|
+
The `i_cont_all` case-insensitive predicate returns all records where a field contains all of the given values and ignores case:
|
172
|
+
|
173
|
+
```ruby
|
174
|
+
>> User.ransack(city_i_cont_all: %w(Grand Rapids)).result.to_sql
|
175
|
+
=> SELECT "users".* FROM "users" WHERE ((LOWER("users"."city") LIKE '%grand%' AND LOWER("users"."city") LIKE '%rapids%'))
|
176
|
+
```
|
177
|
+
|
178
|
+
**Opposite: `not_i_cont_all`**
|
179
|
+
|
180
|
+
## start (starts with)
|
181
|
+
|
182
|
+
The `start` predicate returns all records where a field begins with a given value:
|
183
|
+
|
184
|
+
```ruby
|
185
|
+
>> User.ransack(first_name_start: 'Rya').result.to_sql
|
186
|
+
=> SELECT "users".* FROM "users" WHERE ("users"."first_name" LIKE 'Rya%')
|
187
|
+
```
|
188
|
+
|
189
|
+
**Opposite: `not_start`**
|
190
|
+
|
191
|
+
## end (ends with)
|
192
|
+
|
193
|
+
The `end` predicate returns all records where a field ends with a given value:
|
194
|
+
|
195
|
+
```ruby
|
196
|
+
>> User.ransack(first_name_end: 'yan').result.to_sql
|
197
|
+
=> SELECT "users".* FROM "users" WHERE ("users"."first_name" LIKE '%yan')
|
198
|
+
```
|
199
|
+
|
200
|
+
**Opposite: `not_end`**
|
201
|
+
|
202
|
+
## true
|
203
|
+
|
204
|
+
The `true` predicate returns all records where a field is true. The '1' indicates that
|
205
|
+
to Ransack that you indeed want to check the truthiness of this field. The other truthy
|
206
|
+
values are 'true', 'TRUE', 't' or 'T'.
|
207
|
+
|
208
|
+
```ruby
|
209
|
+
>> User.ransack(awesome_true: '1').result.to_sql
|
210
|
+
=> SELECT "users".* FROM "users" WHERE ("users"."awesome" = 't')
|
211
|
+
```
|
212
|
+
|
213
|
+
*Note: different database systems use different values to represent truth. In the above
|
214
|
+
example, we are using SQLite3.*
|
215
|
+
|
216
|
+
**Opposite: `not_true`**
|
217
|
+
|
218
|
+
## false
|
219
|
+
|
220
|
+
The `false` predicate returns all records where a field is false.
|
221
|
+
|
222
|
+
```ruby
|
223
|
+
>> User.ransack(awesome_false: '1').result.to_sql
|
224
|
+
=> SELECT "users".* FROM "users" WHERE ("users"."awesome" = 'f')
|
225
|
+
```
|
226
|
+
|
227
|
+
**Opposite: `not_false`**
|
228
|
+
|
229
|
+
*Note: the `false` predicate may be considered the opposite of the `true` predicate if the field does not contain `null` values. Otherwise, use `not_false`.*
|
230
|
+
|
231
|
+
## present
|
232
|
+
|
233
|
+
The `present` predicate returns all records where a field is present (not null and not a
|
234
|
+
blank string).
|
235
|
+
|
236
|
+
```ruby
|
237
|
+
>> User.ransack(first_name_present: '1').result.to_sql
|
238
|
+
=> SELECT "users".* FROM "users" WHERE (("users"."first_name" IS NOT NULL AND "users"."first_name" != ''))
|
239
|
+
```
|
240
|
+
|
241
|
+
**Opposite: `blank`**
|
242
|
+
|
243
|
+
## null
|
244
|
+
|
245
|
+
The `null` predicate returns all records where a field is null:
|
246
|
+
|
247
|
+
```ruby
|
248
|
+
>> User.ransack(first_name_null: 1).result.to_sql
|
249
|
+
=> SELECT "users".* FROM "users" WHERE "users"."first_name" IS NULL
|
250
|
+
```
|
251
|
+
|
252
|
+
**Opposite: `not_null`**
|
253
|
+
|
254
|
+
# URL parameter structure
|
255
|
+
|
256
|
+
The search parameters are passed to ransack as a hash. The URL representation of this hash uses the bracket notation: ```hash_name[key]=value```. The hash_name is the parameter which is defined in the controller, for instance ```q```. The key is the attribute and search predicate compound, for instance ```first_name_cont```, the value is the search parameter. When searching without using the search form helpers this URL structure needs to be created manually.
|
257
|
+
|
258
|
+
For example, the URL layout for searching and sorting users could looks like this:
|
259
|
+
|
260
|
+
```
|
261
|
+
/users.json?q[first_name_cont]=pete&q[last_name_cont]=jack&q[s]=created_at+desc
|
262
|
+
```
|
263
|
+
|
264
|
+
_Note that the sorting parameter ```s``` is nested within the ```q``` hash._
|
265
|
+
|
266
|
+
When using JavaScript to create such a URL, a matching jQuery request could look like this:
|
267
|
+
|
268
|
+
```javascript
|
269
|
+
$.ajax({
|
270
|
+
url: "/users.json",
|
271
|
+
data: {
|
272
|
+
q: {
|
273
|
+
first_name_cont: "pete",
|
274
|
+
last_name_cont: "jack",
|
275
|
+
s: "created_at desc"
|
276
|
+
}
|
277
|
+
},
|
278
|
+
success: function (data){
|
279
|
+
console.log(data);
|
280
|
+
}
|
281
|
+
});
|
282
|
+
```
|
@@ -0,0 +1,114 @@
|
|
1
|
+
---
|
2
|
+
title: Acts-as-taggable-on
|
3
|
+
sidebar_position: 13
|
4
|
+
---
|
5
|
+
|
6
|
+
## Using Acts As Taggable On
|
7
|
+
|
8
|
+
If you have an `ActiveRecord` model and you're using [acts-as-taggable-on](https://github.com/mbleigh/acts-as-taggable-on),
|
9
|
+
chances are you might want to search on tagged fields. Follow the instructions to install the gem and then set up your project files.
|
10
|
+
|
11
|
+
### Configure the model
|
12
|
+
|
13
|
+
`app/models/tasks.rb`
|
14
|
+
|
15
|
+
You can call the tagging field anything you like, it just needs to be plural. No migration is needed as this is stored in the internal ActsAsTaggable tables (`tags` and `taggings`).
|
16
|
+
|
17
|
+
```rb
|
18
|
+
class Task < ApplicationRecord
|
19
|
+
acts_as_taggable_on :projects
|
20
|
+
end
|
21
|
+
```
|
22
|
+
|
23
|
+
### Controller
|
24
|
+
|
25
|
+
Add a field to strong params in the controller. Use the singular name with `_list`.
|
26
|
+
|
27
|
+
`app/controllers/tasks_controller.rb`
|
28
|
+
|
29
|
+
```rb
|
30
|
+
def strong_params
|
31
|
+
params
|
32
|
+
.require(:tasks)
|
33
|
+
.permit(:task, :example_field, :project_list)
|
34
|
+
```
|
35
|
+
|
36
|
+
### Form
|
37
|
+
|
38
|
+
We need to `send` the tag fieldname to our model, also using the singular naming.
|
39
|
+
|
40
|
+
```
|
41
|
+
<div class='form-group'>
|
42
|
+
<%= f.label :project_list %>
|
43
|
+
<%= f.text_field :project_list, value: @task.send(:project_list).to_s %>
|
44
|
+
</div>
|
45
|
+
```
|
46
|
+
|
47
|
+
Now we can collect our data via the form, with tags separated by commas.
|
48
|
+
|
49
|
+
## Ransack Search
|
50
|
+
|
51
|
+
Imagine you have the following two instances of `Task`:
|
52
|
+
|
53
|
+
```rb
|
54
|
+
{ id: 1, name: 'Clean up my room', projects: [ 'Home', 'Personal' ] }
|
55
|
+
{ id: 2, name: 'Complete math exercises', projects: [ 'Homework', 'Study' ] }
|
56
|
+
```
|
57
|
+
|
58
|
+
When you're writing a `Ransack` search form, you can choose any of the following options:
|
59
|
+
|
60
|
+
```erb
|
61
|
+
<%= search_form_for @search do |f| %>
|
62
|
+
<%= f.text_field :projects_name_in %> <!-- option a -->
|
63
|
+
<%= f.text_field :projects_name_eq %> <!-- option b -->
|
64
|
+
<%= f.text_field :projects_name_cont %> <!-- option c -->
|
65
|
+
<% end %>
|
66
|
+
```
|
67
|
+
|
68
|
+
### Option A - Match keys exactly
|
69
|
+
|
70
|
+
Option `a` will match keys exactly. This is the solution to choose if you want to distinguish 'Home' from 'Homework': searching for 'Home' will return just the `Task` with id 1. It also allows searching for more than one tag at once (comma separated):
|
71
|
+
- `Home, Personal` will return task 1
|
72
|
+
- `Home, Homework` will return task 1 and 2
|
73
|
+
|
74
|
+
### Option B - match key combinations
|
75
|
+
|
76
|
+
Option `b` will match all keys exactly. This is the solution if you wanna search for specific combinations of tags:
|
77
|
+
- `Home` will return nothing, as there is no Task with just the `Home` tag
|
78
|
+
- `Home, Personal` will return task 1
|
79
|
+
|
80
|
+
### Option C - match substrings
|
81
|
+
|
82
|
+
Option `c` is used to match substrings. This is useful when you don't care for the exact tag, but only for part of it:
|
83
|
+
- `Home` will return task 1 and 2 (`/Home/` matches both `"Home"` and `"Homework"`)
|
84
|
+
|
85
|
+
### Option D - select from a list of tags
|
86
|
+
|
87
|
+
In Option D we allow the user to select a list of valid tags and then search againt them. We use the plural name here.
|
88
|
+
|
89
|
+
```erb
|
90
|
+
<div class='form-group'>
|
91
|
+
<%= f.label :projects_name, 'Project' %>
|
92
|
+
<%= f.select :projects_name_in, ActsAsTaggableOn::Tag.distinct.order(:name).pluck(:name) %>
|
93
|
+
</div>
|
94
|
+
```
|
95
|
+
|
96
|
+
## Multitenancy
|
97
|
+
|
98
|
+
ActsAsTaggableOn allows scoping of tags based on another field on the model. Suppose we have a `language` field on the model, as an effective second level key. We would adjust our model to look like this:
|
99
|
+
|
100
|
+
```rb
|
101
|
+
class Task < ApplicationRecord
|
102
|
+
acts_as_taggable_on :projects
|
103
|
+
acts_as_taggable_tenant :language
|
104
|
+
end
|
105
|
+
```
|
106
|
+
|
107
|
+
The Ransack search is then filtered using the `for_tenant` method
|
108
|
+
|
109
|
+
```
|
110
|
+
<div class='form-group'>
|
111
|
+
<%= f.label :projects_name, 'Project' %>
|
112
|
+
<%= f.select :projects_name_in, ActsAsTaggableOn::Tag.for_tenant('fr').distinct.order(:name).pluck(:name) %>
|
113
|
+
</div>
|
114
|
+
|
@@ -0,0 +1,70 @@
|
|
1
|
+
---
|
2
|
+
sidebar_position: 1
|
3
|
+
title: Associations
|
4
|
+
---
|
5
|
+
|
6
|
+
### Associations
|
7
|
+
|
8
|
+
You can easily use Ransack to search for objects in `has_many` and `belongs_to`
|
9
|
+
associations.
|
10
|
+
|
11
|
+
Given these associations...
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
class Employee < ActiveRecord::Base
|
15
|
+
belongs_to :supervisor
|
16
|
+
|
17
|
+
# has attributes first_name:string and last_name:string
|
18
|
+
end
|
19
|
+
|
20
|
+
class Department < ActiveRecord::Base
|
21
|
+
has_many :supervisors
|
22
|
+
|
23
|
+
# has attribute title:string
|
24
|
+
end
|
25
|
+
|
26
|
+
class Supervisor < ActiveRecord::Base
|
27
|
+
belongs_to :department
|
28
|
+
has_many :employees
|
29
|
+
|
30
|
+
# has attribute last_name:string
|
31
|
+
end
|
32
|
+
```
|
33
|
+
|
34
|
+
... and a controller...
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
class SupervisorsController < ApplicationController
|
38
|
+
def index
|
39
|
+
@q = Supervisor.ransack(params[:q])
|
40
|
+
@supervisors = @q.result.includes(:department, :employees)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
```
|
44
|
+
|
45
|
+
... you might set up your form like this...
|
46
|
+
|
47
|
+
```erb
|
48
|
+
<%= search_form_for @q do |f| %>
|
49
|
+
<%= f.label :last_name_cont %>
|
50
|
+
<%= f.search_field :last_name_cont %>
|
51
|
+
|
52
|
+
<%= f.label :department_title_cont %>
|
53
|
+
<%= f.search_field :department_title_cont %>
|
54
|
+
|
55
|
+
<%= f.label :employees_first_name_or_employees_last_name_cont %>
|
56
|
+
<%= f.search_field :employees_first_name_or_employees_last_name_cont %>
|
57
|
+
|
58
|
+
<%= f.submit "search" %>
|
59
|
+
<% end %>
|
60
|
+
...
|
61
|
+
<%= content_tag :table do %>
|
62
|
+
<%= content_tag :th, sort_link(@q, :last_name) %>
|
63
|
+
<%= content_tag :th, sort_link(@q, :department_title) %>
|
64
|
+
<%= content_tag :th, sort_link(@q, :employees_last_name) %>
|
65
|
+
<% end %>
|
66
|
+
```
|
67
|
+
|
68
|
+
If you have trouble sorting on associations, try using an SQL string with the
|
69
|
+
pluralized table (`'departments.title'`,`'employees.last_name'`) instead of the
|
70
|
+
symbolized association (`:department_title)`, `:employees_last_name`).
|
@@ -0,0 +1,52 @@
|
|
1
|
+
---
|
2
|
+
sidebar_position: 1
|
3
|
+
title: Custom predicates
|
4
|
+
---
|
5
|
+
|
6
|
+
If you'd like to add your own custom Ransack predicates:
|
7
|
+
|
8
|
+
```ruby
|
9
|
+
# config/initializers/ransack.rb
|
10
|
+
|
11
|
+
Ransack.configure do |config|
|
12
|
+
config.add_predicate 'equals_diddly', # Name your predicate
|
13
|
+
# What non-compound ARel predicate will it use? (eq, matches, etc)
|
14
|
+
arel_predicate: 'eq',
|
15
|
+
# Format incoming values as you see fit. (Default: Don't do formatting)
|
16
|
+
formatter: proc { |v| "#{v}-diddly" },
|
17
|
+
# Validate a value. An "invalid" value won't be used in a search.
|
18
|
+
# Below is default.
|
19
|
+
validator: proc { |v| v.present? },
|
20
|
+
# Should compounds be created? Will use the compound (any/all) version
|
21
|
+
# of the arel_predicate to create a corresponding any/all version of
|
22
|
+
# your predicate. (Default: true)
|
23
|
+
compounds: true,
|
24
|
+
# Force a specific column type for type-casting of supplied values.
|
25
|
+
# (Default: use type from DB column)
|
26
|
+
type: :string,
|
27
|
+
# Use LOWER(column on database).
|
28
|
+
# (Default: false)
|
29
|
+
case_insensitive: true
|
30
|
+
end
|
31
|
+
```
|
32
|
+
You can check all Arel predicates [here](https://github.com/rails/rails/blob/main/activerecord/lib/arel/predications.rb).
|
33
|
+
|
34
|
+
If Arel does not have the predicate you are looking for, consider monkey patching it:
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
# config/initializers/ransack.rb
|
38
|
+
|
39
|
+
module Arel
|
40
|
+
module Predications
|
41
|
+
def gteq_or_null(other)
|
42
|
+
left = gteq(other)
|
43
|
+
right = eq(nil)
|
44
|
+
left.or(right)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
Ransack.configure do |config|
|
50
|
+
config.add_predicate 'gteq_or_null', arel_predicate: 'gteq_or_null'
|
51
|
+
end
|
52
|
+
```
|
@@ -0,0 +1,43 @@
|
|
1
|
+
---
|
2
|
+
sidebar_position: 11
|
3
|
+
title: Documentation
|
4
|
+
---
|
5
|
+
|
6
|
+
Ransack uses [Docusaurus](https://docusaurus.io/) for documentation. To contribute to the docs simply use the "Edit this page" link from any page to directly edit, or else pull the repo and edit locally.
|
7
|
+
|
8
|
+
### Local Development
|
9
|
+
|
10
|
+
Switch to docs folder
|
11
|
+
|
12
|
+
```
|
13
|
+
cd docs
|
14
|
+
```
|
15
|
+
|
16
|
+
Install docusaurus and other dependencies
|
17
|
+
|
18
|
+
```
|
19
|
+
yarn install
|
20
|
+
```
|
21
|
+
|
22
|
+
|
23
|
+
Start a local development server and open up a browser window. Most changes are reflected live without having to restart the server.
|
24
|
+
|
25
|
+
```
|
26
|
+
yarn start
|
27
|
+
```
|
28
|
+
|
29
|
+
### Build
|
30
|
+
|
31
|
+
```
|
32
|
+
yarn build
|
33
|
+
```
|
34
|
+
|
35
|
+
This command generates static content into the `build` directory and can be served using any static contents hosting service.
|
36
|
+
|
37
|
+
### Deployment
|
38
|
+
|
39
|
+
Using SSH:
|
40
|
+
|
41
|
+
```
|
42
|
+
USE_SSH=true yarn deploy
|
43
|
+
```
|
@@ -0,0 +1,49 @@
|
|
1
|
+
---
|
2
|
+
sidebar_position: 2
|
3
|
+
title: CSV Export
|
4
|
+
---
|
5
|
+
|
6
|
+
Exporting to CSV
|
7
|
+
|
8
|
+
Example downloading a csv file preserving ransack search, based on [this gist](https://gist.github.com/pama/adff25ed1f4b796ce088ea362a08e1c5)
|
9
|
+
|
10
|
+
```jsx title='index.html.erb'
|
11
|
+
<h1>Users</h1>
|
12
|
+
|
13
|
+
<%= search_form_for @q, url: dashboard_index_path do |f| %>
|
14
|
+
<%= f.label :name_cont %>
|
15
|
+
<%= f.search_field :name_cont %>
|
16
|
+
|
17
|
+
<%= f.submit %>
|
18
|
+
<% end %>
|
19
|
+
|
20
|
+
<ul>
|
21
|
+
<% @users.each do |user| %>
|
22
|
+
<li><%= user.name %> [<%= user.devices.map {|device| device.name }.join(', ') %>]</li>
|
23
|
+
<% end %>
|
24
|
+
</ul>
|
25
|
+
|
26
|
+
<% if params[:q] %>
|
27
|
+
<%= link_to 'Export 1', dashboard_index_path({name: params[:q][:name_cont]}.merge({format: :csv})) %>
|
28
|
+
<% else %>
|
29
|
+
<%= link_to 'Export 2', dashboard_index_path(format: 'csv') %>
|
30
|
+
<% end %>
|
31
|
+
```
|
32
|
+
|
33
|
+
```jsx title='user.rb'
|
34
|
+
require 'csv'
|
35
|
+
|
36
|
+
class User < ApplicationRecord
|
37
|
+
has_many :devices
|
38
|
+
|
39
|
+
def self.get_csv(users)
|
40
|
+
CSV.generate do |csv|
|
41
|
+
csv << ["Name", "Devices"]
|
42
|
+
|
43
|
+
users.each do |user|
|
44
|
+
csv << [user.name, user.devices.map{|device| device.name}.join(', ')]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
```
|
@@ -0,0 +1,57 @@
|
|
1
|
+
---
|
2
|
+
sidebar_position: 9
|
3
|
+
title: External resources
|
4
|
+
---
|
5
|
+
|
6
|
+
There is a plethora of material on Ransack around the internet. We've collected some here for your convenience.
|
7
|
+
|
8
|
+
Do you want to have a blog post or other content on Ransack highlighted? Please just edit the page, add your content and a Pull Request will be sent to Ransack maintainers for approval.
|
9
|
+
|
10
|
+
# Screencasts
|
11
|
+
|
12
|
+
- [DriftingRuby: Ransack Search and Hotwire](https://www.driftingruby.com/episodes/ransack-search-and-hotwire)
|
13
|
+
- [GoRails: Forum Series Part 6: Search with Ransack](https://gorails.com/episodes/forum-search-with-ransack)
|
14
|
+
- [Railscast 370 - Ransack](http://railscasts.com/episodes/370-ransack)
|
15
|
+
- [Search And Sort Ransack Associations With The Rails Ransack Gem | Ruby On Rails 6 Ransack Tutorial](https://www.youtube.com/watch?v=rtg-5EXwpbg)
|
16
|
+
|
17
|
+
|
18
|
+
# Gems
|
19
|
+
|
20
|
+
- [ActiveAdmin](https://activeadmin.info/) The Administration Framework for Rails **_uses Ransack internally_**
|
21
|
+
- [Ransack Memory](https://github.com/richardrails/ransack_memory) Automatically save and load Ransack's filtered params into the Rail's session
|
22
|
+
- [Mobility Ransack](https://github.com/shioyama/mobility-ransack) Search attributes translated by Mobility with Ransack.
|
23
|
+
- [Ransack UI](https://github.com/ndbroadbent/ransack_ui) Framework for building a search UI with Ransack **_seems abandoned_**
|
24
|
+
|
25
|
+
# Blogs
|
26
|
+
|
27
|
+
- [Search And Sort In Ruby On Rails 6 With The Ransack Gem](https://deanin.com/blog/ransack/)
|
28
|
+
- [Implement Ransack Gem in Ruby on Rails](https://www.botreetechnologies.com/blog/implementing-advanced-search-in-ruby-on-rails-with-ransack/)
|
29
|
+
- [Searching and Sorting with Ransack](https://jaspercurry.medium.com/searching-and-sorting-on-rails-with-ransack-560e862e650a)
|
30
|
+
- [How to Build Your Own ActiveAdmin Filters with Ransack](https://www.viget.com/articles/how-to-build-your-own-filters-with-ransack/)
|
31
|
+
- [Avoid Ransack's N+1 Pitfall!](https://dev.to/husteadrobert/avoid-ransacks-n1-pitfall-33of)
|
32
|
+
- [Filter and paging with Kaminari](https://gist.github.com/MyklClason/e4dc96fd0e009b7b3a9c84ddbb1e11d2)
|
33
|
+
- [Pagination for Ransack Forms](https://nicholaide.github.io/ransack/2016/11/26/ransack-pagination.html)
|
34
|
+
- [AJAX Search, Sort, Paginate with Ransack and Kaminari](https://techbrownbags.wordpress.com/2014/01/17/rails-ajax-search-sort-paginate-with-ransack-kaminari/)
|
35
|
+
- [Searching with Ransack in Ruby on Rails](http://blog.magmalabs.io/2019/03/12/searching-with-ransack-in-ruby-on-rails.html)
|
36
|
+
- [Role scopes with gem Ransack](https://blog.corsego.com/rolify-scopes)
|
37
|
+
- [Searching and Sorting with Ransack](https://www.mintbit.com/blog/searching-and-sorting-with-ransack)
|
38
|
+
- [Using custom scopes with Ransack gem in Rails](https://profilehunt.net/blog/using-custom-scopes-with-ransack-in-rails)
|
39
|
+
- [Query Date Range With Ransack](https://lingceng.github.io/blog/2015/12/28/query-date-range-with-ransack/)
|
40
|
+
- [ransack vs searchkick: Building a search feature in Rails](https://www.cookieshq.co.uk/posts/ransack-vs-searchkick-building-a-search-feature-in-rails)
|
41
|
+
- [Using ransack and delegate in Rails](https://huangwenwei.com/blogs/using-ransack-and-delegate-in-rails)
|
42
|
+
- [Using Ransack as a Search Engine](https://medium.com/@jelaniwoods/using-ransack-as-a-search-engine-92e002a68da)
|
43
|
+
- [Advanced Search with Ransack](https://www.sitepoint.com/advanced-search-ransack/)
|
44
|
+
- [Sort a table of records in Rails with Ransack](https://alankydd.wordpress.com/2012/03/12/sort-a-table-of-records-in-rails-with-ransack/)
|
45
|
+
- [Ransack: Search with Multiple Checkboxes (Rails)](https://iamjosh.wordpress.com/2014/03/07/ransack-search-with-multiple-checkboxes/)
|
46
|
+
- [Rails : Ransack : Sorting data by ratings](https://cbabhusal.wordpress.com/2017/01/03/rails-ransack-sorting-data-by-ratings/)
|
47
|
+
- [Setting Up Rails 5 API Only App with ActiveAdmin enabled](https://rrott.com/blog/ror/rails-5-api-with-activeadmin-integration/)
|
48
|
+
- [Ransack, the library formerly known as MetaSearch 2.0](https://ernie.io/2011/04/01/ransack-the-library-formerly-known-as-metasearch-2-0/) **_some Ransack history_**
|
49
|
+
|
50
|
+
## In French
|
51
|
+
|
52
|
+
- [Faciliter les recherches avec Ransack](https://www.synbioz.com/blog/tech/faciliter-les-recherches-avec-ransack)
|
53
|
+
|
54
|
+
## In Vietnamese
|
55
|
+
|
56
|
+
- [Ransack - công cụ tuyệt vời giúp tìm kiếm và sắp xếp dữ liệu đơn giản hơn
|
57
|
+
](https://nddblog.com/posts/ransack-cong-cu-tuyet-voi-giup-tim-kiem-va-sap-xep-du-lieu-don-gian-hon)
|