ransack 1.7.0 → 2.4.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.github/FUNDING.yml +3 -0
- data/.github/SECURITY.md +12 -0
- data/.github/workflows/test.yml +120 -0
- data/.gitignore +3 -0
- data/CHANGELOG.md +463 -27
- data/CONTRIBUTING.md +52 -22
- data/Gemfile +24 -24
- data/README.md +453 -126
- data/Rakefile +6 -25
- data/lib/polyamorous/activerecord_5.2_ruby_2/join_association.rb +24 -0
- data/lib/polyamorous/activerecord_5.2_ruby_2/join_dependency.rb +79 -0
- data/lib/polyamorous/activerecord_5.2_ruby_2/reflection.rb +11 -0
- data/lib/polyamorous/activerecord_6.0_ruby_2/join_association.rb +1 -0
- data/lib/polyamorous/activerecord_6.0_ruby_2/join_dependency.rb +80 -0
- data/lib/polyamorous/activerecord_6.0_ruby_2/reflection.rb +1 -0
- data/lib/polyamorous/activerecord_6.1_ruby_2/join_association.rb +74 -0
- data/lib/polyamorous/activerecord_6.1_ruby_2/join_dependency.rb +93 -0
- data/lib/polyamorous/activerecord_6.1_ruby_2/reflection.rb +1 -0
- data/lib/polyamorous/join.rb +70 -0
- data/lib/polyamorous/polyamorous.rb +24 -0
- data/lib/polyamorous/swapping_reflection_class.rb +11 -0
- data/lib/polyamorous/tree_node.rb +7 -0
- data/lib/ransack/adapters/active_record/base.rb +27 -2
- data/lib/ransack/adapters/active_record/context.rb +213 -139
- data/lib/ransack/adapters/active_record/ransack/constants.rb +70 -55
- data/lib/ransack/adapters/active_record/ransack/context.rb +10 -18
- data/lib/ransack/adapters/active_record/ransack/nodes/condition.rb +42 -32
- data/lib/ransack/adapters/active_record/ransack/translate.rb +1 -5
- data/lib/ransack/adapters/active_record/ransack/visitor.rb +23 -0
- data/lib/ransack/adapters/active_record.rb +11 -10
- data/lib/ransack/adapters.rb +45 -23
- data/lib/ransack/configuration.rb +107 -4
- data/lib/ransack/constants.rb +13 -26
- data/lib/ransack/context.rb +45 -33
- data/lib/ransack/helpers/form_builder.rb +21 -12
- data/lib/ransack/helpers/form_helper.rb +75 -70
- data/lib/ransack/locale/ar.yml +70 -0
- data/lib/ransack/locale/az.yml +70 -0
- data/lib/ransack/locale/bg.yml +70 -0
- data/lib/ransack/locale/ca.yml +70 -0
- data/lib/ransack/locale/da.yml +70 -0
- data/lib/ransack/locale/el.yml +70 -0
- data/lib/ransack/locale/es.yml +22 -22
- data/lib/ransack/locale/fa.yml +70 -0
- data/lib/ransack/locale/fi.yml +71 -0
- data/lib/ransack/locale/id.yml +70 -0
- data/lib/ransack/locale/it.yml +70 -0
- data/lib/ransack/locale/ja.yml +70 -0
- data/lib/ransack/locale/nl.yml +4 -4
- data/lib/ransack/locale/pt-BR.yml +70 -0
- data/lib/ransack/locale/ru.yml +70 -0
- data/lib/ransack/locale/sk.yml +70 -0
- data/lib/ransack/locale/tr.yml +70 -0
- data/lib/ransack/locale/{zh.yml → zh-CN.yml} +13 -13
- data/lib/ransack/locale/zh-TW.yml +70 -0
- data/lib/ransack/nodes/attribute.rb +5 -2
- data/lib/ransack/nodes/bindable.rb +18 -6
- data/lib/ransack/nodes/condition.rb +85 -28
- data/lib/ransack/nodes/grouping.rb +17 -11
- data/lib/ransack/nodes/sort.rb +9 -5
- data/lib/ransack/nodes/value.rb +74 -68
- data/lib/ransack/nodes.rb +1 -1
- data/lib/ransack/predicate.rb +17 -20
- data/lib/ransack/search.rb +17 -8
- data/lib/ransack/translate.rb +115 -115
- data/lib/ransack/version.rb +1 -1
- data/lib/ransack/visitor.rb +1 -12
- data/lib/ransack.rb +9 -9
- data/logo/ransack-h.png +0 -0
- data/logo/ransack-h.svg +34 -0
- data/logo/ransack-v.png +0 -0
- data/logo/ransack-v.svg +34 -0
- data/logo/ransack.png +0 -0
- data/logo/ransack.svg +21 -0
- data/ransack.gemspec +7 -24
- data/spec/console.rb +4 -0
- data/spec/helpers/polyamorous_helper.rb +19 -0
- data/spec/polyamorous/join_association_spec.rb +35 -0
- data/spec/polyamorous/join_dependency_spec.rb +97 -0
- data/spec/polyamorous/join_spec.rb +19 -0
- data/spec/ransack/adapters/active_record/base_spec.rb +370 -75
- data/spec/ransack/adapters/active_record/context_spec.rb +72 -34
- data/spec/ransack/configuration_spec.rb +97 -14
- data/spec/ransack/helpers/form_builder_spec.rb +2 -11
- data/spec/ransack/helpers/form_helper_spec.rb +481 -113
- data/spec/ransack/nodes/condition_spec.rb +24 -0
- data/spec/ransack/nodes/grouping_spec.rb +56 -0
- data/spec/ransack/predicate_spec.rb +79 -5
- data/spec/ransack/search_spec.rb +207 -81
- data/spec/spec_helper.rb +8 -0
- data/spec/support/schema.rb +100 -42
- metadata +57 -184
- data/.travis.yml +0 -69
- data/lib/ransack/adapters/active_record/3.0/compat.rb +0 -179
- data/lib/ransack/adapters/active_record/3.0/context.rb +0 -201
- data/lib/ransack/adapters/active_record/3.1/context.rb +0 -215
- data/lib/ransack/adapters/active_record/3.2/context.rb +0 -44
- data/lib/ransack/adapters/active_record/compat.rb +0 -14
- data/lib/ransack/adapters/mongoid/3.2/.gitkeep +0 -0
- data/lib/ransack/adapters/mongoid/attributes/attribute.rb +0 -37
- data/lib/ransack/adapters/mongoid/attributes/order_predications.rb +0 -17
- data/lib/ransack/adapters/mongoid/attributes/predications.rb +0 -141
- data/lib/ransack/adapters/mongoid/base.rb +0 -130
- data/lib/ransack/adapters/mongoid/context.rb +0 -208
- data/lib/ransack/adapters/mongoid/inquiry_hash.rb +0 -23
- data/lib/ransack/adapters/mongoid/ransack/constants.rb +0 -88
- data/lib/ransack/adapters/mongoid/ransack/context.rb +0 -60
- data/lib/ransack/adapters/mongoid/ransack/nodes/condition.rb +0 -27
- data/lib/ransack/adapters/mongoid/ransack/translate.rb +0 -13
- data/lib/ransack/adapters/mongoid/ransack/visitor.rb +0 -24
- data/lib/ransack/adapters/mongoid/table.rb +0 -35
- data/lib/ransack/adapters/mongoid.rb +0 -13
- data/spec/mongoid/adapters/mongoid/base_spec.rb +0 -276
- data/spec/mongoid/adapters/mongoid/context_spec.rb +0 -56
- data/spec/mongoid/configuration_spec.rb +0 -102
- data/spec/mongoid/dependencies_spec.rb +0 -8
- data/spec/mongoid/helpers/ransack_helper.rb +0 -11
- data/spec/mongoid/nodes/condition_spec.rb +0 -34
- data/spec/mongoid/nodes/grouping_spec.rb +0 -13
- data/spec/mongoid/predicate_spec.rb +0 -155
- data/spec/mongoid/search_spec.rb +0 -446
- data/spec/mongoid/support/mongoid.yml +0 -6
- data/spec/mongoid/support/schema.rb +0 -128
- data/spec/mongoid/translate_spec.rb +0 -14
- data/spec/mongoid_spec_helper.rb +0 -59
- data/spec/ransack/dependencies_spec.rb +0 -12
data/README.md
CHANGED
@@ -1,47 +1,23 @@
|
|
1
|
-
# Ransack
|
2
|
-
|
3
|
-
[![Build Status](https://
|
4
|
-
(https://
|
5
|
-
[![
|
6
|
-
(
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
and maintained by [Ryan Bigg](http://twitter.com/ryanbigg),
|
14
|
-
[Jon Atack](http://twitter.com/jonatack) and a great group of [contributors]
|
15
|
-
(https://github.com/activerecord-hackery/ransack/graphs/contributors).
|
16
|
-
While it supports many of the same features as MetaSearch, its underlying
|
17
|
-
implementation differs greatly from MetaSearch,
|
18
|
-
and backwards compatibility is not a design goal.
|
19
|
-
|
20
|
-
Ransack enables the creation of both simple and
|
21
|
-
[advanced](http://ransack-demo.herokuapp.com/users/advanced_search)
|
22
|
-
search forms for your Ruby on Rails application (demo source code
|
23
|
-
[here](https://github.com/activerecord-hackery/ransack_demo)).
|
1
|
+
# ![Ransack](./logo/ransack-h.png "Ransack")
|
2
|
+
|
3
|
+
[![Build Status](https://github.com/activerecord-hackery/ransack/workflows/test/badge.svg)](https://github.com/activerecord-hackery/ransack/actions)
|
4
|
+
[![Gem Version](https://badge.fury.io/rb/ransack.svg)](http://badge.fury.io/rb/ransack)
|
5
|
+
[![Code Climate](https://codeclimate.com/github/activerecord-hackery/ransack/badges/gpa.svg)](https://codeclimate.com/github/activerecord-hackery/ransack)
|
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
|
+
|
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)).
|
24
13
|
If you're looking for something that simplifies query generation at the model
|
25
14
|
or controller layer, you're probably not looking for Ransack (or MetaSearch,
|
26
15
|
for that matter). Try [Squeel](https://github.com/activerecord-hackery/squeel)
|
27
16
|
instead.
|
28
17
|
|
29
|
-
If you're viewing this at
|
30
|
-
[github.com/activerecord-hackery/ransack](https://github.com/activerecord-hackery/ransack),
|
31
|
-
you're reading the documentation for the master branch with the latest features.
|
32
|
-
[View documentation for the last release (1.7.0).](https://github.com/activerecord-hackery/ransack/tree/v1.7.0)
|
33
|
-
|
34
18
|
## Getting started
|
35
19
|
|
36
|
-
Ransack is
|
37
|
-
recommended). JRuby 9 ought to work as well (see
|
38
|
-
[this](https://github.com/activerecord-hackery/polyamorous/issues/17)).
|
39
|
-
If you are using Ruby 1.8 or an earlier JRuby and run into compatibility
|
40
|
-
issues, you can use an earlier version of Ransack, say, up to 1.3.0.
|
41
|
-
|
42
|
-
Ransack works out-of-the-box with Active Record and also features limited
|
43
|
-
support for Mongoid 4.0 (without associations, further details
|
44
|
-
[below](https://github.com/activerecord-hackery/ransack#mongoid)).
|
20
|
+
Ransack is supported for Rails 6.1, 6.0, 5.2 on Ruby 2.6.6 and later.
|
45
21
|
|
46
22
|
In your Gemfile, for the last officially released gem:
|
47
23
|
|
@@ -49,7 +25,8 @@ In your Gemfile, for the last officially released gem:
|
|
49
25
|
gem 'ransack'
|
50
26
|
```
|
51
27
|
|
52
|
-
|
28
|
+
If you would like to use the latest updates (recommended), use the `master`
|
29
|
+
branch:
|
53
30
|
|
54
31
|
```ruby
|
55
32
|
gem 'ransack', github: 'activerecord-hackery/ransack'
|
@@ -60,49 +37,20 @@ gem 'ransack', github: 'activerecord-hackery/ransack'
|
|
60
37
|
* Before filing an issue, please read the [Contributing Guide](CONTRIBUTING.md).
|
61
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_.
|
62
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!
|
63
41
|
|
64
42
|
## Usage
|
65
43
|
|
66
|
-
Ransack can be used in one of two modes, simple or advanced.
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
This mode works much like MetaSearch, for those of you who are familiar with
|
71
|
-
it, and requires very little setup effort.
|
72
|
-
|
73
|
-
If you're coming from MetaSearch, things to note:
|
74
|
-
|
75
|
-
1. The default param key for search params is now `:q`, instead of `:search`.
|
76
|
-
This is primarily to shorten query strings, though advanced queries (below)
|
77
|
-
will still run afoul of URL length limits in most browsers and require a
|
78
|
-
switch to HTTP POST requests. This key is [configurable]
|
79
|
-
(https://github.com/activerecord-hackery/ransack/wiki/Configuration).
|
80
|
-
|
81
|
-
2. `form_for` is now `search_form_for`, and validates that a Ransack::Search
|
82
|
-
object is passed to it.
|
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.
|
83
47
|
|
84
|
-
|
85
|
-
|
86
|
-
ActiveRecord::Relation in the case of the ActiveRecord adapter) via a call to
|
87
|
-
`Ransack#result`.
|
88
|
-
|
89
|
-
4. If passed `distinct: true`, `result` will generate a `SELECT DISTINCT` to
|
90
|
-
avoid returning duplicate rows, even if conditions on a join would otherwise
|
91
|
-
result in some. It generates the same SQL as calling `uniq` on the relation.
|
48
|
+
If you're coming from MetaSearch (Ransack's predecessor), refer to the
|
49
|
+
[Updating From MetaSearch](#updating-from-metasearch) section
|
92
50
|
|
93
|
-
|
94
|
-
may result in invalid SQL with `distinct: true` -- in those cases, you're on
|
95
|
-
your own, and will need to modify the result as needed to allow these queries
|
96
|
-
to work.
|
97
|
-
|
98
|
-
If `distinct: true` or `uniq` is causing invalid SQL, another way to remove
|
99
|
-
duplicates is to call `to_a.uniq` on the collection at the end (see the next
|
100
|
-
section below) -- with the caveat that the de-duping is taking place in Ruby
|
101
|
-
instead of in SQL, which is potentially slower and uses more memory, and that
|
102
|
-
it may display awkwardly with pagination if the number of results is greater
|
103
|
-
than the page size.
|
51
|
+
### Simple Mode
|
104
52
|
|
105
|
-
####In your controller
|
53
|
+
#### In your controller
|
106
54
|
|
107
55
|
```ruby
|
108
56
|
def index
|
@@ -110,7 +58,7 @@ def index
|
|
110
58
|
@people = @q.result(distinct: true)
|
111
59
|
end
|
112
60
|
```
|
113
|
-
or without `distinct:true`, for sorting on an associated table's columns (in
|
61
|
+
or without `distinct: true`, for sorting on an associated table's columns (in
|
114
62
|
this example, with preloading each Person's Articles and pagination):
|
115
63
|
|
116
64
|
```ruby
|
@@ -123,13 +71,27 @@ def index
|
|
123
71
|
end
|
124
72
|
```
|
125
73
|
|
126
|
-
|
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
|
127
89
|
|
128
90
|
The two primary Ransack view helpers are `search_form_for` and `sort_link`,
|
129
91
|
which are defined in
|
130
92
|
[Ransack::Helpers::FormHelper](lib/ransack/helpers/form_helper.rb).
|
131
93
|
|
132
|
-
####Ransack's `search_form_for` helper replaces `form_for` for creating the view search form
|
94
|
+
#### Ransack's `search_form_for` helper replaces `form_for` for creating the view search form
|
133
95
|
|
134
96
|
```erb
|
135
97
|
<%= search_form_for @q do |f| %>
|
@@ -150,6 +112,11 @@ which are defined in
|
|
150
112
|
<% end %>
|
151
113
|
```
|
152
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
|
+
|
153
120
|
`cont` (contains) and `start` (starts with) are just two of the available
|
154
121
|
search predicates. See
|
155
122
|
[Constants](https://github.com/activerecord-hackery/ransack/blob/master/lib/ransack/constants.rb)
|
@@ -165,7 +132,7 @@ The `search_form_for` answer format can be set like this:
|
|
165
132
|
<%= search_form_for(@q, format: :json) do |f| %>
|
166
133
|
```
|
167
134
|
|
168
|
-
####Ransack's `sort_link` helper creates table headers that are sortable links
|
135
|
+
#### Ransack's `sort_link` helper creates table headers that are sortable links
|
169
136
|
|
170
137
|
```erb
|
171
138
|
<%= sort_link(@q, :name) %>
|
@@ -177,6 +144,14 @@ column title or a default sort order:
|
|
177
144
|
<%= sort_link(@q, :name, 'Last Name', default_order: :desc) %>
|
178
145
|
```
|
179
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
|
+
|
180
155
|
With a polymorphic association, you may need to specify the name of the link
|
181
156
|
explicitly to avoid an `uninitialized constant Model::Xxxable` error (see issue
|
182
157
|
[#421](https://github.com/activerecord-hackery/ransack/issues/421)):
|
@@ -206,13 +181,90 @@ This example toggles the sort directions of both fields, by default
|
|
206
181
|
initially sorting the `last_name` field by ascending order, and the
|
207
182
|
`first_name` field by descending order.
|
208
183
|
|
209
|
-
|
210
|
-
|
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:
|
211
232
|
|
212
233
|
```erb
|
213
234
|
<%= sort_link(@q, :name, hide_indicator: true) %>
|
214
235
|
```
|
215
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
|
+
|
216
268
|
### Advanced Mode
|
217
269
|
|
218
270
|
"Advanced" searches (ab)use Rails' nested attributes functionality in order to
|
@@ -250,11 +302,12 @@ end
|
|
250
302
|
|
251
303
|
Once you've done so, you can make use of the helpers in [Ransack::Helpers::FormBuilder](lib/ransack/helpers/form_builder.rb) to
|
252
304
|
construct much more complex search forms, such as the one on the
|
253
|
-
[demo
|
305
|
+
[demo app](http://ransack-demo.herokuapp.com/users/advanced_search)
|
306
|
+
(source code [here](https://github.com/activerecord-hackery/ransack_demo)).
|
254
307
|
|
255
308
|
### Ransack #search method
|
256
309
|
|
257
|
-
Ransack will try to
|
310
|
+
Ransack will try to make the class method `#search` available in your
|
258
311
|
models, but if `#search` has already been defined elsewhere, you can always use
|
259
312
|
the default `#ransack` class method. So the following are equivalent:
|
260
313
|
|
@@ -264,7 +317,7 @@ Article.search(params[:q])
|
|
264
317
|
```
|
265
318
|
|
266
319
|
Users have reported issues of `#search` name conflicts with other gems, so
|
267
|
-
the `#search` method alias
|
320
|
+
the `#search` method alias will be deprecated in the next major version of
|
268
321
|
Ransack (2.0). It's advisable to use the default `#ransack` instead.
|
269
322
|
|
270
323
|
For now, if Ransack's `#search` method conflicts with the name of another
|
@@ -335,25 +388,187 @@ end
|
|
335
388
|
...
|
336
389
|
<%= content_tag :table do %>
|
337
390
|
<%= content_tag :th, sort_link(@q, :last_name) %>
|
338
|
-
<%= content_tag :th, sort_link(@q,
|
339
|
-
<%= content_tag :th, sort_link(@q,
|
391
|
+
<%= content_tag :th, sort_link(@q, :department_title) %>
|
392
|
+
<%= content_tag :th, sort_link(@q, :employees_last_name) %>
|
340
393
|
<% end %>
|
341
394
|
```
|
342
395
|
|
343
|
-
|
344
|
-
(`'employees.last_name'`)
|
345
|
-
|
346
|
-
|
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.
|
405
|
+
|
406
|
+
```ruby
|
407
|
+
class Post < ActiveRecord::Base
|
408
|
+
belongs_to :author
|
409
|
+
|
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))
|
347
489
|
|
348
490
|
### Using Ransackers to add custom search functions via Arel
|
349
491
|
|
350
492
|
The main premise behind Ransack is to provide access to
|
351
493
|
**Arel predicate methods**. Ransack provides special methods, called
|
352
494
|
_ransackers_, for creating additional search functions via Arel. More
|
353
|
-
information about `ransacker` methods can be found [here in the wiki]
|
354
|
-
(https://github.com/activerecord-hackery/ransack/wiki/Using-Ransackers).
|
495
|
+
information about `ransacker` methods can be found [here in the wiki](https://github.com/activerecord-hackery/ransack/wiki/Using-Ransackers).
|
355
496
|
Feel free to contribute working `ransacker` code examples to the wiki!
|
356
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
|
+
|
357
572
|
### Authorization (whitelisting/blacklisting)
|
358
573
|
|
359
574
|
By default, searching and sorting are authorized on any column of your model
|
@@ -409,16 +624,12 @@ for an `auth_object` key in the options hash which can be used by your own
|
|
409
624
|
overridden methods.
|
410
625
|
|
411
626
|
Here is an example that puts all this together, adapted from
|
412
|
-
[this blog post by Ernie Miller]
|
413
|
-
(http://erniemiller.org/2012/05/11/why-your-ruby-class-macros-might-suck-mine-did/).
|
627
|
+
[this blog post by Ernie Miller](http://erniemiller.org/2012/05/11/why-your-ruby-class-macros-might-suck-mine-did/).
|
414
628
|
In an `Article` model, add the following `ransackable_attributes` class method
|
415
629
|
(preferably private):
|
416
630
|
|
417
631
|
```ruby
|
418
632
|
class Article < ActiveRecord::Base
|
419
|
-
|
420
|
-
private
|
421
|
-
|
422
633
|
def self.ransackable_attributes(auth_object = nil)
|
423
634
|
if auth_object == :admin
|
424
635
|
# whitelist all attributes for admin
|
@@ -428,6 +639,8 @@ class Article < ActiveRecord::Base
|
|
428
639
|
super & %w(title body)
|
429
640
|
end
|
430
641
|
end
|
642
|
+
|
643
|
+
private_class_method :ransackable_attributes
|
431
644
|
end
|
432
645
|
```
|
433
646
|
|
@@ -435,7 +648,6 @@ Here is example code for the `articles_controller`:
|
|
435
648
|
|
436
649
|
```ruby
|
437
650
|
class ArticlesController < ApplicationController
|
438
|
-
|
439
651
|
def index
|
440
652
|
@q = Article.ransack(params[:q], auth_object: set_ransack_auth_object)
|
441
653
|
@articles = @q.result
|
@@ -473,6 +685,43 @@ Trying it out in `rails console`:
|
|
473
685
|
|
474
686
|
That's it! Now you know how to whitelist/blacklist various elements in Ransack.
|
475
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
|
+
|
476
725
|
### Using Scopes/Class Methods
|
477
726
|
|
478
727
|
Continuing on from the preceding section, searching by scopes requires defining
|
@@ -483,7 +732,7 @@ scope accepts a value:
|
|
483
732
|
|
484
733
|
```ruby
|
485
734
|
class Employee < ActiveRecord::Base
|
486
|
-
scope :
|
735
|
+
scope :activated, ->(boolean = true) { where(active: boolean) }
|
487
736
|
scope :salary_gt, ->(amount) { where('salary > ?', amount) }
|
488
737
|
|
489
738
|
# Scopes are just syntactical sugar for class methods, which may also be used:
|
@@ -492,29 +741,53 @@ class Employee < ActiveRecord::Base
|
|
492
741
|
where('start_date >= ?', date)
|
493
742
|
end
|
494
743
|
|
495
|
-
private
|
496
|
-
|
497
744
|
def self.ransackable_scopes(auth_object = nil)
|
498
745
|
if auth_object.try(:admin?)
|
499
746
|
# allow admin users access to all three methods
|
500
|
-
%i(
|
747
|
+
%i(activated hired_since salary_gt)
|
501
748
|
else
|
502
|
-
# allow other users to search on
|
503
|
-
%i(
|
749
|
+
# allow other users to search on `activated` and `hired_since` only
|
750
|
+
%i(activated hired_since)
|
504
751
|
end
|
505
752
|
end
|
506
753
|
end
|
507
754
|
|
508
|
-
Employee.ransack({
|
755
|
+
Employee.ransack({ activated: true, hired_since: '2013-01-01' })
|
509
756
|
|
510
757
|
Employee.ransack({ salary_gt: 100_000 }, { auth_object: current_user })
|
511
758
|
```
|
512
759
|
|
513
|
-
|
514
|
-
that will convert it to a string
|
515
|
-
|
516
|
-
|
517
|
-
|
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
|
+
```
|
518
791
|
|
519
792
|
Scopes are a recent addition to Ransack and currently have a few caveats:
|
520
793
|
First, a scope involving child associations needs to be defined in the parent
|
@@ -523,8 +796,7 @@ argument are not easily usable yet, because the array currently needs to be
|
|
523
796
|
wrapped in an array to function (see
|
524
797
|
[this issue](https://github.com/activerecord-hackery/ransack/issues/404)),
|
525
798
|
which is not compatible with Ransack form helpers. For this use case, it may be
|
526
|
-
better for now to use [ransackers]
|
527
|
-
(https://github.com/activerecord-hackery/ransack/wiki/Using-Ransackers) instead,
|
799
|
+
better for now to use [ransackers](https://github.com/activerecord-hackery/ransack/wiki/Using-Ransackers) instead,
|
528
800
|
where feasible. Pull requests with solutions and tests are welcome!
|
529
801
|
|
530
802
|
### Grouping queries by OR instead of AND
|
@@ -646,11 +918,33 @@ en:
|
|
646
918
|
title: Old Ransack Namespaced Title
|
647
919
|
```
|
648
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
|
+
|
649
943
|
## Mongoid
|
650
944
|
|
651
|
-
|
652
|
-
with Mongoid
|
653
|
-
|
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
|
654
948
|
[here](https://github.com/Zhomart/ransack-mongodb-demo). A `result` method
|
655
949
|
called on a `ransack` search returns a `Mongoid::Criteria` object:
|
656
950
|
|
@@ -662,16 +956,10 @@ called on a `ransack` search returns a `Mongoid::Criteria` object:
|
|
662
956
|
@people = @q.result.active.order_by(updated_at: -1).limit(10)
|
663
957
|
```
|
664
958
|
|
665
|
-
|
959
|
+
NOTE: Ransack currently works with either Active Record or Mongoid, but not
|
666
960
|
both in the same application. If both are present, Ransack will default to
|
667
|
-
Active Record only.
|
668
|
-
|
669
|
-
```ruby
|
670
|
-
@current_adapters ||= {
|
671
|
-
:active_record => defined?(::ActiveRecord::Base),
|
672
|
-
:mongoid => defined?(::Mongoid) && !defined?(::ActiveRecord::Base)
|
673
|
-
}
|
674
|
-
```
|
961
|
+
Active Record only. The logic is contained in
|
962
|
+
`Ransack::Adapters#instantiate_object_mapper` should you need to override it.
|
675
963
|
|
676
964
|
## Semantic Versioning
|
677
965
|
|
@@ -689,6 +977,7 @@ In other words: `Major.Minor.Patch`.
|
|
689
977
|
|
690
978
|
To support the project:
|
691
979
|
|
980
|
+
* Consider supporting via [Open Collective](https://opencollective.com/ransack/backers/badge.svg)
|
692
981
|
* Use Ransack in your apps, and let us know if you encounter anything that's
|
693
982
|
broken or missing. A failing spec to demonstrate the issue is awesome. A pull
|
694
983
|
request with passing tests is even better!
|
@@ -700,6 +989,44 @@ directly related to bug reports, pull requests, or documentation improvements.
|
|
700
989
|
to you. The more people who are using the project, the quicker we can find and
|
701
990
|
fix bugs!
|
702
991
|
|
703
|
-
##
|
992
|
+
## Contributors
|
993
|
+
|
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:
|
999
|
+
|
1000
|
+
- [Greg Molnar](https://github.com/gregmolnar)
|
1001
|
+
- [Deivid Rodriguez](https://github.com/deivid-rodriguez)
|
1002
|
+
- [Sean Carroll](https://github.com/seanfcarroll)
|
1003
|
+
- [Jon Atack](http://twitter.com/jonatack)
|
1004
|
+
- [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
|
+
|
1010
|
+
|
1011
|
+
|
1012
|
+
## Backers
|
1013
|
+
|
1014
|
+
Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/ransack#backer)]
|
1015
|
+
|
1016
|
+
<a href="https://opencollective.com/ransack#backers" target="_blank"><img src="https://opencollective.com/ransack/backers.svg?width=890"></a>
|
1017
|
+
|
1018
|
+
|
1019
|
+
## Sponsors
|
1020
|
+
|
1021
|
+
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)]
|
704
1022
|
|
705
|
-
|
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>
|