ransack 2.4.2 → 2.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a7e53f2a86ba264e751c3311254c7cb4b25efdb0c876310835b6f6221ab24928
4
- data.tar.gz: 161ca5255a24fa42c63d6afbbf38e518581189db773e7e43940b31b1b8d86850
3
+ metadata.gz: 27d9d0b5ebf5727ab6405767c7b545ae6afc6b6dc1ff6eec441d0108cea8cb11
4
+ data.tar.gz: b1cd5f875a4c29434efdcaee2a1b47c9ba7fefacba802f6cac0f6c75545cc912
5
5
  SHA512:
6
- metadata.gz: 17c4701dbf3523dfa16feae0dd0dddc8bd077237d7e695eb69664f1b9c5ab5e2fea4a59b5878fe8e607b135ea834f12284f03c42a9ce2971f2f9c9b65d347205
7
- data.tar.gz: 4c9d09980609ffc43bc505a947781b9fcdc97efb7897c3bbc3278d8391ab219e4fdffd8a813272a3af7403eefc162fddb6f9f379b713894d3951ff2fc7a141f3
6
+ metadata.gz: '0961ec1b9a3874dfd1242d40bae780b1f5a553e418a7bdcd1e599c5a25768831ce22b12c861a2c703beb2ad157751429aa034de1769ef3c83a8837b27356f679'
7
+ data.tar.gz: 7483453ea26b635323d153bd632f8c38ef03d24ca674b5266f62a870f29d7e395b81c678ae7403dd6b54b27985203e12c80701a0e990d47dcdeacc6c8b1d5aa6
@@ -11,9 +11,8 @@ jobs:
11
11
  fail-fast: false
12
12
  matrix:
13
13
  ruby:
14
- - 3.0.0
15
- - 2.7.2
16
- - 2.6.6
14
+ - 3.0.2
15
+ - 2.7.4
17
16
  env:
18
17
  DB: sqlite3
19
18
  RAILS: main
@@ -34,9 +33,8 @@ jobs:
34
33
  fail-fast: false
35
34
  matrix:
36
35
  ruby:
37
- - 3.0.0
38
- - 2.7.2
39
- - 2.6.6
36
+ - 3.0.2
37
+ - 2.7.4
40
38
  env:
41
39
  DB: mysql
42
40
  RAILS: main
@@ -66,9 +64,8 @@ jobs:
66
64
  fail-fast: false
67
65
  matrix:
68
66
  ruby:
69
- - 3.0.0
70
- - 2.7.2
71
- - 2.6.6
67
+ - 3.0.2
68
+ - 2.7.4
72
69
  env:
73
70
  DB: postgres
74
71
  RAILS: main
@@ -13,7 +13,7 @@ jobs:
13
13
  - name: Set up Ruby
14
14
  uses: ruby/setup-ruby@v1
15
15
  with:
16
- ruby-version: 3.0.0
16
+ ruby-version: 3.0.1
17
17
  - name: Install gems
18
18
  run: bundle install --jobs 4 --retry 3
19
19
  - name: Run RuboCop
@@ -11,20 +11,23 @@ jobs:
11
11
  fail-fast: false
12
12
  matrix:
13
13
  rails:
14
- - v6.1.1
15
- - v6.0.3
14
+ - v7.0.0
15
+ - v6.1.4
16
+ - v6.0.4
16
17
  - 6-0-stable
17
18
  - 5-2-stable
18
19
  - v5.2.4
19
20
  ruby:
20
- - 3.0.0
21
- - 2.7.2
22
- - 2.6.6
21
+ - 3.0.2
22
+ - 2.7.4
23
+ - 2.6.7
23
24
  exclude:
25
+ - rails: v7.0.0
26
+ ruby: 2.6.7
24
27
  - rails: v5.2.4
25
- ruby: 3.0.0
28
+ ruby: 3.0.2
26
29
  - rails: 5-2-stable
27
- ruby: 3.0.0
30
+ ruby: 3.0.2
28
31
  env:
29
32
  DB: sqlite3
30
33
  RAILS: ${{ matrix.rails }}
@@ -45,20 +48,23 @@ jobs:
45
48
  fail-fast: false
46
49
  matrix:
47
50
  rails:
48
- - v6.1.1
49
- - v6.0.3
51
+ - v7.0.0
52
+ - v6.1.4
53
+ - v6.0.4
50
54
  - 6-0-stable
51
55
  - 5-2-stable
52
56
  - v5.2.4
53
57
  ruby:
54
- - 3.0.0
55
- - 2.7.2
56
- - 2.6.6
58
+ - 3.0.2
59
+ - 2.7.4
60
+ - 2.6.7
57
61
  exclude:
62
+ - rails: v7.0.0
63
+ ruby: 2.6.7
58
64
  - rails: v5.2.4
59
- ruby: 3.0.0
65
+ ruby: 3.0.2
60
66
  - rails: 5-2-stable
61
- ruby: 3.0.0
67
+ ruby: 3.0.2
62
68
  env:
63
69
  DB: mysql
64
70
  RAILS: ${{ matrix.rails }}
@@ -88,20 +94,23 @@ jobs:
88
94
  fail-fast: false
89
95
  matrix:
90
96
  rails:
91
- - v6.1.1
92
- - v6.0.3
97
+ - v7.0.0
98
+ - v6.1.4
99
+ - v6.0.4
93
100
  - 6-0-stable
94
101
  - 5-2-stable
95
102
  - v5.2.4
96
103
  ruby:
97
- - 3.0.0
98
- - 2.7.2
99
- - 2.6.6
104
+ - 3.0.2
105
+ - 2.7.4
106
+ - 2.6.7
100
107
  exclude:
108
+ - rails: v7.0.0
109
+ ruby: 2.6.7
101
110
  - rails: v5.2.4
102
- ruby: 3.0.0
111
+ ruby: 3.0.2
103
112
  - rails: 5-2-stable
104
- ruby: 3.0.0
113
+ ruby: 3.0.2
105
114
  env:
106
115
  DB: postgres
107
116
  RAILS: ${{ matrix.rails }}
@@ -144,7 +153,7 @@ jobs:
144
153
  - name: Set up Ruby
145
154
  uses: ruby/setup-ruby@v1
146
155
  with:
147
- ruby-version: 3.0.0
156
+ ruby-version: 3.0.2
148
157
  - name: Install dependencies
149
158
  run: bundle install
150
159
  - name: Run bug report templates
data/CHANGELOG.md CHANGED
@@ -1,7 +1,16 @@
1
1
  # Change Log
2
2
 
3
+ ## Unreleased
4
+
5
+ ## 2.5.0 - 2021-12-26
6
+
7
+ * ActiveRecord 7.0 support
8
+
3
9
  * Drop support for rubies under 2.5. PR #1189
4
10
 
11
+ * Have casted array predicates type checked to Arel::Nodes::Casted fixing non-casted array predicates.
12
+ PR [1246](https://github.com/activerecord-hackery/ransack/pull/1246)
13
+
5
14
  ## 2.4.1 - 2020-12-21
6
15
 
7
16
  * Add `ActiveRecord::Base.ransack!` which raises error if passed unknown condition
@@ -200,7 +209,7 @@
200
209
  ignored when block parameter is specified.
201
210
  PR [#818](https://github.com/activerecord-hackery/ransack/pull/818).
202
211
 
203
- * No need pass some arugments to JoinAssociation#join_constraints in Rails 5.1.
212
+ * No need pass some arguments to JoinAssociation#join_constraints in Rails 5.1.
204
213
  PR [#814](https://github.com/activerecord-hackery/ransack/pull/814).
205
214
  Fixes [#807](https://github.com/activerecord-hackery/ransack/issues/807).
206
215
  Reference [rails/rails#28267](https://github.com/rails/rails/pull/28267)
data/CONTRIBUTING.md CHANGED
@@ -115,6 +115,9 @@ Here's a quick guide:
115
115
  $ git config --global user.email "contributor@example.com"
116
116
 
117
117
  10. Commit your changes (`git commit -am 'Add feature/fix bug/improve docs'`).
118
+ If your pull request only contains documentation changes, please remember
119
+ to add `[skip ci]` to the beginning of your commit message so the CI
120
+ test suite doesn't :runner: needlessly.
118
121
 
119
122
  11. If necessary, rebase your commits into logical chunks, without errors. To
120
123
  interactively rebase and cherry-pick from, say, the last 10 commits:
data/README.md CHANGED
@@ -11,9 +11,7 @@ Ransack enables the creation of both
11
11
  for your Ruby on Rails application
12
12
  ([demo source code here](https://github.com/activerecord-hackery/ransack_demo)).
13
13
  If you're looking for something that simplifies query generation at the model
14
- or controller layer, you're probably not looking for Ransack (or MetaSearch,
15
- for that matter). Try [Squeel](https://github.com/activerecord-hackery/squeel)
16
- instead.
14
+ or controller layer, you're probably not looking for Ransack.
17
15
 
18
16
  ## Getting started
19
17
 
@@ -65,19 +63,18 @@ this example, with preloading each Person's Articles and pagination):
65
63
  def index
66
64
  @q = Person.ransack(params[:q])
67
65
  @people = @q.result.includes(:articles).page(params[:page])
68
-
69
- # or use `to_a.uniq` to remove duplicates (can also be done in the view):
70
- @people = @q.result.includes(:articles).page(params[:page]).to_a.uniq
71
66
  end
72
67
  ```
73
68
 
74
- ##### Default search parameter
69
+ ##### Default search options
70
+
71
+ **Search parameter**
75
72
 
76
73
  Ransack uses a default `:q` param key for search params. This may be changed by
77
74
  setting the `search_key` option in a Ransack initializer file (typically
78
75
  `config/initializers/ransack.rb`):
79
76
 
80
- ```
77
+ ```ruby
81
78
  Ransack.configure do |c|
82
79
  # Change default search parameter key name.
83
80
  # Default key name is :q
@@ -85,6 +82,19 @@ Ransack.configure do |c|
85
82
  end
86
83
  ```
87
84
 
85
+ **String search**
86
+
87
+ After version 2.4.0 when searching a string query Ransack by default strips all whitespace around the query string.
88
+ This may be disabled by setting the `strip_whitespace` option in a Ransack initializer file:
89
+
90
+ ```ruby
91
+ Ransack.configure do |c|
92
+ # Change whitespace stripping behaviour.
93
+ # Default is true
94
+ c.strip_whitespace = false
95
+ end
96
+ ```
97
+
88
98
  #### In your view
89
99
 
90
100
  The two primary Ransack view helpers are `search_form_for` and `sort_link`,
@@ -263,8 +273,45 @@ Ransack.configure do |c|
263
273
  end
264
274
  ```
265
275
 
276
+ To treat nulls as having the lowest or highest value respectively. To force nulls to always be first or last, use
277
+
278
+ ```rb
279
+ Ransack.configure do |c|
280
+ c.postgres_fields_sort_option = :nulls_always_first # or :nulls_always_last
281
+ end
282
+ ```
283
+
266
284
  See this feature: https://www.postgresql.org/docs/13/queries-order.html
267
285
 
286
+ #### Case Insensitive Sorting in PostgreSQL
287
+
288
+ In order to request PostgreSQL to do a case insensitive sort for all string columns of a model at once, Ransack can be extended by using this approach:
289
+
290
+ ```ruby
291
+ module RansackObject
292
+
293
+ def self.included(base)
294
+ base.columns.each do |column|
295
+ if column.type == :string
296
+ base.ransacker column.name.to_sym, type: :string do
297
+ Arel.sql("lower(#{base.table_name}.#{column.name})")
298
+ end
299
+ end
300
+ end
301
+ end
302
+ end
303
+ ```
304
+
305
+ ```ruby
306
+ class UserWithManyAttributes < ActiveRecord::Base
307
+ include RansackObject
308
+ end
309
+ ```
310
+
311
+ If this approach is taken, it is advisable to [add a functional index](https://www.postgresql.org/docs/13/citext.html).
312
+
313
+ This was originally asked in [a Ransack issue](https://github.com/activerecord-hackery/ransack/issues/1201) and a solution was found on [Stack Overflow](https://stackoverflow.com/a/34677378).
314
+
268
315
  ### Advanced Mode
269
316
 
270
317
  "Advanced" searches (ab)use Rails' nested attributes functionality in order to
@@ -423,6 +470,25 @@ query parameters in your URLs.
423
470
  <% end %>
424
471
  ```
425
472
 
473
+ You can also use `ransack_alias` for sorting.
474
+
475
+ ```ruby
476
+ class Post < ActiveRecord::Base
477
+ belongs_to :author
478
+
479
+ # Abbreviate :author_first_name to :author
480
+ ransack_alias :author, :author_first_name
481
+ end
482
+ ```
483
+
484
+ Now, you can use `:author` instead of `:author_first_name` in a `sort_link`.
485
+
486
+ ```erb
487
+ <%= sort_link(@q, :author) %>
488
+ ```
489
+
490
+ 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.
491
+
426
492
  ### Search Matchers
427
493
 
428
494
  List of all possible predicates
@@ -719,7 +785,7 @@ Article.ransack!(unknown_attr_eq: 'Ernie')
719
785
  # ArgumentError: Invalid search term unknown_attr_eq
720
786
  ```
721
787
 
722
- This is equivilent to the `ignore_unknown_conditions` configuration option,
788
+ This is equivalent to the `ignore_unknown_conditions` configuration option,
723
789
  except it may be applied on a case-by-case basis.
724
790
 
725
791
  ### Using Scopes/Class Methods
@@ -5,12 +5,9 @@
5
5
  To release a new version of Ransack and publish it to RubyGems, take the following steps:
6
6
 
7
7
  - Create a new release, marked `Prerelease`.
8
- <<<<<<< Updated upstream
9
8
  - Update the versions file to the new release, commit and push to `master`.
10
- =======
11
- - Update the [version.rb](../lib/ransack/version.rb) file to the new release, commit and push to `master`.
12
- >>>>>>> Stashed changes
13
- - From the terminal, run the following commands
9
+ - Update the [`version.rb`](../lib/ransack/version.rb) file to the new release, commit and push to `master`.
10
+ - From the terminal, run the following commands:
14
11
 
15
12
  ```bash
16
13
  rake build
@@ -0,0 +1 @@
1
+ require 'polyamorous/polyamorous'
@@ -70,7 +70,7 @@ module Ransack
70
70
  end
71
71
 
72
72
  # ransack_scope_skip_sanitize_args, by default, returns an empty array.
73
- # i.e. use the sanitize_scope_args setting to determin if args should be converted.
73
+ # i.e. use the sanitize_scope_args setting to determine if args should be converted.
74
74
  # For overriding with a list of scopes which should be passed the args as-is.
75
75
  #
76
76
  def ransackable_scopes_skip_sanitize_args
@@ -44,9 +44,13 @@ module Ransack
44
44
  else
45
45
  case Ransack.options[:postgres_fields_sort_option]
46
46
  when :nulls_first
47
- scope_or_sort = scope_or_sort.direction == :asc ? "#{scope_or_sort.to_sql} NULLS FIRST" : "#{scope_or_sort.to_sql} NULLS LAST"
47
+ scope_or_sort = scope_or_sort.direction == :asc ? Arel.sql("#{scope_or_sort.to_sql} NULLS FIRST") : Arel.sql("#{scope_or_sort.to_sql} NULLS LAST")
48
48
  when :nulls_last
49
- scope_or_sort = scope_or_sort.direction == :asc ? "#{scope_or_sort.to_sql} NULLS LAST" : "#{scope_or_sort.to_sql} NULLS FIRST"
49
+ scope_or_sort = scope_or_sort.direction == :asc ? Arel.sql("#{scope_or_sort.to_sql} NULLS LAST") : Arel.sql("#{scope_or_sort.to_sql} NULLS FIRST")
50
+ when :nulls_always_first
51
+ scope_or_sort = Arel.sql("#{scope_or_sort.to_sql} NULLS FIRST")
52
+ when :nulls_always_last
53
+ scope_or_sort = Arel.sql("#{scope_or_sort.to_sql} NULLS LAST")
50
54
  end
51
55
 
52
56
  relation = relation.order(scope_or_sort)
@@ -47,18 +47,19 @@ module Ransack
47
47
  end
48
48
 
49
49
  def casted_array?(predicate)
50
- (predicate.respond_to?(:value) && predicate.value.is_a?(Array)) || # Rails 6.1
51
- (predicate.respond_to?(:val) && predicate.val.is_a?(Array)) # Rails 5.2, 6.0
50
+ value_from(predicate).is_a?(Array) && predicate.is_a?(Arel::Nodes::Casted)
52
51
  end
53
52
 
54
- def format_values_for(predicate)
55
- value = if predicate.respond_to?(:value)
56
- predicate.value # Rails 6.1
57
- else
58
- predicate.val # Rails 5.2, 6.0
59
- end
53
+ def value_from(predicate)
54
+ if predicate.respond_to?(:value)
55
+ predicate.value # Rails 6.1
56
+ elsif predicate.respond_to?(:val)
57
+ predicate.val # Rails 5.2, 6.0
58
+ end
59
+ end
60
60
 
61
- value.map do |val|
61
+ def format_values_for(predicate)
62
+ value_from(predicate).map do |val|
62
63
  val.is_a?(String) ? Arel::Nodes.build_quoted(val) : val
63
64
  end
64
65
  end
@@ -34,7 +34,8 @@ module Ransack
34
34
  :down_arrow => '&#9650;'.freeze,
35
35
  :default_arrow => nil,
36
36
  :sanitize_scope_args => true,
37
- :postgres_fields_sort_option => nil
37
+ :postgres_fields_sort_option => nil,
38
+ :strip_whitespace => true
38
39
  }
39
40
 
40
41
  def configure
@@ -148,7 +149,7 @@ module Ransack
148
149
  # User may want to configure it like this:
149
150
  #
150
151
  # Ransack.configure do |c|
151
- # c.postgres_fields_sort_option = :nulls_first # or :nulls_last
152
+ # c.postgres_fields_sort_option = :nulls_first # or e.g. :nulls_always_last
152
153
  # end
153
154
  #
154
155
  # See this feature: https://www.postgresql.org/docs/13/queries-order.html
@@ -170,6 +171,19 @@ module Ransack
170
171
  self.options[:hide_sort_order_indicators] = boolean
171
172
  end
172
173
 
174
+ # By default, Ransack displays strips all whitespace when searching for a string.
175
+ # The default may be globally changed in an initializer file like
176
+ # `config/initializers/ransack.rb` as follows:
177
+ #
178
+ # Ransack.configure do |config|
179
+ # # Enable whitespace stripping for string searches
180
+ # config.strip_whitespace = true
181
+ # end
182
+ #
183
+ def strip_whitespace=(boolean)
184
+ self.options[:strip_whitespace] = boolean
185
+ end
186
+
173
187
  def arel_predicate_with_suffix(arel_predicate, suffix)
174
188
  if arel_predicate === Proc
175
189
  proc { |v| "#{arel_predicate.call(v)}#{suffix}" }
@@ -0,0 +1,70 @@
1
+ sv:
2
+ ransack:
3
+ search: "sök"
4
+ predicate: "predikat"
5
+ and: "och"
6
+ or: "eller"
7
+ any: "vilken som"
8
+ all: "alla"
9
+ combinator: "kombinator"
10
+ attribute: "attribut"
11
+ value: "värde"
12
+ condition: "villkor"
13
+ sort: "sortera"
14
+ asc: "stigande"
15
+ desc: "fallande"
16
+ predicates:
17
+ eq: "lika med"
18
+ eq_any: "lika med vilket som"
19
+ eq_all: "lika med alla"
20
+ not_eq: "inte lika med"
21
+ not_eq_any: "inte lika med någon"
22
+ not_eq_all: "inte lika med alla"
23
+ matches: "matchar"
24
+ matches_any: "matchar någon"
25
+ matches_all: "matchar alla"
26
+ does_not_match: "matchar inte"
27
+ does_not_match_any: "matchar inte någon"
28
+ does_not_match_all: "matchar inte alla"
29
+ lt: "mindre än"
30
+ lt_any: "mindre än någon"
31
+ lt_all: "mindre än alla"
32
+ lteq: "mindre än eller lika med"
33
+ lteq_any: "mindre än eller lika med någon"
34
+ lteq_all: "mindre än eller lika med alla"
35
+ gt: "större än"
36
+ gt_any: "större än någon"
37
+ gt_all: "större än alla"
38
+ gteq: "större än eller lika med"
39
+ gteq_any: "större än eller lika med någon"
40
+ gteq_all: "större än eller lika med alla"
41
+ in: "i"
42
+ in_any: "i någon"
43
+ in_all: "i alla"
44
+ not_in: "inte i"
45
+ not_in_any: "inte i någon"
46
+ not_in_all: "inte i alla"
47
+ cont: "innehåller"
48
+ cont_any: "innehåller någon"
49
+ cont_all: "innehåller alla"
50
+ not_cont: "innehåller inte"
51
+ not_cont_any: "innehåller inte någon"
52
+ not_cont_all: "innehåller inte alla"
53
+ start: "börjar med"
54
+ start_any: "börjar med någon"
55
+ start_all: "börjar med alla"
56
+ not_start: "börjar inte med"
57
+ not_start_any: "börjar inte med någon"
58
+ not_start_all: "börjar inte med alla"
59
+ end: "slutar med"
60
+ end_any: "slutar med någon"
61
+ end_all: "slutar med alla"
62
+ not_end: "slutar inte med"
63
+ not_end_any: "slutar inte med någon"
64
+ not_end_all: "slutar inte med alla"
65
+ 'true': "är sant"
66
+ 'false': "är falskt"
67
+ present: "existerar"
68
+ blank: "är tom"
69
+ 'null': "är null"
70
+ not_null: "är inte null"
@@ -31,8 +31,8 @@ module Ransack
31
31
  end
32
32
 
33
33
  def name=(name)
34
- @name = name
35
- context.bind(self, name)
34
+ @name = context.ransackable_alias(name) || name
35
+ context.bind(self, @name)
36
36
  end
37
37
 
38
38
  def dir=(dir)
@@ -15,10 +15,11 @@ module Ransack
15
15
  :translate, :to => :base
16
16
 
17
17
  def initialize(object, params = {}, options = {})
18
+ strip_whitespace = options.fetch(:strip_whitespace, Ransack.options[:strip_whitespace])
18
19
  params = params.to_unsafe_h if params.respond_to?(:to_unsafe_h)
19
20
  if params.is_a? Hash
20
21
  params = params.dup
21
- params = params.transform_values { |v| v.is_a?(String) ? v.strip : v }
22
+ params = params.transform_values { |v| v.is_a?(String) && strip_whitespace ? v.strip : v }
22
23
  params.delete_if { |k, v| [*v].all?{ |i| i.blank? && i != false } }
23
24
  else
24
25
  params = {}
@@ -32,6 +32,7 @@ module Ransack
32
32
  defaults = base_ancestors.map do |klass|
33
33
  "ransack.attributes.#{i18n_key(klass)}.#{original_name}".to_sym
34
34
  end
35
+ defaults << options.delete(:default) if options[:default]
35
36
 
36
37
  translated_names = attribute_names.map do |name|
37
38
  attribute_name(context, name, options[:include_associations])
@@ -48,7 +49,6 @@ module Ransack
48
49
  defaults << "%{attributes}".freeze
49
50
  end
50
51
 
51
- defaults << options.delete(:default) if options[:default]
52
52
  options.reverse_merge! count: 1, default: defaults
53
53
  I18n.translate(defaults.shift, **options.merge(interpolations))
54
54
  end
@@ -1,3 +1,3 @@
1
1
  module Ransack
2
- VERSION = '2.4.2'
2
+ VERSION = '2.5.0'
3
3
  end
@@ -17,7 +17,7 @@ module Polyamorous
17
17
  expect(subject).not_to eq new_join_association(reflection, parent.children, Article)
18
18
  end
19
19
 
20
- it 'leaves the orginal reflection intact for thread safety' do
20
+ it 'leaves the original reflection intact for thread safety' do
21
21
  reflection.instance_variable_set(:@klass, Article)
22
22
  join_association
23
23
  .swapping_reflection_klass(reflection, Person) do |new_reflection|
@@ -44,12 +44,12 @@ module Ransack
44
44
 
45
45
  it 'applies stringy boolean scopes with true value in an array' do
46
46
  s = Person.ransack('of_age' => ['true'])
47
- expect(s.result.to_sql).to (include 'age >= 18')
47
+ expect(s.result.to_sql).to (include rails7_and_mysql ? %q{(age >= '18')} : 'age >= 18')
48
48
  end
49
49
 
50
50
  it 'applies stringy boolean scopes with false value in an array' do
51
51
  s = Person.ransack('of_age' => ['false'])
52
- expect(s.result.to_sql).to (include 'age < 18')
52
+ expect(s.result.to_sql).to (include rails7_and_mysql ? %q{age < '18'} : 'age < 18')
53
53
  end
54
54
 
55
55
  it 'ignores unlisted scopes' do
@@ -69,12 +69,12 @@ module Ransack
69
69
 
70
70
  it 'passes values to scopes' do
71
71
  s = Person.ransack('over_age' => 18)
72
- expect(s.result.to_sql).to (include 'age > 18')
72
+ expect(s.result.to_sql).to (include rails7_and_mysql ? %q{age > '18'} : 'age > 18')
73
73
  end
74
74
 
75
75
  it 'chains scopes' do
76
76
  s = Person.ransack('over_age' => 18, 'active' => true)
77
- expect(s.result.to_sql).to (include 'age > 18')
77
+ expect(s.result.to_sql).to (include rails7_and_mysql ? %q{age > '18'} : 'age > 18')
78
78
  expect(s.result.to_sql).to (include 'active = 1')
79
79
  end
80
80
 
@@ -89,12 +89,12 @@ module Ransack
89
89
 
90
90
  it 'passes true values to scopes' do
91
91
  s = Person.ransack('over_age' => 1)
92
- expect(s.result.to_sql).to (include 'age > 1')
92
+ expect(s.result.to_sql).to (include rails7_and_mysql ? %q{age > '1'} : 'age > 1')
93
93
  end
94
94
 
95
95
  it 'passes false values to scopes' do
96
96
  s = Person.ransack('over_age' => 0)
97
- expect(s.result.to_sql).to (include 'age > 0')
97
+ expect(s.result.to_sql).to (include rails7_and_mysql ? %q{age > '0'} : 'age > 0')
98
98
  end
99
99
  end
100
100
 
@@ -107,12 +107,12 @@ module Ransack
107
107
 
108
108
  it 'passes true values to scopes' do
109
109
  s = Person.ransack('over_age' => 1)
110
- expect(s.result.to_sql).to (include 'age > 1')
110
+ expect(s.result.to_sql).to (include rails7_and_mysql ? %q{age > '1'} : 'age > 1')
111
111
  end
112
112
 
113
113
  it 'passes false values to scopes' do
114
114
  s = Person.ransack('over_age' => 0)
115
- expect(s.result.to_sql).to (include 'age > 0')
115
+ expect(s.result.to_sql).to (include rails7_and_mysql ? %q{age > '0'} : 'age > 0')
116
116
  end
117
117
  end
118
118
 
@@ -123,7 +123,7 @@ module Ransack
123
123
  end
124
124
 
125
125
  it 'raises exception if ransack! called with unknown condition' do
126
- expect { Person.ransack!(unknown_attr_eq: 'Ernie') }.to raise_error
126
+ expect { Person.ransack!(unknown_attr_eq: 'Ernie') }.to raise_error(ArgumentError)
127
127
  end
128
128
 
129
129
  it 'does not modify the parameters' do
@@ -314,7 +314,11 @@ module Ransack
314
314
  end
315
315
 
316
316
  it 'should function correctly with a multi-parameter attribute' do
317
- ::ActiveRecord::Base.default_timezone = :utc
317
+ if ::ActiveRecord::VERSION::MAJOR >= 7
318
+ ::ActiveRecord.default_timezone = :utc
319
+ else
320
+ ::ActiveRecord::Base.default_timezone = :utc
321
+ end
318
322
  Time.zone = 'UTC'
319
323
 
320
324
  date = Date.current
@@ -690,6 +694,10 @@ module Ransack
690
694
  it { should eq [] }
691
695
  end
692
696
 
697
+ private
698
+ def rails7_and_mysql
699
+ ::ActiveRecord::VERSION::MAJOR >= 7 && ENV['DB'] == 'mysql'
700
+ end
693
701
  end
694
702
  end
695
703
  end
@@ -45,6 +45,20 @@ module Ransack
45
45
  Ransack.options = default
46
46
  end
47
47
 
48
+ it 'should have default value for strip_whitespace' do
49
+ expect(Ransack.options[:strip_whitespace]).to eq true
50
+ end
51
+
52
+ it 'changes default search key parameter' do
53
+ default = Ransack.options.clone
54
+
55
+ Ransack.configure { |c| c.strip_whitespace = false }
56
+
57
+ expect(Ransack.options[:strip_whitespace]).to eq false
58
+
59
+ Ransack.options = default
60
+ end
61
+
48
62
  it 'should have default values for arrows' do
49
63
  expect(Ransack.options[:up_arrow]).to eq '&#9660;'
50
64
  expect(Ransack.options[:down_arrow]).to eq '&#9650;'
@@ -3,6 +3,19 @@ require 'spec_helper'
3
3
  module Ransack
4
4
  module Nodes
5
5
  describe Condition do
6
+ context 'bug report #1245' do
7
+ it 'preserves tuple behavior' do
8
+ ransack_hash = {
9
+ m: 'and',
10
+ g: [
11
+ { title_type_in: ['["title 1", ""]'] }
12
+ ]
13
+ }
14
+
15
+ sql = Article.ransack(ransack_hash).result.to_sql
16
+ expect(sql).to include("IN (('title 1', ''))")
17
+ end
18
+ end
6
19
 
7
20
  context 'with an alias' do
8
21
  subject {
@@ -20,10 +20,42 @@ module Ransack
20
20
  Search.new(Person, name_eq: 'foobar')
21
21
  end
22
22
 
23
- it 'strip leading & trailing whitespace before building' do
24
- expect_any_instance_of(Search).to receive(:build)
25
- .with({ 'name_eq' => 'foobar' })
26
- Search.new(Person, name_eq: ' foobar ')
23
+ context 'whitespace stripping' do
24
+ context 'when whitespace_strip option is true' do
25
+ before do
26
+ Ransack.configure { |c| c.strip_whitespace = true }
27
+ end
28
+
29
+ it 'strips leading & trailing whitespace before building' do
30
+ expect_any_instance_of(Search).to receive(:build)
31
+ .with({ 'name_eq' => 'foobar' })
32
+ Search.new(Person, name_eq: ' foobar ')
33
+ end
34
+ end
35
+
36
+ context 'when whitespace_strip option is false' do
37
+ before do
38
+ Ransack.configure { |c| c.strip_whitespace = false }
39
+ end
40
+
41
+ it 'doesn\'t strip leading & trailing whitespace before building' do
42
+ expect_any_instance_of(Search).to receive(:build)
43
+ .with({ 'name_eq' => ' foobar ' })
44
+ Search.new(Person, name_eq: ' foobar ')
45
+ end
46
+ end
47
+
48
+ it 'strips leading & trailing whitespace when strip_whitespace search parameter is true' do
49
+ expect_any_instance_of(Search).to receive(:build)
50
+ .with({ 'name_eq' => 'foobar' })
51
+ Search.new(Person, { name_eq: ' foobar ' }, { strip_whitespace: true })
52
+ end
53
+
54
+ it 'doesn\'t strip leading & trailing whitespace when strip_whitespace search parameter is false' do
55
+ expect_any_instance_of(Search).to receive(:build)
56
+ .with({ 'name_eq' => ' foobar ' })
57
+ Search.new(Person, { name_eq: ' foobar ' }, { strip_whitespace: false })
58
+ end
27
59
  end
28
60
 
29
61
  it 'removes empty suffixed conditions before building' do
@@ -300,8 +332,6 @@ module Ransack
300
332
  end
301
333
 
302
334
  it 'use appropriate table alias' do
303
- skip "Rails 6 regressed here, but it's fixed in 6-0-stable since https://github.com/rails/rails/commit/f9ba52477ca288e7effa5f6794ae3df3f4e982bc" if ENV["RAILS"] == "v6.0.3"
304
-
305
335
  s = Search.new(Person, {
306
336
  name_eq: "person_name_query",
307
337
  articles_title_eq: "person_article_title_query",
@@ -451,82 +481,109 @@ module Ransack
451
481
  expect(sort.dir).to eq 'asc'
452
482
  end
453
483
 
454
- it 'creates sorts based on multiple attributes/directions in array format' do
455
- @s.sorts = ['id desc', { name: 'name', dir: 'asc' }]
484
+ it 'creates sorts based on a single alias/direction' do
485
+ @s.sorts = 'daddy desc'
486
+ expect(@s.sorts.size).to eq(1)
487
+ sort = @s.sorts.first
488
+ expect(sort).to be_a Nodes::Sort
489
+ expect(sort.name).to eq 'parent_name'
490
+ expect(sort.dir).to eq 'desc'
491
+ end
492
+
493
+ it 'creates sorts based on a single alias and uppercase direction' do
494
+ @s.sorts = 'daddy DESC'
495
+ expect(@s.sorts.size).to eq(1)
496
+ sort = @s.sorts.first
497
+ expect(sort).to be_a Nodes::Sort
498
+ expect(sort.name).to eq 'parent_name'
499
+ expect(sort.dir).to eq 'desc'
500
+ end
501
+
502
+ it 'creates sorts based on a single alias and without direction' do
503
+ @s.sorts = 'daddy'
504
+ expect(@s.sorts.size).to eq(1)
505
+ sort = @s.sorts.first
506
+ expect(sort).to be_a Nodes::Sort
507
+ expect(sort.name).to eq 'parent_name'
508
+ expect(sort.dir).to eq 'asc'
509
+ end
510
+
511
+ it 'creates sorts based on attributes, alias and directions in array format' do
512
+ @s.sorts = ['id desc', { name: 'daddy', dir: 'asc' }]
456
513
  expect(@s.sorts.size).to eq(2)
457
514
  sort1, sort2 = @s.sorts
458
515
  expect(sort1).to be_a Nodes::Sort
459
516
  expect(sort1.name).to eq 'id'
460
517
  expect(sort1.dir).to eq 'desc'
461
518
  expect(sort2).to be_a Nodes::Sort
462
- expect(sort2.name).to eq 'name'
519
+ expect(sort2.name).to eq 'parent_name'
463
520
  expect(sort2.dir).to eq 'asc'
464
521
  end
465
522
 
466
- it 'creates sorts based on multiple attributes and uppercase directions in array format' do
467
- @s.sorts = ['id DESC', { name: 'name', dir: 'ASC' }]
523
+ it 'creates sorts based on attributes, alias and uppercase directions in array format' do
524
+ @s.sorts = ['id DESC', { name: 'daddy', dir: 'ASC' }]
468
525
  expect(@s.sorts.size).to eq(2)
469
526
  sort1, sort2 = @s.sorts
470
527
  expect(sort1).to be_a Nodes::Sort
471
528
  expect(sort1.name).to eq 'id'
472
529
  expect(sort1.dir).to eq 'desc'
473
530
  expect(sort2).to be_a Nodes::Sort
474
- expect(sort2.name).to eq 'name'
531
+ expect(sort2.name).to eq 'parent_name'
475
532
  expect(sort2.dir).to eq 'asc'
476
533
  end
477
534
 
478
- it 'creates sorts based on multiple attributes and different directions
535
+ it 'creates sorts based on attributes, alias and different directions
479
536
  in array format' do
480
- @s.sorts = ['id DESC', { name: 'name', dir: nil }]
537
+ @s.sorts = ['id DESC', { name: 'daddy', dir: nil }]
481
538
  expect(@s.sorts.size).to eq(2)
482
539
  sort1, sort2 = @s.sorts
483
540
  expect(sort1).to be_a Nodes::Sort
484
541
  expect(sort1.name).to eq 'id'
485
542
  expect(sort1.dir).to eq 'desc'
486
543
  expect(sort2).to be_a Nodes::Sort
487
- expect(sort2.name).to eq 'name'
544
+ expect(sort2.name).to eq 'parent_name'
488
545
  expect(sort2.dir).to eq 'asc'
489
546
  end
490
547
 
491
- it 'creates sorts based on multiple attributes/directions in hash format' do
548
+ it 'creates sorts based on attributes, alias and directions in hash format' do
492
549
  @s.sorts = {
493
550
  '0' => { name: 'id', dir: 'desc' },
494
- '1' => { name: 'name', dir: 'asc' }
551
+ '1' => { name: 'daddy', dir: 'asc' }
495
552
  }
496
553
  expect(@s.sorts.size).to eq(2)
497
554
  expect(@s.sorts).to be_all { |s| Nodes::Sort === s }
498
555
  id_sort = @s.sorts.detect { |s| s.name == 'id' }
499
- name_sort = @s.sorts.detect { |s| s.name == 'name' }
556
+ daddy_sort = @s.sorts.detect { |s| s.name == 'parent_name' }
500
557
  expect(id_sort.dir).to eq 'desc'
501
- expect(name_sort.dir).to eq 'asc'
558
+ expect(daddy_sort.dir).to eq 'asc'
502
559
  end
503
560
 
504
- it 'creates sorts based on multiple attributes and uppercase directions
561
+ it 'creates sorts based on attributes, alias and uppercase directions
505
562
  in hash format' do
506
563
  @s.sorts = {
507
564
  '0' => { name: 'id', dir: 'DESC' },
508
- '1' => { name: 'name', dir: 'ASC' }
565
+ '1' => { name: 'daddy', dir: 'ASC' }
509
566
  }
510
567
  expect(@s.sorts.size).to eq(2)
511
568
  expect(@s.sorts).to be_all { |s| Nodes::Sort === s }
512
569
  id_sort = @s.sorts.detect { |s| s.name == 'id' }
513
- name_sort = @s.sorts.detect { |s| s.name == 'name' }
570
+ daddy_sort = @s.sorts.detect { |s| s.name == 'parent_name' }
514
571
  expect(id_sort.dir).to eq 'desc'
515
- expect(name_sort.dir).to eq 'asc'
572
+ expect(daddy_sort.dir).to eq 'asc'
516
573
  end
517
574
 
518
- it 'creates sorts based on multiple attributes and different directions
575
+ it 'creates sorts based on attributes, alias and different directions
519
576
  in hash format' do
520
577
  @s.sorts = {
521
578
  '0' => { name: 'id', dir: 'DESC' },
522
- '1' => { name: 'name', dir: nil }
579
+ '1' => { name: 'daddy', dir: nil }
523
580
  }
524
581
  expect(@s.sorts.size).to eq(2)
525
582
  expect(@s.sorts).to be_all { |s| Nodes::Sort === s }
526
583
  id_sort = @s.sorts.detect { |s| s.name == 'id' }
527
- name_sort = @s.sorts.detect { |s| s.name == 'name' }
584
+ daddy_sort = @s.sorts.detect { |s| s.name == 'parent_name' }
528
585
  expect(id_sort.dir).to eq 'desc'
529
- expect(name_sort.dir).to eq 'asc'
586
+ expect(daddy_sort.dir).to eq 'asc'
530
587
  end
531
588
 
532
589
  it 'overrides existing sort' do
@@ -554,6 +611,39 @@ module Ransack
554
611
 
555
612
  Ransack.options = default
556
613
  end
614
+
615
+ it "PG's sort option with double name", if: ::ActiveRecord::Base.connection.adapter_name == "PostgreSQL" do
616
+ default = Ransack.options.clone
617
+
618
+ s = Search.new(Person, s: 'doubled_name asc')
619
+ expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" || \"people\".\"name\" ASC"
620
+
621
+ Ransack.configure { |c| c.postgres_fields_sort_option = :nulls_first }
622
+ s = Search.new(Person, s: 'doubled_name asc')
623
+ expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" || \"people\".\"name\" ASC NULLS FIRST"
624
+ s = Search.new(Person, s: 'doubled_name desc')
625
+ expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" || \"people\".\"name\" DESC NULLS LAST"
626
+
627
+ Ransack.configure { |c| c.postgres_fields_sort_option = :nulls_last }
628
+ s = Search.new(Person, s: 'doubled_name asc')
629
+ expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" || \"people\".\"name\" ASC NULLS LAST"
630
+ s = Search.new(Person, s: 'doubled_name desc')
631
+ expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" || \"people\".\"name\" DESC NULLS FIRST"
632
+
633
+ Ransack.configure { |c| c.postgres_fields_sort_option = :nulls_always_first }
634
+ s = Search.new(Person, s: 'doubled_name asc')
635
+ expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" || \"people\".\"name\" ASC NULLS FIRST"
636
+ s = Search.new(Person, s: 'doubled_name desc')
637
+ expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" || \"people\".\"name\" DESC NULLS FIRST"
638
+
639
+ Ransack.configure { |c| c.postgres_fields_sort_option = :nulls_always_last }
640
+ s = Search.new(Person, s: 'doubled_name asc')
641
+ expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" || \"people\".\"name\" ASC NULLS LAST"
642
+ s = Search.new(Person, s: 'doubled_name desc')
643
+ expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" || \"people\".\"name\" DESC NULLS LAST"
644
+
645
+ Ransack.options = default
646
+ end
557
647
  end
558
648
 
559
649
  describe '#method_missing' do
@@ -138,6 +138,29 @@ class Article < ActiveRecord::Base
138
138
  alias_attribute :content, :body
139
139
 
140
140
  default_scope { where("'default_scope' = 'default_scope'") }
141
+
142
+ ransacker :title_type, formatter: lambda { |tuples|
143
+ title, type = JSON.parse(tuples)
144
+ Arel::Nodes::Grouping.new(
145
+ [
146
+ Arel::Nodes.build_quoted(title),
147
+ Arel::Nodes.build_quoted(type)
148
+ ]
149
+ )
150
+ } do |_parent|
151
+ articles = Article.arel_table
152
+ Arel::Nodes::Grouping.new(
153
+ %i[title type].map do |field|
154
+ Arel::Nodes::NamedFunction.new(
155
+ 'COALESCE',
156
+ [
157
+ Arel::Nodes::NamedFunction.new('TRIM', [articles[field]]),
158
+ Arel::Nodes.build_quoted('')
159
+ ]
160
+ )
161
+ end
162
+ )
163
+ end
141
164
  end
142
165
 
143
166
  class StoryArticle < Article
metadata CHANGED
@@ -1,17 +1,17 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ransack
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.4.2
4
+ version: 2.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ernie Miller
8
8
  - Ryan Bigg
9
9
  - Jon Atack
10
10
  - Sean Carroll
11
- autorequire:
11
+ autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2021-01-23 00:00:00.000000000 Z
14
+ date: 2021-12-25 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: activerecord
@@ -83,6 +83,7 @@ files:
83
83
  - bug_report_templates/test-ransacker-arel-present-predicate.rb
84
84
  - docs/img/create_release.png
85
85
  - docs/release_process.md
86
+ - lib/polyamorous.rb
86
87
  - lib/polyamorous/activerecord_5.2_ruby_2/join_association.rb
87
88
  - lib/polyamorous/activerecord_5.2_ruby_2/join_dependency.rb
88
89
  - lib/polyamorous/activerecord_5.2_ruby_2/reflection.rb
@@ -92,9 +93,9 @@ files:
92
93
  - lib/polyamorous/activerecord_6.1_ruby_2/join_association.rb
93
94
  - lib/polyamorous/activerecord_6.1_ruby_2/join_dependency.rb
94
95
  - lib/polyamorous/activerecord_6.1_ruby_2/reflection.rb
95
- - lib/polyamorous/activerecord_6.2_ruby_2/join_association.rb
96
- - lib/polyamorous/activerecord_6.2_ruby_2/join_dependency.rb
97
- - lib/polyamorous/activerecord_6.2_ruby_2/reflection.rb
96
+ - lib/polyamorous/activerecord_7.0_ruby_2/join_association.rb
97
+ - lib/polyamorous/activerecord_7.0_ruby_2/join_dependency.rb
98
+ - lib/polyamorous/activerecord_7.0_ruby_2/reflection.rb
98
99
  - lib/polyamorous/join.rb
99
100
  - lib/polyamorous/polyamorous.rb
100
101
  - lib/polyamorous/swapping_reflection_class.rb
@@ -137,6 +138,7 @@ files:
137
138
  - lib/ransack/locale/ro.yml
138
139
  - lib/ransack/locale/ru.yml
139
140
  - lib/ransack/locale/sk.yml
141
+ - lib/ransack/locale/sv.yml
140
142
  - lib/ransack/locale/tr.yml
141
143
  - lib/ransack/locale/zh-CN.yml
142
144
  - lib/ransack/locale/zh-TW.yml
@@ -190,7 +192,7 @@ homepage: https://github.com/activerecord-hackery/ransack
190
192
  licenses:
191
193
  - MIT
192
194
  metadata: {}
193
- post_install_message:
195
+ post_install_message:
194
196
  rdoc_options: []
195
197
  require_paths:
196
198
  - lib
@@ -205,8 +207,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
205
207
  - !ruby/object:Gem::Version
206
208
  version: '0'
207
209
  requirements: []
208
- rubygems_version: 3.0.3
209
- signing_key:
210
+ rubygems_version: 3.1.4
211
+ signing_key:
210
212
  specification_version: 4
211
213
  summary: Object-based searching for Active Record and Mongoid (currently).
212
214
  test_files: