ransack 2.3.0 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (159) 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 +102 -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 +128 -0
  10. data/.nojekyll +0 -0
  11. data/.rubocop.yml +47 -0
  12. data/CHANGELOG.md +228 -1
  13. data/CONTRIBUTING.md +47 -22
  14. data/Gemfile +24 -10
  15. data/README.md +49 -917
  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 +79 -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 +40 -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 +8790 -0
  66. data/lib/polyamorous/activerecord_6.1_ruby_2/join_association.rb +70 -0
  67. data/{polyamorous/lib/polyamorous/activerecord_6.0_ruby_2 → lib/polyamorous/activerecord_6.1_ruby_2}/join_dependency.rb +23 -12
  68. data/lib/polyamorous/activerecord_6.1_ruby_2/reflection.rb +11 -0
  69. data/lib/polyamorous/activerecord_7.0_ruby_2/join_association.rb +1 -0
  70. data/lib/polyamorous/activerecord_7.0_ruby_2/join_dependency.rb +1 -0
  71. data/lib/polyamorous/activerecord_7.0_ruby_2/reflection.rb +1 -0
  72. data/lib/polyamorous/activerecord_7.1_ruby_2/join_association.rb +1 -0
  73. data/lib/polyamorous/activerecord_7.1_ruby_2/join_dependency.rb +1 -0
  74. data/lib/polyamorous/activerecord_7.1_ruby_2/reflection.rb +1 -0
  75. data/{polyamorous/lib → lib/polyamorous}/polyamorous.rb +3 -8
  76. data/lib/ransack/adapters/active_record/base.rb +83 -10
  77. data/lib/ransack/adapters/active_record/context.rb +59 -115
  78. data/lib/ransack/configuration.rb +53 -10
  79. data/lib/ransack/constants.rb +126 -7
  80. data/lib/ransack/context.rb +34 -5
  81. data/lib/ransack/helpers/form_builder.rb +11 -17
  82. data/lib/ransack/helpers/form_helper.rb +14 -5
  83. data/lib/ransack/helpers.rb +1 -1
  84. data/lib/ransack/locale/sk.yml +70 -0
  85. data/lib/ransack/locale/sv.yml +70 -0
  86. data/lib/ransack/nodes/attribute.rb +3 -3
  87. data/lib/ransack/nodes/condition.rb +87 -8
  88. data/lib/ransack/nodes/grouping.rb +4 -4
  89. data/lib/ransack/nodes/node.rb +1 -1
  90. data/lib/ransack/nodes/sort.rb +3 -3
  91. data/lib/ransack/nodes/value.rb +3 -3
  92. data/lib/ransack/predicate.rb +3 -2
  93. data/lib/ransack/ransacker.rb +1 -1
  94. data/lib/ransack/search.rb +15 -7
  95. data/lib/ransack/translate.rb +6 -6
  96. data/lib/ransack/version.rb +1 -1
  97. data/lib/ransack/visitor.rb +38 -2
  98. data/lib/ransack.rb +6 -10
  99. data/ransack.gemspec +9 -24
  100. data/spec/blueprints/articles.rb +1 -1
  101. data/spec/blueprints/comments.rb +1 -1
  102. data/spec/blueprints/notes.rb +1 -1
  103. data/spec/blueprints/tags.rb +1 -1
  104. data/spec/console.rb +5 -5
  105. data/spec/helpers/polyamorous_helper.rb +2 -17
  106. data/spec/helpers/ransack_helper.rb +1 -1
  107. data/spec/polyamorous/activerecord_compatibility_spec.rb +15 -0
  108. data/spec/{ransack → polyamorous}/join_association_spec.rb +3 -1
  109. data/spec/{ransack → polyamorous}/join_dependency_spec.rb +0 -16
  110. data/spec/ransack/adapters/active_record/base_spec.rb +109 -16
  111. data/spec/ransack/adapters/active_record/context_spec.rb +19 -18
  112. data/spec/ransack/configuration_spec.rb +33 -9
  113. data/spec/ransack/helpers/form_builder_spec.rb +8 -8
  114. data/spec/ransack/helpers/form_helper_spec.rb +109 -20
  115. data/spec/ransack/nodes/condition_spec.rb +37 -0
  116. data/spec/ransack/nodes/grouping_spec.rb +2 -2
  117. data/spec/ransack/nodes/value_spec.rb +115 -0
  118. data/spec/ransack/predicate_spec.rb +75 -2
  119. data/spec/ransack/search_spec.rb +239 -38
  120. data/spec/ransack/translate_spec.rb +1 -1
  121. data/spec/spec_helper.rb +9 -5
  122. data/spec/support/schema.rb +83 -12
  123. metadata +105 -195
  124. data/.travis.yml +0 -49
  125. data/lib/ransack/adapters/active_record/ransack/constants.rb +0 -116
  126. data/lib/ransack/adapters/active_record/ransack/context.rb +0 -60
  127. data/lib/ransack/adapters/active_record/ransack/nodes/condition.rb +0 -61
  128. data/lib/ransack/adapters/active_record/ransack/translate.rb +0 -8
  129. data/lib/ransack/adapters/active_record/ransack/visitor.rb +0 -47
  130. data/lib/ransack/adapters.rb +0 -64
  131. data/lib/ransack/nodes.rb +0 -8
  132. data/polyamorous/lib/polyamorous/activerecord_5.0_ruby_2/join_association.rb +0 -2
  133. data/polyamorous/lib/polyamorous/activerecord_5.0_ruby_2/join_dependency.rb +0 -2
  134. data/polyamorous/lib/polyamorous/activerecord_5.1_ruby_2/join_association.rb +0 -31
  135. data/polyamorous/lib/polyamorous/activerecord_5.1_ruby_2/join_dependency.rb +0 -112
  136. data/polyamorous/lib/polyamorous/activerecord_5.2.0_ruby_2/join_association.rb +0 -31
  137. data/polyamorous/lib/polyamorous/activerecord_5.2.0_ruby_2/join_dependency.rb +0 -112
  138. data/polyamorous/lib/polyamorous/activerecord_5.2.0_ruby_2/reflection.rb +0 -12
  139. data/polyamorous/lib/polyamorous/activerecord_5.2.1_ruby_2/join_association.rb +0 -22
  140. data/polyamorous/lib/polyamorous/activerecord_5.2.1_ruby_2/join_dependency.rb +0 -81
  141. data/polyamorous/lib/polyamorous/activerecord_5.2.1_ruby_2/reflection.rb +0 -2
  142. data/polyamorous/lib/polyamorous/activerecord_6.0_ruby_2/join_association.rb +0 -2
  143. data/polyamorous/lib/polyamorous/activerecord_6.0_ruby_2/reflection.rb +0 -2
  144. data/polyamorous/lib/polyamorous/activerecord_6.1_ruby_2/join_association.rb +0 -2
  145. data/polyamorous/lib/polyamorous/activerecord_6.1_ruby_2/join_dependency.rb +0 -2
  146. data/polyamorous/lib/polyamorous/activerecord_6.1_ruby_2/reflection.rb +0 -2
  147. data/polyamorous/lib/polyamorous/version.rb +0 -3
  148. data/polyamorous/polyamorous.gemspec +0 -35
  149. /data/{logo → docs/static/logo}/ransack-h.png +0 -0
  150. /data/{logo → docs/static/logo}/ransack-h.svg +0 -0
  151. /data/{logo → docs/static/logo}/ransack-v.png +0 -0
  152. /data/{logo → docs/static/logo}/ransack-v.svg +0 -0
  153. /data/{logo → docs/static/logo}/ransack.png +0 -0
  154. /data/{logo → docs/static/logo}/ransack.svg +0 -0
  155. /data/{polyamorous/lib → lib}/polyamorous/join.rb +0 -0
  156. /data/{polyamorous/lib → lib}/polyamorous/swapping_reflection_class.rb +0 -0
  157. /data/{polyamorous/lib → lib}/polyamorous/tree_node.rb +0 -0
  158. /data/lib/ransack/{adapters/active_record.rb → active_record.rb} +0 -0
  159. /data/spec/{ransack → polyamorous}/join_spec.rb +0 -0
@@ -0,0 +1,79 @@
1
+ ---
2
+ title: Sorting
3
+ ---
4
+
5
+
6
+ # Sorting
7
+
8
+ ## Sorting in the View
9
+
10
+ You can add a form to capture sorting and filtering options together.
11
+
12
+ ```erb
13
+ <div class="filters" id="filtersSidebar">
14
+ <header class="filters-header">
15
+ <div class="filters-header-content">
16
+ <h3>Filters</h3>
17
+ </div>
18
+ </header>
19
+
20
+ <div class="filters-content">
21
+ <%= search_form_for @q,
22
+ class: 'form',
23
+ url: articles_path,
24
+ html: { autocomplete: 'off', autocapitalize: 'none' } do |f| %>
25
+
26
+ <div class="form-group">
27
+ <%= f.label :title_cont, t('Filter_by_keyword') %>
28
+ <%= f.search_field :title_cont %>
29
+ </div>
30
+
31
+ <%= render partial: 'filters/date_title_sort', locals: { f: f } %>
32
+
33
+ <div class="form-group">
34
+ <%= f.label :grade_level_gteq, t('Grade_level') %> >=
35
+ <%= f.search_field :grade_level_gteq %>
36
+ </div>
37
+
38
+ <div class="form-group">
39
+ <%= f.label :readability_gteq, t('Readability') %> >=
40
+ <%= f.search_field :readability_gteq %>
41
+ </div>
42
+
43
+ <div class="form-group">
44
+ <i><%= @articles.total_count %> articles</i>
45
+ </div>
46
+
47
+ <div class="form-group">
48
+ <hr/>
49
+ <div class="filters-header-content">
50
+ <%= link_to request.path, class: 'form-link' do %>
51
+ <i class="far fa-undo icon-l"></i><%= t('Clear_all') %>
52
+ <% end %>
53
+
54
+ <%= f.submit t('Filter'), class: 'btn btn-primary' %>
55
+ </div>
56
+ </div>
57
+ <% end %>
58
+ </div>
59
+ </div>
60
+ ```
61
+
62
+
63
+ ## Sorting in the Controller
64
+
65
+ To specify a default search sort field + order in the controller `index`:
66
+
67
+ ```ruby
68
+ @search = Post.ransack(params[:q])
69
+ @search.sorts = 'name asc' if @search.sorts.empty?
70
+ @posts = @search.result.paginate(page: params[:page], per_page: 20)
71
+ ```
72
+
73
+ Multiple sorts can be set by:
74
+
75
+ ```ruby
76
+ @search = Post.ransack(params[:q])
77
+ @search.sorts = ['name asc', 'created_at desc'] if @search.sorts.empty?
78
+ @posts = @search.result.paginate(page: params[:page], per_page: 20)
79
+ ```
@@ -0,0 +1,282 @@
1
+ ---
2
+ title: Using Predicates
3
+ ---
4
+
5
+ The primary method of searching in Ransack is by using what is known as *predicates*.
6
+
7
+ Predicates are used within Ransack search queries to determine what information to
8
+ match. For instance, the `cont` predicate will check to see if an attribute called
9
+ "first_name" contains a value using a wildcard query:
10
+
11
+ ```ruby
12
+ >> User.ransack(first_name_cont: 'Rya').result.to_sql
13
+ => SELECT "users".* FROM "users" WHERE ("users"."first_name" LIKE '%Rya%')
14
+ ```
15
+
16
+ You can also combine predicates for OR queries:
17
+ ```ruby
18
+ >> User.ransack(first_name_or_last_name_cont: 'Rya').result.to_sql
19
+ => SELECT "users".* FROM "users" WHERE ("users"."first_name" LIKE '%Rya%'
20
+ OR "users"."last_name" LIKE '%Rya%')
21
+ ```
22
+
23
+ The syntax for `OR` queries on an associated model is not immediately obvious, but makes sense. Assuming a `User` `has_one` `Account` and the `Account` has `attributes` `foo` and `bar`:
24
+
25
+ ```ruby
26
+ >> User.ransack(account_foo_or_account_bar_cont: 'val').result.to_sql
27
+ => SELECT "users".* FROM "users" INNER JOIN accounts ON accounts.user_id = users.id WHERE ("accounts.foo LIKE '%val%' OR accounts.bar LIKE '%val%')
28
+ ```
29
+
30
+ Below is a list of the built-in predicates of Ransack and their opposites. You may already
31
+ be familiar with some of the predicates, as they also exist in the ARel library.
32
+
33
+ If you want to add your own, please
34
+ see the [[Custom-Predicates|Custom Predicates]] page.
35
+
36
+ **Please note:** any attempt to use a predicate for an attribute that does not exist will
37
+ *silently fail*. For instance, this will not work when there is no `name` attribute:
38
+
39
+ ```ruby
40
+ >> User.ransack(name_cont: 'Rya').result.to_sql
41
+ => "SELECT "users".* FROM "users"
42
+ ```
43
+
44
+ ## eq (equals)
45
+
46
+ The `eq` predicate returns all records where a field is *exactly* equal to a given value:
47
+
48
+ ```ruby
49
+ >> User.ransack(first_name_eq: 'Ryan').result.to_sql
50
+ => SELECT "users".* FROM "users" WHERE "users"."first_name" = 'Ryan'
51
+ ```
52
+
53
+ **Opposite: `not_eq`**
54
+
55
+ ## matches
56
+
57
+ The `matches` predicate returns all records where a field is like a given value:
58
+
59
+ ```ruby
60
+ >> User.ransack(first_name_matches: 'Ryan').result.to_sql
61
+ => SELECT "users".* FROM "users" WHERE ("users"."first_name" LIKE 'Ryan')
62
+ ```
63
+
64
+ On Postgres, the case-insensitive ILIKE will be used.
65
+
66
+ **Opposite: `does_not_match`**
67
+
68
+ *Note: If you want to do wildcard matching, you may be looking for the `cont`/`not_cont`
69
+ predicates instead.*
70
+
71
+ ## lt (less than)
72
+
73
+ The `lt` predicate returns all records where a field is less than a given value:
74
+
75
+ ```ruby
76
+ >> User.ransack(age_lt: 25).result.to_sql
77
+ => SELECT "users".* FROM "users" WHERE ("users"."age" < 25)
78
+ ```
79
+
80
+ **Opposite: `gteq` (greater than or equal to)**
81
+
82
+ ## lteq (less than or equal to)
83
+
84
+ The `lteq` predicate returns all records where a field is less than *or equal to* a given value:
85
+
86
+ ```ruby
87
+ >> User.ransack(age_lteq: 25).result.to_sql
88
+ => SELECT "users".* FROM "users" WHERE ("users"."age" <= 25)
89
+ ```
90
+
91
+ **Opposite: `gt` (greater than)**
92
+
93
+ ## in
94
+
95
+ The `in` predicate returns all records where a field is within a specified list:
96
+
97
+ ```ruby
98
+ >> User.ransack(age_in: 20..25).result.to_sql
99
+ => SELECT "users".* FROM "users" WHERE "users"."age" IN (20, 21, 22, 23, 24, 25)
100
+ ```
101
+
102
+ It can also take an array:
103
+
104
+ ```ruby
105
+ >> User.ransack(age_in: [20, 21, 22, 23, 24, 25]).result.to_sql
106
+ => SELECT "users".* FROM "users" WHERE "users"."age" IN (20, 21, 22, 23, 24, 25)
107
+ ```
108
+
109
+ **Opposite: `not_in`**
110
+
111
+ ## cont
112
+
113
+ The `cont` predicate returns all records where a field contains a given value:
114
+
115
+ ```ruby
116
+ >> User.ransack(first_name_cont: 'Rya').result.to_sql
117
+ => SELECT "users".* FROM "users" WHERE ("users"."first_name" LIKE '%Rya%')
118
+ ```
119
+
120
+ **Opposite: `not_cont`**
121
+
122
+ ## cont_any (contains any)
123
+
124
+ The `cont_any` predicate returns all records where a field contains any of the given values:
125
+
126
+ ```ruby
127
+ >> User.ransack(first_name_cont_any: %w(Rya Lis)).result.to_sql
128
+ => SELECT "users".* FROM "users" WHERE (("users"."first_name" LIKE '%Rya%' OR "users"."first_name" LIKE '%Lis%'))
129
+ ```
130
+
131
+ **Opposite: `not_cont_any`**
132
+
133
+
134
+ ## cont_all (contains all)
135
+
136
+ The `cont_all` predicate returns all records where a field contains all of the given values:
137
+
138
+ ```ruby
139
+ >> User.ransack(city_cont_all: %w(Grand Rapids)).result.to_sql
140
+ => SELECT "users".* FROM "users" WHERE (("users"."city" LIKE '%Grand%' AND "users"."city" LIKE '%Rapids%'))
141
+ ```
142
+
143
+ **Opposite: `not_cont_all`**
144
+
145
+
146
+ ## i_cont
147
+
148
+ The `i_cont` case-insensitive predicate returns all records where a field contains a given value and ignores case:
149
+
150
+ ```ruby
151
+ >> User.ransack(first_name_i_cont: 'Rya').result.to_sql
152
+ => SELECT "users".* FROM "users" WHERE (LOWER("users"."first_name") LIKE '%rya%')
153
+ ```
154
+
155
+ **Opposite: `not_i_cont`**
156
+
157
+ ## i_cont_any
158
+
159
+ The `i_cont_any` case-insensitive predicate returns all records where a field contains any of the given values and ignores case:
160
+
161
+ ```ruby
162
+ >> User.ransack(first_name_i_cont_any: %w(Rya Lis)).result.to_sql
163
+ => SELECT "users".* FROM "users" WHERE ((LOWER("users"."first_name") LIKE '%rya%' OR LOWER("users"."first_name") LIKE '%lis%'))
164
+ ```
165
+
166
+ **Opposite: `not_i_cont_any`**
167
+
168
+
169
+ ## i_cont_all
170
+
171
+ The `i_cont_all` case-insensitive predicate returns all records where a field contains all of the given values and ignores case:
172
+
173
+ ```ruby
174
+ >> User.ransack(city_i_cont_all: %w(Grand Rapids)).result.to_sql
175
+ => SELECT "users".* FROM "users" WHERE ((LOWER("users"."city") LIKE '%grand%' AND LOWER("users"."city") LIKE '%rapids%'))
176
+ ```
177
+
178
+ **Opposite: `not_i_cont_all`**
179
+
180
+ ## start (starts with)
181
+
182
+ The `start` predicate returns all records where a field begins with a given value:
183
+
184
+ ```ruby
185
+ >> User.ransack(first_name_start: 'Rya').result.to_sql
186
+ => SELECT "users".* FROM "users" WHERE ("users"."first_name" LIKE 'Rya%')
187
+ ```
188
+
189
+ **Opposite: `not_start`**
190
+
191
+ ## end (ends with)
192
+
193
+ The `end` predicate returns all records where a field ends with a given value:
194
+
195
+ ```ruby
196
+ >> User.ransack(first_name_end: 'yan').result.to_sql
197
+ => SELECT "users".* FROM "users" WHERE ("users"."first_name" LIKE '%yan')
198
+ ```
199
+
200
+ **Opposite: `not_end`**
201
+
202
+ ## true
203
+
204
+ The `true` predicate returns all records where a field is true. The '1' indicates that
205
+ to Ransack that you indeed want to check the truthiness of this field. The other truthy
206
+ values are 'true', 'TRUE', 't' or 'T'.
207
+
208
+ ```ruby
209
+ >> User.ransack(awesome_true: '1').result.to_sql
210
+ => SELECT "users".* FROM "users" WHERE ("users"."awesome" = 't')
211
+ ```
212
+
213
+ *Note: different database systems use different values to represent truth. In the above
214
+ example, we are using SQLite3.*
215
+
216
+ **Opposite: `not_true`**
217
+
218
+ ## false
219
+
220
+ The `false` predicate returns all records where a field is false.
221
+
222
+ ```ruby
223
+ >> User.ransack(awesome_false: '1').result.to_sql
224
+ => SELECT "users".* FROM "users" WHERE ("users"."awesome" = 'f')
225
+ ```
226
+
227
+ **Opposite: `not_false`**
228
+
229
+ *Note: the `false` predicate may be considered the opposite of the `true` predicate if the field does not contain `null` values. Otherwise, use `not_false`.*
230
+
231
+ ## present
232
+
233
+ The `present` predicate returns all records where a field is present (not null and not a
234
+ blank string).
235
+
236
+ ```ruby
237
+ >> User.ransack(first_name_present: '1').result.to_sql
238
+ => SELECT "users".* FROM "users" WHERE (("users"."first_name" IS NOT NULL AND "users"."first_name" != ''))
239
+ ```
240
+
241
+ **Opposite: `blank`**
242
+
243
+ ## null
244
+
245
+ The `null` predicate returns all records where a field is null:
246
+
247
+ ```ruby
248
+ >> User.ransack(first_name_null: 1).result.to_sql
249
+ => SELECT "users".* FROM "users" WHERE "users"."first_name" IS NULL
250
+ ```
251
+
252
+ **Opposite: `not_null`**
253
+
254
+ # URL parameter structure
255
+
256
+ The search parameters are passed to ransack as a hash. The URL representation of this hash uses the bracket notation: ```hash_name[key]=value```. The hash_name is the parameter which is defined in the controller, for instance ```q```. The key is the attribute and search predicate compound, for instance ```first_name_cont```, the value is the search parameter. When searching without using the search form helpers this URL structure needs to be created manually.
257
+
258
+ For example, the URL layout for searching and sorting users could looks like this:
259
+
260
+ ```
261
+ /users.json?q[first_name_cont]=pete&q[last_name_cont]=jack&q[s]=created_at+desc
262
+ ```
263
+
264
+ _Note that the sorting parameter ```s``` is nested within the ```q``` hash._
265
+
266
+ When using JavaScript to create such a URL, a matching jQuery request could look like this:
267
+
268
+ ```javascript
269
+ $.ajax({
270
+ url: "/users.json",
271
+ data: {
272
+ q: {
273
+ first_name_cont: "pete",
274
+ last_name_cont: "jack",
275
+ s: "created_at desc"
276
+ }
277
+ },
278
+ success: function (data){
279
+ console.log(data);
280
+ }
281
+ });
282
+ ```
@@ -0,0 +1,4 @@
1
+ {
2
+ "label": "Going further",
3
+ "position": 2
4
+ }
@@ -0,0 +1,114 @@
1
+ ---
2
+ title: Acts-as-taggable-on
3
+ sidebar_position: 13
4
+ ---
5
+
6
+ ## Using Acts As Taggable On
7
+
8
+ If you have an `ActiveRecord` model and you're using [acts-as-taggable-on](https://github.com/mbleigh/acts-as-taggable-on),
9
+ chances are you might want to search on tagged fields. Follow the instructions to install the gem and then set up your project files.
10
+
11
+ ### Configure the model
12
+
13
+ `app/models/tasks.rb`
14
+
15
+ You can call the tagging field anything you like, it just needs to be plural. No migration is needed as this is stored in the internal ActsAsTaggable tables (`tags` and `taggings`).
16
+
17
+ ```ruby
18
+ class Task < ApplicationRecord
19
+ acts_as_taggable_on :projects
20
+ end
21
+ ```
22
+
23
+ ### Controller
24
+
25
+ Add a field to strong params in the controller. Use the singular name with `_list`.
26
+
27
+ `app/controllers/tasks_controller.rb`
28
+
29
+ ```ruby
30
+ def strong_params
31
+ params
32
+ .require(:tasks)
33
+ .permit(:task, :example_field, :project_list)
34
+ ```
35
+
36
+ ### Form
37
+
38
+ We need to `send` the tag fieldname to our model, also using the singular naming.
39
+
40
+ ```erb
41
+ <div class='form-group'>
42
+ <%= f.label :project_list %>
43
+ <%= f.text_field :project_list, value: @task.send(:project_list).to_s %>
44
+ </div>
45
+ ```
46
+
47
+ Now we can collect our data via the form, with tags separated by commas.
48
+
49
+ ## Ransack Search
50
+
51
+ Imagine you have the following two instances of `Task`:
52
+
53
+ ```ruby
54
+ { id: 1, name: 'Clean up my room', projects: [ 'Home', 'Personal' ] }
55
+ { id: 2, name: 'Complete math exercises', projects: [ 'Homework', 'Study' ] }
56
+ ```
57
+
58
+ When you're writing a `Ransack` search form, you can choose any of the following options:
59
+
60
+ ```erb
61
+ <%= search_form_for @search do |f| %>
62
+ <%= f.text_field :projects_name_in %> <!-- option a -->
63
+ <%= f.text_field :projects_name_eq %> <!-- option b -->
64
+ <%= f.text_field :projects_name_cont %> <!-- option c -->
65
+ <% end %>
66
+ ```
67
+
68
+ ### Option A - Match keys exactly
69
+
70
+ Option `A` will match keys exactly. This is the solution to choose if you want to distinguish 'Home' from 'Homework': searching for 'Home' will return just the `Task` with id 1. It also allows searching for more than one tag at once (comma separated):
71
+ - `Home, Personal` will return task 1
72
+ - `Home, Homework` will return task 1 and 2
73
+
74
+ ### Option B - match key combinations
75
+
76
+ Option `B` will match all keys exactly. This is the solution if you wanna search for specific combinations of tags:
77
+ - `Home` will return nothing, as there is no Task with just the `Home` tag
78
+ - `Home, Personal` will return task 1
79
+
80
+ ### Option C - match substrings
81
+
82
+ Option `C` is used to match substrings. This is useful when you don't care for the exact tag, but only for part of it:
83
+ - `Home` will return task 1 and 2 (`/Home/` matches both `"Home"` and `"Homework"`)
84
+
85
+ ### Option D - select from a list of tags
86
+
87
+ In Option `D` we allow the user to select a list of valid tags and then search against them. We use the plural name here.
88
+
89
+ ```erb
90
+ <div class='form-group'>
91
+ <%= f.label :projects_name, 'Project' %>
92
+ <%= f.select :projects_name_in, ActsAsTaggableOn::Tag.distinct.order(:name).pluck(:name) %>
93
+ </div>
94
+ ```
95
+
96
+ ## Multitenancy
97
+
98
+ ActsAsTaggableOn allows scoping of tags based on another field on the model. Suppose we have a `language` field on the model, as an effective second level key. We would adjust our model to look like this:
99
+
100
+ ```ruby
101
+ class Task < ApplicationRecord
102
+ acts_as_taggable_on :projects
103
+ acts_as_taggable_tenant :language
104
+ end
105
+ ```
106
+
107
+ The Ransack search is then filtered using the `for_tenant` method
108
+
109
+ ```erb
110
+ <div class='form-group'>
111
+ <%= f.label :projects_name, 'Project' %>
112
+ <%= f.select :projects_name_in, ActsAsTaggableOn::Tag.for_tenant('fr').distinct.order(:name).pluck(:name) %>
113
+ </div>
114
+
@@ -0,0 +1,70 @@
1
+ ---
2
+ sidebar_position: 1
3
+ title: Associations
4
+ ---
5
+
6
+ ### Associations
7
+
8
+ You can easily use Ransack to search for objects in `has_many` and `belongs_to`
9
+ associations.
10
+
11
+ Given these associations...
12
+
13
+ ```ruby
14
+ class Employee < ActiveRecord::Base
15
+ belongs_to :supervisor
16
+
17
+ # has attributes first_name:string and last_name:string
18
+ end
19
+
20
+ class Department < ActiveRecord::Base
21
+ has_many :supervisors
22
+
23
+ # has attribute title:string
24
+ end
25
+
26
+ class Supervisor < ActiveRecord::Base
27
+ belongs_to :department
28
+ has_many :employees
29
+
30
+ # has attribute last_name:string
31
+ end
32
+ ```
33
+
34
+ ... and a controller...
35
+
36
+ ```ruby
37
+ class SupervisorsController < ApplicationController
38
+ def index
39
+ @q = Supervisor.ransack(params[:q])
40
+ @supervisors = @q.result.includes(:department, :employees)
41
+ end
42
+ end
43
+ ```
44
+
45
+ ... you might set up your form like this...
46
+
47
+ ```erb
48
+ <%= search_form_for @q do |f| %>
49
+ <%= f.label :last_name_cont %>
50
+ <%= f.search_field :last_name_cont %>
51
+
52
+ <%= f.label :department_title_cont %>
53
+ <%= f.search_field :department_title_cont %>
54
+
55
+ <%= f.label :employees_first_name_or_employees_last_name_cont %>
56
+ <%= f.search_field :employees_first_name_or_employees_last_name_cont %>
57
+
58
+ <%= f.submit "search" %>
59
+ <% end %>
60
+ ...
61
+ <%= content_tag :table do %>
62
+ <%= content_tag :th, sort_link(@q, :last_name) %>
63
+ <%= content_tag :th, sort_link(@q, :department_title) %>
64
+ <%= content_tag :th, sort_link(@q, :employees_last_name) %>
65
+ <% end %>
66
+ ```
67
+
68
+ If you have trouble sorting on associations, try using an SQL string with the
69
+ pluralized table (`'departments.title'`,`'employees.last_name'`) instead of the
70
+ symbolized association (`:department_title)`, `:employees_last_name`).
@@ -0,0 +1,52 @@
1
+ ---
2
+ sidebar_position: 1
3
+ title: Custom predicates
4
+ ---
5
+
6
+ If you'd like to add your own custom Ransack predicates:
7
+
8
+ ```ruby
9
+ # config/initializers/ransack.rb
10
+
11
+ Ransack.configure do |config|
12
+ config.add_predicate 'equals_diddly', # Name your predicate
13
+ # What non-compound ARel predicate will it use? (eq, matches, etc)
14
+ arel_predicate: 'eq',
15
+ # Format incoming values as you see fit. (Default: Don't do formatting)
16
+ formatter: proc { |v| "#{v}-diddly" },
17
+ # Validate a value. An "invalid" value won't be used in a search.
18
+ # Below is default.
19
+ validator: proc { |v| v.present? },
20
+ # Should compounds be created? Will use the compound (any/all) version
21
+ # of the arel_predicate to create a corresponding any/all version of
22
+ # your predicate. (Default: true)
23
+ compounds: true,
24
+ # Force a specific column type for type-casting of supplied values.
25
+ # (Default: use type from DB column)
26
+ type: :string,
27
+ # Use LOWER(column on database).
28
+ # (Default: false)
29
+ case_insensitive: true
30
+ end
31
+ ```
32
+ You can check all Arel predicates [here](https://github.com/rails/rails/blob/main/activerecord/lib/arel/predications.rb).
33
+
34
+ If Arel does not have the predicate you are looking for, consider monkey patching it:
35
+
36
+ ```ruby
37
+ # config/initializers/ransack.rb
38
+
39
+ module Arel
40
+ module Predications
41
+ def gteq_or_null(other)
42
+ left = gteq(other)
43
+ right = eq(nil)
44
+ left.or(right)
45
+ end
46
+ end
47
+ end
48
+
49
+ Ransack.configure do |config|
50
+ config.add_predicate 'gteq_or_null', arel_predicate: 'gteq_or_null'
51
+ end
52
+ ```
@@ -0,0 +1,43 @@
1
+ ---
2
+ sidebar_position: 11
3
+ title: Documentation
4
+ ---
5
+
6
+ Ransack uses [Docusaurus](https://docusaurus.io/) for documentation. To contribute to the docs simply use the "Edit this page" link from any page to directly edit, or else pull the repo and edit locally.
7
+
8
+ ### Local Development
9
+
10
+ Switch to docs folder
11
+
12
+ ```
13
+ cd docs
14
+ ```
15
+
16
+ Install docusaurus and other dependencies
17
+
18
+ ```
19
+ yarn install
20
+ ```
21
+
22
+
23
+ Start a local development server and open up a browser window. Most changes are reflected live without having to restart the server.
24
+
25
+ ```
26
+ yarn start
27
+ ```
28
+
29
+ ### Build
30
+
31
+ ```
32
+ yarn build
33
+ ```
34
+
35
+ This command generates static content into the `build` directory and can be served using any static contents hosting service.
36
+
37
+ ### Deployment
38
+
39
+ Using SSH:
40
+
41
+ ```
42
+ USE_SSH=true yarn deploy
43
+ ```
@@ -0,0 +1,49 @@
1
+ ---
2
+ sidebar_position: 2
3
+ title: CSV Export
4
+ ---
5
+
6
+ Exporting to CSV
7
+
8
+ Example downloading a csv file preserving ransack search, based on [this gist](https://gist.github.com/pama/adff25ed1f4b796ce088ea362a08e1c5)
9
+
10
+ ```ruby title='index.html.erb'
11
+ <h1>Users</h1>
12
+
13
+ <%= search_form_for @q, url: dashboard_index_path do |f| %>
14
+ <%= f.label :name_cont %>
15
+ <%= f.search_field :name_cont %>
16
+
17
+ <%= f.submit %>
18
+ <% end %>
19
+
20
+ <ul>
21
+ <% @users.each do |user| %>
22
+ <li><%= user.name %> [<%= user.devices.map {|device| device.name }.join(', ') %>]</li>
23
+ <% end %>
24
+ </ul>
25
+
26
+ <% if params[:q] %>
27
+ <%= link_to 'Export 1', dashboard_index_path({name: params[:q][:name_cont]}.merge({format: :csv})) %>
28
+ <% else %>
29
+ <%= link_to 'Export 2', dashboard_index_path(format: 'csv') %>
30
+ <% end %>
31
+ ```
32
+
33
+ ```ruby title='user.rb'
34
+ require 'csv'
35
+
36
+ class User < ApplicationRecord
37
+ has_many :devices
38
+
39
+ def self.get_csv(users)
40
+ CSV.generate do |csv|
41
+ csv << ["Name", "Devices"]
42
+
43
+ users.each do |user|
44
+ csv << [user.name, user.devices.map{|device| device.name}.join(', ')]
45
+ end
46
+ end
47
+ end
48
+ end
49
+ ```