ransack 2.4.2 → 3.2.0

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