ransack 1.7.0 → 2.4.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (127) hide show
  1. checksums.yaml +5 -5
  2. data/.github/FUNDING.yml +3 -0
  3. data/.github/SECURITY.md +12 -0
  4. data/.github/workflows/test.yml +120 -0
  5. data/.gitignore +3 -0
  6. data/CHANGELOG.md +463 -27
  7. data/CONTRIBUTING.md +52 -22
  8. data/Gemfile +24 -24
  9. data/README.md +453 -126
  10. data/Rakefile +6 -25
  11. data/lib/polyamorous/activerecord_5.2_ruby_2/join_association.rb +24 -0
  12. data/lib/polyamorous/activerecord_5.2_ruby_2/join_dependency.rb +79 -0
  13. data/lib/polyamorous/activerecord_5.2_ruby_2/reflection.rb +11 -0
  14. data/lib/polyamorous/activerecord_6.0_ruby_2/join_association.rb +1 -0
  15. data/lib/polyamorous/activerecord_6.0_ruby_2/join_dependency.rb +80 -0
  16. data/lib/polyamorous/activerecord_6.0_ruby_2/reflection.rb +1 -0
  17. data/lib/polyamorous/activerecord_6.1_ruby_2/join_association.rb +74 -0
  18. data/lib/polyamorous/activerecord_6.1_ruby_2/join_dependency.rb +93 -0
  19. data/lib/polyamorous/activerecord_6.1_ruby_2/reflection.rb +1 -0
  20. data/lib/polyamorous/join.rb +70 -0
  21. data/lib/polyamorous/polyamorous.rb +24 -0
  22. data/lib/polyamorous/swapping_reflection_class.rb +11 -0
  23. data/lib/polyamorous/tree_node.rb +7 -0
  24. data/lib/ransack/adapters/active_record/base.rb +27 -2
  25. data/lib/ransack/adapters/active_record/context.rb +213 -139
  26. data/lib/ransack/adapters/active_record/ransack/constants.rb +70 -55
  27. data/lib/ransack/adapters/active_record/ransack/context.rb +10 -18
  28. data/lib/ransack/adapters/active_record/ransack/nodes/condition.rb +42 -32
  29. data/lib/ransack/adapters/active_record/ransack/translate.rb +1 -5
  30. data/lib/ransack/adapters/active_record/ransack/visitor.rb +23 -0
  31. data/lib/ransack/adapters/active_record.rb +11 -10
  32. data/lib/ransack/adapters.rb +45 -23
  33. data/lib/ransack/configuration.rb +107 -4
  34. data/lib/ransack/constants.rb +13 -26
  35. data/lib/ransack/context.rb +45 -33
  36. data/lib/ransack/helpers/form_builder.rb +21 -12
  37. data/lib/ransack/helpers/form_helper.rb +75 -70
  38. data/lib/ransack/locale/ar.yml +70 -0
  39. data/lib/ransack/locale/az.yml +70 -0
  40. data/lib/ransack/locale/bg.yml +70 -0
  41. data/lib/ransack/locale/ca.yml +70 -0
  42. data/lib/ransack/locale/da.yml +70 -0
  43. data/lib/ransack/locale/el.yml +70 -0
  44. data/lib/ransack/locale/es.yml +22 -22
  45. data/lib/ransack/locale/fa.yml +70 -0
  46. data/lib/ransack/locale/fi.yml +71 -0
  47. data/lib/ransack/locale/id.yml +70 -0
  48. data/lib/ransack/locale/it.yml +70 -0
  49. data/lib/ransack/locale/ja.yml +70 -0
  50. data/lib/ransack/locale/nl.yml +4 -4
  51. data/lib/ransack/locale/pt-BR.yml +70 -0
  52. data/lib/ransack/locale/ru.yml +70 -0
  53. data/lib/ransack/locale/sk.yml +70 -0
  54. data/lib/ransack/locale/tr.yml +70 -0
  55. data/lib/ransack/locale/{zh.yml → zh-CN.yml} +13 -13
  56. data/lib/ransack/locale/zh-TW.yml +70 -0
  57. data/lib/ransack/nodes/attribute.rb +5 -2
  58. data/lib/ransack/nodes/bindable.rb +18 -6
  59. data/lib/ransack/nodes/condition.rb +85 -28
  60. data/lib/ransack/nodes/grouping.rb +17 -11
  61. data/lib/ransack/nodes/sort.rb +9 -5
  62. data/lib/ransack/nodes/value.rb +74 -68
  63. data/lib/ransack/nodes.rb +1 -1
  64. data/lib/ransack/predicate.rb +17 -20
  65. data/lib/ransack/search.rb +17 -8
  66. data/lib/ransack/translate.rb +115 -115
  67. data/lib/ransack/version.rb +1 -1
  68. data/lib/ransack/visitor.rb +1 -12
  69. data/lib/ransack.rb +9 -9
  70. data/logo/ransack-h.png +0 -0
  71. data/logo/ransack-h.svg +34 -0
  72. data/logo/ransack-v.png +0 -0
  73. data/logo/ransack-v.svg +34 -0
  74. data/logo/ransack.png +0 -0
  75. data/logo/ransack.svg +21 -0
  76. data/ransack.gemspec +7 -24
  77. data/spec/console.rb +4 -0
  78. data/spec/helpers/polyamorous_helper.rb +19 -0
  79. data/spec/polyamorous/join_association_spec.rb +35 -0
  80. data/spec/polyamorous/join_dependency_spec.rb +97 -0
  81. data/spec/polyamorous/join_spec.rb +19 -0
  82. data/spec/ransack/adapters/active_record/base_spec.rb +370 -75
  83. data/spec/ransack/adapters/active_record/context_spec.rb +72 -34
  84. data/spec/ransack/configuration_spec.rb +97 -14
  85. data/spec/ransack/helpers/form_builder_spec.rb +2 -11
  86. data/spec/ransack/helpers/form_helper_spec.rb +481 -113
  87. data/spec/ransack/nodes/condition_spec.rb +24 -0
  88. data/spec/ransack/nodes/grouping_spec.rb +56 -0
  89. data/spec/ransack/predicate_spec.rb +79 -5
  90. data/spec/ransack/search_spec.rb +207 -81
  91. data/spec/spec_helper.rb +8 -0
  92. data/spec/support/schema.rb +100 -42
  93. metadata +57 -184
  94. data/.travis.yml +0 -69
  95. data/lib/ransack/adapters/active_record/3.0/compat.rb +0 -179
  96. data/lib/ransack/adapters/active_record/3.0/context.rb +0 -201
  97. data/lib/ransack/adapters/active_record/3.1/context.rb +0 -215
  98. data/lib/ransack/adapters/active_record/3.2/context.rb +0 -44
  99. data/lib/ransack/adapters/active_record/compat.rb +0 -14
  100. data/lib/ransack/adapters/mongoid/3.2/.gitkeep +0 -0
  101. data/lib/ransack/adapters/mongoid/attributes/attribute.rb +0 -37
  102. data/lib/ransack/adapters/mongoid/attributes/order_predications.rb +0 -17
  103. data/lib/ransack/adapters/mongoid/attributes/predications.rb +0 -141
  104. data/lib/ransack/adapters/mongoid/base.rb +0 -130
  105. data/lib/ransack/adapters/mongoid/context.rb +0 -208
  106. data/lib/ransack/adapters/mongoid/inquiry_hash.rb +0 -23
  107. data/lib/ransack/adapters/mongoid/ransack/constants.rb +0 -88
  108. data/lib/ransack/adapters/mongoid/ransack/context.rb +0 -60
  109. data/lib/ransack/adapters/mongoid/ransack/nodes/condition.rb +0 -27
  110. data/lib/ransack/adapters/mongoid/ransack/translate.rb +0 -13
  111. data/lib/ransack/adapters/mongoid/ransack/visitor.rb +0 -24
  112. data/lib/ransack/adapters/mongoid/table.rb +0 -35
  113. data/lib/ransack/adapters/mongoid.rb +0 -13
  114. data/spec/mongoid/adapters/mongoid/base_spec.rb +0 -276
  115. data/spec/mongoid/adapters/mongoid/context_spec.rb +0 -56
  116. data/spec/mongoid/configuration_spec.rb +0 -102
  117. data/spec/mongoid/dependencies_spec.rb +0 -8
  118. data/spec/mongoid/helpers/ransack_helper.rb +0 -11
  119. data/spec/mongoid/nodes/condition_spec.rb +0 -34
  120. data/spec/mongoid/nodes/grouping_spec.rb +0 -13
  121. data/spec/mongoid/predicate_spec.rb +0 -155
  122. data/spec/mongoid/search_spec.rb +0 -446
  123. data/spec/mongoid/support/mongoid.yml +0 -6
  124. data/spec/mongoid/support/schema.rb +0 -128
  125. data/spec/mongoid/translate_spec.rb +0 -14
  126. data/spec/mongoid_spec_helper.rb +0 -59
  127. data/spec/ransack/dependencies_spec.rb +0 -12
data/README.md CHANGED
@@ -1,47 +1,23 @@
1
- # Ransack
2
-
3
- [![Build Status](https://travis-ci.org/activerecord-hackery/ransack.svg)]
4
- (https://travis-ci.org/activerecord-hackery/ransack)
5
- [![Gem Version](https://badge.fury.io/rb/ransack.svg)]
6
- (http://badge.fury.io/rb/ransack)
7
- [![Code Climate](https://codeclimate.com/github/activerecord-hackery/ransack/badges/gpa.svg)]
8
- (https://codeclimate.com/github/activerecord-hackery/ransack)
9
-
10
- Ransack is a rewrite of [MetaSearch]
11
- (https://github.com/activerecord-hackery/meta_search)
12
- created by [Ernie Miller](http://twitter.com/erniemiller)
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 compatible with Rails 3 and 4 on Ruby 1.9 and later (Ruby 2.2
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
- Or, if you would like to use the latest updates, use the `master` branch:
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
- ### Simple Mode
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
- 3. Common ActiveRecord::Relation methods are no longer delegated by the
85
- search object. Instead, you will get your search results (an
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
- Please note that for many databases, a sort on an associated table's columns
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
- ####In your view
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
- The sort link may be displayed without the order indicator arrow by passing
210
- `hide_indicator: true`:
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 page](http://ransack-demo.heroku.com) (source code [here](https://github.com/activerecord-hackery/ransack_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 to make the class method `#search` available in your
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 might be deprecated in the next major version of
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, 'departments.title') %>
339
- <%= content_tag :th, sort_link(@q, 'employees.last_name') %>
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
- Please note that in a sort link, the association is expressed as an SQL string
344
- (`'employees.last_name'`) with a pluralized table name, instead of the symbol
345
- `:employee_last_name` syntax with a class#underscore table name used for
346
- Ransack objects elsewhere.
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 :active, ->(boolean = true) { where(active: boolean) }
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(active hired_since salary_gt)
747
+ %i(activated hired_since salary_gt)
501
748
  else
502
- # allow other users to search on active and hired_since only
503
- %i(active hired_since)
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({ active: true, hired_since: '2013-01-01' })
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
- If the `true` value is being passed via url params or by some other mechanism
514
- that will convert it to a string (i.e. `active: 'true'` instead of
515
- `active: true`), the true value will *not* be passed to the scope. If you want
516
- to pass a `'true'` string to the scope, you should wrap it in an array (i.e.
517
- `active: ['true']`).
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
- Ransack now works with Mongoid in the same way as Active Record, except that
652
- with Mongoid, associations are not currently supported. A demo app may be found
653
- [here](http://ransack-mongodb-demo.herokuapp.com/) and the demo source code is
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
- _NOTE: Ransack currently works with either Active Record or Mongoid, but not
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. Here is the code containing the logic:_
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
- ## Copyright
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
- Copyright &copy; 2011-2015 [Ernie Miller](http://twitter.com/erniemiller)
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>