ransack 2.4.2 → 3.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/cronjob.yml +6 -9
- data/.github/workflows/deploy.yml +35 -0
- data/.github/workflows/rubocop.yml +1 -1
- data/.github/workflows/test-deploy.yml +29 -0
- data/.github/workflows/test.yml +16 -40
- data/.nojekyll +0 -0
- data/CHANGELOG.md +137 -11
- data/CONTRIBUTING.md +4 -3
- data/Gemfile +2 -2
- data/README.md +45 -973
- data/docs/.gitignore +19 -0
- data/docs/.nojekyll +0 -0
- data/docs/babel.config.js +3 -0
- data/docs/blog/2022-03-27-ransack-3.0.0.md +20 -0
- data/docs/docs/getting-started/_category_.json +4 -0
- data/docs/docs/getting-started/advanced-mode.md +46 -0
- data/docs/docs/getting-started/configuration.md +47 -0
- data/docs/docs/getting-started/search-matches.md +67 -0
- data/docs/docs/getting-started/simple-mode.md +284 -0
- data/docs/docs/getting-started/sorting.md +79 -0
- data/docs/docs/getting-started/using-predicates.md +282 -0
- data/docs/docs/going-further/_category_.json +4 -0
- data/docs/docs/going-further/acts-as-taggable-on.md +114 -0
- data/docs/docs/going-further/associations.md +70 -0
- data/docs/docs/going-further/custom-predicates.md +52 -0
- data/docs/docs/going-further/documentation.md +43 -0
- data/docs/docs/going-further/exporting-to-csv.md +49 -0
- data/docs/docs/going-further/external-guides.md +57 -0
- data/docs/docs/going-further/form-customisation.md +63 -0
- data/docs/docs/going-further/i18n.md +53 -0
- data/docs/docs/going-further/merging-searches.md +41 -0
- data/docs/docs/going-further/other-notes.md +428 -0
- data/docs/docs/going-further/polymorphic-search.md +40 -0
- data/docs/docs/going-further/ransackers.md +331 -0
- data/docs/docs/going-further/release_process.md +36 -0
- data/docs/docs/going-further/saving-queries.md +82 -0
- data/docs/docs/going-further/searching-postgres.md +57 -0
- data/docs/docs/going-further/wiki-contributors.md +82 -0
- data/docs/docs/intro.md +99 -0
- data/docs/docusaurus.config.js +107 -0
- data/docs/package.json +37 -0
- data/docs/sidebars.js +31 -0
- data/docs/src/components/HomepageFeatures/index.js +64 -0
- data/docs/src/components/HomepageFeatures/styles.module.css +11 -0
- data/docs/src/css/custom.css +39 -0
- data/docs/src/pages/index.module.css +23 -0
- data/docs/src/pages/markdown-page.md +7 -0
- data/docs/static/.nojekyll +0 -0
- data/docs/static/img/docusaurus.png +0 -0
- data/docs/static/img/favicon.ico +0 -0
- data/docs/static/img/logo.svg +1 -0
- data/docs/static/img/tutorial/docsVersionDropdown.png +0 -0
- data/docs/static/img/tutorial/localeDropdown.png +0 -0
- data/docs/static/img/undraw_docusaurus_mountain.svg +171 -0
- data/docs/static/img/undraw_docusaurus_react.svg +170 -0
- data/docs/static/img/undraw_docusaurus_tree.svg +40 -0
- data/docs/yarn.lock +7671 -0
- data/lib/polyamorous/activerecord_6.1_ruby_2/join_association.rb +0 -4
- data/lib/polyamorous/activerecord_6.1_ruby_2/join_dependency.rb +0 -1
- data/lib/polyamorous/activerecord_6.1_ruby_2/reflection.rb +11 -1
- data/lib/polyamorous/activerecord_7.1_ruby_2/join_association.rb +1 -0
- data/lib/polyamorous/activerecord_7.1_ruby_2/join_dependency.rb +1 -0
- data/lib/polyamorous/activerecord_7.1_ruby_2/reflection.rb +1 -0
- data/lib/polyamorous.rb +1 -0
- data/lib/ransack/adapters/active_record/base.rb +1 -3
- data/lib/ransack/adapters/active_record/context.rb +24 -51
- data/lib/ransack/adapters/active_record/ransack/nodes/condition.rb +2 -9
- data/lib/ransack/configuration.rb +16 -2
- data/lib/ransack/constants.rb +0 -3
- data/lib/ransack/helpers/form_helper.rb +11 -3
- data/lib/ransack/locale/sv.yml +70 -0
- data/lib/ransack/nodes/sort.rb +2 -2
- data/lib/ransack/search.rb +4 -3
- data/lib/ransack/translate.rb +1 -1
- data/lib/ransack/version.rb +1 -1
- data/ransack.gemspec +5 -5
- data/spec/helpers/polyamorous_helper.rb +2 -8
- data/spec/polyamorous/activerecord_compatibility_spec.rb +15 -0
- data/spec/polyamorous/join_association_spec.rb +1 -6
- data/spec/polyamorous/join_dependency_spec.rb +0 -16
- data/spec/ransack/adapters/active_record/base_spec.rb +28 -11
- data/spec/ransack/configuration_spec.rb +14 -0
- data/spec/ransack/helpers/form_helper_spec.rb +57 -2
- data/spec/ransack/nodes/condition_spec.rb +13 -0
- data/spec/ransack/search_spec.rb +140 -27
- data/spec/support/schema.rb +49 -0
- metadata +80 -29
- data/docs/release_process.md +0 -20
- data/lib/polyamorous/activerecord_5.2_ruby_2/join_association.rb +0 -24
- data/lib/polyamorous/activerecord_5.2_ruby_2/join_dependency.rb +0 -79
- data/lib/polyamorous/activerecord_5.2_ruby_2/reflection.rb +0 -11
- data/lib/polyamorous/activerecord_6.0_ruby_2/join_association.rb +0 -1
- data/lib/polyamorous/activerecord_6.0_ruby_2/join_dependency.rb +0 -80
- data/lib/polyamorous/activerecord_6.0_ruby_2/reflection.rb +0 -1
- /data/docs/{img → docs/going-further/img}/create_release.png +0 -0
- /data/{logo → docs/static/logo}/ransack-h.png +0 -0
- /data/{logo → docs/static/logo}/ransack-h.svg +0 -0
- /data/{logo → docs/static/logo}/ransack-v.png +0 -0
- /data/{logo → docs/static/logo}/ransack-v.svg +0 -0
- /data/{logo → docs/static/logo}/ransack.png +0 -0
- /data/{logo → docs/static/logo}/ransack.svg +0 -0
- /data/lib/polyamorous/{activerecord_6.2_ruby_2 → activerecord_7.0_ruby_2}/join_association.rb +0 -0
- /data/lib/polyamorous/{activerecord_6.2_ruby_2 → activerecord_7.0_ruby_2}/join_dependency.rb +0 -0
- /data/lib/polyamorous/{activerecord_6.2_ruby_2 → activerecord_7.0_ruby_2}/reflection.rb +0 -0
@@ -0,0 +1,331 @@
|
|
1
|
+
---
|
2
|
+
sidebar_position: 6
|
3
|
+
title: Ransackers
|
4
|
+
---
|
5
|
+
|
6
|
+
## Add custom search functions
|
7
|
+
|
8
|
+
The main premise behind Ransack is to provide access to **Arel predicate methods**. Ransack provides special methods, called _ransackers_, for creating additional search functions via Arel.
|
9
|
+
|
10
|
+
A `ransacker` method can **return any Arel node that allows the usual predicate methods**. Custom `ransacker`s are an expert feature, and require a thorough understanding of Arel.
|
11
|
+
|
12
|
+
## Arel
|
13
|
+
|
14
|
+
Here are some resources for more information about Arel:
|
15
|
+
|
16
|
+
* [Using Arel to Compose SQL Queries](https://robots.thoughtbot.com/using-arel-to-compose-sql-queries)
|
17
|
+
* [The definitive guide to Arel, the SQL manager for Ruby](http://jpospisil.com/2014/06/16/the-definitive-guide-to-arel-the-sql-manager-for-ruby.html)
|
18
|
+
* [Creating Advanced Active Record DB Queries with Arel](https://www.cloudbees.com/blog/creating-advanced-active-record-db-queries-arel)
|
19
|
+
|
20
|
+
Ransacker methods enable search customization and are placed in the model. Arguments may be passed to a ransacker method via `ransacker_args` (see Example #6 below).
|
21
|
+
|
22
|
+
Ransackers, like scopes, are not a cure-all. Many use cases can be better solved with a standard Ransack search on a dedicated database search field, which is faster, index-able, and scales better than converting/ransacking data on the fly.
|
23
|
+
|
24
|
+
## Example Ransackers
|
25
|
+
|
26
|
+
### Search on field
|
27
|
+
|
28
|
+
_Search on the `name` field reversed:_
|
29
|
+
```ruby
|
30
|
+
# in the model:
|
31
|
+
ransacker :reversed_name, formatter: proc { |v| v.reverse } do |parent|
|
32
|
+
parent.table[:name]
|
33
|
+
end
|
34
|
+
```
|
35
|
+
### Search using Datetime
|
36
|
+
|
37
|
+
_Convert a user `string` input and a database `datetime` field to the same `date` format to find all records with a `datetime` field (`created_at` in this example) equal to that date :_
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
# in the model:
|
41
|
+
ransacker :created_at do
|
42
|
+
Arel.sql('date(created_at)')
|
43
|
+
end
|
44
|
+
```
|
45
|
+
```erb
|
46
|
+
in the view:
|
47
|
+
<%= f.search_field(
|
48
|
+
:created_at_date_equals, placeholder: t(:date_format)
|
49
|
+
) %>
|
50
|
+
...
|
51
|
+
<%= sort_link(@search, :created_at, default_order: :desc) %>
|
52
|
+
```
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
# config/initializers/ransack.rb
|
56
|
+
Ransack.configure do |config|
|
57
|
+
config.add_predicate 'date_equals',
|
58
|
+
arel_predicate: 'eq',
|
59
|
+
formatter: proc { |v| v.to_date },
|
60
|
+
validator: proc { |v| v.present? },
|
61
|
+
type: :string
|
62
|
+
end
|
63
|
+
```
|
64
|
+
|
65
|
+
#### 2.1
|
66
|
+
It seems to be enough to change the model only, but don't forget to define the type that will returned as well.
|
67
|
+
|
68
|
+
```ruby
|
69
|
+
# in the model:
|
70
|
+
ransacker :created_at, type: :date do
|
71
|
+
Arel.sql('date(created_at)')
|
72
|
+
end
|
73
|
+
```
|
74
|
+
|
75
|
+
#### 2.2. Postgresql with time zones
|
76
|
+
|
77
|
+
If you're using different time zones for Rails and Postgresql you should expect to have some problems using the above solution.
|
78
|
+
Example:
|
79
|
+
- Rails at GMT -03:00
|
80
|
+
- Postgresql at GMT -00:00 (UTC)
|
81
|
+
|
82
|
+
A timestamp like `2019-07-18 01:21:29.826484` will be truncated to `2019-07-18`.
|
83
|
+
But for your Rails application `2019-07-18 01:21:29.826484` is `2019-07-17 22:21:29.826484` at your time zone (GMT -03:00). So it should be truncated to `2019-07-17` instead.
|
84
|
+
|
85
|
+
|
86
|
+
So, you should convert the timestamp to your current Rails time zone before extracting the date.
|
87
|
+
|
88
|
+
```ruby
|
89
|
+
# in the model:
|
90
|
+
ransacker :created_at, type: :date do
|
91
|
+
Arel.sql("date(created_at at time zone 'UTC' at time zone '#{Time.zone.name}')")
|
92
|
+
end
|
93
|
+
```
|
94
|
+
|
95
|
+
Note that `Time.zone.name` should return a time zone string suitable for Postgresql.
|
96
|
+
|
97
|
+
### Postgres columns
|
98
|
+
|
99
|
+
_Search on a fixed key in a jsonb / hstore column:_
|
100
|
+
|
101
|
+
In this example, we are searching a table with a column called `properties` for records containing a key called `link_type`.
|
102
|
+
|
103
|
+
For anything up to and including Rails 4.1, add this to your model
|
104
|
+
```ruby
|
105
|
+
ransacker :link_type do |parent|
|
106
|
+
Arel::Nodes::InfixOperation.new('->>', parent.table[:properties], 'link_type')
|
107
|
+
end
|
108
|
+
```
|
109
|
+
When using Rails 4.2+ (Arel 6.0+), wrap the value in a `build_quoted` call
|
110
|
+
```ruby
|
111
|
+
ransacker :link_type do |parent|
|
112
|
+
Arel::Nodes::InfixOperation.new('->>', parent.table[:properties], Arel::Nodes.build_quoted('link_type'))
|
113
|
+
end
|
114
|
+
```
|
115
|
+
In the view, with a search on `link_type_eq` using a collection select (for example with options like 'twitter', 'facebook', etc.), if the user selects 'twitter', Ransack will run a query like:
|
116
|
+
```
|
117
|
+
SELECT * FROM "foos" WHERE "foos"."properties" ->> 'link_type' = 'twitter';
|
118
|
+
```
|
119
|
+
|
120
|
+
To use the JSONB contains operator @> see here: [[PostgreSQL JSONB searches]].
|
121
|
+
|
122
|
+
### Type conversions
|
123
|
+
|
124
|
+
_Convert an `integer` database field to a `string` in order to be able to use a `cont` predicate (instead of the usual `eq` which works out of the box with integers) to find all records where an integer field (`id` in this example) **contains** an input string:_
|
125
|
+
|
126
|
+
Simple version, using PostgreSQL:
|
127
|
+
```ruby
|
128
|
+
# in the model:
|
129
|
+
ransacker :id do
|
130
|
+
Arel.sql("to_char(id, '9999999')")
|
131
|
+
end
|
132
|
+
```
|
133
|
+
and the same, using MySQL:
|
134
|
+
```ruby
|
135
|
+
ransacker :id do
|
136
|
+
Arel.sql("CONVERT(#{table_name}.id, CHAR(8))")
|
137
|
+
end
|
138
|
+
```
|
139
|
+
A more complete version (using PostgreSQL) that adds the table name to avoid ambiguity and strips spaces from the input:
|
140
|
+
```ruby
|
141
|
+
ransacker :id do
|
142
|
+
Arel.sql(
|
143
|
+
"regexp_replace(
|
144
|
+
to_char(\"#{table_name}\".\"id\", '9999999'), ' ', '', 'g')"
|
145
|
+
)
|
146
|
+
end
|
147
|
+
```
|
148
|
+
In the view, for all 3 versions:
|
149
|
+
```erb
|
150
|
+
<%= f.search_field :id_cont, placeholder: 'Id' %>
|
151
|
+
...
|
152
|
+
<%= sort_link(@search, :id) %>
|
153
|
+
```
|
154
|
+
|
155
|
+
### Concatenated fields
|
156
|
+
|
157
|
+
_Search on a concatenated full name from `first_name` and `last_name` (several examples):_
|
158
|
+
```ruby
|
159
|
+
# in the model:
|
160
|
+
ransacker :full_name do |parent|
|
161
|
+
Arel::Nodes::InfixOperation.new('||',
|
162
|
+
parent.table[:first_name], parent.table[:last_name])
|
163
|
+
end
|
164
|
+
|
165
|
+
# or, to insert a space between `first_name` and `last_name`:
|
166
|
+
ransacker :full_name do |parent|
|
167
|
+
Arel::Nodes::InfixOperation.new('||',
|
168
|
+
Arel::Nodes::InfixOperation.new('||',
|
169
|
+
parent.table[:first_name], ' '
|
170
|
+
),
|
171
|
+
parent.table[:last_name]
|
172
|
+
)
|
173
|
+
end
|
174
|
+
# Caveat: with Arel >= 6 the separator ' ' string in the
|
175
|
+
# preceding example needs to be quoted as follows:
|
176
|
+
ransacker :full_name do |parent|
|
177
|
+
Arel::Nodes::InfixOperation.new('||',
|
178
|
+
Arel::Nodes::InfixOperation.new('||',
|
179
|
+
parent.table[:first_name], Arel::Nodes.build_quoted(' ')
|
180
|
+
),
|
181
|
+
parent.table[:last_name]
|
182
|
+
)
|
183
|
+
end
|
184
|
+
|
185
|
+
# works also in mariadb
|
186
|
+
ransacker :full_name do |parent|
|
187
|
+
Arel::Nodes::NamedFunction.new('concat_ws',
|
188
|
+
[Arel::Nodes::SqlLiteral.new("' '"), parent.table[:first_name], parent.table[:last_name]])
|
189
|
+
end
|
190
|
+
|
191
|
+
# case insensitive lookup
|
192
|
+
ransacker :full_name, formatter: proc { |v| v.mb_chars.downcase.to_s } do |parent|
|
193
|
+
Arel::Nodes::NamedFunction.new('LOWER',
|
194
|
+
[Arel::Nodes::NamedFunction.new('concat_ws',
|
195
|
+
[Arel::Nodes::SqlLiteral.new("' '"), parent.table[:first_name], parent.table[:last_name]])])
|
196
|
+
end
|
197
|
+
```
|
198
|
+
|
199
|
+
### Passing arguments
|
200
|
+
|
201
|
+
_Passing arguments to a ransacker:_
|
202
|
+
Arguments may be passed to a ransacker method via `ransacker_args`:
|
203
|
+
```ruby
|
204
|
+
|
205
|
+
class Person
|
206
|
+
ransacker :author_max_title_of_article_where_body_length_between,
|
207
|
+
args: [:parent, :ransacker_args] do |parent, args|
|
208
|
+
min, max = args
|
209
|
+
query = <<-SQL
|
210
|
+
(SELECT MAX(articles.title)
|
211
|
+
FROM articles
|
212
|
+
WHERE articles.person_id = people.id
|
213
|
+
AND CHAR_LENGTH(articles.body) BETWEEN #{min.to_i} AND #{max.to_i}
|
214
|
+
GROUP BY articles.person_id
|
215
|
+
)
|
216
|
+
SQL
|
217
|
+
Arel.sql(query)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
# Usage
|
222
|
+
Person.ransack(
|
223
|
+
conditions: [{
|
224
|
+
attributes: {
|
225
|
+
'0' => {
|
226
|
+
name: 'author_max_title_of_article_where_body_length_between',
|
227
|
+
ransacker_args: [10, 100]
|
228
|
+
}
|
229
|
+
},
|
230
|
+
predicate_name: 'cont',
|
231
|
+
values: ['Ransackers can take arguments']
|
232
|
+
}]
|
233
|
+
)
|
234
|
+
|
235
|
+
=> SELECT "people".* FROM "people" WHERE (
|
236
|
+
(SELECT MAX(articles.title)
|
237
|
+
FROM articles
|
238
|
+
WHERE articles.person_id = people.id
|
239
|
+
AND CHAR_LENGTH(articles.body) BETWEEN 10 AND 100
|
240
|
+
GROUP BY articles.person_id
|
241
|
+
)
|
242
|
+
LIKE '%Ransackers can take arguments%')
|
243
|
+
ORDER BY "people"."id" DESC
|
244
|
+
```
|
245
|
+
|
246
|
+
### Dropdowns
|
247
|
+
|
248
|
+
_Adding the attribute values associated with a column name to a searchable attribute in a dropdown options (instead of a traditional column name coming from a table). This is useful if using an associated table which is acting as a join table between a parent table and domain table. This will cache the data as the selections:_
|
249
|
+
|
250
|
+
```ruby
|
251
|
+
# in the model:
|
252
|
+
Model.pluck(:name).each do |ground|
|
253
|
+
ransacker ground.to_sym do |parent|
|
254
|
+
Arel::Nodes::InfixOperation.new('AND',
|
255
|
+
Arel::Nodes::InfixOperation.new('=', parent.table[:gor_name], ground),
|
256
|
+
parent.table[:status]
|
257
|
+
)
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
# This will not include the column names in the dropdown
|
262
|
+
def self.ransackable_attributes(auth_object = nil)
|
263
|
+
%w() + _ransackers.keys
|
264
|
+
end
|
265
|
+
```
|
266
|
+
|
267
|
+
### Testing for existence
|
268
|
+
|
269
|
+
_Testing for the existence of a row in another table via a join:_
|
270
|
+
|
271
|
+
```ruby
|
272
|
+
# in the model:
|
273
|
+
ransacker :price_exists do |parent|
|
274
|
+
# SQL syntax for PostgreSQL -- others may differ
|
275
|
+
# This returns boolean true or false
|
276
|
+
Arel.sql("(select exists (select 1 from prices where prices.book_id = books.id))")
|
277
|
+
end
|
278
|
+
```
|
279
|
+
|
280
|
+
In the view
|
281
|
+
```haml
|
282
|
+
%td= f.select :price_exists_true, [["Any", 2], ["No", 0], ["Yes", 1]]
|
283
|
+
```
|
284
|
+
|
285
|
+
### Associations
|
286
|
+
|
287
|
+
_Performing a query on an association with a differing class name:_
|
288
|
+
|
289
|
+
Say we have a model "SalesAccount", which represents a relationship between two users,
|
290
|
+
one being designated as a "sales_rep". We want to query SalesAccounts by
|
291
|
+
the name of the sales_rep:
|
292
|
+
|
293
|
+
```ruby
|
294
|
+
# in the model:
|
295
|
+
class SalesAccount < ActiveRecord::Base
|
296
|
+
belongs_to :user
|
297
|
+
belongs_to :sales_rep, class_name: :User
|
298
|
+
|
299
|
+
# in the controller:
|
300
|
+
# The line below would lead to errors thrown later if not for the
|
301
|
+
# "joins(:sales_reps)".
|
302
|
+
@q = SalesAccount.includes(:user).joins(:sales_rep).ransack(params[:q])
|
303
|
+
@sales_accounts = @q.result(distinct: true)
|
304
|
+
```
|
305
|
+
|
306
|
+
In the view:
|
307
|
+
```erb
|
308
|
+
<%= f.search_field :sales_rep_name_start %>
|
309
|
+
```
|
310
|
+
|
311
|
+
### Search on translations
|
312
|
+
|
313
|
+
_Search for a translated value in a jsonb column:_
|
314
|
+
|
315
|
+
_Note: There is also a gem, [Mobility Ransack](https://github.com/shioyama/mobility-ransack), which allows you to search on translated attributes independent of their storage backend._
|
316
|
+
|
317
|
+
This will work with any `jsonb` data type. In this case I have a column translated with [Mobility](https://github.com/shioyama/mobility) called `name` with the value `{'en': "Hello", 'es': "Hola"}`.
|
318
|
+
|
319
|
+
```ruby
|
320
|
+
ransacker :name do |parent|
|
321
|
+
Arel::Nodes::InfixOperation.new('->>', parent.table[:name], Arel::Nodes.build_quoted(Mobility.locale))
|
322
|
+
end
|
323
|
+
```
|
324
|
+
|
325
|
+
_If using Rails 4.1 or under, remove the `build_quoted` call._
|
326
|
+
|
327
|
+
You can then search for `name_eq` or `name_cont` and it will do the proper SQL.
|
328
|
+
|
329
|
+
***
|
330
|
+
|
331
|
+
Please feel free to contribute further code examples!
|
@@ -0,0 +1,36 @@
|
|
1
|
+
---
|
2
|
+
title: Versions and Releases
|
3
|
+
sidebar_position: 11
|
4
|
+
---
|
5
|
+
|
6
|
+
|
7
|
+
## Semantic Versioning
|
8
|
+
|
9
|
+
Ransack attempts to follow semantic versioning in the format of `x.y.z`, where:
|
10
|
+
|
11
|
+
`x` stands for a major version (new features that are not backward-compatible).
|
12
|
+
|
13
|
+
`y` stands for a minor version (new features that are backward-compatible).
|
14
|
+
|
15
|
+
`z` stands for a patch (bug fixes).
|
16
|
+
|
17
|
+
In other words: `Major.Minor.Patch`.
|
18
|
+
|
19
|
+
|
20
|
+
## Release Process
|
21
|
+
|
22
|
+
*For the maintainers of Ransack.*
|
23
|
+
|
24
|
+
To release a new version of Ransack and publish it to RubyGems, take the following steps:
|
25
|
+
|
26
|
+
- Create a new release, marked `Prerelease`.
|
27
|
+
- Update the versions file to the new release, commit and push to `master`.
|
28
|
+
- Update the [`version.rb`](https://github.com/activerecord-hackery/ransack/lib/ransack/version.rb) file to the new release, commit and push to `master`.
|
29
|
+
- From the terminal, run the following commands:
|
30
|
+
|
31
|
+
```bash
|
32
|
+
rake build
|
33
|
+
rake release
|
34
|
+
```
|
35
|
+
|
36
|
+
![Create a Release](img/create_release.png)
|
@@ -0,0 +1,82 @@
|
|
1
|
+
---
|
2
|
+
sidebar_position: 7
|
3
|
+
title: Saving queries
|
4
|
+
---
|
5
|
+
|
6
|
+
## Ransack Memory Gem
|
7
|
+
|
8
|
+
The [Ransack Memory](https://github.com/richardrails/ransack_memory) gem accomplishes this.
|
9
|
+
|
10
|
+
## Custom solution
|
11
|
+
|
12
|
+
If you want a custom solution, you can build it yourself. My ransack AJAX searching doesn’t save your search parameters across transactions. In this post I’ll show you how to easily add this capability in a generic way.
|
13
|
+
|
14
|
+
In this example I added AJAX search ability to index pages.
|
15
|
+
|
16
|
+
```ruby
|
17
|
+
def index
|
18
|
+
@search = ComponentDefinition.search(search_params)
|
19
|
+
# make name the default sort column
|
20
|
+
@search.sorts = 'name' if @search.sorts.empty?
|
21
|
+
@component_definitions = @search.result().page(params[:page])
|
22
|
+
end
|
23
|
+
```
|
24
|
+
|
25
|
+
I added methods(search_params, clear_search_index) in the ApplicationController to add a level of abstraction from the search gem I was using. Turns out this made things super easy, especially considering I won’t have to update my code generation tools for index pages.
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
class ApplicationController < ActionController::Base
|
29
|
+
def search_params
|
30
|
+
params[:q]
|
31
|
+
end
|
32
|
+
def clear_search_index
|
33
|
+
if params[:search_cancel]
|
34
|
+
params.delete(:search_cancel)
|
35
|
+
if(!search_params.nil?)
|
36
|
+
search_params.each do |key, param|
|
37
|
+
search_params[key] = nil
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
```
|
44
|
+
|
45
|
+
I decided to store the ransack search parameters, params[:q], in the session. To make the session parameter unique I used a key creed from the controllers name and “_search”.
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
class ApplicationController < ActionController::Base
|
49
|
+
|
50
|
+
# CHECK THE SESSION FOR SEARCH PARAMETERS IS THEY AREN'T IN THE REQUEST
|
51
|
+
def search_params
|
52
|
+
if params[:q] == nil
|
53
|
+
params[:q] = session[search_key]
|
54
|
+
end
|
55
|
+
if params[:q]
|
56
|
+
session[search_key] = params[:q]
|
57
|
+
end
|
58
|
+
params[:q]
|
59
|
+
end
|
60
|
+
# DELETE SEARCH PARAMETERS FROM THE SESSION
|
61
|
+
def clear_search_index
|
62
|
+
if params[:search_cancel]
|
63
|
+
params.delete(:search_cancel)
|
64
|
+
if(!search_params.nil?)
|
65
|
+
search_params.each do |key, param|
|
66
|
+
search_params[key] = nil
|
67
|
+
end
|
68
|
+
end
|
69
|
+
# REMOVE FROM SESSION
|
70
|
+
session.delete(search_key)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
protected
|
75
|
+
# GENERATE A GENERIC SESSION KEY BASED ON TEH CONTROLLER NAME
|
76
|
+
def search_key
|
77
|
+
"#{controller_name}_search".to_sym
|
78
|
+
end
|
79
|
+
end
|
80
|
+
```
|
81
|
+
|
82
|
+
Based on [Saving queries](https://techbrownbags.wordpress.com/2015/02/18/rails-save-ransack-search-queries/)
|
@@ -0,0 +1,57 @@
|
|
1
|
+
---
|
2
|
+
sidebar_position: 8
|
3
|
+
title: Postgres searches
|
4
|
+
---
|
5
|
+
|
6
|
+
Searching on Postgres-specific column types.
|
7
|
+
|
8
|
+
## Postgres Array searches
|
9
|
+
|
10
|
+
See [this issue](https://github.com/activerecord-hackery/ransack/issues/321) for details.
|
11
|
+
|
12
|
+
## PostgreSQL JSONB searches
|
13
|
+
|
14
|
+
### Using a fixed key
|
15
|
+
|
16
|
+
See here for searching on a fixed key in a JSONB column: https://github.com/activerecord-hackery/ransack/wiki/Using-Ransackers#3-search-on-a-fixed-key-in-a-jsonb--hstore-column
|
17
|
+
|
18
|
+
### Using the JSONB contains operator
|
19
|
+
|
20
|
+
To fully use the power of the JSONB column you may want to filter on any key though:
|
21
|
+
|
22
|
+
Install the [ActiveRecordExtended](https://github.com/GeorgeKaraszi/ActiveRecordExtended) gem to add the `contains` arel predicate to your project. It let's you use the [Postgres contains operator @>](https://www.postgresql.org/docs/12/functions-json.html#FUNCTIONS-JSONB-OP-TABLE).
|
23
|
+
|
24
|
+
Add a custom predicate in the `config/initializers/ransack.rb` file:
|
25
|
+
```ruby
|
26
|
+
Ransack.configure do |config|
|
27
|
+
config.add_predicate 'jcont', arel_predicate: 'contains', formatter: proc { |v| JSON.parse(v) }
|
28
|
+
end
|
29
|
+
```
|
30
|
+
|
31
|
+
Now you can ransack the JSONB columns using the _jcont predicate. For example the Person model has a `data` JSONB column, find entries where the column contains the {"group": "experts"} key-value pair:
|
32
|
+
|
33
|
+
Person.ransack(data_jcont: '{"group": "experts"}').result.to_sql
|
34
|
+
|
35
|
+
SELECT "persons".* FROM "persons" WHERE "persons"."data" @> '"{\"group\": \"experts\"}"'
|
36
|
+
|
37
|
+
If you have a GIN index on that column, the database will quickly be able to find that result.
|
38
|
+
|
39
|
+
### Treating the column as a string
|
40
|
+
|
41
|
+
Warning: This method converts the column to a string and matches the given string to the result. This will be slow on large data_sets and does not make good use of the JSONB capabilities of Postgres, such as indexes.
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
class Contact < ApplicationRecord
|
45
|
+
ransacker :within_json do |parent|
|
46
|
+
Arel.sql("table.jsonb_data::text")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
Contact.all.ransack("within_json_cont" => "my")
|
51
|
+
```
|
52
|
+
|
53
|
+
Will generate
|
54
|
+
|
55
|
+
`SELECT "contacts".* FROM "contacts" WHERE contacts.json_data ILIKE '%my%'`
|
56
|
+
|
57
|
+
Note that this search treats the entire JSON as string, including parens, etc. i.e. you can search for e.g.: `Contact.all.ransack("within_json_cont" => '{"key": "value"}')`
|
@@ -0,0 +1,82 @@
|
|
1
|
+
---
|
2
|
+
title: Wiki Contributors
|
3
|
+
sidebar_position: 20
|
4
|
+
---
|
5
|
+
|
6
|
+
Ransack previously had documentation contained in a GitHub Wiki, and this content has been merged into this documentation website. The following long list of _amazing_ people all made contributions to the Wiki:
|
7
|
+
|
8
|
+
* Abinoam P. Marques Jr
|
9
|
+
* Alex Stophel
|
10
|
+
* Andrea Schiavini
|
11
|
+
* Andrew Vit
|
12
|
+
* Ben Koshy
|
13
|
+
* Brainkurv
|
14
|
+
* Brandan Lennox
|
15
|
+
* Brendon Muir
|
16
|
+
* Chris Salzberg
|
17
|
+
* Colleen McGuckin
|
18
|
+
* David Aldridge
|
19
|
+
* Davidson Mohanty
|
20
|
+
* Denis Tataurov
|
21
|
+
* Drew Moore
|
22
|
+
* Eike Send
|
23
|
+
* Feodor Cherashev
|
24
|
+
* Glauco Custódio
|
25
|
+
* Grey Baker
|
26
|
+
* Harold.Luo
|
27
|
+
* Herman Singh
|
28
|
+
* Ian Smith
|
29
|
+
* Jake Haber
|
30
|
+
* Jan Klimo
|
31
|
+
* Jared Beck
|
32
|
+
* Jon Atack
|
33
|
+
* Juanito Fatas
|
34
|
+
* JungaJk
|
35
|
+
* Leo Chen
|
36
|
+
* Leon Miller-Out
|
37
|
+
* Luca F
|
38
|
+
* Marc Poris
|
39
|
+
* Matt Oakley
|
40
|
+
* Michael Kopchick
|
41
|
+
* Nathan Colgate
|
42
|
+
* Nguyen Phi Viet(Sun*)
|
43
|
+
* Nguyễn Đức Long
|
44
|
+
* NielsKSchjoedt
|
45
|
+
* Patrick Copeland
|
46
|
+
* Pedro Chambino
|
47
|
+
* Rene Hopf
|
48
|
+
* Richa Arora
|
49
|
+
* Rob Jones
|
50
|
+
* Roman Sokhan
|
51
|
+
* Ryan Bates
|
52
|
+
* Ryan Bigg
|
53
|
+
* Sean
|
54
|
+
* Sean Linsley
|
55
|
+
* Sergey
|
56
|
+
* Sunny Ripert
|
57
|
+
* Tanbir Hasan
|
58
|
+
* ThuyNguyen97
|
59
|
+
* Vanda
|
60
|
+
* Yana Agun Siswanto
|
61
|
+
* bonyiii
|
62
|
+
* charly
|
63
|
+
* chifung7
|
64
|
+
* colorfulberry
|
65
|
+
* ddonahue99
|
66
|
+
* ernie
|
67
|
+
* gaaady
|
68
|
+
* gingerlime
|
69
|
+
* grumpit
|
70
|
+
* itsalongstory
|
71
|
+
* jonatack
|
72
|
+
* kogre
|
73
|
+
* nguyentrungson97
|
74
|
+
* nslocum
|
75
|
+
* omitter
|
76
|
+
* radar
|
77
|
+
* rilian
|
78
|
+
* terraplane
|
79
|
+
* tyronewilson
|
80
|
+
* vansy61
|
81
|
+
* willnet
|
82
|
+
* wzcolon
|
data/docs/docs/intro.md
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
---
|
2
|
+
sidebar_position: 1
|
3
|
+
slug: '/'
|
4
|
+
---
|
5
|
+
|
6
|
+
# Introduction
|
7
|
+
|
8
|
+
Ransack will help you easily add **searching to your Rails application**, without any additional dependencies.
|
9
|
+
|
10
|
+
There are advanced searching solutions around, like ElasticSearch or Algolia. **Ransack** will do the job for many Rails websites, without the need to run additional infrastructure or work in a different language. With Ransack you do it all with standard Ruby and ERB.
|
11
|
+
|
12
|
+
Ready to move beyond the basics? Use **advanced features** like i18n and extensive configuration options.
|
13
|
+
|
14
|
+
Ransack is supported for Rails 7.0, 6.x on Ruby 2.6.6 and later.
|
15
|
+
|
16
|
+
## Installation
|
17
|
+
|
18
|
+
To install `ransack` and add it to your Gemfile, run
|
19
|
+
|
20
|
+
```jsx title='Gemfile'
|
21
|
+
gem 'ransack'
|
22
|
+
```
|
23
|
+
|
24
|
+
### Bleeding edge
|
25
|
+
|
26
|
+
If you would like to use the latest updates not yet published to RubyGems, use the `main` branch:
|
27
|
+
|
28
|
+
```jsx title='Gemfile'
|
29
|
+
gem 'ransack', :github => 'activerecord-hackery/ransack', :branch => 'main'
|
30
|
+
```
|
31
|
+
|
32
|
+
### Demo application
|
33
|
+
|
34
|
+
The [Ransack Demo application](https://github.com/activerecord-hackery/ransack_demo) shows how to create [simple](http://ransack-demo.herokuapp.com) and
|
35
|
+
[advanced](http://ransack-demo.herokuapp.com/users/advanced_search) search forms for your Ruby on Rails application.
|
36
|
+
|
37
|
+
|
38
|
+
## Issues tracker
|
39
|
+
|
40
|
+
* Before filing an issue, please read the [Contributing Guide](https://github.com/activerecord-hackery/ransack/CONTRIBUTING.md).
|
41
|
+
* 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_.
|
42
|
+
* Please consider adding a branch with a failing spec describing the problem.
|
43
|
+
* Contributions are welcome. :smiley:
|
44
|
+
* 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!
|
45
|
+
|
46
|
+
|
47
|
+
## Contributions
|
48
|
+
|
49
|
+
To support the project:
|
50
|
+
|
51
|
+
* Consider supporting us via [Open Collective](https://opencollective.com/ransack/backers/badge.svg)
|
52
|
+
* Use Ransack in your apps, and let us know if you encounter anything that's
|
53
|
+
broken or missing. A failing spec to demonstrate the issue is awesome. A pull
|
54
|
+
request with passing tests is even better!
|
55
|
+
* Before filing an issue or pull request, be sure to read and follow the
|
56
|
+
[Contributing Guide](https://github.com/activerecord-hackery/ransack/CONTRIBUTING.md).
|
57
|
+
* Please use Stack Overflow or other sites for questions or discussion not
|
58
|
+
directly related to bug reports, pull requests, or documentation improvements.
|
59
|
+
* Spread the word on Twitter, Facebook, and elsewhere if Ransack's been useful
|
60
|
+
to you. The more people who are using the project, the quicker we can find and
|
61
|
+
fix bugs!
|
62
|
+
|
63
|
+
## Contributors
|
64
|
+
|
65
|
+
Ransack was created by [Ernie Miller](http://twitter.com/erniemiller) and is developed and maintained by:
|
66
|
+
* [Sean Carroll](https://github.com/scarroll32)
|
67
|
+
* [Deivid Rodriguez](https://github.com/deivid-rodriguez)
|
68
|
+
* [Greg Molnar](https://github.com/gregmolnar)
|
69
|
+
* [A great group of contributors](https://github.com/activerecord-hackery/ransack/graphs/contributors).
|
70
|
+
- Ransack's logo is designed by [Anıl Kılıç](https://github.com/anilkilic).
|
71
|
+
|
72
|
+
Alumni Maintainers
|
73
|
+
- [Jon Atack](http://twitter.com/jonatack)
|
74
|
+
- [Ryan Bigg](http://twitter.com/ryanbigg)
|
75
|
+
|
76
|
+
This project exists thanks to all the people who contribute. <img src="https://opencollective.com/ransack/contributors.svg?width=890&button=false" />
|
77
|
+
|
78
|
+
|
79
|
+
## Backers
|
80
|
+
|
81
|
+
Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/ransack#backer)]
|
82
|
+
|
83
|
+
<a href="https://opencollective.com/ransack#backers" target="_blank"><img src="https://opencollective.com/ransack/backers.svg?width=890" /></a>
|
84
|
+
|
85
|
+
|
86
|
+
## Sponsors
|
87
|
+
|
88
|
+
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)]
|
89
|
+
|
90
|
+
<a href="https://opencollective.com/ransack/sponsor/0/website" target="_blank"><img src="https://opencollective.com/ransack/sponsor/0/avatar.svg" /></a>
|
91
|
+
<a href="https://opencollective.com/ransack/sponsor/1/website" target="_blank"><img src="https://opencollective.com/ransack/sponsor/1/avatar.svg" /></a>
|
92
|
+
<a href="https://opencollective.com/ransack/sponsor/2/website" target="_blank"><img src="https://opencollective.com/ransack/sponsor/2/avatar.svg" /></a>
|
93
|
+
<a href="https://opencollective.com/ransack/sponsor/3/website" target="_blank"><img src="https://opencollective.com/ransack/sponsor/3/avatar.svg" /></a>
|
94
|
+
<a href="https://opencollective.com/ransack/sponsor/4/website" target="_blank"><img src="https://opencollective.com/ransack/sponsor/4/avatar.svg"/ ></a>
|
95
|
+
<a href="https://opencollective.com/ransack/sponsor/5/website" target="_blank"><img src="https://opencollective.com/ransack/sponsor/5/avatar.svg" /></a>
|
96
|
+
<a href="https://opencollective.com/ransack/sponsor/6/website" target="_blank"><img src="https://opencollective.com/ransack/sponsor/6/avatar.svg" /></a>
|
97
|
+
<a href="https://opencollective.com/ransack/sponsor/7/website" target="_blank"><img src="https://opencollective.com/ransack/sponsor/7/avatar.svg" /></a>
|
98
|
+
<a href="https://opencollective.com/ransack/sponsor/8/website" target="_blank"><img src="https://opencollective.com/ransack/sponsor/8/avatar.svg" /></a>
|
99
|
+
<a href="https://opencollective.com/ransack/sponsor/9/website" target="_blank"><img src="https://opencollective.com/ransack/sponsor/9/avatar.svg" /></a>
|