ransack 2.6.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/.nojekyll +0 -0
  3. data/CHANGELOG.md +75 -13
  4. data/README.md +45 -1039
  5. data/docs/.gitignore +20 -0
  6. data/docs/.nojekyll +0 -0
  7. data/docs/babel.config.js +3 -0
  8. data/docs/blog/2022-03-27-ransack-3.0.0.md +20 -0
  9. data/docs/docs/getting-started/_category_.json +4 -0
  10. data/docs/docs/getting-started/advanced-mode.md +46 -0
  11. data/docs/docs/getting-started/configuration.md +47 -0
  12. data/docs/docs/getting-started/search-matches.md +67 -0
  13. data/docs/docs/getting-started/simple-mode.md +284 -0
  14. data/docs/docs/getting-started/sorting.md +79 -0
  15. data/docs/docs/getting-started/using-predicates.md +282 -0
  16. data/docs/docs/going-further/_category_.json +4 -0
  17. data/docs/docs/going-further/associations.md +70 -0
  18. data/docs/docs/going-further/custom-predicates.md +52 -0
  19. data/docs/docs/going-further/documentation.md +31 -0
  20. data/docs/docs/going-further/exporting-to-csv.md +49 -0
  21. data/docs/docs/going-further/external-guides.md +57 -0
  22. data/docs/docs/going-further/form-customisation.md +63 -0
  23. data/docs/docs/going-further/i18n.md +53 -0
  24. data/docs/{img → docs/going-further/img}/create_release.png +0 -0
  25. data/docs/docs/going-further/merging-searches.md +41 -0
  26. data/docs/docs/going-further/other-notes.md +428 -0
  27. data/docs/docs/going-further/ransackers.md +331 -0
  28. data/docs/docs/going-further/release_process.md +36 -0
  29. data/docs/docs/going-further/saving-queries.md +82 -0
  30. data/docs/docs/going-further/searching-postgres.md +57 -0
  31. data/docs/docs/intro.md +99 -0
  32. data/docs/docusaurus.config.js +108 -0
  33. data/docs/package-lock.json +9207 -0
  34. data/docs/package.json +37 -0
  35. data/docs/sidebars.js +31 -0
  36. data/docs/src/components/HomepageFeatures/index.js +64 -0
  37. data/docs/src/components/HomepageFeatures/styles.module.css +11 -0
  38. data/docs/src/css/custom.css +39 -0
  39. data/docs/src/pages/index.module.css +23 -0
  40. data/docs/src/pages/markdown-page.md +7 -0
  41. data/docs/static/.nojekyll +0 -0
  42. data/docs/static/img/docusaurus.png +0 -0
  43. data/docs/static/img/favicon.ico +0 -0
  44. data/docs/static/img/logo.svg +1 -0
  45. data/docs/static/img/tutorial/docsVersionDropdown.png +0 -0
  46. data/docs/static/img/tutorial/localeDropdown.png +0 -0
  47. data/docs/static/img/undraw_docusaurus_mountain.svg +171 -0
  48. data/docs/static/img/undraw_docusaurus_react.svg +170 -0
  49. data/docs/static/img/undraw_docusaurus_tree.svg +40 -0
  50. data/{logo → docs/static/logo}/ransack-h.png +0 -0
  51. data/{logo → docs/static/logo}/ransack-h.svg +0 -0
  52. data/{logo → docs/static/logo}/ransack-v.png +0 -0
  53. data/{logo → docs/static/logo}/ransack-v.svg +0 -0
  54. data/{logo → docs/static/logo}/ransack.png +0 -0
  55. data/{logo → docs/static/logo}/ransack.svg +0 -0
  56. data/docs/yarn.lock +7671 -0
  57. data/lib/ransack/adapters/active_record/base.rb +0 -2
  58. data/lib/ransack/adapters/active_record/context.rb +1 -0
  59. data/lib/ransack/helpers/form_helper.rb +10 -2
  60. data/lib/ransack/search.rb +2 -2
  61. data/lib/ransack/version.rb +1 -1
  62. data/ransack.gemspec +2 -2
  63. data/spec/ransack/adapters/active_record/base_spec.rb +10 -1
  64. data/spec/ransack/helpers/form_helper_spec.rb +32 -0
  65. data/spec/ransack/search_spec.rb +23 -0
  66. data/spec/support/schema.rb +16 -0
  67. metadata +58 -12
  68. data/docs/release_process.md +0 -17
data/README.md CHANGED
@@ -1,1054 +1,64 @@
1
- # ![Ransack](./logo/ransack-h.png "Ransack")
1
+ # ![Ransack](./docs/static/logo/ransack-h.png "Ransack")
2
2
 
3
3
  [![Build Status](https://github.com/activerecord-hackery/ransack/workflows/test/badge.svg)](https://github.com/activerecord-hackery/ransack/actions)
4
4
  [![Gem Version](https://badge.fury.io/rb/ransack.svg)](http://badge.fury.io/rb/ransack)
5
5
  [![Code Climate](https://codeclimate.com/github/activerecord-hackery/ransack/badges/gpa.svg)](https://codeclimate.com/github/activerecord-hackery/ransack)
6
6
  [![Backers on Open Collective](https://opencollective.com/ransack/backers/badge.svg)](#backers) [![Sponsors on Open Collective](https://opencollective.com/ransack/sponsors/badge.svg)](#sponsors)
7
7
 
8
- Ransack enables the creation of both
9
- [simple](http://ransack-demo.herokuapp.com) and
10
- [advanced](http://ransack-demo.herokuapp.com/users/advanced_search) search forms
11
- for your Ruby on Rails application
12
- ([demo source code here](https://github.com/activerecord-hackery/ransack_demo)).
13
- If you're looking for something that simplifies query generation at the model
14
- or controller layer, you're probably not looking for Ransack.
8
+ # SPECIAL ANNOUNCEMENT
15
9
 
16
- ## Getting started
10
+ Please see the [Ransack Blog](https://activerecord-hackery.github.io/ransack/blog) for a special announcement from the Ransack maintainers and Ernie Miller, the original author of Ransack.
17
11
 
18
- Ransack is supported for Rails 7.0, 6.x on Ruby 2.6.6 and later.
19
-
20
- In your Gemfile, for the last officially released gem:
21
-
22
- ```ruby
23
- gem 'ransack'
24
- ```
25
-
26
- If you would like to use the latest updates (recommended), use the `master`
27
- branch:
28
-
29
- ```ruby
30
- gem 'ransack', github: 'activerecord-hackery/ransack'
31
- ```
32
-
33
- ## Issues tracker
34
-
35
- * Before filing an issue, please read the [Contributing Guide](CONTRIBUTING.md).
36
- * File an issue if a bug is caused by Ransack, is new (has not already been reported), and _can be reproduced from the information you provide_.
37
- * Contributions are welcome, but please do not add "+1" comments to issues or pull requests :smiley:
38
- * Please do not use the issue tracker for personal support requests. Stack Overflow is a better place for that where a wider community can help you!
39
-
40
- ## Usage
41
-
42
- Ransack can be used in one of two modes, simple or advanced. For
43
- searching/filtering not requiring complex boolean logic, Ransack's simple
44
- mode should meet your needs.
45
-
46
- If you're coming from MetaSearch (Ransack's predecessor), refer to the
47
- [Updating From MetaSearch](#updating-from-metasearch) section
48
-
49
- ### Simple Mode
50
-
51
- #### In your controller
52
-
53
- ```ruby
54
- def index
55
- @q = Person.ransack(params[:q])
56
- @people = @q.result(distinct: true)
57
- end
58
- ```
59
- or without `distinct: true`, for sorting on an associated table's columns (in
60
- this example, with preloading each Person's Articles and pagination):
61
-
62
- ```ruby
63
- def index
64
- @q = Person.ransack(params[:q])
65
- @people = @q.result.includes(:articles).page(params[:page])
66
- end
67
- ```
68
-
69
- ##### Default search options
70
-
71
- **Search parameter**
72
-
73
- Ransack uses a default `:q` param key for search params. This may be changed by
74
- setting the `search_key` option in a Ransack initializer file (typically
75
- `config/initializers/ransack.rb`):
76
-
77
- ```ruby
78
- Ransack.configure do |c|
79
- # Change default search parameter key name.
80
- # Default key name is :q
81
- c.search_key = :query
82
- end
83
- ```
84
-
85
- **String search**
86
-
87
- After version 2.4.0 when searching a string query Ransack by default strips all whitespace around the query string.
88
- This may be disabled by setting the `strip_whitespace` option in a Ransack initializer file:
89
-
90
- ```ruby
91
- Ransack.configure do |c|
92
- # Change whitespace stripping behaviour.
93
- # Default is true
94
- c.strip_whitespace = false
95
- end
96
- ```
97
-
98
- #### In your view
99
-
100
- The two primary Ransack view helpers are `search_form_for` and `sort_link`,
101
- which are defined in
102
- [Ransack::Helpers::FormHelper](lib/ransack/helpers/form_helper.rb).
103
-
104
- #### Ransack's `search_form_for` helper replaces `form_for` for creating the view search form
105
-
106
- ```erb
107
- <%= search_form_for @q do |f| %>
108
-
109
- # Search if the name field contains...
110
- <%= f.label :name_cont %>
111
- <%= f.search_field :name_cont %>
112
-
113
- # Search if an associated articles.title starts with...
114
- <%= f.label :articles_title_start %>
115
- <%= f.search_field :articles_title_start %>
116
-
117
- # Attributes may be chained. Search multiple attributes for one value...
118
- <%= f.label :name_or_description_or_email_or_articles_title_cont %>
119
- <%= f.search_field :name_or_description_or_email_or_articles_title_cont %>
120
-
121
- <%= f.submit %>
122
- <% end %>
123
- ```
124
-
125
- The argument of `f.search_field` has to be in this form:
126
- `attribute_name[_or_attribute_name]..._predicate`
127
-
128
- where `[_or_another_attribute_name]...` means any repetition of `_or_` plus the name of the attribute.
129
-
130
- `cont` (contains) and `start` (starts with) are just two of the available
131
- search predicates. See
132
- [Constants](https://github.com/activerecord-hackery/ransack/blob/master/lib/ransack/constants.rb)
133
- for a full list and the
134
- [wiki](https://github.com/activerecord-hackery/ransack/wiki/Basic-Searching)
135
- for more information.
136
-
137
- The `search_form_for` answer format can be set like this:
138
-
139
- ```erb
140
- <%= search_form_for(@q, format: :pdf) do |f| %>
141
-
142
- <%= search_form_for(@q, format: :json) do |f| %>
143
- ```
144
-
145
- #### Ransack's `sort_link` helper creates table headers that are sortable links
146
-
147
- ```erb
148
- <%= sort_link(@q, :name) %>
149
- ```
150
- Additional options can be passed after the column attribute, like a different
151
- column title or a default sort order:
152
-
153
- ```erb
154
- <%= sort_link(@q, :name, 'Last Name', default_order: :desc) %>
155
- ```
156
-
157
- You can use a block if the link markup is hard to fit into the label parameter:
158
-
159
- ```erb
160
- <%= sort_link(@q, :name) do %>
161
- <strong>Player Name</strong>
162
- <% end %>
163
- ```
164
-
165
- With a polymorphic association, you may need to specify the name of the link
166
- explicitly to avoid an `uninitialized constant Model::Xxxable` error (see issue
167
- [#421](https://github.com/activerecord-hackery/ransack/issues/421)):
168
-
169
- ```erb
170
- <%= sort_link(@q, :xxxable_of_Ymodel_type_some_attribute, 'Attribute Name') %>
171
- ```
172
-
173
- You can also sort on multiple fields by specifying an ordered array:
174
-
175
- ```erb
176
- <%= sort_link(@q, :last_name, [:last_name, 'first_name asc'], 'Last Name') %>
177
- ```
178
-
179
- In the example above, clicking the link will sort by `last_name` and then
180
- `first_name`. Specifying the sort direction on a field in the array tells
181
- Ransack to _always_ sort that particular field in the specified direction.
182
-
183
- Multiple `default_order` fields may also be specified with a hash:
184
-
185
- ```erb
186
- <%= sort_link(@q, :last_name, %i(last_name first_name),
187
- default_order: { last_name: 'asc', first_name: 'desc' }) %>
188
- ```
189
-
190
- This example toggles the sort directions of both fields, by default
191
- initially sorting the `last_name` field by ascending order, and the
192
- `first_name` field by descending order.
193
-
194
- In the case that you wish to sort by some complex value, such as the result
195
- of a SQL function, you may do so using scopes. In your model, define scopes
196
- whose names line up with the name of the virtual field you wish to sort by,
197
- as so:
198
-
199
- ```ruby
200
- class Person < ActiveRecord::Base
201
- scope :sort_by_reverse_name_asc, lambda { order("REVERSE(name) ASC") }
202
- scope :sort_by_reverse_name_desc, lambda { order("REVERSE(name) DESC") }
203
- ...
204
- ```
205
-
206
- and you can then sort by this virtual field:
207
-
208
- ```erb
209
- <%= sort_link(@q, :reverse_name) %>
210
- ```
211
-
212
- The sort link order indicator arrows may be globally customized by setting a
213
- `custom_arrows` option in an initializer file like
214
- `config/initializers/ransack.rb`.
215
12
 
216
- You can also enable a `default_arrow` which is displayed on all sortable fields
217
- which are not currently used in the sorting. This is disabled by default so
218
- nothing will be displayed:
13
+ # Introduction
219
14
 
220
- ```ruby
221
- Ransack.configure do |c|
222
- c.custom_arrows = {
223
- up_arrow: '<i class="custom-up-arrow-icon"></i>',
224
- down_arrow: 'U+02193',
225
- default_arrow: '<i class="default-arrow-icon"></i>'
226
- }
227
- end
228
- ```
229
-
230
- All sort links may be displayed without the order indicator
231
- arrows by setting `hide_sort_order_indicators` to true in the initializer file.
232
- Note that this hides the arrows even if they were customized:
233
-
234
- ```ruby
235
- Ransack.configure do |c|
236
- c.hide_sort_order_indicators = true
237
- end
238
- ```
239
-
240
- Without setting it globally, individual sort links may be displayed without
241
- the order indicator arrow by passing `hide_indicator: true` in the sort link:
242
-
243
- ```erb
244
- <%= sort_link(@q, :name, hide_indicator: true) %>
245
- ```
246
-
247
- #### Ransack's `sort_url` helper is like a `sort_link` but returns only the url
248
-
249
- `sort_url` has the same API as `sort_link`:
250
-
251
- ```erb
252
- <%= sort_url(@q, :name, default_order: :desc) %>
253
- ```
254
-
255
- ```erb
256
- <%= sort_url(@q, :last_name, [:last_name, 'first_name asc']) %>
257
- ```
258
-
259
- ```erb
260
- <%= sort_url(@q, :last_name, %i(last_name first_name),
261
- default_order: { last_name: 'asc', first_name: 'desc' }) %>
262
- ```
263
-
264
- #### PostgreSQL's sort option
265
-
266
- The `NULLS FIRST` and `NULLS LAST` options can be used to determine whether nulls appear before or after non-null values in the sort ordering.
267
-
268
- You may want to configure it like this:
269
-
270
- ```rb
271
- Ransack.configure do |c|
272
- c.postgres_fields_sort_option = :nulls_first # or :nulls_last
273
- end
274
- ```
275
-
276
- To treat nulls as having the lowest or highest value respectively. To force nulls to always be first or last, use
277
-
278
- ```rb
279
- Ransack.configure do |c|
280
- c.postgres_fields_sort_option = :nulls_always_first # or :nulls_always_last
281
- end
282
- ```
283
-
284
- See this feature: https://www.postgresql.org/docs/13/queries-order.html
285
-
286
- #### Case Insensitive Sorting in PostgreSQL
287
-
288
- In order to request PostgreSQL to do a case insensitive sort for all string columns of a model at once, Ransack can be extended by using this approach:
289
-
290
- ```ruby
291
- module RansackObject
292
-
293
- def self.included(base)
294
- base.columns.each do |column|
295
- if column.type == :string
296
- base.ransacker column.name.to_sym, type: :string do
297
- Arel.sql("lower(#{base.table_name}.#{column.name})")
298
- end
299
- end
300
- end
301
- end
302
- end
303
- ```
304
-
305
- ```ruby
306
- class UserWithManyAttributes < ActiveRecord::Base
307
- include RansackObject
308
- end
309
- ```
310
-
311
- If this approach is taken, it is advisable to [add a functional index](https://www.postgresql.org/docs/13/citext.html).
312
-
313
- This was originally asked in [a Ransack issue](https://github.com/activerecord-hackery/ransack/issues/1201) and a solution was found on [Stack Overflow](https://stackoverflow.com/a/34677378).
314
-
315
- ### Advanced Mode
316
-
317
- "Advanced" searches (ab)use Rails' nested attributes functionality in order to
318
- generate complex queries with nested AND/OR groupings, etc. This takes a bit
319
- more work but can generate some pretty cool search interfaces that put a lot of
320
- power in the hands of your users. A notable drawback with these searches is
321
- that the increased size of the parameter string will typically force you to use
322
- the HTTP POST method instead of GET. :(
323
-
324
- This means you'll need to tweak your routes...
325
-
326
- ```ruby
327
- resources :people do
328
- collection do
329
- match 'search' => 'people#search', via: [:get, :post], as: :search
330
- end
331
- end
332
- ```
333
-
334
- ... and add another controller action ...
335
-
336
- ```ruby
337
- def search
338
- index
339
- render :index
340
- end
341
- ```
342
-
343
- ... and update your `search_form_for` line in the view ...
344
-
345
- ```erb
346
- <%= search_form_for @q, url: search_people_path,
347
- html: { method: :post } do |f| %>
348
- ```
349
-
350
- Once you've done so, you can make use of the helpers in [Ransack::Helpers::FormBuilder](lib/ransack/helpers/form_builder.rb) to
351
- construct much more complex search forms, such as the one on the
352
- [demo app](http://ransack-demo.herokuapp.com/users/advanced_search)
353
- (source code [here](https://github.com/activerecord-hackery/ransack_demo)).
354
-
355
- ### Ransack #search method
356
-
357
- Ransack will try to make the class method `#search` available in your
358
- models, but if `#search` has already been defined elsewhere, you can always use
359
- the default `#ransack` class method. So the following are equivalent:
360
-
361
- ```ruby
362
- Article.ransack(params[:q])
363
- Article.search(params[:q])
364
- ```
365
-
366
- Users have reported issues of `#search` name conflicts with other gems, so
367
- the `#search` method alias will be deprecated in the next major version of
368
- Ransack (2.0). It's advisable to use the default `#ransack` instead.
369
-
370
- For now, if Ransack's `#search` method conflicts with the name of another
371
- method named `search` in your code or another gem, you may resolve it either by
372
- patching the `extended` class_method in `Ransack::Adapters::ActiveRecord::Base`
373
- to remove the line `alias :search :ransack unless base.respond_to? :search`, or
374
- by placing the following line in your Ransack initializer file at
375
- `config/initializers/ransack.rb`:
376
-
377
- ```ruby
378
- Ransack::Adapters::ActiveRecord::Base.class_eval('remove_method :search')
379
- ```
380
-
381
- ### Associations
382
-
383
- You can easily use Ransack to search for objects in `has_many` and `belongs_to`
384
- associations.
385
-
386
- Given these associations...
387
-
388
- ```ruby
389
- class Employee < ActiveRecord::Base
390
- belongs_to :supervisor
391
-
392
- # has attributes first_name:string and last_name:string
393
- end
394
-
395
- class Department < ActiveRecord::Base
396
- has_many :supervisors
397
-
398
- # has attribute title:string
399
- end
400
-
401
- class Supervisor < ActiveRecord::Base
402
- belongs_to :department
403
- has_many :employees
404
-
405
- # has attribute last_name:string
406
- end
407
- ```
408
-
409
- ... and a controller...
410
-
411
- ```ruby
412
- class SupervisorsController < ApplicationController
413
- def index
414
- @q = Supervisor.ransack(params[:q])
415
- @supervisors = @q.result.includes(:department, :employees)
416
- end
417
- end
418
- ```
419
-
420
- ... you might set up your form like this...
421
-
422
- ```erb
423
- <%= search_form_for @q do |f| %>
424
- <%= f.label :last_name_cont %>
425
- <%= f.search_field :last_name_cont %>
426
-
427
- <%= f.label :department_title_cont %>
428
- <%= f.search_field :department_title_cont %>
429
-
430
- <%= f.label :employees_first_name_or_employees_last_name_cont %>
431
- <%= f.search_field :employees_first_name_or_employees_last_name_cont %>
432
-
433
- <%= f.submit "search" %>
434
- <% end %>
435
- ...
436
- <%= content_tag :table do %>
437
- <%= content_tag :th, sort_link(@q, :last_name) %>
438
- <%= content_tag :th, sort_link(@q, :department_title) %>
439
- <%= content_tag :th, sort_link(@q, :employees_last_name) %>
440
- <% end %>
441
- ```
15
+ Ransack will help you easily add **searching to your Rails application**, without any additional dependencies.
442
16
 
443
- If you have trouble sorting on associations, try using an SQL string with the
444
- pluralized table (`'departments.title'`,`'employees.last_name'`) instead of the
445
- symbolized association (`:department_title)`, `:employees_last_name`).
17
+ There are advanced searching solutions around, like ElasticSearch or Algolia. **Ransack** will do the job for many Rails websites, without the need to run additional infrastructure or work in a different language. With Ransack you do it all with standard Ruby and ERB.
446
18
 
447
- ### Ransack Aliases
19
+ Ready to move beyond the basics? Use **advanced features** like i18n and extensive configuration options.
448
20
 
449
- You can customize the attribute names for your Ransack searches by using a
450
- `ransack_alias`. This is particularly useful for long attribute names that are
451
- necessary when querying associations or multiple columns.
452
-
453
- ```ruby
454
- class Post < ActiveRecord::Base
455
- belongs_to :author
456
-
457
- # Abbreviate :author_first_name_or_author_last_name to :author
458
- ransack_alias :author, :author_first_name_or_author_last_name
459
- end
460
- ```
461
-
462
- Now, rather than using `:author_first_name_or_author_last_name_cont` in your
463
- form, you can simply use `:author_cont`. This serves to produce more expressive
464
- query parameters in your URLs.
465
-
466
- ```erb
467
- <%= search_form_for @q do |f| %>
468
- <%= f.label :author_cont %>
469
- <%= f.search_field :author_cont %>
470
- <% end %>
471
- ```
472
-
473
- You can also use `ransack_alias` for sorting.
474
-
475
- ```ruby
476
- class Post < ActiveRecord::Base
477
- belongs_to :author
478
-
479
- # Abbreviate :author_first_name to :author
480
- ransack_alias :author, :author_first_name
481
- end
482
- ```
483
-
484
- Now, you can use `:author` instead of `:author_first_name` in a `sort_link`.
485
-
486
- ```erb
487
- <%= sort_link(@q, :author) %>
488
- ```
489
-
490
- 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.
491
-
492
- ### Search Matchers
493
-
494
- List of all possible predicates
495
-
496
-
497
- | Predicate | Description | Notes |
498
- | ------------- | ------------- |-------- |
499
- | `*_eq` | equal | |
500
- | `*_not_eq` | not equal | |
501
- | `*_matches` | matches with `LIKE` | e.g. `q[email_matches]=%@gmail.com`|
502
- | `*_does_not_match` | does not match with `LIKE` | |
503
- | `*_matches_any` | Matches any | |
504
- | `*_matches_all` | Matches all | |
505
- | `*_does_not_match_any` | Does not match any | |
506
- | `*_does_not_match_all` | Does not match all | |
507
- | `*_lt` | less than | |
508
- | `*_lteq` | less than or equal | |
509
- | `*_gt` | greater than | |
510
- | `*_gteq` | greater than or equal | |
511
- | `*_present` | not null and not empty | Only compatible with string columns. Example: `q[name_present]=1` (SQL: `col is not null AND col != ''`) |
512
- | `*_blank` | is null or empty. | (SQL: `col is null OR col = ''`) |
513
- | `*_null` | is null | |
514
- | `*_not_null` | is not null | |
515
- | `*_in` | match any values in array | e.g. `q[name_in][]=Alice&q[name_in][]=Bob` |
516
- | `*_not_in` | match none of values in array | |
517
- | `*_lt_any` | Less than any | SQL: `col < value1 OR col < value2` |
518
- | `*_lteq_any` | Less than or equal to any | |
519
- | `*_gt_any` | Greater than any | |
520
- | `*_gteq_any` | Greater than or equal to any | |
521
- | `*_lt_all` | Less than all | SQL: `col < value1 AND col < value2` |
522
- | `*_lteq_all` | Less than or equal to all | |
523
- | `*_gt_all` | Greater than all | |
524
- | `*_gteq_all` | Greater than or equal to all | |
525
- | `*_not_eq_all` | none of values in a set | |
526
- | `*_start` | Starts with | SQL: `col LIKE 'value%'` |
527
- | `*_not_start` | Does not start with | |
528
- | `*_start_any` | Starts with any of | |
529
- | `*_start_all` | Starts with all of | |
530
- | `*_not_start_any` | Does not start with any of | |
531
- | `*_not_start_all` | Does not start with all of | |
532
- | `*_end` | Ends with | SQL: `col LIKE '%value'` |
533
- | `*_not_end` | Does not end with | |
534
- | `*_end_any` | Ends with any of | |
535
- | `*_end_all` | Ends with all of | |
536
- | `*_not_end_any` | | |
537
- | `*_not_end_all` | | |
538
- | `*_cont` | Contains value | uses `LIKE` |
539
- | `*_cont_any` | Contains any of | |
540
- | `*_cont_all` | Contains all of | |
541
- | `*_not_cont` | Does not contain |
542
- | `*_not_cont_any` | Does not contain any of | |
543
- | `*_not_cont_all` | Does not contain all of | |
544
- | `*_i_cont` | Contains value with case insensitive | uses `ILIKE` |
545
- | `*_i_cont_any` | Contains any of values with case insensitive | |
546
- | `*_i_cont_all` | Contains all of values with case insensitive | |
547
- | `*_not_i_cont` | Does not contain with case insensitive |
548
- | `*_not_i_cont_any` | Does not contain any of values with case insensitive | |
549
- | `*_not_i_cont_all` | Does not contain all of values with case insensitive | |
550
- | `*_true` | is true | |
551
- | `*_false` | is false | |
552
-
553
-
554
- (See full list: https://github.com/activerecord-hackery/ransack/blob/master/lib/ransack/locale/en.yml#L15 and [wiki](https://github.com/activerecord-hackery/ransack/wiki/Basic-Searching))
555
-
556
- ### Using Ransackers to add custom search functions via Arel
557
-
558
- The main premise behind Ransack is to provide access to
559
- **Arel predicate methods**. Ransack provides special methods, called
560
- _ransackers_, for creating additional search functions via Arel. More
561
- information about `ransacker` methods can be found [here in the wiki](https://github.com/activerecord-hackery/ransack/wiki/Using-Ransackers).
562
- Feel free to contribute working `ransacker` code examples to the wiki!
563
-
564
- ### Problem with DISTINCT selects
565
-
566
- If passed `distinct: true`, `result` will generate a `SELECT DISTINCT` to
567
- avoid returning duplicate rows, even if conditions on a join would otherwise
568
- result in some. It generates the same SQL as calling `uniq` on the relation.
569
-
570
- Please note that for many databases, a sort on an associated table's columns
571
- may result in invalid SQL with `distinct: true` -- in those cases, you
572
- will need to modify the result as needed to allow these queries to work.
573
-
574
- For example, you could call joins and includes on the result which has the
575
- effect of adding those tables columns to the select statement, overcoming
576
- the issue, like so:
577
-
578
- ```ruby
579
- def index
580
- @q = Person.ransack(params[:q])
581
- @people = @q.result(distinct: true)
582
- .includes(:articles)
583
- .joins(:articles)
584
- .page(params[:page])
585
- end
586
- ```
587
-
588
- If the above doesn't help, you can also use ActiveRecord's `select` query
589
- to explicitly add the columns you need, which brute force's adding the
590
- columns you need that your SQL engine is complaining about, you need to
591
- make sure you give all of the columns you care about, for example:
592
-
593
- ```ruby
594
- def index
595
- @q = Person.ransack(params[:q])
596
- @people = @q.result(distinct: true)
597
- .select('people.*, articles.name, articles.description')
598
- .page(params[:page])
599
- end
600
- ```
601
-
602
- Another method to approach this when using Postgresql is to use ActiveRecords's `.includes` in combination with `.group` instead of `distinct: true`.
603
-
604
- For example:
605
- ```ruby
606
- def index
607
- @q = Person.ransack(params[:q])
608
- @people = @q.result
609
- .group('persons.id')
610
- .includes(:articles)
611
- .page(params[:page])
612
- end
613
-
614
- ```
615
-
616
- A final way of last resort is to call `to_a.uniq` on the collection at the end
617
- with the caveat that the de-duping is taking place in Ruby instead of in SQL,
618
- which is potentially slower and uses more memory, and that it may display
619
- awkwardly with pagination if the number of results is greater than the page size.
620
-
621
- For example:
622
-
623
- ```ruby
624
- def index
625
- @q = Person.ransack(params[:q])
626
- @people = @q.result.includes(:articles).page(params[:page]).to_a.uniq
627
- end
628
- ```
629
-
630
- #### `PG::UndefinedFunction: ERROR: could not identify an equality operator for type json`
631
-
632
- If you get the above error while using `distinct: true` that means that
633
- one of the columns that Ransack is selecting is a `json` column.
634
- PostgreSQL does not provide comparison operators for the `json` type. While
635
- it is possible to work around this, in practice it's much better to convert those
636
- to `jsonb`, as [recommended by the PostgreSQL documentation](https://www.postgresql.org/docs/9.6/static/datatype-json.html).
637
-
638
- ### Authorization (whitelisting/blacklisting)
639
-
640
- By default, searching and sorting are authorized on any column of your model
641
- and no class methods/scopes are whitelisted.
642
-
643
- Ransack adds four methods to `ActiveRecord::Base` that you can redefine as
644
- class methods in your models to apply selective authorization:
645
- `ransackable_attributes`, `ransackable_associations`, `ransackable_scopes` and
646
- `ransortable_attributes`.
647
-
648
- Here is how these four methods are implemented in Ransack:
649
-
650
- ```ruby
651
- # `ransackable_attributes` by default returns all column names
652
- # and any defined ransackers as an array of strings.
653
- # For overriding with a whitelist array of strings.
654
- #
655
- def ransackable_attributes(auth_object = nil)
656
- column_names + _ransackers.keys
657
- end
658
-
659
- # `ransackable_associations` by default returns the names
660
- # of all associations as an array of strings.
661
- # For overriding with a whitelist array of strings.
662
- #
663
- def ransackable_associations(auth_object = nil)
664
- reflect_on_all_associations.map { |a| a.name.to_s }
665
- end
666
-
667
- # `ransortable_attributes` by default returns the names
668
- # of all attributes available for sorting as an array of strings.
669
- # For overriding with a whitelist array of strings.
670
- #
671
- def ransortable_attributes(auth_object = nil)
672
- ransackable_attributes(auth_object)
673
- end
674
-
675
- # `ransackable_scopes` by default returns an empty array
676
- # i.e. no class methods/scopes are authorized.
677
- # For overriding with a whitelist array of *symbols*.
678
- #
679
- def ransackable_scopes(auth_object = nil)
680
- []
681
- end
682
- ```
683
-
684
- Any values not returned from these methods will be ignored by Ransack, i.e.
685
- they are not authorized.
686
-
687
- All four methods can receive a single optional parameter, `auth_object`. When
688
- you call the search or ransack method on your model, you can provide a value
689
- for an `auth_object` key in the options hash which can be used by your own
690
- overridden methods.
691
-
692
- Here is an example that puts all this together, adapted from
693
- [this blog post by Ernie Miller](http://erniemiller.org/2012/05/11/why-your-ruby-class-macros-might-suck-mine-did/).
694
- In an `Article` model, add the following `ransackable_attributes` class method
695
- (preferably private):
696
-
697
- ```ruby
698
- class Article < ActiveRecord::Base
699
- def self.ransackable_attributes(auth_object = nil)
700
- if auth_object == :admin
701
- # whitelist all attributes for admin
702
- super
703
- else
704
- # whitelist only the title and body attributes for other users
705
- super & %w(title body)
706
- end
707
- end
708
-
709
- private_class_method :ransackable_attributes
710
- end
711
- ```
712
-
713
- Here is example code for the `articles_controller`:
714
-
715
- ```ruby
716
- class ArticlesController < ApplicationController
717
- def index
718
- @q = Article.ransack(params[:q], auth_object: set_ransack_auth_object)
719
- @articles = @q.result
720
- end
721
-
722
- private
723
-
724
- def set_ransack_auth_object
725
- current_user.admin? ? :admin : nil
726
- end
727
- end
728
- ```
729
-
730
- Trying it out in `rails console`:
731
-
732
- ```ruby
733
- > Article
734
- => Article(id: integer, person_id: integer, title: string, body: text)
735
-
736
- > Article.ransackable_attributes
737
- => ["title", "body"]
738
-
739
- > Article.ransackable_attributes(:admin)
740
- => ["id", "person_id", "title", "body"]
741
-
742
- > Article.ransack(id_eq: 1).result.to_sql
743
- => SELECT "articles".* FROM "articles" # Note that search param was ignored!
744
-
745
- > Article.ransack({ id_eq: 1 }, { auth_object: nil }).result.to_sql
746
- => SELECT "articles".* FROM "articles" # Search param still ignored!
747
-
748
- > Article.ransack({ id_eq: 1 }, { auth_object: :admin }).result.to_sql
749
- => SELECT "articles".* FROM "articles" WHERE "articles"."id" = 1
750
- ```
751
-
752
- That's it! Now you know how to whitelist/blacklist various elements in Ransack.
753
-
754
- ### Handling unknown predicates or attributes
755
-
756
- By default, Ransack will ignore any unknown predicates or attributes:
757
-
758
- ```ruby
759
- Article.ransack(unknown_attr_eq: 'Ernie').result.to_sql
760
- => SELECT "articles".* FROM "articles"
761
- ```
762
-
763
- Ransack may be configured to raise an error if passed an unknown predicate or
764
- attributes, by setting the `ignore_unknown_conditions` option to `false` in your
765
- Ransack initializer file at `config/initializers/ransack.rb`:
766
-
767
- ```ruby
768
- Ransack.configure do |c|
769
- # Raise errors if a query contains an unknown predicate or attribute.
770
- # Default is true (do not raise error on unknown conditions).
771
- c.ignore_unknown_conditions = false
772
- end
773
- ```
774
-
775
- ```ruby
776
- Article.ransack(unknown_attr_eq: 'Ernie')
777
- # ArgumentError (Invalid search term unknown_attr_eq)
778
- ```
779
-
780
- As an alternative to setting a global configuration option, the `.ransack!`
781
- class method also raises an error if passed an unknown condition:
782
-
783
- ```ruby
784
- Article.ransack!(unknown_attr_eq: 'Ernie')
785
- # ArgumentError: Invalid search term unknown_attr_eq
786
- ```
787
-
788
- This is equivalent to the `ignore_unknown_conditions` configuration option,
789
- except it may be applied on a case-by-case basis.
790
-
791
- ### Using Scopes/Class Methods
792
-
793
- Continuing on from the preceding section, searching by scopes requires defining
794
- a whitelist of `ransackable_scopes` on the model class. The whitelist should be
795
- an array of *symbols*. By default, all class methods (e.g. scopes) are ignored.
796
- Scopes will be applied for matching `true` values, or for given values if the
797
- scope accepts a value:
798
-
799
- ```ruby
800
- class Employee < ActiveRecord::Base
801
- scope :activated, ->(boolean = true) { where(active: boolean) }
802
- scope :salary_gt, ->(amount) { where('salary > ?', amount) }
803
-
804
- # Scopes are just syntactical sugar for class methods, which may also be used:
805
-
806
- def self.hired_since(date)
807
- where('start_date >= ?', date)
808
- end
809
-
810
- def self.ransackable_scopes(auth_object = nil)
811
- if auth_object.try(:admin?)
812
- # allow admin users access to all three methods
813
- %i(activated hired_since salary_gt)
814
- else
815
- # allow other users to search on `activated` and `hired_since` only
816
- %i(activated hired_since)
817
- end
818
- end
819
- end
820
-
821
- Employee.ransack({ activated: true, hired_since: '2013-01-01' })
822
-
823
- Employee.ransack({ salary_gt: 100_000 }, { auth_object: current_user })
824
- ```
825
-
826
- In Rails 3 and 4, if the `true` value is being passed via url params or some
827
- other mechanism that will convert it to a string, the true value may not be
828
- passed to the ransackable scope unless you wrap it in an array
829
- (i.e. `activated: ['true']`). Ransack will take care of changing 'true' into a
830
- boolean. This is currently resolved in Rails 5 :smiley:
831
-
832
- However, perhaps you have `user_id: [1]` and you do not want Ransack to convert
833
- 1 into a boolean. (Values sanitized to booleans can be found in the
834
- [constants.rb](https://github.com/activerecord-hackery/ransack/blob/master/lib/ransack/constants.rb#L28)).
835
- To turn this off globally, and handle type conversions yourself, set
836
- `sanitize_custom_scope_booleans` to false in an initializer file like
837
- config/initializers/ransack.rb:
838
-
839
- ```ruby
840
- Ransack.configure do |c|
841
- c.sanitize_custom_scope_booleans = false
842
- end
843
- ```
844
-
845
- To turn this off on a per-scope basis Ransack adds the following method to
846
- `ActiveRecord::Base` that you can redefine to selectively override sanitization:
847
-
848
- `ransackable_scopes_skip_sanitize_args`
849
-
850
- Add the scope you wish to bypass this behavior to ransackable_scopes_skip_sanitize_args:
851
-
852
- ```ruby
853
- def self.ransackable_scopes_skip_sanitize_args
854
- [:scope_to_skip_sanitize_args]
855
- end
856
- ```
857
-
858
- Scopes are a recent addition to Ransack and currently have a few caveats:
859
- First, a scope involving child associations needs to be defined in the parent
860
- table model, not in the child model. Second, scopes with an array as an
861
- argument are not easily usable yet, because the array currently needs to be
862
- wrapped in an array to function (see
863
- [this issue](https://github.com/activerecord-hackery/ransack/issues/404)),
864
- which is not compatible with Ransack form helpers. For this use case, it may be
865
- better for now to use [ransackers](https://github.com/activerecord-hackery/ransack/wiki/Using-Ransackers) instead,
866
- where feasible. Pull requests with solutions and tests are welcome!
867
-
868
- ### Grouping queries by OR instead of AND
869
-
870
- The default `AND` grouping can be changed to `OR` by adding `m: 'or'` to the
871
- query hash.
872
-
873
- You can easily try it in your controller code by changing `params[:q]` in the
874
- `index` action to `params[:q].try(:merge, m: 'or')` as follows:
875
-
876
- ```ruby
877
- def index
878
- @q = Artist.ransack(params[:q].try(:merge, m: 'or'))
879
- @artists = @q.result
880
- end
881
- ```
882
- Normally, if you wanted users to be able to toggle between `AND` and `OR`
883
- query grouping, you would probably set up your search form so that `m` was in
884
- the URL params hash, but here we assigned `m` manually just to try it out
885
- quickly.
886
-
887
- Alternatively, trying it in the Rails console:
888
-
889
- ```ruby
890
- artists = Artist.ransack(name_cont: 'foo', style_cont: 'bar', m: 'or')
891
- => Ransack::Search<class: Artist, base: Grouping <conditions: [
892
- Condition <attributes: ["name"], predicate: cont, values: ["foo"]>,
893
- Condition <attributes: ["style"], predicate: cont, values: ["bar"]>
894
- ], combinator: or>>
895
-
896
- artists.result.to_sql
897
- => "SELECT \"artists\".* FROM \"artists\"
898
- WHERE ((\"artists\".\"name\" ILIKE '%foo%'
899
- OR \"artists\".\"style\" ILIKE '%bar%'))"
900
- ```
901
-
902
- The combinator becomes `or` instead of the default `and`, and the SQL query
903
- becomes `WHERE...OR` instead of `WHERE...AND`.
904
-
905
- This works with associations as well. Imagine an Artist model that has many
906
- Memberships, and many Musicians through Memberships:
907
-
908
- ```ruby
909
- artists = Artist.ransack(name_cont: 'foo', musicians_email_cont: 'bar', m: 'or')
910
- => Ransack::Search<class: Artist, base: Grouping <conditions: [
911
- Condition <attributes: ["name"], predicate: cont, values: ["foo"]>,
912
- Condition <attributes: ["musicians_email"], predicate: cont, values: ["bar"]>
913
- ], combinator: or>>
914
-
915
- artists.result.to_sql
916
- => "SELECT \"artists\".* FROM \"artists\"
917
- LEFT OUTER JOIN \"memberships\"
918
- ON \"memberships\".\"artist_id\" = \"artists\".\"id\"
919
- LEFT OUTER JOIN \"musicians\"
920
- ON \"musicians\".\"id\" = \"memberships\".\"musician_id\"
921
- WHERE ((\"artists\".\"name\" ILIKE '%foo%'
922
- OR \"musicians\".\"email\" ILIKE '%bar%'))"
923
- ```
924
-
925
- ### Using SimpleForm
926
-
927
- If you would like to combine the Ransack and SimpleForm form builders, set the
928
- `RANSACK_FORM_BUILDER` environment variable before Rails boots up, e.g. in
929
- `config/application.rb` before `require 'rails/all'` as shown below (and add
930
- `gem 'simple_form'` in your Gemfile).
931
-
932
- ```ruby
933
- require File.expand_path('../boot', __FILE__)
934
- ENV['RANSACK_FORM_BUILDER'] = '::SimpleForm::FormBuilder'
935
- require 'rails/all'
936
- ```
937
-
938
- ### I18n
939
-
940
- Ransack translation files are available in
941
- [Ransack::Locale](lib/ransack/locale). You may also be interested in one of the
942
- many translations for Ransack available at
943
- http://www.localeapp.com/projects/2999.
944
-
945
- Predicate and attribute translations in forms may be specified as follows (see
946
- the translation files in [Ransack::Locale](lib/ransack/locale) for more examples):
21
+ Ransack is supported for Rails 7.0, 6.x on Ruby 2.6.6 and later.
947
22
 
948
- locales/en.yml:
949
- ```yml
950
- en:
951
- ransack:
952
- asc: ascending
953
- desc: descending
954
- predicates:
955
- cont: contains
956
- not_cont: not contains
957
- start: starts with
958
- end: ends with
959
- gt: greater than
960
- lt: less than
961
- models:
962
- person: Passanger
963
- attributes:
964
- person:
965
- name: Full Name
966
- article:
967
- title: Article Title
968
- body: Main Content
969
- ```
23
+ ## Installation
970
24
 
971
- Attribute names may also be changed globally, or under `activerecord`:
25
+ To install `ransack` and add it to your Gemfile, run
972
26
 
973
- ```yml
974
- en:
975
- attributes:
976
- model_name:
977
- model_field1: field name1
978
- model_field2: field name2
979
- activerecord:
980
- attributes:
981
- namespace/article:
982
- title: AR Namespaced Title
983
- namespace_article:
984
- title: Old Ransack Namespaced Title
27
+ ```jsx title='Gemfile'
28
+ gem 'ransack'
985
29
  ```
986
30
 
987
- ### Updating From MetaSearch
31
+ ### Bleeding edge
988
32
 
989
- Ransack works much like MetaSearch, for those of you who are familiar with
990
- it, and requires very little setup effort.
33
+ If you would like to use the latest updates not yet published to RubyGems, use the `main` branch:
991
34
 
992
- If you're coming from MetaSearch, things to note:
993
-
994
- 1. The default param key for search params is now `:q`, instead of `:search`.
995
- This is primarily to shorten query strings, though advanced queries (below)
996
- will still run afoul of URL length limits in most browsers and require a
997
- switch to HTTP POST requests. This key is
998
- [configurable](default-search-parameter) via setting the `search_key` option
999
- in your Ransack intitializer file.
1000
-
1001
- 2. `form_for` is now `search_form_for`, and validates that a Ransack::Search
1002
- object is passed to it.
1003
-
1004
- 3. Common ActiveRecord::Relation methods are no longer delegated by the
1005
- search object. Instead, you will get your search results (an
1006
- ActiveRecord::Relation in the case of the ActiveRecord adapter) via a call to
1007
- `Ransack#result`.
1008
-
1009
- ## Mongoid
1010
-
1011
- Mongoid support has been moved to its own gem at [ransack-mongoid](https://github.com/activerecord-hackery/ransack-mongoid).
1012
- Ransack works with Mongoid in the same way as Active Record, except that with
1013
- Mongoid, associations are not currently supported. Demo source code may be found
1014
- [here](https://github.com/Zhomart/ransack-mongodb-demo). A `result` method
1015
- called on a `ransack` search returns a `Mongoid::Criteria` object:
1016
-
1017
- ```ruby
1018
- @q = Person.ransack(params[:q])
1019
- @people = @q.result # => Mongoid::Criteria
1020
-
1021
- # or you can add more Mongoid queries
1022
- @people = @q.result.active.order_by(updated_at: -1).limit(10)
35
+ ```jsx title='Gemfile'
36
+ gem 'ransack', :github => 'activerecord-hackery/ransack', :branch => 'main'
1023
37
  ```
1024
38
 
1025
- NOTE: Ransack currently works with either Active Record or Mongoid, but not
1026
- both in the same application. If both are present, Ransack will default to
1027
- Active Record only. The logic is contained in
1028
- `Ransack::Adapters#instantiate_object_mapper` should you need to override it.
1029
-
1030
- ## Semantic Versioning
1031
-
1032
- Ransack attempts to follow semantic versioning in the format of `x.y.z`, where:
39
+ ### Documentation
1033
40
 
1034
- `x` stands for a major version (new features that are not backward-compatible).
41
+ There is [extensive documentation on Ransack](https://activerecord-hackery.github.io/ransack/), which is a [Docusaurus](https://docusaurus.io/) project and run as a GitHub Pages site.
1035
42
 
1036
- `y` stands for a minor version (new features that are backward-compatible).
43
+ ## Issues tracker
1037
44
 
1038
- `z` stands for a patch (bug fixes).
45
+ * Before filing an issue, please read the [Contributing Guide](https://github.com/activerecord-hackery/ransack/CONTRIBUTING.md).
46
+ * File an issue if a bug is caused by Ransack, is new (has not already been reported), and _can be reproduced from the information you provide_.
47
+ * Please consider adding a branch with a failing spec describing the problem.
48
+ * Contributions are welcome. :smiley:
49
+ * Please do not use the issue tracker for personal support requests. Stack Overflow is a better place for that where a wider community can help you!
1039
50
 
1040
- In other words: `Major.Minor.Patch`.
1041
51
 
1042
52
  ## Contributions
1043
53
 
1044
54
  To support the project:
1045
55
 
1046
- * Consider supporting via [Open Collective](https://opencollective.com/ransack/backers/badge.svg)
56
+ * Consider supporting us via [Open Collective](https://opencollective.com/ransack/backers/badge.svg)
1047
57
  * Use Ransack in your apps, and let us know if you encounter anything that's
1048
58
  broken or missing. A failing spec to demonstrate the issue is awesome. A pull
1049
59
  request with passing tests is even better!
1050
60
  * Before filing an issue or pull request, be sure to read and follow the
1051
- [Contributing Guide](CONTRIBUTING.md).
61
+ [Contributing Guide](https://github.com/activerecord-hackery/ransack/CONTRIBUTING.md).
1052
62
  * Please use Stack Overflow or other sites for questions or discussion not
1053
63
  directly related to bug reports, pull requests, or documentation improvements.
1054
64
  * Spread the word on Twitter, Facebook, and elsewhere if Ransack's been useful
@@ -1057,42 +67,38 @@ fix bugs!
1057
67
 
1058
68
  ## Contributors
1059
69
 
1060
- This project exists thanks to all the people who contribute. <img src="https://opencollective.com/ransack/contributors.svg?width=890&button=false" />
1061
-
1062
- Ransack is a rewrite of [MetaSearch](https://github.com/activerecord-hackery/meta_search)
1063
- created by [Ernie Miller](http://twitter.com/erniemiller)
1064
- and developed/maintained by:
70
+ Ransack was created by [Ernie Miller](http://twitter.com/erniemiller) and is developed and maintained by:
71
+ * [Sean Carroll](https://github.com/scarroll32)
72
+ * [Deivid Rodriguez](https://github.com/deivid-rodriguez)
73
+ * [Greg Molnar](https://github.com/gregmolnar)
74
+ * [A great group of contributors](https://github.com/activerecord-hackery/ransack/graphs/contributors).
75
+ - Ransack's logo is designed by [Anıl Kılıç](https://github.com/anilkilic).
1065
76
 
1066
- - [Greg Molnar](https://github.com/gregmolnar)
1067
- - [Deivid Rodriguez](https://github.com/deivid-rodriguez)
1068
- - [Sean Carroll](https://github.com/seanfcarroll)
77
+ Alumni Maintainers
1069
78
  - [Jon Atack](http://twitter.com/jonatack)
1070
79
  - [Ryan Bigg](http://twitter.com/ryanbigg)
1071
- - a great group of [contributors](https://github.com/activerecord-hackery/ransack/graphs/contributors).
1072
- - Ransack's logo is designed by [Anıl Kılıç](https://github.com/anilkilic).
1073
-
1074
- While it supports many of the same features as MetaSearch, its underlying implementation differs greatly from MetaSearch, and backwards compatibility is not a design goal.
1075
80
 
81
+ This project exists thanks to all the people who contribute. <img src="https://opencollective.com/ransack/contributors.svg?width=890&button=false" />
1076
82
 
1077
83
 
1078
84
  ## Backers
1079
85
 
1080
86
  Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/ransack#backer)]
1081
87
 
1082
- <a href="https://opencollective.com/ransack#backers" target="_blank"><img src="https://opencollective.com/ransack/backers.svg?width=890"></a>
88
+ <a href="https://opencollective.com/ransack#backers" target="_blank"><img src="https://opencollective.com/ransack/backers.svg?width=890" /></a>
1083
89
 
1084
90
 
1085
91
  ## Sponsors
1086
92
 
1087
93
  Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/ransack#sponsor)]
1088
94
 
1089
- <a href="https://opencollective.com/ransack/sponsor/0/website" target="_blank"><img src="https://opencollective.com/ransack/sponsor/0/avatar.svg"></a>
1090
- <a href="https://opencollective.com/ransack/sponsor/1/website" target="_blank"><img src="https://opencollective.com/ransack/sponsor/1/avatar.svg"></a>
1091
- <a href="https://opencollective.com/ransack/sponsor/2/website" target="_blank"><img src="https://opencollective.com/ransack/sponsor/2/avatar.svg"></a>
1092
- <a href="https://opencollective.com/ransack/sponsor/3/website" target="_blank"><img src="https://opencollective.com/ransack/sponsor/3/avatar.svg"></a>
1093
- <a href="https://opencollective.com/ransack/sponsor/4/website" target="_blank"><img src="https://opencollective.com/ransack/sponsor/4/avatar.svg"></a>
1094
- <a href="https://opencollective.com/ransack/sponsor/5/website" target="_blank"><img src="https://opencollective.com/ransack/sponsor/5/avatar.svg"></a>
1095
- <a href="https://opencollective.com/ransack/sponsor/6/website" target="_blank"><img src="https://opencollective.com/ransack/sponsor/6/avatar.svg"></a>
1096
- <a href="https://opencollective.com/ransack/sponsor/7/website" target="_blank"><img src="https://opencollective.com/ransack/sponsor/7/avatar.svg"></a>
1097
- <a href="https://opencollective.com/ransack/sponsor/8/website" target="_blank"><img src="https://opencollective.com/ransack/sponsor/8/avatar.svg"></a>
1098
- <a href="https://opencollective.com/ransack/sponsor/9/website" target="_blank"><img src="https://opencollective.com/ransack/sponsor/9/avatar.svg"></a>
95
+ <a href="https://opencollective.com/ransack/sponsor/0/website" target="_blank"><img src="https://opencollective.com/ransack/sponsor/0/avatar.svg" /></a>
96
+ <a href="https://opencollective.com/ransack/sponsor/1/website" target="_blank"><img src="https://opencollective.com/ransack/sponsor/1/avatar.svg" /></a>
97
+ <a href="https://opencollective.com/ransack/sponsor/2/website" target="_blank"><img src="https://opencollective.com/ransack/sponsor/2/avatar.svg" /></a>
98
+ <a href="https://opencollective.com/ransack/sponsor/3/website" target="_blank"><img src="https://opencollective.com/ransack/sponsor/3/avatar.svg" /></a>
99
+ <a href="https://opencollective.com/ransack/sponsor/4/website" target="_blank"><img src="https://opencollective.com/ransack/sponsor/4/avatar.svg"/ ></a>
100
+ <a href="https://opencollective.com/ransack/sponsor/5/website" target="_blank"><img src="https://opencollective.com/ransack/sponsor/5/avatar.svg" /></a>
101
+ <a href="https://opencollective.com/ransack/sponsor/6/website" target="_blank"><img src="https://opencollective.com/ransack/sponsor/6/avatar.svg" /></a>
102
+ <a href="https://opencollective.com/ransack/sponsor/7/website" target="_blank"><img src="https://opencollective.com/ransack/sponsor/7/avatar.svg" /></a>
103
+ <a href="https://opencollective.com/ransack/sponsor/8/website" target="_blank"><img src="https://opencollective.com/ransack/sponsor/8/avatar.svg" /></a>
104
+ <a href="https://opencollective.com/ransack/sponsor/9/website" target="_blank"><img src="https://opencollective.com/ransack/sponsor/9/avatar.svg" /></a>