ransack 1.7.0 → 2.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (118) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +2 -0
  3. data/.travis.yml +16 -48
  4. data/CHANGELOG.md +409 -26
  5. data/CONTRIBUTING.md +48 -20
  6. data/Gemfile +9 -13
  7. data/README.md +352 -92
  8. data/Rakefile +6 -25
  9. data/lib/polyamorous/activerecord_5.0_ruby_2/join_association.rb +2 -0
  10. data/lib/polyamorous/activerecord_5.0_ruby_2/join_dependency.rb +2 -0
  11. data/lib/polyamorous/activerecord_5.1_ruby_2/join_association.rb +32 -0
  12. data/lib/polyamorous/activerecord_5.1_ruby_2/join_dependency.rb +112 -0
  13. data/lib/polyamorous/activerecord_5.2.0_ruby_2/join_association.rb +32 -0
  14. data/lib/polyamorous/activerecord_5.2.0_ruby_2/join_dependency.rb +113 -0
  15. data/lib/polyamorous/activerecord_5.2.1_ruby_2/join_association.rb +31 -0
  16. data/lib/polyamorous/activerecord_5.2.1_ruby_2/join_dependency.rb +57 -0
  17. data/lib/polyamorous/join.rb +70 -0
  18. data/lib/polyamorous/swapping_reflection_class.rb +11 -0
  19. data/lib/polyamorous/tree_node.rb +7 -0
  20. data/lib/polyamorous.rb +25 -0
  21. data/lib/ransack/adapters/active_record/base.rb +23 -2
  22. data/lib/ransack/adapters/active_record/context.rb +210 -135
  23. data/lib/ransack/adapters/active_record/ransack/constants.rb +53 -53
  24. data/lib/ransack/adapters/active_record/ransack/context.rb +11 -15
  25. data/lib/ransack/adapters/active_record/ransack/nodes/condition.rb +33 -30
  26. data/lib/ransack/adapters/active_record/ransack/translate.rb +1 -5
  27. data/lib/ransack/adapters/active_record/ransack/visitor.rb +23 -0
  28. data/lib/ransack/adapters/active_record.rb +11 -10
  29. data/lib/ransack/adapters.rb +45 -23
  30. data/lib/ransack/configuration.rb +91 -4
  31. data/lib/ransack/constants.rb +14 -26
  32. data/lib/ransack/context.rb +29 -18
  33. data/lib/ransack/helpers/form_builder.rb +27 -12
  34. data/lib/ransack/helpers/form_helper.rb +75 -70
  35. data/lib/ransack/locale/ar.yml +70 -0
  36. data/lib/ransack/locale/az.yml +70 -0
  37. data/lib/ransack/locale/bg.yml +70 -0
  38. data/lib/ransack/locale/da.yml +70 -0
  39. data/lib/ransack/locale/el.yml +70 -0
  40. data/lib/ransack/locale/id.yml +70 -0
  41. data/lib/ransack/locale/it.yml +70 -0
  42. data/lib/ransack/locale/ja.yml +70 -0
  43. data/lib/ransack/locale/nl.yml +4 -4
  44. data/lib/ransack/locale/pt-BR.yml +70 -0
  45. data/lib/ransack/locale/ru.yml +70 -0
  46. data/lib/ransack/locale/tr.yml +70 -0
  47. data/lib/ransack/locale/{zh.yml → zh-CN.yml} +13 -13
  48. data/lib/ransack/locale/zh-TW.yml +70 -0
  49. data/lib/ransack/nodes/attribute.rb +5 -2
  50. data/lib/ransack/nodes/bindable.rb +18 -6
  51. data/lib/ransack/nodes/condition.rb +77 -28
  52. data/lib/ransack/nodes/grouping.rb +16 -10
  53. data/lib/ransack/nodes/sort.rb +9 -5
  54. data/lib/ransack/nodes/value.rb +74 -68
  55. data/lib/ransack/nodes.rb +1 -1
  56. data/lib/ransack/predicate.rb +15 -19
  57. data/lib/ransack/search.rb +14 -7
  58. data/lib/ransack/translate.rb +3 -3
  59. data/lib/ransack/version.rb +1 -1
  60. data/lib/ransack/visitor.rb +1 -12
  61. data/lib/ransack.rb +7 -7
  62. data/logo/ransack-h.png +0 -0
  63. data/logo/ransack-h.svg +34 -0
  64. data/logo/ransack-v.png +0 -0
  65. data/logo/ransack-v.svg +34 -0
  66. data/logo/ransack.png +0 -0
  67. data/logo/ransack.svg +21 -0
  68. data/ransack.gemspec +9 -10
  69. data/spec/console.rb +4 -0
  70. data/spec/helpers/polyamorous_helper.rb +24 -0
  71. data/spec/ransack/adapters/active_record/base_spec.rb +365 -74
  72. data/spec/ransack/adapters/active_record/context_spec.rb +14 -19
  73. data/spec/ransack/configuration_spec.rb +87 -14
  74. data/spec/ransack/helpers/form_builder_spec.rb +2 -11
  75. data/spec/ransack/helpers/form_helper_spec.rb +481 -113
  76. data/spec/ransack/join_association_spec.rb +28 -0
  77. data/spec/ransack/join_dependency_spec.rb +86 -0
  78. data/spec/ransack/join_spec.rb +19 -0
  79. data/spec/ransack/nodes/condition_spec.rb +24 -0
  80. data/spec/ransack/nodes/grouping_spec.rb +56 -0
  81. data/spec/ransack/predicate_spec.rb +27 -5
  82. data/spec/ransack/search_spec.rb +84 -70
  83. data/spec/spec_helper.rb +4 -0
  84. data/spec/support/schema.rb +86 -41
  85. metadata +60 -81
  86. data/lib/ransack/adapters/active_record/3.0/compat.rb +0 -179
  87. data/lib/ransack/adapters/active_record/3.0/context.rb +0 -201
  88. data/lib/ransack/adapters/active_record/3.1/context.rb +0 -215
  89. data/lib/ransack/adapters/active_record/3.2/context.rb +0 -44
  90. data/lib/ransack/adapters/active_record/compat.rb +0 -14
  91. data/lib/ransack/adapters/mongoid/3.2/.gitkeep +0 -0
  92. data/lib/ransack/adapters/mongoid/attributes/attribute.rb +0 -37
  93. data/lib/ransack/adapters/mongoid/attributes/order_predications.rb +0 -17
  94. data/lib/ransack/adapters/mongoid/attributes/predications.rb +0 -141
  95. data/lib/ransack/adapters/mongoid/base.rb +0 -130
  96. data/lib/ransack/adapters/mongoid/context.rb +0 -208
  97. data/lib/ransack/adapters/mongoid/inquiry_hash.rb +0 -23
  98. data/lib/ransack/adapters/mongoid/ransack/constants.rb +0 -88
  99. data/lib/ransack/adapters/mongoid/ransack/context.rb +0 -60
  100. data/lib/ransack/adapters/mongoid/ransack/nodes/condition.rb +0 -27
  101. data/lib/ransack/adapters/mongoid/ransack/translate.rb +0 -13
  102. data/lib/ransack/adapters/mongoid/ransack/visitor.rb +0 -24
  103. data/lib/ransack/adapters/mongoid/table.rb +0 -35
  104. data/lib/ransack/adapters/mongoid.rb +0 -13
  105. data/spec/mongoid/adapters/mongoid/base_spec.rb +0 -276
  106. data/spec/mongoid/adapters/mongoid/context_spec.rb +0 -56
  107. data/spec/mongoid/configuration_spec.rb +0 -102
  108. data/spec/mongoid/dependencies_spec.rb +0 -8
  109. data/spec/mongoid/helpers/ransack_helper.rb +0 -11
  110. data/spec/mongoid/nodes/condition_spec.rb +0 -34
  111. data/spec/mongoid/nodes/grouping_spec.rb +0 -13
  112. data/spec/mongoid/predicate_spec.rb +0 -155
  113. data/spec/mongoid/search_spec.rb +0 -446
  114. data/spec/mongoid/support/mongoid.yml +0 -6
  115. data/spec/mongoid/support/schema.rb +0 -128
  116. data/spec/mongoid/translate_spec.rb +0 -14
  117. data/spec/mongoid_spec_helper.rb +0 -59
  118. data/spec/ransack/dependencies_spec.rb +0 -12
data/README.md CHANGED
@@ -1,26 +1,25 @@
1
- # Ransack
1
+ # ![Ransack](./logo/ransack-h.png "Ransack")
2
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)
3
+ [![Build Status](https://travis-ci.org/activerecord-hackery/ransack.svg)](https://travis-ci.org/activerecord-hackery/ransack)
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)
9
7
 
10
- Ransack is a rewrite of [MetaSearch]
11
- (https://github.com/activerecord-hackery/meta_search)
8
+ Ransack is a rewrite of [MetaSearch](https://github.com/activerecord-hackery/meta_search)
12
9
  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).
10
+ and developed/maintained for years by
11
+ [Jon Atack](http://twitter.com/jonatack) and
12
+ [Ryan Bigg](http://twitter.com/ryanbigg) with the help of a great group of
13
+ [contributors](https://github.com/activerecord-hackery/ransack/graphs/contributors). Ransack's logo is designed by [Anıl Kılıç](https://github.com/anilkilic).
16
14
  While it supports many of the same features as MetaSearch, its underlying
17
15
  implementation differs greatly from MetaSearch,
18
16
  and backwards compatibility is not a design goal.
19
17
 
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)).
18
+ Ransack enables the creation of both
19
+ [simple](http://ransack-demo.herokuapp.com) and
20
+ [advanced](http://ransack-demo.herokuapp.com/users/advanced_search) search forms
21
+ for your Ruby on Rails application
22
+ ([demo source code here](https://github.com/activerecord-hackery/ransack_demo)).
24
23
  If you're looking for something that simplifies query generation at the model
25
24
  or controller layer, you're probably not looking for Ransack (or MetaSearch,
26
25
  for that matter). Try [Squeel](https://github.com/activerecord-hackery/squeel)
@@ -29,18 +28,17 @@ instead.
29
28
  If you're viewing this at
30
29
  [github.com/activerecord-hackery/ransack](https://github.com/activerecord-hackery/ransack),
31
30
  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)
31
+ [View documentation for the last release (2.0.0).](https://github.com/activerecord-hackery/ransack/tree/v2.0.0)
33
32
 
34
33
  ## Getting started
35
34
 
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)).
35
+ Ransack is compatible with Rails 5.0, 5.1 and 5.2 on Ruby 2.2 and later.
36
+ If you are using Rails <5.0 use the 1.8 line of Ransack.
39
37
  If you are using Ruby 1.8 or an earlier JRuby and run into compatibility
40
38
  issues, you can use an earlier version of Ransack, say, up to 1.3.0.
41
39
 
42
40
  Ransack works out-of-the-box with Active Record and also features limited
43
- support for Mongoid 4.0 (without associations, further details
41
+ support for Mongoid 4 and 5 (without associations, further details
44
42
  [below](https://github.com/activerecord-hackery/ransack#mongoid)).
45
43
 
46
44
  In your Gemfile, for the last officially released gem:
@@ -49,7 +47,8 @@ In your Gemfile, for the last officially released gem:
49
47
  gem 'ransack'
50
48
  ```
51
49
 
52
- Or, if you would like to use the latest updates, use the `master` branch:
50
+ If you would like to use the latest updates (recommended), use the `master`
51
+ branch:
53
52
 
54
53
  ```ruby
55
54
  gem 'ransack', github: 'activerecord-hackery/ransack'
@@ -60,6 +59,7 @@ gem 'ransack', github: 'activerecord-hackery/ransack'
60
59
  * Before filing an issue, please read the [Contributing Guide](CONTRIBUTING.md).
61
60
  * 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
61
  * Contributions are welcome, but please do not add "+1" comments to issues or pull requests :smiley:
62
+ * 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
63
 
64
64
  ## Usage
65
65
 
@@ -75,8 +75,7 @@ If you're coming from MetaSearch, things to note:
75
75
  1. The default param key for search params is now `:q`, instead of `:search`.
76
76
  This is primarily to shorten query strings, though advanced queries (below)
77
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).
78
+ switch to HTTP POST requests. This key is [configurable](https://github.com/activerecord-hackery/ransack/wiki/Configuration).
80
79
 
81
80
  2. `form_for` is now `search_form_for`, and validates that a Ransack::Search
82
81
  object is passed to it.
@@ -86,23 +85,7 @@ If you're coming from MetaSearch, things to note:
86
85
  ActiveRecord::Relation in the case of the ActiveRecord adapter) via a call to
87
86
  `Ransack#result`.
88
87
 
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.
92
-
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.
104
-
105
- ####In your controller
88
+ #### In your controller
106
89
 
107
90
  ```ruby
108
91
  def index
@@ -110,7 +93,7 @@ def index
110
93
  @people = @q.result(distinct: true)
111
94
  end
112
95
  ```
113
- or without `distinct:true`, for sorting on an associated table's columns (in
96
+ or without `distinct: true`, for sorting on an associated table's columns (in
114
97
  this example, with preloading each Person's Articles and pagination):
115
98
 
116
99
  ```ruby
@@ -123,13 +106,13 @@ def index
123
106
  end
124
107
  ```
125
108
 
126
- ####In your view
109
+ #### In your view
127
110
 
128
111
  The two primary Ransack view helpers are `search_form_for` and `sort_link`,
129
112
  which are defined in
130
113
  [Ransack::Helpers::FormHelper](lib/ransack/helpers/form_helper.rb).
131
114
 
132
- ####Ransack's `search_form_for` helper replaces `form_for` for creating the view search form
115
+ #### Ransack's `search_form_for` helper replaces `form_for` for creating the view search form
133
116
 
134
117
  ```erb
135
118
  <%= search_form_for @q do |f| %>
@@ -150,6 +133,11 @@ which are defined in
150
133
  <% end %>
151
134
  ```
152
135
 
136
+ The argument of `f.search_field` has to be in this form:
137
+ `attribute_name[_or_attribute_name]..._predicate`
138
+
139
+ where `[_or_another_attribute_name]...` means any repetition of `_or_` plus the name of the attribute.
140
+
153
141
  `cont` (contains) and `start` (starts with) are just two of the available
154
142
  search predicates. See
155
143
  [Constants](https://github.com/activerecord-hackery/ransack/blob/master/lib/ransack/constants.rb)
@@ -165,7 +153,7 @@ The `search_form_for` answer format can be set like this:
165
153
  <%= search_form_for(@q, format: :json) do |f| %>
166
154
  ```
167
155
 
168
- ####Ransack's `sort_link` helper creates table headers that are sortable links
156
+ #### Ransack's `sort_link` helper creates table headers that are sortable links
169
157
 
170
158
  ```erb
171
159
  <%= sort_link(@q, :name) %>
@@ -177,6 +165,14 @@ column title or a default sort order:
177
165
  <%= sort_link(@q, :name, 'Last Name', default_order: :desc) %>
178
166
  ```
179
167
 
168
+ You can use a block if the link markup is hard to fit into the label parameter:
169
+
170
+ ```erb
171
+ <%= sort_link(@q, :name) do %>
172
+ <strong>Player Name</strong>
173
+ <% end %>
174
+ ```
175
+
180
176
  With a polymorphic association, you may need to specify the name of the link
181
177
  explicitly to avoid an `uninitialized constant Model::Xxxable` error (see issue
182
178
  [#421](https://github.com/activerecord-hackery/ransack/issues/421)):
@@ -206,13 +202,76 @@ This example toggles the sort directions of both fields, by default
206
202
  initially sorting the `last_name` field by ascending order, and the
207
203
  `first_name` field by descending order.
208
204
 
209
- The sort link may be displayed without the order indicator arrow by passing
210
- `hide_indicator: true`:
205
+ In the case that you wish to sort by some complex value, such as the result
206
+ of a SQL function, you may do so using scopes. In your model, define scopes
207
+ whose names line up with the name of the virtual field you wish to sort by,
208
+ as so:
209
+
210
+ ```ruby
211
+ class Person < ActiveRecord::Base
212
+ scope :sort_by_reverse_name_asc, lambda { order("REVERSE(name) ASC") }
213
+ scope :sort_by_reverse_name_desc, lambda { order("REVERSE(name) DESC") }
214
+ ...
215
+ ```
216
+
217
+ and you can then sort by this virtual field:
218
+
219
+ ```erb
220
+ <%= sort_link(@q, :reverse_name) %>
221
+ ```
222
+
223
+ The sort link order indicator arrows may be globally customized by setting a
224
+ `custom_arrows` option in an initializer file like
225
+ `config/initializers/ransack.rb`.
226
+
227
+ You can also enable a `default_arrow` which is displayed on all sortable fields
228
+ which are not currently used in the sorting. This is disabled by default so
229
+ nothing will be displayed:
230
+
231
+ ```ruby
232
+ Ransack.configure do |c|
233
+ c.custom_arrows = {
234
+ up_arrow: '<i class="custom-up-arrow-icon"></i>',
235
+ down_arrow: 'U+02193',
236
+ default_arrow: '<i class="default-arrow-icon"></i>'
237
+ }
238
+ end
239
+ ```
240
+
241
+ All sort links may be displayed without the order indicator
242
+ arrows by setting `hide_sort_order_indicators` to true in the initializer file.
243
+ Note that this hides the arrows even if they were customized:
244
+
245
+ ```ruby
246
+ Ransack.configure do |c|
247
+ c.hide_sort_order_indicators = true
248
+ end
249
+ ```
250
+
251
+ Without setting it globally, individual sort links may be displayed without
252
+ the order indicator arrow by passing `hide_indicator: true` in the sort link:
211
253
 
212
254
  ```erb
213
255
  <%= sort_link(@q, :name, hide_indicator: true) %>
214
256
  ```
215
257
 
258
+ #### Ransack's `sort_url` helper is like a `sort_link` but returns only the url
259
+
260
+ `sort_url` has the same API as `sort_link`:
261
+
262
+ ```erb
263
+ <%= sort_url(@q, :name, default_order: :desc) %>
264
+ ```
265
+
266
+ ```erb
267
+ <%= sort_url(@q, :last_name, [:last_name, 'first_name asc']) %>
268
+ ```
269
+
270
+ ```erb
271
+ <%= sort_url(@q, :last_name, %i(last_name first_name),
272
+ default_order: { last_name: 'asc', first_name: 'desc' }) %>
273
+ ```
274
+
216
275
  ### Advanced Mode
217
276
 
218
277
  "Advanced" searches (ab)use Rails' nested attributes functionality in order to
@@ -250,11 +309,12 @@ end
250
309
 
251
310
  Once you've done so, you can make use of the helpers in [Ransack::Helpers::FormBuilder](lib/ransack/helpers/form_builder.rb) to
252
311
  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)).
312
+ [demo app](http://ransack-demo.herokuapp.com/users/advanced_search)
313
+ (source code [here](https://github.com/activerecord-hackery/ransack_demo)).
254
314
 
255
315
  ### Ransack #search method
256
316
 
257
- Ransack will try to to make the class method `#search` available in your
317
+ Ransack will try to make the class method `#search` available in your
258
318
  models, but if `#search` has already been defined elsewhere, you can always use
259
319
  the default `#ransack` class method. So the following are equivalent:
260
320
 
@@ -264,7 +324,7 @@ Article.search(params[:q])
264
324
  ```
265
325
 
266
326
  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
327
+ the `#search` method alias will be deprecated in the next major version of
268
328
  Ransack (2.0). It's advisable to use the default `#ransack` instead.
269
329
 
270
330
  For now, if Ransack's `#search` method conflicts with the name of another
@@ -335,25 +395,184 @@ end
335
395
  ...
336
396
  <%= content_tag :table do %>
337
397
  <%= 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') %>
398
+ <%= content_tag :th, sort_link(@q, :department_title) %>
399
+ <%= content_tag :th, sort_link(@q, :employees_last_name) %>
400
+ <% end %>
401
+ ```
402
+
403
+ If you have trouble sorting on associations, try using an SQL string with the
404
+ pluralized table (`'departments.title'`,`'employees.last_name'`) instead of the
405
+ symbolized association (`:department_title)`, `:employees_last_name`).
406
+
407
+ ### Ransack Aliases
408
+
409
+ You can customize the attribute names for your Ransack searches by using a
410
+ `ransack_alias`. This is particularly useful for long attribute names that are
411
+ necessary when querying associations or multiple columns.
412
+
413
+ ```ruby
414
+ class Post < ActiveRecord::Base
415
+ belongs_to :author
416
+
417
+ # Abbreviate :author_first_name_or_author_last_name to :author
418
+ ransack_alias :author, :author_first_name_or_author_last_name
419
+ end
420
+ ```
421
+
422
+ Now, rather than using `:author_first_name_or_author_last_name_cont` in your
423
+ form, you can simply use `:author_cont`. This serves to produce more expressive
424
+ query parameters in your URLs.
425
+
426
+ ```erb
427
+ <%= search_form_for @q do |f| %>
428
+ <%= f.label :author_cont %>
429
+ <%= f.search_field :author_cont %>
340
430
  <% end %>
341
431
  ```
342
432
 
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.
433
+ ### Search Matchers
434
+
435
+ List of all possible predicates
436
+
437
+
438
+ | Predicate | Description | Notes |
439
+ | ------------- | ------------- |-------- |
440
+ | `*_eq` | equal | |
441
+ | `*_not_eq` | not equal | |
442
+ | `*_matches` | matches with `LIKE` | e.g. `q[email_matches]=%@gmail.com`|
443
+ | `*_does_not_match` | does not match with `LIKE` | |
444
+ | `*_matches_any` | Matches any | |
445
+ | `*_matches_all` | Matches all | |
446
+ | `*_does_not_match_any` | Does not match any | |
447
+ | `*_does_not_match_all` | Does not match all | |
448
+ | `*_lt` | less than | |
449
+ | `*_lteq` | less than or equal | |
450
+ | `*_gt` | greater than | |
451
+ | `*_gteq` | greater than or equal | |
452
+ | `*_present` | not null and not empty | Only compatible with string columns. Example: `q[name_present]=1` (SQL: `col is not null AND col != ''`) |
453
+ | `*_blank` | is null or empty. | (SQL: `col is null OR col = ''`) |
454
+ | `*_null` | is null | |
455
+ | `*_not_null` | is not null | |
456
+ | `*_in` | match any values in array | e.g. `q[name_in][]=Alice&q[name_in][]=Bob` |
457
+ | `*_not_in` | match none of values in array | |
458
+ | `*_lt_any` | Less than any | SQL: `col < value1 OR col < value2` |
459
+ | `*_lteq_any` | Less than or equal to any | |
460
+ | `*_gt_any` | Greater than any | |
461
+ | `*_gteq_any` | Greater than or equal to any | |
462
+ | `*_matches_any` | `*_does_not_match_any` | same as above but with `LIKE` |
463
+ | `*_lt_all` | Less than all | SQL: `col < value1 AND col < value2` |
464
+ | `*_lteq_all` | Less than or equal to all | |
465
+ | `*_gt_all` | Greater than all | |
466
+ | `*_gteq_all` | Greater than or equal to all | |
467
+ | `*_matches_all` | Matches all | same as above but with `LIKE` |
468
+ | `*_does_not_match_all` | Does not match all | |
469
+ | `*_not_eq_all` | none of values in a set | |
470
+ | `*_start` | Starts with | SQL: `col LIKE 'value%'` |
471
+ | `*_not_start` | Does not start with | |
472
+ | `*_start_any` | Starts with any of | |
473
+ | `*_start_all` | Starts with all of | |
474
+ | `*_not_start_any` | Does not start with any of | |
475
+ | `*_not_start_all` | Does not start with all of | |
476
+ | `*_end` | Ends with | SQL: `col LIKE '%value'` |
477
+ | `*_not_end` | Does not end with | |
478
+ | `*_end_any` | Ends with any of | |
479
+ | `*_end_all` | Ends with all of | |
480
+ | `*_not_end_any` | | |
481
+ | `*_not_end_all` | | |
482
+ | `*_cont` | Contains value | uses `LIKE` |
483
+ | `*_cont_any` | Contains any of | |
484
+ | `*_cont_all` | Contains all of | |
485
+ | `*_not_cont` | Does not contain |
486
+ | `*_not_cont_any` | Does not contain any of | |
487
+ | `*_not_cont_all` | Does not contain all of | |
488
+ | `*_true` | is true | |
489
+ | `*_false` | is false | |
490
+
491
+
492
+ (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
493
 
348
494
  ### Using Ransackers to add custom search functions via Arel
349
495
 
350
496
  The main premise behind Ransack is to provide access to
351
497
  **Arel predicate methods**. Ransack provides special methods, called
352
498
  _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).
499
+ information about `ransacker` methods can be found [here in the wiki](https://github.com/activerecord-hackery/ransack/wiki/Using-Ransackers).
355
500
  Feel free to contribute working `ransacker` code examples to the wiki!
356
501
 
502
+ ### Problem with DISTINCT selects
503
+
504
+ If passed `distinct: true`, `result` will generate a `SELECT DISTINCT` to
505
+ avoid returning duplicate rows, even if conditions on a join would otherwise
506
+ result in some. It generates the same SQL as calling `uniq` on the relation.
507
+
508
+ Please note that for many databases, a sort on an associated table's columns
509
+ may result in invalid SQL with `distinct: true` -- in those cases, you
510
+ will need to modify the result as needed to allow these queries to work.
511
+
512
+ For example, you could call joins and includes on the result which has the
513
+ effect of adding those tables columns to the select statement, overcoming
514
+ the issue, like so:
515
+
516
+ ```ruby
517
+ def index
518
+ @q = Person.ransack(params[:q])
519
+ @people = @q.result(distinct: true)
520
+ .includes(:articles)
521
+ .joins(:articles)
522
+ .page(params[:page])
523
+ end
524
+ ```
525
+
526
+ If the above doesn't help, you can also use ActiveRecord's `select` query
527
+ to explicitly add the columns you need, which brute force's adding the
528
+ columns you need that your SQL engine is complaining about, you need to
529
+ make sure you give all of the columns you care about, for example:
530
+
531
+ ```ruby
532
+ def index
533
+ @q = Person.ransack(params[:q])
534
+ @people = @q.result(distinct: true)
535
+ .select('people.*, articles.name, articles.description')
536
+ .page(params[:page])
537
+ end
538
+ ```
539
+
540
+ Another method to approach this when using Postgresql is to use ActiveRecords's `.includes` in combination with `.group` instead of `distinct: true`.
541
+
542
+ For example:
543
+ ```ruby
544
+ def index
545
+ @q = Person.ransack(params[:q])
546
+ @people = @q.result
547
+ .group('persons.id')
548
+ .includes(:articles)
549
+ .page(params[:page])
550
+ end
551
+
552
+ ```
553
+
554
+ A final way of last resort is to call `to_a.uniq` on the collection at the end
555
+ with the caveat that the de-duping is taking place in Ruby instead of in SQL,
556
+ which is potentially slower and uses more memory, and that it may display
557
+ awkwardly with pagination if the number of results is greater than the page size.
558
+
559
+ For example:
560
+
561
+ ```ruby
562
+ def index
563
+ @q = Person.ransack(params[:q])
564
+ @people = @q.result.includes(:articles).page(params[:page]).to_a.uniq
565
+ end
566
+ ```
567
+
568
+ #### `PG::UndefinedFunction: ERROR: could not identify an equality operator for type json`
569
+
570
+ If you get the above error while using `distinct: true` that means that
571
+ one of the columns that Ransack is selecting is a `json` column.
572
+ PostgreSQL does not provide comparison operators for the `json` type. While
573
+ it is possible to work around this, in practice it's much better to convert those
574
+ to `jsonb`, as [recommended by the PostgreSQL documentation](https://www.postgresql.org/docs/9.6/static/datatype-json.html).
575
+
357
576
  ### Authorization (whitelisting/blacklisting)
358
577
 
359
578
  By default, searching and sorting are authorized on any column of your model
@@ -409,16 +628,12 @@ for an `auth_object` key in the options hash which can be used by your own
409
628
  overridden methods.
410
629
 
411
630
  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/).
631
+ [this blog post by Ernie Miller](http://erniemiller.org/2012/05/11/why-your-ruby-class-macros-might-suck-mine-did/).
414
632
  In an `Article` model, add the following `ransackable_attributes` class method
415
633
  (preferably private):
416
634
 
417
635
  ```ruby
418
636
  class Article < ActiveRecord::Base
419
-
420
- private
421
-
422
637
  def self.ransackable_attributes(auth_object = nil)
423
638
  if auth_object == :admin
424
639
  # whitelist all attributes for admin
@@ -428,6 +643,8 @@ class Article < ActiveRecord::Base
428
643
  super & %w(title body)
429
644
  end
430
645
  end
646
+
647
+ private_class_method :ransackable_attributes
431
648
  end
432
649
  ```
433
650
 
@@ -435,7 +652,6 @@ Here is example code for the `articles_controller`:
435
652
 
436
653
  ```ruby
437
654
  class ArticlesController < ApplicationController
438
-
439
655
  def index
440
656
  @q = Article.ransack(params[:q], auth_object: set_ransack_auth_object)
441
657
  @articles = @q.result
@@ -483,7 +699,7 @@ scope accepts a value:
483
699
 
484
700
  ```ruby
485
701
  class Employee < ActiveRecord::Base
486
- scope :active, ->(boolean = true) { where(active: boolean) }
702
+ scope :activated, ->(boolean = true) { where(active: boolean) }
487
703
  scope :salary_gt, ->(amount) { where('salary > ?', amount) }
488
704
 
489
705
  # Scopes are just syntactical sugar for class methods, which may also be used:
@@ -492,29 +708,55 @@ class Employee < ActiveRecord::Base
492
708
  where('start_date >= ?', date)
493
709
  end
494
710
 
495
- private
496
-
497
711
  def self.ransackable_scopes(auth_object = nil)
498
712
  if auth_object.try(:admin?)
499
713
  # allow admin users access to all three methods
500
- %i(active hired_since salary_gt)
714
+ %i(activated hired_since salary_gt)
501
715
  else
502
- # allow other users to search on active and hired_since only
503
- %i(active hired_since)
716
+ # allow other users to search on `activated` and `hired_since` only
717
+ %i(activated hired_since)
504
718
  end
505
719
  end
720
+
721
+ private_class_method :ransackable_scopes
506
722
  end
507
723
 
508
- Employee.ransack({ active: true, hired_since: '2013-01-01' })
724
+ Employee.ransack({ activated: true, hired_since: '2013-01-01' })
509
725
 
510
726
  Employee.ransack({ salary_gt: 100_000 }, { auth_object: current_user })
511
727
  ```
512
728
 
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']`).
729
+ In Rails 3 and 4, if the `true` value is being passed via url params or some
730
+ other mechanism that will convert it to a string, the true value may not be
731
+ passed to the ransackable scope unless you wrap it in an array
732
+ (i.e. `activated: ['true']`). Ransack will take care of changing 'true' into a
733
+ boolean. This is currently resolved in Rails 5 :smiley:
734
+
735
+ However, perhaps you have `user_id: [1]` and you do not want Ransack to convert
736
+ 1 into a boolean. (Values sanitized to booleans can be found in the
737
+ [constants.rb](https://github.com/activerecord-hackery/ransack/blob/master/lib/ransack/constants.rb#L28)).
738
+ To turn this off globally, and handle type conversions yourself, set
739
+ `sanitize_custom_scope_booleans` to false in an initializer file like
740
+ config/initializers/ransack.rb:
741
+
742
+ ```ruby
743
+ Ransack.configure do |c|
744
+ c.sanitize_custom_scope_booleans = false
745
+ end
746
+ ```
747
+
748
+ To turn this off on a per-scope basis Ransack adds the following method to
749
+ `ActiveRecord::Base` that you can redefine to selectively override sanitization:
750
+
751
+ `ransackable_scopes_skip_sanitize_args`
752
+
753
+ Add the scope you wish to bypass this behavior to ransackable_scopes_skip_sanitize_args:
754
+
755
+ ```ruby
756
+ def ransackable_scopes_skip_sanitize_args
757
+ [:scope_to_skip_sanitize_args]
758
+ end
759
+ ```
518
760
 
519
761
  Scopes are a recent addition to Ransack and currently have a few caveats:
520
762
  First, a scope involving child associations needs to be defined in the parent
@@ -523,8 +765,7 @@ argument are not easily usable yet, because the array currently needs to be
523
765
  wrapped in an array to function (see
524
766
  [this issue](https://github.com/activerecord-hackery/ransack/issues/404)),
525
767
  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,
768
+ better for now to use [ransackers](https://github.com/activerecord-hackery/ransack/wiki/Using-Ransackers) instead,
528
769
  where feasible. Pull requests with solutions and tests are welcome!
529
770
 
530
771
  ### Grouping queries by OR instead of AND
@@ -648,9 +889,9 @@ en:
648
889
 
649
890
  ## Mongoid
650
891
 
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
892
+ Mongoid support has been moved to its own gem at [ransack-mongoid](https://github.com/activerecord-hackery/ransack-mongoid).
893
+ Ransack works with Mongoid in the same way as Active Record, except that with
894
+ Mongoid, associations are not currently supported. Demo source code may be found
654
895
  [here](https://github.com/Zhomart/ransack-mongodb-demo). A `result` method
655
896
  called on a `ransack` search returns a `Mongoid::Criteria` object:
656
897
 
@@ -662,16 +903,10 @@ called on a `ransack` search returns a `Mongoid::Criteria` object:
662
903
  @people = @q.result.active.order_by(updated_at: -1).limit(10)
663
904
  ```
664
905
 
665
- _NOTE: Ransack currently works with either Active Record or Mongoid, but not
906
+ NOTE: Ransack currently works with either Active Record or Mongoid, but not
666
907
  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
- ```
908
+ Active Record only. The logic is contained in
909
+ `Ransack::Adapters#instantiate_object_mapper` should you need to override it.
675
910
 
676
911
  ## Semantic Versioning
677
912
 
@@ -700,6 +935,31 @@ directly related to bug reports, pull requests, or documentation improvements.
700
935
  to you. The more people who are using the project, the quicker we can find and
701
936
  fix bugs!
702
937
 
703
- ## Copyright
938
+ ## Contributors
939
+
940
+ This project exists thanks to all the people who contribute. <img src="https://opencollective.com/ransack/contributors.svg?width=890&button=false" />
941
+
942
+
943
+ ## Backers
944
+
945
+ Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/ransack#backer)]
946
+
947
+ <a href="https://opencollective.com/ransack#backers" target="_blank"><img src="https://opencollective.com/ransack/backers.svg?width=890"></a>
948
+
949
+
950
+ ## Sponsors
951
+
952
+ 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)]
953
+
954
+ <a href="https://opencollective.com/ransack/sponsor/0/website" target="_blank"><img src="https://opencollective.com/ransack/sponsor/0/avatar.svg"></a>
955
+ <a href="https://opencollective.com/ransack/sponsor/1/website" target="_blank"><img src="https://opencollective.com/ransack/sponsor/1/avatar.svg"></a>
956
+ <a href="https://opencollective.com/ransack/sponsor/2/website" target="_blank"><img src="https://opencollective.com/ransack/sponsor/2/avatar.svg"></a>
957
+ <a href="https://opencollective.com/ransack/sponsor/3/website" target="_blank"><img src="https://opencollective.com/ransack/sponsor/3/avatar.svg"></a>
958
+ <a href="https://opencollective.com/ransack/sponsor/4/website" target="_blank"><img src="https://opencollective.com/ransack/sponsor/4/avatar.svg"></a>
959
+ <a href="https://opencollective.com/ransack/sponsor/5/website" target="_blank"><img src="https://opencollective.com/ransack/sponsor/5/avatar.svg"></a>
960
+ <a href="https://opencollective.com/ransack/sponsor/6/website" target="_blank"><img src="https://opencollective.com/ransack/sponsor/6/avatar.svg"></a>
961
+ <a href="https://opencollective.com/ransack/sponsor/7/website" target="_blank"><img src="https://opencollective.com/ransack/sponsor/7/avatar.svg"></a>
962
+ <a href="https://opencollective.com/ransack/sponsor/8/website" target="_blank"><img src="https://opencollective.com/ransack/sponsor/8/avatar.svg"></a>
963
+ <a href="https://opencollective.com/ransack/sponsor/9/website" target="_blank"><img src="https://opencollective.com/ransack/sponsor/9/avatar.svg"></a>
964
+
704
965
 
705
- Copyright &copy; 2011-2015 [Ernie Miller](http://twitter.com/erniemiller)