ransack 2.3.2 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/FUNDING.yml +3 -0
- data/.github/SECURITY.md +12 -0
- data/.github/workflows/cronjob.yml +102 -0
- data/.github/workflows/rubocop.yml +20 -0
- data/.github/workflows/test.yml +163 -0
- data/.rubocop.yml +44 -0
- data/CHANGELOG.md +28 -1
- data/CONTRIBUTING.md +16 -11
- data/Gemfile +5 -3
- data/README.md +167 -30
- data/bug_report_templates/test-ransack-scope-and-column-same-name.rb +78 -0
- data/bug_report_templates/test-ransacker-arel-present-predicate.rb +71 -0
- data/docs/img/create_release.png +0 -0
- data/docs/release_process.md +17 -0
- data/{polyamorous/lib → lib}/polyamorous/activerecord_5.2_ruby_2/join_association.rb +4 -0
- data/{polyamorous/lib → lib}/polyamorous/activerecord_5.2_ruby_2/join_dependency.rb +0 -0
- data/lib/polyamorous/activerecord_5.2_ruby_2/reflection.rb +11 -0
- data/{polyamorous/lib → lib}/polyamorous/activerecord_6.0_ruby_2/join_association.rb +0 -1
- data/{polyamorous/lib → lib}/polyamorous/activerecord_6.0_ruby_2/join_dependency.rb +0 -1
- data/{polyamorous/lib → lib}/polyamorous/activerecord_6.0_ruby_2/reflection.rb +0 -1
- data/lib/polyamorous/activerecord_6.1_ruby_2/join_association.rb +74 -0
- data/lib/polyamorous/activerecord_6.1_ruby_2/join_dependency.rb +93 -0
- data/{polyamorous/lib → lib}/polyamorous/activerecord_6.1_ruby_2/reflection.rb +0 -1
- data/lib/polyamorous/activerecord_7.0_ruby_2/join_association.rb +1 -0
- data/lib/polyamorous/activerecord_7.0_ruby_2/join_dependency.rb +1 -0
- data/lib/polyamorous/activerecord_7.0_ruby_2/reflection.rb +1 -0
- data/{polyamorous/lib → lib}/polyamorous/join.rb +0 -0
- data/{polyamorous/lib → lib/polyamorous}/polyamorous.rb +1 -1
- data/{polyamorous/lib → lib}/polyamorous/swapping_reflection_class.rb +0 -0
- data/{polyamorous/lib → lib}/polyamorous/tree_node.rb +0 -0
- data/lib/polyamorous.rb +1 -0
- data/lib/ransack/adapters/active_record/base.rb +5 -1
- data/lib/ransack/adapters/active_record/context.rb +55 -13
- data/lib/ransack/adapters/active_record/ransack/constants.rb +1 -1
- data/lib/ransack/adapters/active_record/ransack/context.rb +1 -0
- data/lib/ransack/adapters/active_record/ransack/nodes/condition.rb +11 -3
- data/lib/ransack/configuration.rb +31 -1
- data/lib/ransack/constants.rb +2 -2
- data/lib/ransack/helpers/form_builder.rb +3 -3
- data/lib/ransack/helpers.rb +1 -1
- data/lib/ransack/locale/sv.yml +70 -0
- data/lib/ransack/nodes/attribute.rb +1 -1
- data/lib/ransack/nodes/condition.rb +0 -2
- data/lib/ransack/nodes/grouping.rb +1 -1
- data/lib/ransack/nodes/sort.rb +3 -3
- data/lib/ransack/nodes/value.rb +1 -1
- data/lib/ransack/search.rb +4 -1
- data/lib/ransack/translate.rb +4 -4
- data/lib/ransack/version.rb +1 -1
- data/lib/ransack.rb +2 -2
- data/ransack.gemspec +8 -14
- data/spec/blueprints/articles.rb +1 -1
- data/spec/blueprints/comments.rb +1 -1
- data/spec/blueprints/notes.rb +1 -1
- data/spec/blueprints/tags.rb +1 -1
- data/spec/console.rb +5 -5
- data/spec/helpers/ransack_helper.rb +1 -1
- data/spec/{ransack → polyamorous}/join_association_spec.rb +8 -1
- data/spec/{ransack → polyamorous}/join_dependency_spec.rb +0 -0
- data/spec/{ransack → polyamorous}/join_spec.rb +0 -0
- data/spec/ransack/adapters/active_record/base_spec.rb +26 -15
- data/spec/ransack/adapters/active_record/context_spec.rb +19 -18
- data/spec/ransack/configuration_spec.rb +24 -0
- data/spec/ransack/helpers/form_helper_spec.rb +16 -16
- data/spec/ransack/nodes/condition_spec.rb +13 -0
- data/spec/ransack/nodes/grouping_spec.rb +2 -2
- data/spec/ransack/predicate_spec.rb +1 -1
- data/spec/ransack/search_spec.rb +215 -30
- data/spec/spec_helper.rb +7 -5
- data/spec/support/schema.rb +28 -2
- metadata +45 -47
- data/.travis.yml +0 -47
- data/polyamorous/lib/polyamorous/activerecord_5.2_ruby_2/reflection.rb +0 -12
- data/polyamorous/lib/polyamorous/activerecord_6.1_ruby_2/join_association.rb +0 -2
- data/polyamorous/lib/polyamorous/activerecord_6.1_ruby_2/join_dependency.rb +0 -2
- data/polyamorous/lib/polyamorous/version.rb +0 -3
- data/polyamorous/polyamorous.gemspec +0 -27
data/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# 
|
|
2
2
|
|
|
3
|
-
[](https://github.com/activerecord-hackery/ransack/actions)
|
|
4
4
|
[](http://badge.fury.io/rb/ransack)
|
|
5
5
|
[](https://codeclimate.com/github/activerecord-hackery/ransack)
|
|
6
6
|
[](#backers) [](#sponsors)
|
|
@@ -11,13 +11,11 @@ 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
|
|
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
|
|
|
20
|
-
Ransack is
|
|
18
|
+
Ransack is supported for Rails 6.1, 6.0, 5.2 on Ruby 2.6.6 and later.
|
|
21
19
|
|
|
22
20
|
In your Gemfile, for the last officially released gem:
|
|
23
21
|
|
|
@@ -41,27 +39,14 @@ gem 'ransack', github: 'activerecord-hackery/ransack'
|
|
|
41
39
|
|
|
42
40
|
## Usage
|
|
43
41
|
|
|
44
|
-
Ransack can be used in one of two modes, simple or advanced.
|
|
42
|
+
Ransack can be used in one of two modes, simple or advanced. For
|
|
43
|
+
searching/filtering not requiring complex boolean logic, Ransack's simple
|
|
44
|
+
mode should meet your needs.
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
This mode works much like MetaSearch, for those of you who are familiar with
|
|
49
|
-
it, and requires very little setup effort.
|
|
50
|
-
|
|
51
|
-
If you're coming from MetaSearch, things to note:
|
|
52
|
-
|
|
53
|
-
1. The default param key for search params is now `:q`, instead of `:search`.
|
|
54
|
-
This is primarily to shorten query strings, though advanced queries (below)
|
|
55
|
-
will still run afoul of URL length limits in most browsers and require a
|
|
56
|
-
switch to HTTP POST requests. This key is [configurable](https://github.com/activerecord-hackery/ransack/wiki/Configuration).
|
|
46
|
+
If you're coming from MetaSearch (Ransack's predecessor), refer to the
|
|
47
|
+
[Updating From MetaSearch](#updating-from-metasearch) section
|
|
57
48
|
|
|
58
|
-
|
|
59
|
-
object is passed to it.
|
|
60
|
-
|
|
61
|
-
3. Common ActiveRecord::Relation methods are no longer delegated by the
|
|
62
|
-
search object. Instead, you will get your search results (an
|
|
63
|
-
ActiveRecord::Relation in the case of the ActiveRecord adapter) via a call to
|
|
64
|
-
`Ransack#result`.
|
|
49
|
+
### Simple Mode
|
|
65
50
|
|
|
66
51
|
#### In your controller
|
|
67
52
|
|
|
@@ -78,9 +63,35 @@ this example, with preloading each Person's Articles and pagination):
|
|
|
78
63
|
def index
|
|
79
64
|
@q = Person.ransack(params[:q])
|
|
80
65
|
@people = @q.result.includes(:articles).page(params[:page])
|
|
66
|
+
end
|
|
67
|
+
```
|
|
81
68
|
|
|
82
|
-
|
|
83
|
-
|
|
69
|
+
##### Default search options
|
|
70
|
+
|
|
71
|
+
**Search parameter**
|
|
72
|
+
|
|
73
|
+
Ransack uses a default `:q` param key for search params. This may be changed by
|
|
74
|
+
setting the `search_key` option in a Ransack initializer file (typically
|
|
75
|
+
`config/initializers/ransack.rb`):
|
|
76
|
+
|
|
77
|
+
```ruby
|
|
78
|
+
Ransack.configure do |c|
|
|
79
|
+
# Change default search parameter key name.
|
|
80
|
+
# Default key name is :q
|
|
81
|
+
c.search_key = :query
|
|
82
|
+
end
|
|
83
|
+
```
|
|
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
|
|
84
95
|
end
|
|
85
96
|
```
|
|
86
97
|
|
|
@@ -250,6 +261,57 @@ the order indicator arrow by passing `hide_indicator: true` in the sort link:
|
|
|
250
261
|
default_order: { last_name: 'asc', first_name: 'desc' }) %>
|
|
251
262
|
```
|
|
252
263
|
|
|
264
|
+
#### PostgreSQL's sort option
|
|
265
|
+
|
|
266
|
+
The `NULLS FIRST` and `NULLS LAST` options can be used to determine whether nulls appear before or after non-null values in the sort ordering.
|
|
267
|
+
|
|
268
|
+
You may want to configure it like this:
|
|
269
|
+
|
|
270
|
+
```rb
|
|
271
|
+
Ransack.configure do |c|
|
|
272
|
+
c.postgres_fields_sort_option = :nulls_first # or :nulls_last
|
|
273
|
+
end
|
|
274
|
+
```
|
|
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
|
+
|
|
284
|
+
See this feature: https://www.postgresql.org/docs/13/queries-order.html
|
|
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
|
+
|
|
253
315
|
### Advanced Mode
|
|
254
316
|
|
|
255
317
|
"Advanced" searches (ab)use Rails' nested attributes functionality in order to
|
|
@@ -408,6 +470,25 @@ query parameters in your URLs.
|
|
|
408
470
|
<% end %>
|
|
409
471
|
```
|
|
410
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
|
+
|
|
411
492
|
### Search Matchers
|
|
412
493
|
|
|
413
494
|
List of all possible predicates
|
|
@@ -437,13 +518,10 @@ List of all possible predicates
|
|
|
437
518
|
| `*_lteq_any` | Less than or equal to any | |
|
|
438
519
|
| `*_gt_any` | Greater than any | |
|
|
439
520
|
| `*_gteq_any` | Greater than or equal to any | |
|
|
440
|
-
| `*_matches_any` | `*_does_not_match_any` | same as above but with `LIKE` |
|
|
441
521
|
| `*_lt_all` | Less than all | SQL: `col < value1 AND col < value2` |
|
|
442
522
|
| `*_lteq_all` | Less than or equal to all | |
|
|
443
523
|
| `*_gt_all` | Greater than all | |
|
|
444
524
|
| `*_gteq_all` | Greater than or equal to all | |
|
|
445
|
-
| `*_matches_all` | Matches all | same as above but with `LIKE` |
|
|
446
|
-
| `*_does_not_match_all` | Does not match all | |
|
|
447
525
|
| `*_not_eq_all` | none of values in a set | |
|
|
448
526
|
| `*_start` | Starts with | SQL: `col LIKE 'value%'` |
|
|
449
527
|
| `*_not_start` | Does not start with | |
|
|
@@ -463,7 +541,7 @@ List of all possible predicates
|
|
|
463
541
|
| `*_not_cont` | Does not contain |
|
|
464
542
|
| `*_not_cont_any` | Does not contain any of | |
|
|
465
543
|
| `*_not_cont_all` | Does not contain all of | |
|
|
466
|
-
| `*_i_cont` | Contains value with case insensitive | uses `
|
|
544
|
+
| `*_i_cont` | Contains value with case insensitive | uses `ILIKE` |
|
|
467
545
|
| `*_i_cont_any` | Contains any of values with case insensitive | |
|
|
468
546
|
| `*_i_cont_all` | Contains all of values with case insensitive | |
|
|
469
547
|
| `*_not_i_cont` | Does not contain with case insensitive |
|
|
@@ -673,6 +751,43 @@ Trying it out in `rails console`:
|
|
|
673
751
|
|
|
674
752
|
That's it! Now you know how to whitelist/blacklist various elements in Ransack.
|
|
675
753
|
|
|
754
|
+
### Handling unknown predicates or attributes
|
|
755
|
+
|
|
756
|
+
By default, Ransack will ignore any unknown predicates or attributes:
|
|
757
|
+
|
|
758
|
+
```ruby
|
|
759
|
+
Article.ransack(unknown_attr_eq: 'Ernie').result.to_sql
|
|
760
|
+
=> SELECT "articles".* FROM "articles"
|
|
761
|
+
```
|
|
762
|
+
|
|
763
|
+
Ransack may be configured to raise an error if passed an unknown predicate or
|
|
764
|
+
attributes, by setting the `ignore_unknown_conditions` option to `false` in your
|
|
765
|
+
Ransack initializer file at `config/initializers/ransack.rb`:
|
|
766
|
+
|
|
767
|
+
```ruby
|
|
768
|
+
Ransack.configure do |c|
|
|
769
|
+
# Raise errors if a query contains an unknown predicate or attribute.
|
|
770
|
+
# Default is true (do not raise error on unknown conditions).
|
|
771
|
+
c.ignore_unknown_conditions = false
|
|
772
|
+
end
|
|
773
|
+
```
|
|
774
|
+
|
|
775
|
+
```ruby
|
|
776
|
+
Article.ransack(unknown_attr_eq: 'Ernie')
|
|
777
|
+
# ArgumentError (Invalid search term unknown_attr_eq)
|
|
778
|
+
```
|
|
779
|
+
|
|
780
|
+
As an alternative to setting a global configuration option, the `.ransack!`
|
|
781
|
+
class method also raises an error if passed an unknown condition:
|
|
782
|
+
|
|
783
|
+
```ruby
|
|
784
|
+
Article.ransack!(unknown_attr_eq: 'Ernie')
|
|
785
|
+
# ArgumentError: Invalid search term unknown_attr_eq
|
|
786
|
+
```
|
|
787
|
+
|
|
788
|
+
This is equivalent to the `ignore_unknown_conditions` configuration option,
|
|
789
|
+
except it may be applied on a case-by-case basis.
|
|
790
|
+
|
|
676
791
|
### Using Scopes/Class Methods
|
|
677
792
|
|
|
678
793
|
Continuing on from the preceding section, searching by scopes requires defining
|
|
@@ -869,6 +984,28 @@ en:
|
|
|
869
984
|
title: Old Ransack Namespaced Title
|
|
870
985
|
```
|
|
871
986
|
|
|
987
|
+
### Updating From MetaSearch
|
|
988
|
+
|
|
989
|
+
Ransack works much like MetaSearch, for those of you who are familiar with
|
|
990
|
+
it, and requires very little setup effort.
|
|
991
|
+
|
|
992
|
+
If you're coming from MetaSearch, things to note:
|
|
993
|
+
|
|
994
|
+
1. The default param key for search params is now `:q`, instead of `:search`.
|
|
995
|
+
This is primarily to shorten query strings, though advanced queries (below)
|
|
996
|
+
will still run afoul of URL length limits in most browsers and require a
|
|
997
|
+
switch to HTTP POST requests. This key is
|
|
998
|
+
[configurable](default-search-parameter) via setting the `search_key` option
|
|
999
|
+
in your Ransack intitializer file.
|
|
1000
|
+
|
|
1001
|
+
2. `form_for` is now `search_form_for`, and validates that a Ransack::Search
|
|
1002
|
+
object is passed to it.
|
|
1003
|
+
|
|
1004
|
+
3. Common ActiveRecord::Relation methods are no longer delegated by the
|
|
1005
|
+
search object. Instead, you will get your search results (an
|
|
1006
|
+
ActiveRecord::Relation in the case of the ActiveRecord adapter) via a call to
|
|
1007
|
+
`Ransack#result`.
|
|
1008
|
+
|
|
872
1009
|
## Mongoid
|
|
873
1010
|
|
|
874
1011
|
Mongoid support has been moved to its own gem at [ransack-mongoid](https://github.com/activerecord-hackery/ransack-mongoid).
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# test-ransack-scope-and-column-same-name.rb
|
|
2
|
+
|
|
3
|
+
# This is a stand-alone test case.
|
|
4
|
+
|
|
5
|
+
# Run it in your console with: `ruby test-ransack-scope-and-column-same-name.rb`
|
|
6
|
+
|
|
7
|
+
# If you change the gem dependencies, run it with:
|
|
8
|
+
# `rm gemfile* && ruby test-ransack-scope-and-column-same-name.rb`
|
|
9
|
+
|
|
10
|
+
unless File.exist?('Gemfile')
|
|
11
|
+
File.write('Gemfile', <<-GEMFILE)
|
|
12
|
+
source 'https://rubygems.org'
|
|
13
|
+
|
|
14
|
+
# Rails master
|
|
15
|
+
gem 'rails', github: 'rails/rails', branch: '6-1-stable'
|
|
16
|
+
|
|
17
|
+
# Rails last release
|
|
18
|
+
# gem 'rails'
|
|
19
|
+
|
|
20
|
+
gem 'sqlite3'
|
|
21
|
+
gem 'ransack', github: 'activerecord-hackery/ransack'
|
|
22
|
+
GEMFILE
|
|
23
|
+
|
|
24
|
+
system 'bundle install'
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
require 'bundler'
|
|
28
|
+
Bundler.setup(:default)
|
|
29
|
+
|
|
30
|
+
require 'active_record'
|
|
31
|
+
require 'minitest/autorun'
|
|
32
|
+
require 'logger'
|
|
33
|
+
require 'ransack'
|
|
34
|
+
|
|
35
|
+
# This connection will do for database-independent bug reports.
|
|
36
|
+
ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')
|
|
37
|
+
ActiveRecord::Base.logger = Logger.new(STDOUT)
|
|
38
|
+
|
|
39
|
+
# Display versions.
|
|
40
|
+
message = "Running test case with Ruby #{RUBY_VERSION}, Active Record #{
|
|
41
|
+
::ActiveRecord::VERSION::STRING}, Arel #{Arel::VERSION} and #{
|
|
42
|
+
::ActiveRecord::Base.connection.adapter_name}"
|
|
43
|
+
line = '=' * message.length
|
|
44
|
+
puts line, message, line
|
|
45
|
+
|
|
46
|
+
ActiveRecord::Schema.define do
|
|
47
|
+
create_table :users, force: true do |t|
|
|
48
|
+
t.boolean :active, null: false, default: true
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
class User < ActiveRecord::Base
|
|
53
|
+
scope :activated, -> (boolean = true) { where(active: boolean) }
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def self.ransackable_scopes(auth_object = nil)
|
|
58
|
+
%i(activated)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
class BugTest < Minitest::Test
|
|
63
|
+
def test_activated_scope_equals_true
|
|
64
|
+
sql = User.ransack({ activated: true }).result.to_sql
|
|
65
|
+
puts sql
|
|
66
|
+
assert_equal(
|
|
67
|
+
"SELECT \"users\".* FROM \"users\" WHERE \"users\".\"active\" = 1", sql
|
|
68
|
+
)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def test_activated_scope_equals_false
|
|
72
|
+
sql = User.ransack({ activated: false }).result.to_sql
|
|
73
|
+
puts sql
|
|
74
|
+
assert_equal(
|
|
75
|
+
"SELECT \"users\".* FROM \"users\"", sql
|
|
76
|
+
)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# test-ransacker-arel-present-predicate.rb
|
|
2
|
+
|
|
3
|
+
# Run it in your console with: `ruby test-ransacker-arel-present-predicate.rb`
|
|
4
|
+
|
|
5
|
+
# If you change the gem dependencies, run it with:
|
|
6
|
+
# `rm gemfile* && ruby test-ransacker-arel-present-predicate.rb`
|
|
7
|
+
|
|
8
|
+
unless File.exist?('Gemfile')
|
|
9
|
+
File.write('Gemfile', <<-GEMFILE)
|
|
10
|
+
source 'https://rubygems.org'
|
|
11
|
+
|
|
12
|
+
# Rails master
|
|
13
|
+
gem 'rails', github: 'rails/rails', branch: '6-1-stable'
|
|
14
|
+
|
|
15
|
+
# Rails last release
|
|
16
|
+
# gem 'rails'
|
|
17
|
+
|
|
18
|
+
gem 'sqlite3'
|
|
19
|
+
gem 'ransack', github: 'activerecord-hackery/ransack'
|
|
20
|
+
GEMFILE
|
|
21
|
+
|
|
22
|
+
system 'bundle install'
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
require 'bundler'
|
|
26
|
+
Bundler.setup(:default)
|
|
27
|
+
|
|
28
|
+
require 'active_record'
|
|
29
|
+
require 'minitest/autorun'
|
|
30
|
+
require 'logger'
|
|
31
|
+
require 'ransack'
|
|
32
|
+
|
|
33
|
+
# This connection will do for database-independent bug reports.
|
|
34
|
+
ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')
|
|
35
|
+
ActiveRecord::Base.logger = Logger.new(STDOUT)
|
|
36
|
+
|
|
37
|
+
# Display versions.
|
|
38
|
+
message = "Running test case with Ruby #{RUBY_VERSION}, Active Record #{
|
|
39
|
+
::ActiveRecord::VERSION::STRING}, Arel #{Arel::VERSION} and #{
|
|
40
|
+
::ActiveRecord::Base.connection.adapter_name}"
|
|
41
|
+
line = '=' * message.length
|
|
42
|
+
puts line, message, line
|
|
43
|
+
|
|
44
|
+
ActiveRecord::Schema.define do
|
|
45
|
+
create_table :projects, force: true do |t|
|
|
46
|
+
t.string :name
|
|
47
|
+
t.string :number
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
class Project < ActiveRecord::Base
|
|
52
|
+
ransacker :name do
|
|
53
|
+
Arel.sql('projects.name')
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
ransacker :number do |parent|
|
|
57
|
+
parent.table[:number]
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
class BugTest < Minitest::Test
|
|
62
|
+
def test_ransackers
|
|
63
|
+
sql = Project.ransack({ number_present: 1 }).result.to_sql
|
|
64
|
+
puts sql
|
|
65
|
+
assert_equal "SELECT \"projects\".* FROM \"projects\" WHERE (\"projects\".\"number\" IS NOT NULL AND \"projects\".\"number\" != '')", sql
|
|
66
|
+
|
|
67
|
+
sql = Project.ransack({ name_present: 1 }).result.to_sql
|
|
68
|
+
puts sql
|
|
69
|
+
assert_equal "SELECT \"projects\".* FROM \"projects\" WHERE (projects.name IS NOT NULL AND projects.name != '')", sql
|
|
70
|
+
end
|
|
71
|
+
end
|
|
Binary file
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
## Release Process
|
|
2
|
+
|
|
3
|
+
*For maintainers of Ransack.*
|
|
4
|
+
|
|
5
|
+
To release a new version of Ransack and publish it to RubyGems, take the following steps:
|
|
6
|
+
|
|
7
|
+
- Create a new release, marked `Prerelease`.
|
|
8
|
+
- Update the versions file to the new release, commit and push to `master`.
|
|
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:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
rake build
|
|
14
|
+
rake release
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+

|
|
File without changes
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
module Polyamorous
|
|
2
|
+
module JoinAssociationExtensions
|
|
3
|
+
include SwappingReflectionClass
|
|
4
|
+
def self.prepended(base)
|
|
5
|
+
base.class_eval { attr_reader :join_type }
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def initialize(reflection, children, polymorphic_class = nil, join_type = Arel::Nodes::InnerJoin)
|
|
9
|
+
@join_type = join_type
|
|
10
|
+
if polymorphic_class && ::ActiveRecord::Base > polymorphic_class
|
|
11
|
+
swapping_reflection_klass(reflection, polymorphic_class) do |reflection|
|
|
12
|
+
super(reflection, children)
|
|
13
|
+
self.reflection.options[:polymorphic] = true
|
|
14
|
+
end
|
|
15
|
+
else
|
|
16
|
+
super(reflection, children)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Same as #join_constraints, but instead of constructing tables from the
|
|
21
|
+
# given block, uses the ones passed
|
|
22
|
+
def join_constraints_with_tables(foreign_table, foreign_klass, join_type, alias_tracker, tables)
|
|
23
|
+
joins = []
|
|
24
|
+
chain = []
|
|
25
|
+
|
|
26
|
+
reflection.chain.each.with_index do |reflection, i|
|
|
27
|
+
table = tables[i]
|
|
28
|
+
|
|
29
|
+
@table ||= table
|
|
30
|
+
chain << [reflection, table]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# The chain starts with the target table, but we want to end with it here (makes
|
|
34
|
+
# more sense in this context), so we reverse
|
|
35
|
+
chain.reverse_each do |reflection, table|
|
|
36
|
+
klass = reflection.klass
|
|
37
|
+
|
|
38
|
+
join_scope = reflection.join_scope(table, foreign_table, foreign_klass)
|
|
39
|
+
|
|
40
|
+
unless join_scope.references_values.empty?
|
|
41
|
+
join_dependency = join_scope.construct_join_dependency(
|
|
42
|
+
join_scope.eager_load_values | join_scope.includes_values, Arel::Nodes::OuterJoin
|
|
43
|
+
)
|
|
44
|
+
join_scope.joins!(join_dependency)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
arel = join_scope.arel(alias_tracker.aliases)
|
|
48
|
+
nodes = arel.constraints.first
|
|
49
|
+
|
|
50
|
+
if nodes.is_a?(Arel::Nodes::And)
|
|
51
|
+
others = nodes.children.extract! do |node|
|
|
52
|
+
!Arel.fetch_attribute(node) { |attr| attr.relation.name == table.name }
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
joins << table.create_join(table, table.create_on(nodes), join_type)
|
|
57
|
+
|
|
58
|
+
if others && !others.empty?
|
|
59
|
+
joins.concat arel.join_sources
|
|
60
|
+
append_constraints(joins.last, others)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# The current table in this iteration becomes the foreign table in the next
|
|
64
|
+
foreign_table, foreign_klass = table, klass
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
joins
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def ==(other)
|
|
71
|
+
base_klass == other.base_klass
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# active_record_6.1_ruby_2/join_dependency.rb
|
|
2
|
+
module Polyamorous
|
|
3
|
+
module JoinDependencyExtensions
|
|
4
|
+
# Replaces ActiveRecord::Associations::JoinDependency#build
|
|
5
|
+
def build(associations, base_klass)
|
|
6
|
+
associations.map do |name, right|
|
|
7
|
+
if name.is_a? Join
|
|
8
|
+
reflection = find_reflection base_klass, name.name
|
|
9
|
+
reflection.check_validity!
|
|
10
|
+
reflection.check_eager_loadable!
|
|
11
|
+
|
|
12
|
+
klass = if reflection.polymorphic?
|
|
13
|
+
name.klass || base_klass
|
|
14
|
+
else
|
|
15
|
+
reflection.klass
|
|
16
|
+
end
|
|
17
|
+
JoinAssociation.new(reflection, build(right, klass), name.klass, name.type)
|
|
18
|
+
else
|
|
19
|
+
reflection = find_reflection base_klass, name
|
|
20
|
+
reflection.check_validity!
|
|
21
|
+
reflection.check_eager_loadable!
|
|
22
|
+
|
|
23
|
+
if reflection.polymorphic?
|
|
24
|
+
raise ActiveRecord::EagerLoadPolymorphicError.new(reflection)
|
|
25
|
+
end
|
|
26
|
+
JoinAssociation.new(reflection, build(right, reflection.klass))
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def join_constraints(joins_to_add, alias_tracker, references)
|
|
32
|
+
@alias_tracker = alias_tracker
|
|
33
|
+
@joined_tables = {}
|
|
34
|
+
@references = {}
|
|
35
|
+
|
|
36
|
+
references.each do |table_name|
|
|
37
|
+
@references[table_name.to_sym] = table_name if table_name.is_a?(String)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
joins = make_join_constraints(join_root, join_type)
|
|
41
|
+
|
|
42
|
+
joins.concat joins_to_add.flat_map { |oj|
|
|
43
|
+
if join_root.match?(oj.join_root) && join_root.table.name == oj.join_root.table.name
|
|
44
|
+
walk join_root, oj.join_root, oj.join_type
|
|
45
|
+
else
|
|
46
|
+
make_join_constraints(oj.join_root, oj.join_type)
|
|
47
|
+
end
|
|
48
|
+
}
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def construct_tables_for_association!(join_root, association)
|
|
52
|
+
tables = table_aliases_for(join_root, association)
|
|
53
|
+
association.table = tables.first
|
|
54
|
+
tables
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
def table_aliases_for(parent, node)
|
|
60
|
+
node.reflection.chain.map { |reflection|
|
|
61
|
+
alias_tracker.aliased_table_for(reflection.klass.arel_table) do
|
|
62
|
+
root = reflection == node.reflection
|
|
63
|
+
name = reflection.alias_candidate(parent.table_name)
|
|
64
|
+
root ? name : "#{name}_join"
|
|
65
|
+
end
|
|
66
|
+
}
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
module ClassMethods
|
|
70
|
+
# Prepended before ActiveRecord::Associations::JoinDependency#walk_tree
|
|
71
|
+
#
|
|
72
|
+
def walk_tree(associations, hash)
|
|
73
|
+
case associations
|
|
74
|
+
when TreeNode
|
|
75
|
+
associations.add_to_tree(hash)
|
|
76
|
+
when Hash
|
|
77
|
+
associations.each do |k, v|
|
|
78
|
+
cache =
|
|
79
|
+
if TreeNode === k
|
|
80
|
+
k.add_to_tree(hash)
|
|
81
|
+
else
|
|
82
|
+
hash[k] ||= {}
|
|
83
|
+
end
|
|
84
|
+
walk_tree(v, cache)
|
|
85
|
+
end
|
|
86
|
+
else
|
|
87
|
+
super(associations, hash)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require 'polyamorous/activerecord_6.1_ruby_2/join_association'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require 'polyamorous/activerecord_6.1_ruby_2/join_dependency'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require 'polyamorous/activerecord_6.1_ruby_2/reflection'
|
|
File without changes
|
|
@@ -11,7 +11,7 @@ if defined?(::ActiveRecord)
|
|
|
11
11
|
require 'polyamorous/join'
|
|
12
12
|
require 'polyamorous/swapping_reflection_class'
|
|
13
13
|
|
|
14
|
-
ar_version = ::ActiveRecord::VERSION::STRING[0,3]
|
|
14
|
+
ar_version = ::ActiveRecord::VERSION::STRING[0, 3]
|
|
15
15
|
%w(join_association join_dependency reflection).each do |file|
|
|
16
16
|
require "polyamorous/activerecord_#{ar_version}_ruby_2/#{file}"
|
|
17
17
|
end
|
|
File without changes
|
|
File without changes
|
data/lib/polyamorous.rb
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require 'polyamorous/polyamorous'
|