ransack 4.3.0 → 4.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -2
  3. data/lib/polyamorous/polyamorous.rb +1 -1
  4. data/lib/ransack/adapters/active_record/context.rb +30 -3
  5. data/lib/ransack/context.rb +3 -0
  6. data/lib/ransack/helpers/form_builder.rb +6 -7
  7. data/lib/ransack/helpers/form_helper.rb +86 -20
  8. data/lib/ransack/locale/ja.yml +51 -51
  9. data/lib/ransack/locale/ko.yml +6 -6
  10. data/lib/ransack/locale/uk.yml +72 -0
  11. data/lib/ransack/nodes/condition.rb +36 -6
  12. data/lib/ransack/nodes/grouping.rb +1 -1
  13. data/lib/ransack/nodes/sort.rb +1 -1
  14. data/lib/ransack/nodes/value.rb +9 -1
  15. data/lib/ransack/search.rb +1 -1
  16. data/lib/ransack/version.rb +1 -1
  17. data/lib/ransack.rb +8 -0
  18. data/spec/polyamorous/join_association_spec.rb +0 -1
  19. data/spec/polyamorous/join_dependency_spec.rb +0 -1
  20. data/spec/ransack/adapters/active_record/base_spec.rb +101 -2
  21. data/spec/ransack/adapters/active_record/context_spec.rb +72 -0
  22. data/spec/ransack/helpers/form_builder_spec.rb +0 -2
  23. data/spec/ransack/helpers/form_helper_spec.rb +219 -5
  24. data/spec/ransack/nodes/condition_spec.rb +229 -0
  25. data/spec/ransack/nodes/grouping_spec.rb +2 -2
  26. data/spec/ransack/nodes/value_spec.rb +12 -1
  27. data/spec/ransack/predicate_spec.rb +0 -1
  28. data/spec/ransack/search_spec.rb +107 -1
  29. data/spec/ransack/translate_spec.rb +0 -1
  30. data/spec/spec_helper.rb +2 -3
  31. data/spec/support/schema.rb +30 -0
  32. metadata +41 -87
  33. data/.github/FUNDING.yml +0 -3
  34. data/.github/SECURITY.md +0 -12
  35. data/.github/workflows/codeql.yml +0 -72
  36. data/.github/workflows/cronjob.yml +0 -141
  37. data/.github/workflows/deploy.yml +0 -35
  38. data/.github/workflows/rubocop.yml +0 -20
  39. data/.github/workflows/test-deploy.yml +0 -29
  40. data/.github/workflows/test.yml +0 -183
  41. data/.gitignore +0 -7
  42. data/.nojekyll +0 -0
  43. data/.rubocop.yml +0 -50
  44. data/CHANGELOG.md +0 -1193
  45. data/CONTRIBUTING.md +0 -171
  46. data/Gemfile +0 -58
  47. data/Rakefile +0 -24
  48. data/bug_report_templates/test-ransack-scope-and-column-same-name.rb +0 -78
  49. data/bug_report_templates/test-ransacker-arel-present-predicate.rb +0 -75
  50. data/docs/.gitignore +0 -19
  51. data/docs/.nojekyll +0 -0
  52. data/docs/babel.config.js +0 -3
  53. data/docs/blog/2022-03-27-ransack-3.0.0.md +0 -20
  54. data/docs/docs/getting-started/_category_.json +0 -4
  55. data/docs/docs/getting-started/advanced-mode.md +0 -46
  56. data/docs/docs/getting-started/configuration.md +0 -47
  57. data/docs/docs/getting-started/search-matches.md +0 -67
  58. data/docs/docs/getting-started/simple-mode.md +0 -289
  59. data/docs/docs/getting-started/sorting.md +0 -71
  60. data/docs/docs/getting-started/using-predicates.md +0 -282
  61. data/docs/docs/going-further/_category_.json +0 -4
  62. data/docs/docs/going-further/acts-as-taggable-on.md +0 -114
  63. data/docs/docs/going-further/associations.md +0 -70
  64. data/docs/docs/going-further/custom-predicates.md +0 -52
  65. data/docs/docs/going-further/documentation.md +0 -43
  66. data/docs/docs/going-further/exporting-to-csv.md +0 -49
  67. data/docs/docs/going-further/external-guides.md +0 -57
  68. data/docs/docs/going-further/form-customisation.md +0 -63
  69. data/docs/docs/going-further/i18n.md +0 -53
  70. data/docs/docs/going-further/img/create_release.png +0 -0
  71. data/docs/docs/going-further/merging-searches.md +0 -41
  72. data/docs/docs/going-further/other-notes.md +0 -425
  73. data/docs/docs/going-further/polymorphic-search.md +0 -46
  74. data/docs/docs/going-further/ransackers.md +0 -331
  75. data/docs/docs/going-further/release_process.md +0 -36
  76. data/docs/docs/going-further/saving-queries.md +0 -82
  77. data/docs/docs/going-further/searching-postgres.md +0 -57
  78. data/docs/docs/going-further/wiki-contributors.md +0 -82
  79. data/docs/docs/intro.md +0 -99
  80. data/docs/docusaurus.config.js +0 -120
  81. data/docs/package.json +0 -42
  82. data/docs/sidebars.js +0 -31
  83. data/docs/src/components/HomepageFeatures/index.js +0 -64
  84. data/docs/src/components/HomepageFeatures/styles.module.css +0 -11
  85. data/docs/src/css/custom.css +0 -39
  86. data/docs/src/pages/index.module.css +0 -23
  87. data/docs/src/pages/markdown-page.md +0 -7
  88. data/docs/static/.nojekyll +0 -0
  89. data/docs/static/img/docusaurus.png +0 -0
  90. data/docs/static/img/favicon.ico +0 -0
  91. data/docs/static/img/logo.svg +0 -1
  92. data/docs/static/img/tutorial/docsVersionDropdown.png +0 -0
  93. data/docs/static/img/tutorial/localeDropdown.png +0 -0
  94. data/docs/static/img/undraw_docusaurus_mountain.svg +0 -171
  95. data/docs/static/img/undraw_docusaurus_react.svg +0 -170
  96. data/docs/static/img/undraw_docusaurus_tree.svg +0 -40
  97. data/docs/static/logo/ransack-h.png +0 -0
  98. data/docs/static/logo/ransack-h.svg +0 -34
  99. data/docs/static/logo/ransack-v.png +0 -0
  100. data/docs/static/logo/ransack-v.svg +0 -34
  101. data/docs/static/logo/ransack.png +0 -0
  102. data/docs/static/logo/ransack.svg +0 -21
  103. data/docs/yarn.lock +0 -8884
  104. data/ransack.gemspec +0 -26
@@ -1,425 +0,0 @@
1
- ---
2
- sidebar_position: 8
3
- title: Other notes
4
- ---
5
-
6
- ### Ransack Aliases
7
-
8
- You can customize the attribute names for your Ransack searches by using a
9
- `ransack_alias`. This is particularly useful for long attribute names that are
10
- necessary when querying associations or multiple columns.
11
-
12
- ```ruby
13
- class Post < ActiveRecord::Base
14
- belongs_to :author
15
-
16
- # Abbreviate :author_first_name_or_author_last_name to :author
17
- ransack_alias :author, :author_first_name_or_author_last_name
18
- end
19
- ```
20
-
21
- Now, rather than using `:author_first_name_or_author_last_name_cont` in your
22
- form, you can simply use `:author_cont`. This serves to produce more expressive
23
- query parameters in your URLs.
24
-
25
- ```erb
26
- <%= search_form_for @q do |f| %>
27
- <%= f.label :author_cont %>
28
- <%= f.search_field :author_cont %>
29
- <% end %>
30
- ```
31
-
32
- You can also use `ransack_alias` for sorting.
33
-
34
- ```ruby
35
- class Post < ActiveRecord::Base
36
- belongs_to :author
37
-
38
- # Abbreviate :author_first_name to :author
39
- ransack_alias :author, :author_first_name
40
- end
41
- ```
42
-
43
- Now, you can use `:author` instead of `:author_first_name` in a `sort_link`.
44
-
45
- ```erb
46
- <%= sort_link(@q, :author) %>
47
- ```
48
-
49
- Note that using `:author_first_name_or_author_last_name_cont` would produce an invalid sql query. In those cases, Ransack ignores the sorting clause.
50
-
51
-
52
-
53
- ### Problem with DISTINCT selects
54
-
55
- If passed `distinct: true`, `result` will generate a `SELECT DISTINCT` to
56
- avoid returning duplicate rows, even if conditions on a join would otherwise
57
- result in some. It generates the same SQL as calling `uniq` on the relation.
58
-
59
- Please note that for many databases, a sort on an associated table's columns
60
- may result in invalid SQL with `distinct: true` -- in those cases, you
61
- will need to modify the result as needed to allow these queries to work.
62
-
63
- For example, you could call joins and includes on the result which has the
64
- effect of adding those tables columns to the select statement, overcoming
65
- the issue, like so:
66
-
67
- ```ruby
68
- def index
69
- @q = Person.ransack(params[:q])
70
- @people = @q.result(distinct: true)
71
- .includes(:articles)
72
- .joins(:articles)
73
- .page(params[:page])
74
- end
75
- ```
76
-
77
- If the above doesn't help, you can also use ActiveRecord's `select` query
78
- to explicitly add the columns you need, which brute force's adding the
79
- columns you need that your SQL engine is complaining about, you need to
80
- make sure you give all of the columns you care about, for example:
81
-
82
- ```ruby
83
- def index
84
- @q = Person.ransack(params[:q])
85
- @people = @q.result(distinct: true)
86
- .select('people.*, articles.name, articles.description')
87
- .page(params[:page])
88
- end
89
- ```
90
-
91
- Another method to approach this when using Postgresql is to use ActiveRecords's `.includes` in combination with `.group` instead of `distinct: true`.
92
-
93
- For example:
94
- ```ruby
95
- def index
96
- @q = Person.ransack(params[:q])
97
- @people = @q.result
98
- .group('persons.id')
99
- .includes(:articles)
100
- .page(params[:page])
101
- end
102
-
103
- ```
104
-
105
- A final way of last resort is to call `to_a.uniq` on the collection at the end
106
- with the caveat that the de-duping is taking place in Ruby instead of in SQL,
107
- which is potentially slower and uses more memory, and that it may display
108
- awkwardly with pagination if the number of results is greater than the page size.
109
-
110
- For example:
111
-
112
- ```ruby
113
- def index
114
- @q = Person.ransack(params[:q])
115
- @people = @q.result.includes(:articles).page(params[:page]).to_a.uniq
116
- end
117
- ```
118
-
119
- #### `PG::UndefinedFunction: ERROR: could not identify an equality operator for type json`
120
-
121
- If you get the above error while using `distinct: true` that means that
122
- one of the columns that Ransack is selecting is a `json` column.
123
- PostgreSQL does not provide comparison operators for the `json` type. While
124
- it is possible to work around this, in practice it's much better to convert those
125
- to `jsonb`, as [recommended by the PostgreSQL documentation](https://www.postgresql.org/docs/9.6/static/datatype-json.html).
126
-
127
- ### Authorization (allowlisting/denylisting)
128
-
129
- By default, searching and sorting are not authorized on any column of your model
130
- and no class methods/scopes are allowlisted.
131
-
132
- Ransack adds four methods to `ActiveRecord::Base` that you can redefine as
133
- class methods in your models to apply selective authorization:
134
-
135
- - `ransackable_attributes`
136
- - `ransackable_associations`
137
- - `ransackable_scopes`
138
- - `ransortable_attributes`
139
-
140
- Here is how these four methods could be implemented in your application:
141
-
142
- ```ruby
143
- # `ransackable_attributes` returns searchable column names
144
- # and any defined ransackers as an array of strings.
145
- #
146
- def ransackable_attributes(auth_object = nil)
147
- %w(title body) + _ransackers.keys
148
- end
149
-
150
- # `ransackable_associations` returns the names
151
- # of searchable associations as an array of strings.
152
- #
153
- def ransackable_associations(auth_object = nil)
154
- %w[author]
155
- end
156
-
157
- # `ransortable_attributes` by default returns the names
158
- # of all attributes available for sorting as an array of strings.
159
- #
160
- def ransortable_attributes(auth_object = nil)
161
- ransackable_attributes(auth_object)
162
- end
163
-
164
- # `ransackable_scopes` by default returns an empty array
165
- # i.e. no class methods/scopes are authorized.
166
- # For overriding with an allowlist, return an array of *symbols*.
167
- #
168
- def ransackable_scopes(auth_object = nil)
169
- []
170
- end
171
- ```
172
-
173
- Any values not returned from these methods will be ignored by Ransack, i.e.
174
- they are not authorized.
175
-
176
- All four methods can receive a single optional parameter, `auth_object`. When
177
- you call the search or ransack method on your model, you can provide a value
178
- for an `auth_object` key in the options hash which can be used by your own
179
- overridden methods.
180
-
181
- Here is an example that puts all this together, adapted from
182
- [this blog post by Ernie Miller](https://ernie.io/2012/05/11/why-your-ruby-class-macros-might-suck-mine-did/).
183
- In an `Article` model, add the following `ransackable_attributes` class method
184
- (preferably private):
185
-
186
- ```ruby
187
- class Article < ActiveRecord::Base
188
- def self.ransackable_attributes(auth_object = nil)
189
- if auth_object == :admin
190
- # allow all attributes for admin
191
- column_names + _ransackers.keys
192
- else
193
- # allow only the title and body attributes for other users
194
- %w(title body)
195
- end
196
- end
197
-
198
- private_class_method :ransackable_attributes
199
- end
200
- ```
201
-
202
- Here is example code for the `articles_controller`:
203
-
204
- ```ruby
205
- class ArticlesController < ApplicationController
206
- def index
207
- @q = Article.ransack(params[:q], auth_object: set_ransack_auth_object)
208
- @articles = @q.result
209
- end
210
-
211
- private
212
-
213
- def set_ransack_auth_object
214
- current_user.admin? ? :admin : nil
215
- end
216
- end
217
- ```
218
-
219
- Trying it out in `rails console`:
220
-
221
- ```ruby
222
- > Article
223
- => Article(id: integer, person_id: integer, title: string, body: text)
224
-
225
- > Article.ransackable_attributes
226
- => ["title", "body"]
227
-
228
- > Article.ransackable_attributes(:admin)
229
- => ["id", "person_id", "title", "body"]
230
-
231
- > Article.ransack(id_eq: 1).result.to_sql
232
- => SELECT "articles".* FROM "articles" # Note that search param was ignored!
233
-
234
- > Article.ransack({ id_eq: 1 }, { auth_object: nil }).result.to_sql
235
- => SELECT "articles".* FROM "articles" # Search param still ignored!
236
-
237
- > Article.ransack({ id_eq: 1 }, { auth_object: :admin }).result.to_sql
238
- => SELECT "articles".* FROM "articles" WHERE "articles"."id" = 1
239
- ```
240
-
241
- That's it! Now you know how to allow/block various elements in Ransack.
242
-
243
- ### Handling unknown predicates or attributes
244
-
245
- By default, Ransack will ignore any unknown predicates or attributes:
246
-
247
- ```ruby
248
- Article.ransack(unknown_attr_eq: 'Ernie').result.to_sql
249
- => SELECT "articles".* FROM "articles"
250
- ```
251
-
252
- Ransack may be configured to raise an error if passed an unknown predicate or
253
- attributes, by setting the `ignore_unknown_conditions` option to `false` in your
254
- Ransack initializer file at `config/initializers/ransack.rb`:
255
-
256
- ```ruby
257
- Ransack.configure do |c|
258
- # Raise errors if a query contains an unknown predicate or attribute.
259
- # Default is true (do not raise error on unknown conditions).
260
- c.ignore_unknown_conditions = false
261
- end
262
- ```
263
-
264
- ```ruby
265
- Article.ransack(unknown_attr_eq: 'Ernie')
266
- # Ransack::InvalidSearchError (Invalid search term unknown_attr_eq)
267
- ```
268
-
269
- As an alternative to setting a global configuration option, the `.ransack!`
270
- class method also raises an error if passed an unknown condition:
271
-
272
- ```ruby
273
- Article.ransack!(unknown_attr_eq: 'Ernie')
274
- # Ransack::InvalidSearchError: Invalid search term unknown_attr_eq
275
- ```
276
-
277
- This is equivalent to the `ignore_unknown_conditions` configuration option,
278
- except it may be applied on a case-by-case basis.
279
-
280
- ### Using Scopes/Class Methods
281
-
282
- Continuing on from the preceding section, searching by scopes requires defining
283
- a whitelist of `ransackable_scopes` on the model class. The whitelist should be
284
- an array of *symbols*. By default, all class methods (e.g. scopes) are ignored.
285
- Scopes will be applied for matching `true` values, or for given values if the
286
- scope accepts a value:
287
-
288
- ```ruby
289
- class Employee < ActiveRecord::Base
290
- scope :activated, ->(boolean = true) { where(active: boolean) }
291
- scope :salary_gt, ->(amount) { where('salary > ?', amount) }
292
-
293
- # Scopes are just syntactical sugar for class methods, which may also be used:
294
-
295
- def self.hired_since(date)
296
- where('start_date >= ?', date)
297
- end
298
-
299
- def self.ransackable_scopes(auth_object = nil)
300
- if auth_object.try(:admin?)
301
- # allow admin users access to all three methods
302
- %i(activated hired_since salary_gt)
303
- else
304
- # allow other users to search on `activated` and `hired_since` only
305
- %i(activated hired_since)
306
- end
307
- end
308
- end
309
-
310
- Employee.ransack({ activated: true, hired_since: '2013-01-01' })
311
-
312
- Employee.ransack({ salary_gt: 100_000 }, { auth_object: current_user })
313
- ```
314
-
315
- In Rails 3 and 4, if the `true` value is being passed via url params or some
316
- other mechanism that will convert it to a string, the true value may not be
317
- passed to the ransackable scope unless you wrap it in an array
318
- (i.e. `activated: ['true']`). Ransack will take care of changing 'true' into a
319
- boolean. This is currently resolved in Rails 5 :smiley:
320
-
321
- However, perhaps you have `user_id: [1]` and you do not want Ransack to convert
322
- 1 into a boolean. (Values sanitized to booleans can be found in the
323
- [constants.rb](https://github.com/activerecord-hackery/ransack/blob/master/lib/ransack/constants.rb#L28)).
324
- To turn this off globally, and handle type conversions yourself, set
325
- `sanitize_custom_scope_booleans` to false in an initializer file like
326
- config/initializers/ransack.rb:
327
-
328
- ```ruby
329
- Ransack.configure do |c|
330
- c.sanitize_custom_scope_booleans = false
331
- end
332
- ```
333
-
334
- To turn this off on a per-scope basis Ransack adds the following method to
335
- `ActiveRecord::Base` that you can redefine to selectively override sanitization:
336
-
337
- `ransackable_scopes_skip_sanitize_args`
338
-
339
- Add the scope you wish to bypass this behavior to ransackable_scopes_skip_sanitize_args:
340
-
341
- ```ruby
342
- def self.ransackable_scopes_skip_sanitize_args
343
- [:scope_to_skip_sanitize_args]
344
- end
345
- ```
346
-
347
- Scopes are a recent addition to Ransack and currently have a few caveats:
348
- First, a scope involving child associations needs to be defined in the parent
349
- table model, not in the child model. Second, scopes with an array as an
350
- argument are not easily usable yet, because the array currently needs to be
351
- wrapped in an array to function (see
352
- [this issue](https://github.com/activerecord-hackery/ransack/issues/404)),
353
- which is not compatible with Ransack form helpers. For this use case, it may be
354
- better for now to use [ransackers](https://activerecord-hackery.github.io/ransack/going-further/ransackers) instead,
355
- where feasible. Pull requests with solutions and tests are welcome!
356
-
357
- ### Grouping queries by OR instead of AND
358
-
359
- The default `AND` grouping can be changed to `OR` by adding `m: 'or'` to the
360
- query hash.
361
-
362
- You can easily try it in your controller code by changing `params[:q]` in the
363
- `index` action to `params[:q].try(:merge, m: 'or')` as follows:
364
-
365
- ```ruby
366
- def index
367
- @q = Artist.ransack(params[:q].try(:merge, m: 'or'))
368
- @artists = @q.result
369
- end
370
- ```
371
- Normally, if you wanted users to be able to toggle between `AND` and `OR`
372
- query grouping, you would probably set up your search form so that `m` was in
373
- the URL params hash, but here we assigned `m` manually just to try it out
374
- quickly.
375
-
376
- Alternatively, trying it in the Rails console:
377
-
378
- ```ruby
379
- artists = Artist.ransack(name_cont: 'foo', style_cont: 'bar', m: 'or')
380
- => Ransack::Search<class: Artist, base: Grouping <conditions: [
381
- Condition <attributes: ["name"], predicate: cont, values: ["foo"]>,
382
- Condition <attributes: ["style"], predicate: cont, values: ["bar"]>
383
- ], combinator: or>>
384
-
385
- artists.result.to_sql
386
- => "SELECT \"artists\".* FROM \"artists\"
387
- WHERE ((\"artists\".\"name\" ILIKE '%foo%'
388
- OR \"artists\".\"style\" ILIKE '%bar%'))"
389
- ```
390
-
391
- The combinator becomes `or` instead of the default `and`, and the SQL query
392
- becomes `WHERE...OR` instead of `WHERE...AND`.
393
-
394
- This works with associations as well. Imagine an Artist model that has many
395
- Memberships, and many Musicians through Memberships:
396
-
397
- ```ruby
398
- artists = Artist.ransack(name_cont: 'foo', musicians_email_cont: 'bar', m: 'or')
399
- => Ransack::Search<class: Artist, base: Grouping <conditions: [
400
- Condition <attributes: ["name"], predicate: cont, values: ["foo"]>,
401
- Condition <attributes: ["musicians_email"], predicate: cont, values: ["bar"]>
402
- ], combinator: or>>
403
-
404
- artists.result.to_sql
405
- => "SELECT \"artists\".* FROM \"artists\"
406
- LEFT OUTER JOIN \"memberships\"
407
- ON \"memberships\".\"artist_id\" = \"artists\".\"id\"
408
- LEFT OUTER JOIN \"musicians\"
409
- ON \"musicians\".\"id\" = \"memberships\".\"musician_id\"
410
- WHERE ((\"artists\".\"name\" ILIKE '%foo%'
411
- OR \"musicians\".\"email\" ILIKE '%bar%'))"
412
- ```
413
-
414
- ### Using SimpleForm
415
-
416
- If you would like to combine the Ransack and SimpleForm form builders, set the
417
- `RANSACK_FORM_BUILDER` environment variable before Rails boots up, e.g. in
418
- `config/application.rb` before `require 'rails/all'` as shown below (and add
419
- `gem 'simple_form'` in your Gemfile).
420
-
421
- ```ruby
422
- require File.expand_path('../boot', __FILE__)
423
- ENV['RANSACK_FORM_BUILDER'] = '::SimpleForm::FormBuilder'
424
- require 'rails/all'
425
- ```
@@ -1,46 +0,0 @@
1
- ---
2
- title: Polymorphic Searches
3
- sidebar_position: 14
4
- ---
5
-
6
- When making searches from polymorphic models it is necessary to specify the type of model you are searching.
7
-
8
- For example:
9
-
10
- Given two models
11
-
12
- ```ruby
13
- class House < ActiveRecord::Base
14
- has_one :location, as: :locatable
15
- end
16
-
17
- class Location < ActiveRecord::Base
18
- belongs_to :locatable, polymorphic: true
19
- end
20
- ```
21
-
22
- Normally (without polymorphic relationship) you would be able to search as per below:
23
-
24
- ```ruby
25
- Location.ransack(locatable_number_eq: 100).result
26
- ```
27
-
28
- However when this is searched you will get the following error
29
-
30
- ```ruby
31
- ActiveRecord::EagerLoadPolymorphicError: Can not eagerly load the polymorphic association :locatable
32
- ```
33
-
34
- In order to search for locations by house number when the relationship is polymorphic you have to specify the type of records you will be searching and construct your search as below:
35
-
36
- ```ruby
37
- Location.ransack(locatable_of_House_type_number_eq: 100).result
38
- ```
39
-
40
- note the `_of_House_type_` added to the search key. This allows Ransack to correctly specify the table names in SQL join queries.
41
-
42
- For namespaced models you should use a quoted string containing the standard Ruby module notation
43
-
44
- ```ruby
45
- Location.ransack('locatable_of_Residences::House_type_number_eq' => 100).result
46
- ```