ransack 2.3.2 → 4.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (145) hide show
  1. checksums.yaml +4 -4
  2. data/.github/FUNDING.yml +3 -0
  3. data/.github/SECURITY.md +12 -0
  4. data/.github/workflows/codeql.yml +72 -0
  5. data/.github/workflows/cronjob.yml +99 -0
  6. data/.github/workflows/deploy.yml +35 -0
  7. data/.github/workflows/rubocop.yml +20 -0
  8. data/.github/workflows/test-deploy.yml +29 -0
  9. data/.github/workflows/test.yml +131 -0
  10. data/.nojekyll +0 -0
  11. data/.rubocop.yml +50 -0
  12. data/CHANGELOG.md +251 -1
  13. data/CONTRIBUTING.md +51 -29
  14. data/Gemfile +12 -10
  15. data/README.md +45 -907
  16. data/bug_report_templates/test-ransack-scope-and-column-same-name.rb +78 -0
  17. data/bug_report_templates/test-ransacker-arel-present-predicate.rb +75 -0
  18. data/docs/.gitignore +19 -0
  19. data/docs/.nojekyll +0 -0
  20. data/docs/babel.config.js +3 -0
  21. data/docs/blog/2022-03-27-ransack-3.0.0.md +20 -0
  22. data/docs/docs/getting-started/_category_.json +4 -0
  23. data/docs/docs/getting-started/advanced-mode.md +46 -0
  24. data/docs/docs/getting-started/configuration.md +47 -0
  25. data/docs/docs/getting-started/search-matches.md +67 -0
  26. data/docs/docs/getting-started/simple-mode.md +288 -0
  27. data/docs/docs/getting-started/sorting.md +71 -0
  28. data/docs/docs/getting-started/using-predicates.md +282 -0
  29. data/docs/docs/going-further/_category_.json +4 -0
  30. data/docs/docs/going-further/acts-as-taggable-on.md +114 -0
  31. data/docs/docs/going-further/associations.md +70 -0
  32. data/docs/docs/going-further/custom-predicates.md +52 -0
  33. data/docs/docs/going-further/documentation.md +43 -0
  34. data/docs/docs/going-further/exporting-to-csv.md +49 -0
  35. data/docs/docs/going-further/external-guides.md +57 -0
  36. data/docs/docs/going-further/form-customisation.md +63 -0
  37. data/docs/docs/going-further/i18n.md +53 -0
  38. data/docs/docs/going-further/img/create_release.png +0 -0
  39. data/docs/docs/going-further/merging-searches.md +41 -0
  40. data/docs/docs/going-further/other-notes.md +428 -0
  41. data/docs/docs/going-further/polymorphic-search.md +46 -0
  42. data/docs/docs/going-further/ransackers.md +331 -0
  43. data/docs/docs/going-further/release_process.md +36 -0
  44. data/docs/docs/going-further/saving-queries.md +82 -0
  45. data/docs/docs/going-further/searching-postgres.md +57 -0
  46. data/docs/docs/going-further/wiki-contributors.md +82 -0
  47. data/docs/docs/intro.md +99 -0
  48. data/docs/docusaurus.config.js +120 -0
  49. data/docs/package.json +42 -0
  50. data/docs/sidebars.js +31 -0
  51. data/docs/src/components/HomepageFeatures/index.js +64 -0
  52. data/docs/src/components/HomepageFeatures/styles.module.css +11 -0
  53. data/docs/src/css/custom.css +39 -0
  54. data/docs/src/pages/index.module.css +23 -0
  55. data/docs/src/pages/markdown-page.md +7 -0
  56. data/docs/static/.nojekyll +0 -0
  57. data/docs/static/img/docusaurus.png +0 -0
  58. data/docs/static/img/favicon.ico +0 -0
  59. data/docs/static/img/logo.svg +1 -0
  60. data/docs/static/img/tutorial/docsVersionDropdown.png +0 -0
  61. data/docs/static/img/tutorial/localeDropdown.png +0 -0
  62. data/docs/static/img/undraw_docusaurus_mountain.svg +171 -0
  63. data/docs/static/img/undraw_docusaurus_react.svg +170 -0
  64. data/docs/static/img/undraw_docusaurus_tree.svg +40 -0
  65. data/docs/yarn.lock +8879 -0
  66. data/lib/polyamorous/activerecord/join_association.rb +70 -0
  67. data/{polyamorous/lib/polyamorous/activerecord_6.0_ruby_2 → lib/polyamorous/activerecord}/join_dependency.rb +33 -12
  68. data/lib/polyamorous/activerecord/reflection.rb +11 -0
  69. data/{polyamorous/lib → lib/polyamorous}/polyamorous.rb +3 -4
  70. data/lib/ransack/adapters/active_record/base.rb +83 -10
  71. data/lib/ransack/adapters/active_record/context.rb +56 -44
  72. data/lib/ransack/configuration.rb +53 -10
  73. data/lib/ransack/constants.rb +126 -4
  74. data/lib/ransack/context.rb +34 -5
  75. data/lib/ransack/helpers/form_builder.rb +6 -6
  76. data/lib/ransack/helpers/form_helper.rb +14 -5
  77. data/lib/ransack/helpers.rb +1 -1
  78. data/lib/ransack/locale/sv.yml +70 -0
  79. data/lib/ransack/nodes/attribute.rb +3 -3
  80. data/lib/ransack/nodes/condition.rb +80 -9
  81. data/lib/ransack/nodes/grouping.rb +4 -4
  82. data/lib/ransack/nodes/node.rb +1 -1
  83. data/lib/ransack/nodes/sort.rb +3 -3
  84. data/lib/ransack/nodes/value.rb +3 -3
  85. data/lib/ransack/predicate.rb +1 -1
  86. data/lib/ransack/ransacker.rb +1 -1
  87. data/lib/ransack/search.rb +15 -7
  88. data/lib/ransack/translate.rb +6 -6
  89. data/lib/ransack/version.rb +1 -1
  90. data/lib/ransack/visitor.rb +38 -2
  91. data/lib/ransack.rb +5 -8
  92. data/ransack.gemspec +9 -15
  93. data/spec/blueprints/articles.rb +1 -1
  94. data/spec/blueprints/comments.rb +1 -1
  95. data/spec/blueprints/notes.rb +1 -1
  96. data/spec/blueprints/tags.rb +1 -1
  97. data/spec/console.rb +5 -5
  98. data/spec/helpers/polyamorous_helper.rb +2 -8
  99. data/spec/helpers/ransack_helper.rb +1 -1
  100. data/spec/polyamorous/activerecord_compatibility_spec.rb +15 -0
  101. data/spec/{ransack → polyamorous}/join_association_spec.rb +3 -1
  102. data/spec/{ransack → polyamorous}/join_dependency_spec.rb +0 -16
  103. data/spec/ransack/adapters/active_record/base_spec.rb +125 -16
  104. data/spec/ransack/adapters/active_record/context_spec.rb +19 -18
  105. data/spec/ransack/configuration_spec.rb +33 -9
  106. data/spec/ransack/helpers/form_builder_spec.rb +8 -8
  107. data/spec/ransack/helpers/form_helper_spec.rb +109 -20
  108. data/spec/ransack/nodes/condition_spec.rb +37 -0
  109. data/spec/ransack/nodes/grouping_spec.rb +2 -2
  110. data/spec/ransack/nodes/value_spec.rb +115 -0
  111. data/spec/ransack/predicate_spec.rb +37 -2
  112. data/spec/ransack/search_spec.rb +238 -30
  113. data/spec/ransack/translate_spec.rb +1 -1
  114. data/spec/spec_helper.rb +7 -5
  115. data/spec/support/schema.rb +108 -11
  116. metadata +98 -62
  117. data/.travis.yml +0 -47
  118. data/lib/ransack/adapters/active_record/ransack/constants.rb +0 -128
  119. data/lib/ransack/adapters/active_record/ransack/context.rb +0 -55
  120. data/lib/ransack/adapters/active_record/ransack/nodes/condition.rb +0 -61
  121. data/lib/ransack/adapters/active_record/ransack/translate.rb +0 -8
  122. data/lib/ransack/adapters/active_record/ransack/visitor.rb +0 -47
  123. data/lib/ransack/adapters.rb +0 -64
  124. data/lib/ransack/nodes.rb +0 -8
  125. data/polyamorous/lib/polyamorous/activerecord_5.2_ruby_2/join_association.rb +0 -20
  126. data/polyamorous/lib/polyamorous/activerecord_5.2_ruby_2/join_dependency.rb +0 -79
  127. data/polyamorous/lib/polyamorous/activerecord_5.2_ruby_2/reflection.rb +0 -12
  128. data/polyamorous/lib/polyamorous/activerecord_6.0_ruby_2/join_association.rb +0 -2
  129. data/polyamorous/lib/polyamorous/activerecord_6.0_ruby_2/reflection.rb +0 -2
  130. data/polyamorous/lib/polyamorous/activerecord_6.1_ruby_2/join_association.rb +0 -2
  131. data/polyamorous/lib/polyamorous/activerecord_6.1_ruby_2/join_dependency.rb +0 -2
  132. data/polyamorous/lib/polyamorous/activerecord_6.1_ruby_2/reflection.rb +0 -2
  133. data/polyamorous/lib/polyamorous/version.rb +0 -3
  134. data/polyamorous/polyamorous.gemspec +0 -27
  135. /data/{logo → docs/static/logo}/ransack-h.png +0 -0
  136. /data/{logo → docs/static/logo}/ransack-h.svg +0 -0
  137. /data/{logo → docs/static/logo}/ransack-v.png +0 -0
  138. /data/{logo → docs/static/logo}/ransack-v.svg +0 -0
  139. /data/{logo → docs/static/logo}/ransack.png +0 -0
  140. /data/{logo → docs/static/logo}/ransack.svg +0 -0
  141. /data/{polyamorous/lib → lib}/polyamorous/join.rb +0 -0
  142. /data/{polyamorous/lib → lib}/polyamorous/swapping_reflection_class.rb +0 -0
  143. /data/{polyamorous/lib → lib}/polyamorous/tree_node.rb +0 -0
  144. /data/lib/ransack/{adapters/active_record.rb → active_record.rb} +0 -0
  145. /data/spec/{ransack → polyamorous}/join_spec.rb +0 -0
@@ -0,0 +1,57 @@
1
+ ---
2
+ sidebar_position: 9
3
+ title: External resources
4
+ ---
5
+
6
+ There is a plethora of material on Ransack around the internet. We've collected some here for your convenience.
7
+
8
+ Do you want to have a blog post or other content on Ransack highlighted? Please just edit the page, add your content and a Pull Request will be sent to Ransack maintainers for approval.
9
+
10
+ # Screencasts
11
+
12
+ - [DriftingRuby: Ransack Search and Hotwire](https://www.driftingruby.com/episodes/ransack-search-and-hotwire)
13
+ - [GoRails: Forum Series Part 6: Search with Ransack](https://gorails.com/episodes/forum-search-with-ransack)
14
+ - [Railscast 370 - Ransack](http://railscasts.com/episodes/370-ransack)
15
+ - [Search And Sort Ransack Associations With The Rails Ransack Gem | Ruby On Rails 6 Ransack Tutorial](https://www.youtube.com/watch?v=rtg-5EXwpbg)
16
+
17
+
18
+ # Gems
19
+
20
+ - [ActiveAdmin](https://activeadmin.info/) The Administration Framework for Rails **_uses Ransack internally_**
21
+ - [Ransack Memory](https://github.com/richardrails/ransack_memory) Automatically save and load Ransack's filtered params into the Rail's session
22
+ - [Mobility Ransack](https://github.com/shioyama/mobility-ransack) Search attributes translated by Mobility with Ransack.
23
+ - [Ransack UI](https://github.com/ndbroadbent/ransack_ui) Framework for building a search UI with Ransack **_seems abandoned_**
24
+
25
+ # Blogs
26
+
27
+ - [Search And Sort In Ruby On Rails 6 With The Ransack Gem](https://deanin.com/blog/ransack/)
28
+ - [Implement Ransack Gem in Ruby on Rails](https://www.botreetechnologies.com/blog/implementing-advanced-search-in-ruby-on-rails-with-ransack/)
29
+ - [Searching and Sorting with Ransack](https://jaspercurry.medium.com/searching-and-sorting-on-rails-with-ransack-560e862e650a)
30
+ - [How to Build Your Own ActiveAdmin Filters with Ransack](https://www.viget.com/articles/how-to-build-your-own-filters-with-ransack/)
31
+ - [Avoid Ransack's N+1 Pitfall!](https://dev.to/husteadrobert/avoid-ransacks-n1-pitfall-33of)
32
+ - [Filter and paging with Kaminari](https://gist.github.com/MyklClason/e4dc96fd0e009b7b3a9c84ddbb1e11d2)
33
+ - [Pagination for Ransack Forms](https://nicholaide.github.io/ransack/2016/11/26/ransack-pagination.html)
34
+ - [AJAX Search, Sort, Paginate with Ransack and Kaminari](https://techbrownbags.wordpress.com/2014/01/17/rails-ajax-search-sort-paginate-with-ransack-kaminari/)
35
+ - [Searching with Ransack in Ruby on Rails](http://blog.magmalabs.io/2019/03/12/searching-with-ransack-in-ruby-on-rails.html)
36
+ - [Role scopes with gem Ransack](https://blog.corsego.com/rolify-scopes)
37
+ - [Searching and Sorting with Ransack](https://www.mintbit.com/blog/searching-and-sorting-with-ransack)
38
+ - [Using custom scopes with Ransack gem in Rails](https://profilehunt.net/blog/using-custom-scopes-with-ransack-in-rails)
39
+ - [Query Date Range With Ransack](https://lingceng.github.io/blog/2015/12/28/query-date-range-with-ransack/)
40
+ - [ransack vs searchkick: Building a search feature in Rails](https://www.cookieshq.co.uk/posts/ransack-vs-searchkick-building-a-search-feature-in-rails)
41
+ - [Using ransack and delegate in Rails](https://huangwenwei.com/blogs/using-ransack-and-delegate-in-rails)
42
+ - [Using Ransack as a Search Engine](https://medium.com/@jelaniwoods/using-ransack-as-a-search-engine-92e002a68da)
43
+ - [Advanced Search with Ransack](https://www.sitepoint.com/advanced-search-ransack/)
44
+ - [Sort a table of records in Rails with Ransack](https://alankydd.wordpress.com/2012/03/12/sort-a-table-of-records-in-rails-with-ransack/)
45
+ - [Ransack: Search with Multiple Checkboxes (Rails)](https://iamjosh.wordpress.com/2014/03/07/ransack-search-with-multiple-checkboxes/)
46
+ - [Rails : Ransack : Sorting data by ratings](https://cbabhusal.wordpress.com/2017/01/03/rails-ransack-sorting-data-by-ratings/)
47
+ - [Setting Up Rails 5 API Only App with ActiveAdmin enabled](https://rrott.com/blog/ror/rails-5-api-with-activeadmin-integration/)
48
+ - [Ransack, the library formerly known as MetaSearch 2.0](https://ernie.io/2011/04/01/ransack-the-library-formerly-known-as-metasearch-2-0/) **_some Ransack history_**
49
+
50
+ ## In French
51
+
52
+ - [Faciliter les recherches avec Ransack](https://www.synbioz.com/blog/tech/faciliter-les-recherches-avec-ransack)
53
+
54
+ ## In Vietnamese
55
+
56
+ - [Ransack - công cụ tuyệt vời giúp tìm kiếm và sắp xếp dữ liệu đơn giản hơn
57
+ ](https://nddblog.com/posts/ransack-cong-cu-tuyet-voi-giup-tim-kiem-va-sap-xep-du-lieu-don-gian-hon)
@@ -0,0 +1,63 @@
1
+ ---
2
+ sidebar_position: 4
3
+ title: Form customisation
4
+ ---
5
+
6
+ Predicate and attribute labels in forms may be specified with I18n in a translation file (see the locale files in [Ransack::Locale](https://github.com/activerecord-hackery/ransack/tree/main/lib/ransack/locale) for more examples):
7
+
8
+ ```yml
9
+ # locales/en.yml
10
+ en:
11
+ ransack:
12
+ asc: ascending
13
+ desc: descending
14
+ predicates:
15
+ cont: contains
16
+ not_cont: not contains
17
+ start: starts with
18
+ end: ends with
19
+ gt: greater than
20
+ lt: less than
21
+ attributes:
22
+ person:
23
+ name: Full Name
24
+ article:
25
+ title: Article Title
26
+ body: Main Content
27
+ ```
28
+ The names of attribute fields may also be changed globally or under activerecord:
29
+
30
+ ```yml
31
+ # locales/en.yml
32
+ en:
33
+ attributes:
34
+ model_name:
35
+ model_field1: field name1
36
+ model_field2: field name2
37
+ activerecord:
38
+ attributes:
39
+ namespace/article:
40
+ title: AR Namespaced Title
41
+ namespace_article:
42
+ title: Old Ransack Namespaced Title
43
+ ```
44
+
45
+ To limit the predicates in the `predicate_select` form helper in a view template, pass an array of permitted predicates with `only`:
46
+
47
+ ```erb
48
+ <%= f.predicate_select only: %i(cont not_cont eq not_eq blank null) %>
49
+ ```
50
+
51
+ Compound predicates (`_any` & `_all`) may be removed by passing the option `compounds: false`.
52
+
53
+ ```erb
54
+ <%= f.predicate_select compounds: false %>
55
+ ```
56
+
57
+ Searchable attributes versus non-searchable ones may be specified as follows:
58
+
59
+ ```ruby
60
+ def self.ransackable_attributes(auth_object = nil)
61
+ %w(searchable_attribute_1 searchable_attribute_2 ...) + _ransackers.keys
62
+ end
63
+ ```
@@ -0,0 +1,53 @@
1
+ ---
2
+ sidebar_position: 3
3
+ title: i18n
4
+ ---
5
+
6
+ # i18n and Ransack
7
+
8
+ Ransack translation files are available in
9
+ [Ransack::Locale](https://github.com/activerecord-hackery/ransack/tree/main/lib/ransack/locale). You may also be interested in one of the
10
+ many translations for Ransack available at
11
+ http://www.localeapp.com/projects/2999.
12
+
13
+ Predicate and attribute translations in forms may be specified as follows (see
14
+ the translation files in [Ransack::Locale](https://github.com/activerecord-hackery/ransack/tree/main/lib/ransack/locale) for more examples):
15
+
16
+ locales/en.yml:
17
+ ```yml
18
+ en:
19
+ ransack:
20
+ asc: ascending
21
+ desc: descending
22
+ predicates:
23
+ cont: contains
24
+ not_cont: not contains
25
+ start: starts with
26
+ end: ends with
27
+ gt: greater than
28
+ lt: less than
29
+ models:
30
+ person: Passenger
31
+ attributes:
32
+ person:
33
+ name: Full Name
34
+ article:
35
+ title: Article Title
36
+ body: Main Content
37
+ ```
38
+
39
+ Attribute names may also be changed globally, or under `activerecord`:
40
+
41
+ ```yml
42
+ en:
43
+ attributes:
44
+ model_name:
45
+ model_field1: field name1
46
+ model_field2: field name2
47
+ activerecord:
48
+ attributes:
49
+ namespace/article:
50
+ title: AR Namespaced Title
51
+ namespace_article:
52
+ title: Old Ransack Namespaced Title
53
+ ```
@@ -0,0 +1,41 @@
1
+ ---
2
+ sidebar_position: 5
3
+ title: Merging searches
4
+ ---
5
+
6
+ To find records that match multiple searches, it's possible to merge all the ransack search conditions into an ActiveRecord relation to perform a single query. In order to avoid conflicts between joined table names it's necessary to set up a shared context to track table aliases used across all the conditions before initializing the searches:
7
+
8
+ ```ruby
9
+ shared_context = Ransack::Context.for(Person)
10
+
11
+ search_parents = Person.ransack(
12
+ { parent_name_eq: "A" }, context: shared_context
13
+ )
14
+
15
+ search_children = Person.ransack(
16
+ { children_name_eq: "B" }, context: shared_context
17
+ )
18
+
19
+ shared_conditions = [search_parents, search_children].map { |search|
20
+ Ransack::Visitor.new.accept(search.base)
21
+ }
22
+
23
+ Person.joins(shared_context.join_sources)
24
+ .where(shared_conditions.reduce(&:or))
25
+ .to_sql
26
+ ```
27
+ Produces:
28
+ ```sql
29
+ SELECT "people".*
30
+ FROM "people"
31
+ LEFT OUTER JOIN "people" "parents_people"
32
+ ON "parents_people"."id" = "people"."parent_id"
33
+ LEFT OUTER JOIN "people" "children_people"
34
+ ON "children_people"."parent_id" = "people"."id"
35
+ WHERE (
36
+ ("parents_people"."name" = 'A' OR "children_people"."name" = 'B')
37
+ )
38
+ ORDER BY "people"."id" DESC
39
+ ```
40
+
41
+ Admittedly this is not as simple as it should be, but it's workable for now. (Implementing [issue 417](https://github.com/activerecord-hackery/ransack/issues/417) could make this more straightforward.)
@@ -0,0 +1,428 @@
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 authorized on any column of your model
130
+ and no class methods/scopes are whitelisted.
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 are implemented in Ransack:
141
+
142
+ ```ruby
143
+ # `ransackable_attributes` by default returns all column names
144
+ # and any defined ransackers as an array of strings.
145
+ # For overriding with a whitelist array of strings.
146
+ #
147
+ def ransackable_attributes(auth_object = nil)
148
+ column_names + _ransackers.keys
149
+ end
150
+
151
+ # `ransackable_associations` by default returns the names
152
+ # of all associations as an array of strings.
153
+ # For overriding with a whitelist array of strings.
154
+ #
155
+ def ransackable_associations(auth_object = nil)
156
+ reflect_on_all_associations.map { |a| a.name.to_s }
157
+ end
158
+
159
+ # `ransortable_attributes` by default returns the names
160
+ # of all attributes available for sorting as an array of strings.
161
+ # For overriding with a whitelist array of strings.
162
+ #
163
+ def ransortable_attributes(auth_object = nil)
164
+ ransackable_attributes(auth_object)
165
+ end
166
+
167
+ # `ransackable_scopes` by default returns an empty array
168
+ # i.e. no class methods/scopes are authorized.
169
+ # For overriding with a whitelist array of *symbols*.
170
+ #
171
+ def ransackable_scopes(auth_object = nil)
172
+ []
173
+ end
174
+ ```
175
+
176
+ Any values not returned from these methods will be ignored by Ransack, i.e.
177
+ they are not authorized.
178
+
179
+ All four methods can receive a single optional parameter, `auth_object`. When
180
+ you call the search or ransack method on your model, you can provide a value
181
+ for an `auth_object` key in the options hash which can be used by your own
182
+ overridden methods.
183
+
184
+ Here is an example that puts all this together, adapted from
185
+ [this blog post by Ernie Miller](https://ernie.io/2012/05/11/why-your-ruby-class-macros-might-suck-mine-did/).
186
+ In an `Article` model, add the following `ransackable_attributes` class method
187
+ (preferably private):
188
+
189
+ ```ruby
190
+ class Article < ActiveRecord::Base
191
+ def self.ransackable_attributes(auth_object = nil)
192
+ if auth_object == :admin
193
+ # whitelist all attributes for admin
194
+ super
195
+ else
196
+ # whitelist only the title and body attributes for other users
197
+ super & %w(title body)
198
+ end
199
+ end
200
+
201
+ private_class_method :ransackable_attributes
202
+ end
203
+ ```
204
+
205
+ Here is example code for the `articles_controller`:
206
+
207
+ ```ruby
208
+ class ArticlesController < ApplicationController
209
+ def index
210
+ @q = Article.ransack(params[:q], auth_object: set_ransack_auth_object)
211
+ @articles = @q.result
212
+ end
213
+
214
+ private
215
+
216
+ def set_ransack_auth_object
217
+ current_user.admin? ? :admin : nil
218
+ end
219
+ end
220
+ ```
221
+
222
+ Trying it out in `rails console`:
223
+
224
+ ```ruby
225
+ > Article
226
+ => Article(id: integer, person_id: integer, title: string, body: text)
227
+
228
+ > Article.ransackable_attributes
229
+ => ["title", "body"]
230
+
231
+ > Article.ransackable_attributes(:admin)
232
+ => ["id", "person_id", "title", "body"]
233
+
234
+ > Article.ransack(id_eq: 1).result.to_sql
235
+ => SELECT "articles".* FROM "articles" # Note that search param was ignored!
236
+
237
+ > Article.ransack({ id_eq: 1 }, { auth_object: nil }).result.to_sql
238
+ => SELECT "articles".* FROM "articles" # Search param still ignored!
239
+
240
+ > Article.ransack({ id_eq: 1 }, { auth_object: :admin }).result.to_sql
241
+ => SELECT "articles".* FROM "articles" WHERE "articles"."id" = 1
242
+ ```
243
+
244
+ That's it! Now you know how to whitelist/blacklist various elements in Ransack.
245
+
246
+ ### Handling unknown predicates or attributes
247
+
248
+ By default, Ransack will ignore any unknown predicates or attributes:
249
+
250
+ ```ruby
251
+ Article.ransack(unknown_attr_eq: 'Ernie').result.to_sql
252
+ => SELECT "articles".* FROM "articles"
253
+ ```
254
+
255
+ Ransack may be configured to raise an error if passed an unknown predicate or
256
+ attributes, by setting the `ignore_unknown_conditions` option to `false` in your
257
+ Ransack initializer file at `config/initializers/ransack.rb`:
258
+
259
+ ```ruby
260
+ Ransack.configure do |c|
261
+ # Raise errors if a query contains an unknown predicate or attribute.
262
+ # Default is true (do not raise error on unknown conditions).
263
+ c.ignore_unknown_conditions = false
264
+ end
265
+ ```
266
+
267
+ ```ruby
268
+ Article.ransack(unknown_attr_eq: 'Ernie')
269
+ # ArgumentError (Invalid search term unknown_attr_eq)
270
+ ```
271
+
272
+ As an alternative to setting a global configuration option, the `.ransack!`
273
+ class method also raises an error if passed an unknown condition:
274
+
275
+ ```ruby
276
+ Article.ransack!(unknown_attr_eq: 'Ernie')
277
+ # ArgumentError: Invalid search term unknown_attr_eq
278
+ ```
279
+
280
+ This is equivalent to the `ignore_unknown_conditions` configuration option,
281
+ except it may be applied on a case-by-case basis.
282
+
283
+ ### Using Scopes/Class Methods
284
+
285
+ Continuing on from the preceding section, searching by scopes requires defining
286
+ a whitelist of `ransackable_scopes` on the model class. The whitelist should be
287
+ an array of *symbols*. By default, all class methods (e.g. scopes) are ignored.
288
+ Scopes will be applied for matching `true` values, or for given values if the
289
+ scope accepts a value:
290
+
291
+ ```ruby
292
+ class Employee < ActiveRecord::Base
293
+ scope :activated, ->(boolean = true) { where(active: boolean) }
294
+ scope :salary_gt, ->(amount) { where('salary > ?', amount) }
295
+
296
+ # Scopes are just syntactical sugar for class methods, which may also be used:
297
+
298
+ def self.hired_since(date)
299
+ where('start_date >= ?', date)
300
+ end
301
+
302
+ def self.ransackable_scopes(auth_object = nil)
303
+ if auth_object.try(:admin?)
304
+ # allow admin users access to all three methods
305
+ %i(activated hired_since salary_gt)
306
+ else
307
+ # allow other users to search on `activated` and `hired_since` only
308
+ %i(activated hired_since)
309
+ end
310
+ end
311
+ end
312
+
313
+ Employee.ransack({ activated: true, hired_since: '2013-01-01' })
314
+
315
+ Employee.ransack({ salary_gt: 100_000 }, { auth_object: current_user })
316
+ ```
317
+
318
+ In Rails 3 and 4, if the `true` value is being passed via url params or some
319
+ other mechanism that will convert it to a string, the true value may not be
320
+ passed to the ransackable scope unless you wrap it in an array
321
+ (i.e. `activated: ['true']`). Ransack will take care of changing 'true' into a
322
+ boolean. This is currently resolved in Rails 5 :smiley:
323
+
324
+ However, perhaps you have `user_id: [1]` and you do not want Ransack to convert
325
+ 1 into a boolean. (Values sanitized to booleans can be found in the
326
+ [constants.rb](https://github.com/activerecord-hackery/ransack/blob/master/lib/ransack/constants.rb#L28)).
327
+ To turn this off globally, and handle type conversions yourself, set
328
+ `sanitize_custom_scope_booleans` to false in an initializer file like
329
+ config/initializers/ransack.rb:
330
+
331
+ ```ruby
332
+ Ransack.configure do |c|
333
+ c.sanitize_custom_scope_booleans = false
334
+ end
335
+ ```
336
+
337
+ To turn this off on a per-scope basis Ransack adds the following method to
338
+ `ActiveRecord::Base` that you can redefine to selectively override sanitization:
339
+
340
+ `ransackable_scopes_skip_sanitize_args`
341
+
342
+ Add the scope you wish to bypass this behavior to ransackable_scopes_skip_sanitize_args:
343
+
344
+ ```ruby
345
+ def self.ransackable_scopes_skip_sanitize_args
346
+ [:scope_to_skip_sanitize_args]
347
+ end
348
+ ```
349
+
350
+ Scopes are a recent addition to Ransack and currently have a few caveats:
351
+ First, a scope involving child associations needs to be defined in the parent
352
+ table model, not in the child model. Second, scopes with an array as an
353
+ argument are not easily usable yet, because the array currently needs to be
354
+ wrapped in an array to function (see
355
+ [this issue](https://github.com/activerecord-hackery/ransack/issues/404)),
356
+ which is not compatible with Ransack form helpers. For this use case, it may be
357
+ better for now to use [ransackers](https://activerecord-hackery.github.io/ransack/going-further/ransackers) instead,
358
+ where feasible. Pull requests with solutions and tests are welcome!
359
+
360
+ ### Grouping queries by OR instead of AND
361
+
362
+ The default `AND` grouping can be changed to `OR` by adding `m: 'or'` to the
363
+ query hash.
364
+
365
+ You can easily try it in your controller code by changing `params[:q]` in the
366
+ `index` action to `params[:q].try(:merge, m: 'or')` as follows:
367
+
368
+ ```ruby
369
+ def index
370
+ @q = Artist.ransack(params[:q].try(:merge, m: 'or'))
371
+ @artists = @q.result
372
+ end
373
+ ```
374
+ Normally, if you wanted users to be able to toggle between `AND` and `OR`
375
+ query grouping, you would probably set up your search form so that `m` was in
376
+ the URL params hash, but here we assigned `m` manually just to try it out
377
+ quickly.
378
+
379
+ Alternatively, trying it in the Rails console:
380
+
381
+ ```ruby
382
+ artists = Artist.ransack(name_cont: 'foo', style_cont: 'bar', m: 'or')
383
+ => Ransack::Search<class: Artist, base: Grouping <conditions: [
384
+ Condition <attributes: ["name"], predicate: cont, values: ["foo"]>,
385
+ Condition <attributes: ["style"], predicate: cont, values: ["bar"]>
386
+ ], combinator: or>>
387
+
388
+ artists.result.to_sql
389
+ => "SELECT \"artists\".* FROM \"artists\"
390
+ WHERE ((\"artists\".\"name\" ILIKE '%foo%'
391
+ OR \"artists\".\"style\" ILIKE '%bar%'))"
392
+ ```
393
+
394
+ The combinator becomes `or` instead of the default `and`, and the SQL query
395
+ becomes `WHERE...OR` instead of `WHERE...AND`.
396
+
397
+ This works with associations as well. Imagine an Artist model that has many
398
+ Memberships, and many Musicians through Memberships:
399
+
400
+ ```ruby
401
+ artists = Artist.ransack(name_cont: 'foo', musicians_email_cont: 'bar', m: 'or')
402
+ => Ransack::Search<class: Artist, base: Grouping <conditions: [
403
+ Condition <attributes: ["name"], predicate: cont, values: ["foo"]>,
404
+ Condition <attributes: ["musicians_email"], predicate: cont, values: ["bar"]>
405
+ ], combinator: or>>
406
+
407
+ artists.result.to_sql
408
+ => "SELECT \"artists\".* FROM \"artists\"
409
+ LEFT OUTER JOIN \"memberships\"
410
+ ON \"memberships\".\"artist_id\" = \"artists\".\"id\"
411
+ LEFT OUTER JOIN \"musicians\"
412
+ ON \"musicians\".\"id\" = \"memberships\".\"musician_id\"
413
+ WHERE ((\"artists\".\"name\" ILIKE '%foo%'
414
+ OR \"musicians\".\"email\" ILIKE '%bar%'))"
415
+ ```
416
+
417
+ ### Using SimpleForm
418
+
419
+ If you would like to combine the Ransack and SimpleForm form builders, set the
420
+ `RANSACK_FORM_BUILDER` environment variable before Rails boots up, e.g. in
421
+ `config/application.rb` before `require 'rails/all'` as shown below (and add
422
+ `gem 'simple_form'` in your Gemfile).
423
+
424
+ ```ruby
425
+ require File.expand_path('../boot', __FILE__)
426
+ ENV['RANSACK_FORM_BUILDER'] = '::SimpleForm::FormBuilder'
427
+ require 'rails/all'
428
+ ```
@@ -0,0 +1,46 @@
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
+ ```