ransack 2.3.2 → 4.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/FUNDING.yml +3 -0
- data/.github/SECURITY.md +12 -0
- data/.github/workflows/codeql.yml +72 -0
- data/.github/workflows/cronjob.yml +99 -0
- data/.github/workflows/deploy.yml +35 -0
- data/.github/workflows/rubocop.yml +20 -0
- data/.github/workflows/test-deploy.yml +29 -0
- data/.github/workflows/test.yml +131 -0
- data/.nojekyll +0 -0
- data/.rubocop.yml +50 -0
- data/CHANGELOG.md +251 -1
- data/CONTRIBUTING.md +51 -29
- data/Gemfile +12 -10
- data/README.md +45 -907
- data/bug_report_templates/test-ransack-scope-and-column-same-name.rb +78 -0
- data/bug_report_templates/test-ransacker-arel-present-predicate.rb +75 -0
- 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 +288 -0
- data/docs/docs/getting-started/sorting.md +71 -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/img/create_release.png +0 -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 +46 -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 +120 -0
- data/docs/package.json +42 -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 +8879 -0
- data/lib/polyamorous/activerecord/join_association.rb +70 -0
- data/{polyamorous/lib/polyamorous/activerecord_6.0_ruby_2 → lib/polyamorous/activerecord}/join_dependency.rb +33 -12
- data/lib/polyamorous/activerecord/reflection.rb +11 -0
- data/{polyamorous/lib → lib/polyamorous}/polyamorous.rb +3 -4
- data/lib/ransack/adapters/active_record/base.rb +83 -10
- data/lib/ransack/adapters/active_record/context.rb +56 -44
- data/lib/ransack/configuration.rb +53 -10
- data/lib/ransack/constants.rb +126 -4
- data/lib/ransack/context.rb +34 -5
- data/lib/ransack/helpers/form_builder.rb +6 -6
- data/lib/ransack/helpers/form_helper.rb +14 -5
- data/lib/ransack/helpers.rb +1 -1
- data/lib/ransack/locale/sv.yml +70 -0
- data/lib/ransack/nodes/attribute.rb +3 -3
- data/lib/ransack/nodes/condition.rb +80 -9
- data/lib/ransack/nodes/grouping.rb +4 -4
- data/lib/ransack/nodes/node.rb +1 -1
- data/lib/ransack/nodes/sort.rb +3 -3
- data/lib/ransack/nodes/value.rb +3 -3
- data/lib/ransack/predicate.rb +1 -1
- data/lib/ransack/ransacker.rb +1 -1
- data/lib/ransack/search.rb +15 -7
- data/lib/ransack/translate.rb +6 -6
- data/lib/ransack/version.rb +1 -1
- data/lib/ransack/visitor.rb +38 -2
- data/lib/ransack.rb +5 -8
- data/ransack.gemspec +9 -15
- 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/polyamorous_helper.rb +2 -8
- data/spec/helpers/ransack_helper.rb +1 -1
- data/spec/polyamorous/activerecord_compatibility_spec.rb +15 -0
- data/spec/{ransack → polyamorous}/join_association_spec.rb +3 -1
- data/spec/{ransack → polyamorous}/join_dependency_spec.rb +0 -16
- data/spec/ransack/adapters/active_record/base_spec.rb +125 -16
- data/spec/ransack/adapters/active_record/context_spec.rb +19 -18
- data/spec/ransack/configuration_spec.rb +33 -9
- data/spec/ransack/helpers/form_builder_spec.rb +8 -8
- data/spec/ransack/helpers/form_helper_spec.rb +109 -20
- data/spec/ransack/nodes/condition_spec.rb +37 -0
- data/spec/ransack/nodes/grouping_spec.rb +2 -2
- data/spec/ransack/nodes/value_spec.rb +115 -0
- data/spec/ransack/predicate_spec.rb +37 -2
- data/spec/ransack/search_spec.rb +238 -30
- data/spec/ransack/translate_spec.rb +1 -1
- data/spec/spec_helper.rb +7 -5
- data/spec/support/schema.rb +108 -11
- metadata +98 -62
- data/.travis.yml +0 -47
- data/lib/ransack/adapters/active_record/ransack/constants.rb +0 -128
- data/lib/ransack/adapters/active_record/ransack/context.rb +0 -55
- data/lib/ransack/adapters/active_record/ransack/nodes/condition.rb +0 -61
- data/lib/ransack/adapters/active_record/ransack/translate.rb +0 -8
- data/lib/ransack/adapters/active_record/ransack/visitor.rb +0 -47
- data/lib/ransack/adapters.rb +0 -64
- data/lib/ransack/nodes.rb +0 -8
- data/polyamorous/lib/polyamorous/activerecord_5.2_ruby_2/join_association.rb +0 -20
- data/polyamorous/lib/polyamorous/activerecord_5.2_ruby_2/join_dependency.rb +0 -79
- data/polyamorous/lib/polyamorous/activerecord_5.2_ruby_2/reflection.rb +0 -12
- data/polyamorous/lib/polyamorous/activerecord_6.0_ruby_2/join_association.rb +0 -2
- data/polyamorous/lib/polyamorous/activerecord_6.0_ruby_2/reflection.rb +0 -2
- 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/activerecord_6.1_ruby_2/reflection.rb +0 -2
- data/polyamorous/lib/polyamorous/version.rb +0 -3
- data/polyamorous/polyamorous.gemspec +0 -27
- /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/{polyamorous/lib → lib}/polyamorous/join.rb +0 -0
- /data/{polyamorous/lib → lib}/polyamorous/swapping_reflection_class.rb +0 -0
- /data/{polyamorous/lib → lib}/polyamorous/tree_node.rb +0 -0
- /data/lib/ransack/{adapters/active_record.rb → active_record.rb} +0 -0
- /data/spec/{ransack → polyamorous}/join_spec.rb +0 -0
@@ -0,0 +1,70 @@
|
|
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
|
+
end
|
70
|
+
end
|
@@ -1,5 +1,3 @@
|
|
1
|
-
# active_record_6.0_ruby_2/join_dependency.rb
|
2
|
-
|
3
1
|
module Polyamorous
|
4
2
|
module JoinDependencyExtensions
|
5
3
|
# Replaces ActiveRecord::Associations::JoinDependency#build
|
@@ -29,14 +27,18 @@ module Polyamorous
|
|
29
27
|
end
|
30
28
|
end
|
31
29
|
|
32
|
-
def join_constraints(joins_to_add, alias_tracker)
|
30
|
+
def join_constraints(joins_to_add, alias_tracker, references)
|
33
31
|
@alias_tracker = alias_tracker
|
32
|
+
@joined_tables = {}
|
33
|
+
@references = {}
|
34
|
+
|
35
|
+
references.each do |table_name|
|
36
|
+
@references[table_name.to_sym] = table_name if table_name.is_a?(String)
|
37
|
+
end
|
34
38
|
|
35
|
-
construct_tables!(join_root)
|
36
39
|
joins = make_join_constraints(join_root, join_type)
|
37
40
|
|
38
41
|
joins.concat joins_to_add.flat_map { |oj|
|
39
|
-
construct_tables!(oj.join_root)
|
40
42
|
if join_root.match?(oj.join_root) && join_root.table.name == oj.join_root.table.name
|
41
43
|
walk join_root, oj.join_root, oj.join_type
|
42
44
|
else
|
@@ -45,14 +47,33 @@ module Polyamorous
|
|
45
47
|
}
|
46
48
|
end
|
47
49
|
|
50
|
+
def construct_tables_for_association!(join_root, association)
|
51
|
+
tables = table_aliases_for(join_root, association)
|
52
|
+
association.table = tables.first
|
53
|
+
tables
|
54
|
+
end
|
55
|
+
|
48
56
|
private
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
57
|
+
|
58
|
+
def table_aliases_for(parent, node)
|
59
|
+
@joined_tables ||= {}
|
60
|
+
node.reflection.chain.map { |reflection|
|
61
|
+
table, terminated = @joined_tables[reflection]
|
62
|
+
root = reflection == node.reflection
|
63
|
+
|
64
|
+
if table && (!root || !terminated)
|
65
|
+
@joined_tables[reflection] = [table, true] if root
|
66
|
+
table
|
67
|
+
else
|
68
|
+
table = alias_tracker.aliased_table_for(reflection.klass.arel_table) do
|
69
|
+
name = reflection.alias_candidate(parent.table_name)
|
70
|
+
root ? name : "#{name}_join"
|
71
|
+
end
|
72
|
+
@joined_tables[reflection] ||= [table, root] if join_type == Arel::Nodes::OuterJoin
|
73
|
+
table
|
74
|
+
end
|
75
|
+
}
|
76
|
+
end
|
56
77
|
|
57
78
|
module ClassMethods
|
58
79
|
# Prepended before ActiveRecord::Associations::JoinDependency#walk_tree
|
@@ -11,10 +11,9 @@ if defined?(::ActiveRecord)
|
|
11
11
|
require 'polyamorous/join'
|
12
12
|
require 'polyamorous/swapping_reflection_class'
|
13
13
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
end
|
14
|
+
require 'polyamorous/activerecord/join_association'
|
15
|
+
require 'polyamorous/activerecord/join_dependency'
|
16
|
+
require 'polyamorous/activerecord/reflection'
|
18
17
|
|
19
18
|
ActiveRecord::Reflection::AbstractReflection.send(:prepend, Polyamorous::ReflectionExtensions)
|
20
19
|
|
@@ -4,7 +4,6 @@ module Ransack
|
|
4
4
|
module Base
|
5
5
|
|
6
6
|
def self.extended(base)
|
7
|
-
alias :search :ransack unless base.respond_to? :search
|
8
7
|
base.class_eval do
|
9
8
|
class_attribute :_ransackers
|
10
9
|
class_attribute :_ransack_aliases
|
@@ -14,10 +13,13 @@ module Ransack
|
|
14
13
|
end
|
15
14
|
|
16
15
|
def ransack(params = {}, options = {})
|
17
|
-
ActiveSupport::Deprecation.warn("#search is deprecated and will be removed in 2.3, please use #ransack instead") if __callee__ == :search
|
18
16
|
Search.new(self, params, options)
|
19
17
|
end
|
20
18
|
|
19
|
+
def ransack!(params = {}, options = {})
|
20
|
+
ransack(params, options.merge(ignore_unknown_conditions: false))
|
21
|
+
end
|
22
|
+
|
21
23
|
def ransacker(name, opts = {}, &block)
|
22
24
|
self._ransackers = _ransackers.merge name.to_s => Ransacker
|
23
25
|
.new(self, name, opts, &block)
|
@@ -33,12 +35,7 @@ module Ransack
|
|
33
35
|
# For overriding with a whitelist array of strings.
|
34
36
|
#
|
35
37
|
def ransackable_attributes(auth_object = nil)
|
36
|
-
@ransackable_attributes ||=
|
37
|
-
column_names + _ransackers.keys + _ransack_aliases.keys +
|
38
|
-
attribute_aliases.keys
|
39
|
-
else
|
40
|
-
column_names + _ransackers.keys + _ransack_aliases.keys
|
41
|
-
end
|
38
|
+
@ransackable_attributes ||= deprecated_ransackable_list(:ransackable_attributes)
|
42
39
|
end
|
43
40
|
|
44
41
|
# Ransackable_associations, by default, returns the names
|
@@ -46,7 +43,7 @@ module Ransack
|
|
46
43
|
# For overriding with a whitelist array of strings.
|
47
44
|
#
|
48
45
|
def ransackable_associations(auth_object = nil)
|
49
|
-
@ransackable_associations ||=
|
46
|
+
@ransackable_associations ||= deprecated_ransackable_list(:ransackable_associations)
|
50
47
|
end
|
51
48
|
|
52
49
|
# Ransortable_attributes, by default, returns the names
|
@@ -66,13 +63,89 @@ module Ransack
|
|
66
63
|
end
|
67
64
|
|
68
65
|
# ransack_scope_skip_sanitize_args, by default, returns an empty array.
|
69
|
-
# i.e. use the sanitize_scope_args setting to
|
66
|
+
# i.e. use the sanitize_scope_args setting to determine if args should be converted.
|
70
67
|
# For overriding with a list of scopes which should be passed the args as-is.
|
71
68
|
#
|
72
69
|
def ransackable_scopes_skip_sanitize_args
|
73
70
|
[]
|
74
71
|
end
|
75
72
|
|
73
|
+
# Bare list of all potentially searchable attributes. Searchable attributes
|
74
|
+
# need to be explicitly allowlisted through the `ransackable_attributes`
|
75
|
+
# method in each model, but if you're allowing almost everything to be
|
76
|
+
# searched, this list can be used as a base for exclusions.
|
77
|
+
#
|
78
|
+
def authorizable_ransackable_attributes
|
79
|
+
if Ransack::SUPPORTS_ATTRIBUTE_ALIAS
|
80
|
+
column_names + _ransackers.keys + _ransack_aliases.keys +
|
81
|
+
attribute_aliases.keys
|
82
|
+
else
|
83
|
+
column_names + _ransackers.keys + _ransack_aliases.keys
|
84
|
+
end.uniq
|
85
|
+
end
|
86
|
+
|
87
|
+
# Bare list of all potentially searchable associations. Searchable
|
88
|
+
# associations need to be explicitly allowlisted through the
|
89
|
+
# `ransackable_associations` method in each model, but if you're
|
90
|
+
# allowing almost everything to be searched, this list can be used as a
|
91
|
+
# base for exclusions.
|
92
|
+
#
|
93
|
+
def authorizable_ransackable_associations
|
94
|
+
reflect_on_all_associations.map { |a| a.name.to_s }
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
|
99
|
+
def deprecated_ransackable_list(method)
|
100
|
+
list_type = method.to_s.delete_prefix("ransackable_")
|
101
|
+
|
102
|
+
if explicitly_defined?(method)
|
103
|
+
warn_deprecated <<~ERROR
|
104
|
+
Ransack's builtin `#{method}` method is deprecated and will result
|
105
|
+
in an error in the future. If you want to authorize the full list
|
106
|
+
of searchable #{list_type} for this model, use
|
107
|
+
`authorizable_#{method}` instead of delegating to `super`.
|
108
|
+
ERROR
|
109
|
+
|
110
|
+
public_send("authorizable_#{method}")
|
111
|
+
else
|
112
|
+
raise <<~MESSAGE
|
113
|
+
Ransack needs #{name} #{list_type} explicitly allowlisted as
|
114
|
+
searchable. Define a `#{method}` class method in your `#{name}`
|
115
|
+
model, watching out for items you DON'T want searchable (for
|
116
|
+
example, `encrypted_password`, `password_reset_token`, `owner` or
|
117
|
+
other sensitive information). You can use the following as a base:
|
118
|
+
|
119
|
+
```ruby
|
120
|
+
class #{name} < ApplicationRecord
|
121
|
+
|
122
|
+
# ...
|
123
|
+
|
124
|
+
def self.#{method}(auth_object = nil)
|
125
|
+
#{public_send("authorizable_#{method}").sort.inspect}
|
126
|
+
end
|
127
|
+
|
128
|
+
# ...
|
129
|
+
|
130
|
+
end
|
131
|
+
```
|
132
|
+
MESSAGE
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def explicitly_defined?(method)
|
137
|
+
definer_ancestor = singleton_class.ancestors.find do |ancestor|
|
138
|
+
ancestor.instance_methods(false).include?(method)
|
139
|
+
end
|
140
|
+
|
141
|
+
definer_ancestor != Ransack::Adapters::ActiveRecord::Base
|
142
|
+
end
|
143
|
+
|
144
|
+
def warn_deprecated(message)
|
145
|
+
caller_location = caller_locations.find { |location| !location.path.start_with?(File.expand_path("../..", __dir__)) }
|
146
|
+
|
147
|
+
warn "DEPRECATION WARNING: #{message.squish} (called at #{caller_location.path}:#{caller_location.lineno})"
|
148
|
+
end
|
76
149
|
end
|
77
150
|
end
|
78
151
|
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
require 'ransack/context'
|
2
|
-
require 'polyamorous'
|
2
|
+
require 'polyamorous/polyamorous'
|
3
3
|
|
4
4
|
module Ransack
|
5
5
|
module Adapters
|
@@ -12,8 +12,9 @@ module Ransack
|
|
12
12
|
|
13
13
|
def type_for(attr)
|
14
14
|
return nil unless attr && attr.valid?
|
15
|
+
relation = attr.arel_attribute.relation
|
15
16
|
name = attr.arel_attribute.name.to_s
|
16
|
-
table =
|
17
|
+
table = relation.respond_to?(:table_name) ? relation.table_name : relation.name
|
17
18
|
schema_cache = self.klass.connection.schema_cache
|
18
19
|
unless schema_cache.send(:data_source_exists?, table)
|
19
20
|
raise "No table named #{table} exists."
|
@@ -42,6 +43,17 @@ module Ransack
|
|
42
43
|
if scope_or_sort.is_a?(Symbol)
|
43
44
|
relation = relation.send(scope_or_sort)
|
44
45
|
else
|
46
|
+
case Ransack.options[:postgres_fields_sort_option]
|
47
|
+
when :nulls_first
|
48
|
+
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")
|
49
|
+
when :nulls_last
|
50
|
+
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")
|
51
|
+
when :nulls_always_first
|
52
|
+
scope_or_sort = Arel.sql("#{scope_or_sort.to_sql} NULLS FIRST")
|
53
|
+
when :nulls_always_last
|
54
|
+
scope_or_sort = Arel.sql("#{scope_or_sort.to_sql} NULLS LAST")
|
55
|
+
end
|
56
|
+
|
45
57
|
relation = relation.order(scope_or_sort)
|
46
58
|
end
|
47
59
|
end
|
@@ -99,11 +111,7 @@ module Ransack
|
|
99
111
|
def join_sources
|
100
112
|
base, joins = begin
|
101
113
|
alias_tracker = ::ActiveRecord::Associations::AliasTracker.create(self.klass.connection, @object.table.name, [])
|
102
|
-
constraints =
|
103
|
-
@join_dependency.join_constraints(@object.joins_values, alias_tracker)
|
104
|
-
else
|
105
|
-
@join_dependency.join_constraints(@object.joins_values, @join_type, alias_tracker)
|
106
|
-
end
|
114
|
+
constraints = @join_dependency.join_constraints(@object.joins_values, alias_tracker, @object.references_values)
|
107
115
|
|
108
116
|
[
|
109
117
|
Arel::SelectManager.new(@object.table),
|
@@ -130,6 +138,7 @@ module Ransack
|
|
130
138
|
stashed.eql?(association)
|
131
139
|
}
|
132
140
|
@object.joins_values.delete_if { |jd|
|
141
|
+
jd.instance_variables.include?(:@join_root) &&
|
133
142
|
jd.instance_variable_get(:@join_root).children.map(&:object_id) == [association.object_id]
|
134
143
|
}
|
135
144
|
end
|
@@ -172,16 +181,31 @@ module Ransack
|
|
172
181
|
private
|
173
182
|
|
174
183
|
def extract_correlated_key(join_root)
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
184
|
+
case join_root
|
185
|
+
when Arel::Nodes::OuterJoin
|
186
|
+
# one of join_root.right/join_root.left is expected to be Arel::Nodes::On
|
187
|
+
if join_root.right.is_a?(Arel::Nodes::On)
|
188
|
+
extract_correlated_key(join_root.right.expr)
|
189
|
+
elsif join_root.left.is_a?(Arel::Nodes::On)
|
190
|
+
extract_correlated_key(join_root.left.expr)
|
191
|
+
else
|
192
|
+
raise 'Ransack encountered an unexpected arel structure'
|
193
|
+
end
|
194
|
+
when Arel::Nodes::Equality
|
195
|
+
pk = primary_key
|
196
|
+
if join_root.left == pk
|
197
|
+
join_root.right
|
198
|
+
elsif join_root.right == pk
|
199
|
+
join_root.left
|
200
|
+
else
|
201
|
+
nil
|
202
|
+
end
|
203
|
+
when Arel::Nodes::And
|
204
|
+
extract_correlated_key(join_root.left) || extract_correlated_key(join_root.right)
|
183
205
|
else
|
184
|
-
|
206
|
+
# eg parent was Arel::Nodes::And and the evaluated side was one of
|
207
|
+
# Arel::Nodes::Grouping or MultiTenant::TenantEnforcementClause
|
208
|
+
nil
|
185
209
|
end
|
186
210
|
end
|
187
211
|
|
@@ -255,11 +279,7 @@ module Ransack
|
|
255
279
|
join_list = join_nodes + convert_join_strings_to_ast(relation.table, string_joins)
|
256
280
|
|
257
281
|
alias_tracker = ::ActiveRecord::Associations::AliasTracker.create(self.klass.connection, relation.table.name, join_list)
|
258
|
-
join_dependency =
|
259
|
-
Polyamorous::JoinDependency.new(relation.klass, relation.table, association_joins, Arel::Nodes::OuterJoin)
|
260
|
-
else
|
261
|
-
Polyamorous::JoinDependency.new(relation.klass, relation.table, association_joins)
|
262
|
-
end
|
282
|
+
join_dependency = Polyamorous::JoinDependency.new(relation.klass, relation.table, association_joins, Arel::Nodes::OuterJoin)
|
263
283
|
join_dependency.instance_variable_set(:@alias_tracker, alias_tracker)
|
264
284
|
join_nodes.each do |join|
|
265
285
|
join_dependency.send(:alias_tracker).aliases[join.left.name.downcase] = 1
|
@@ -286,22 +306,13 @@ module Ransack
|
|
286
306
|
end
|
287
307
|
|
288
308
|
def build_association(name, parent = @base, klass = nil)
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
found_association = jd.instance_variable_get(:@join_root).children.last
|
297
|
-
else
|
298
|
-
jd = Polyamorous::JoinDependency.new(
|
299
|
-
parent.base_klass,
|
300
|
-
parent.table,
|
301
|
-
Polyamorous::Join.new(name, @join_type, klass)
|
302
|
-
)
|
303
|
-
found_association = jd.instance_variable_get(:@join_root).children.last
|
304
|
-
end
|
309
|
+
jd = Polyamorous::JoinDependency.new(
|
310
|
+
parent.base_klass,
|
311
|
+
parent.table,
|
312
|
+
Polyamorous::Join.new(name, @join_type, klass),
|
313
|
+
@join_type
|
314
|
+
)
|
315
|
+
found_association = jd.instance_variable_get(:@join_root).children.last
|
305
316
|
|
306
317
|
@associations_pot[found_association] = parent
|
307
318
|
|
@@ -310,7 +321,7 @@ module Ransack
|
|
310
321
|
@join_dependency.instance_variable_get(:@join_root).children.push found_association
|
311
322
|
|
312
323
|
# Builds the arel nodes properly for this association
|
313
|
-
@join_dependency.
|
324
|
+
@tables_pot[found_association] = @join_dependency.construct_tables_for_association!(jd.instance_variable_get(:@join_root), found_association)
|
314
325
|
|
315
326
|
# Leverage the stashed association functionality in AR
|
316
327
|
@object = @object.joins(jd)
|
@@ -320,12 +331,13 @@ module Ransack
|
|
320
331
|
def extract_joins(association)
|
321
332
|
parent = @join_dependency.instance_variable_get(:@join_root)
|
322
333
|
reflection = association.reflection
|
323
|
-
join_constraints = association.
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
334
|
+
join_constraints = association.join_constraints_with_tables(
|
335
|
+
parent.table,
|
336
|
+
parent.base_klass,
|
337
|
+
Arel::Nodes::OuterJoin,
|
338
|
+
@join_dependency.instance_variable_get(:@alias_tracker),
|
339
|
+
@tables_pot[association]
|
340
|
+
)
|
329
341
|
join_constraints.to_a.flatten
|
330
342
|
end
|
331
343
|
end
|
@@ -27,13 +27,15 @@ module Ransack
|
|
27
27
|
self.predicates = PredicateCollection.new
|
28
28
|
|
29
29
|
self.options = {
|
30
|
-
:
|
31
|
-
:
|
32
|
-
:
|
33
|
-
:
|
34
|
-
:
|
35
|
-
:
|
36
|
-
:
|
30
|
+
search_key: :q,
|
31
|
+
ignore_unknown_conditions: true,
|
32
|
+
hide_sort_order_indicators: false,
|
33
|
+
up_arrow: '▼'.freeze,
|
34
|
+
down_arrow: '▲'.freeze,
|
35
|
+
default_arrow: nil,
|
36
|
+
sanitize_scope_args: true,
|
37
|
+
postgres_fields_sort_option: nil,
|
38
|
+
strip_whitespace: true
|
37
39
|
}
|
38
40
|
|
39
41
|
def configure
|
@@ -53,11 +55,11 @@ module Ransack
|
|
53
55
|
compound_name = name + suffix
|
54
56
|
self.predicates[compound_name] = Predicate.new(
|
55
57
|
opts.merge(
|
56
|
-
:
|
57
|
-
:
|
58
|
+
name: compound_name,
|
59
|
+
arel_predicate: arel_predicate_with_suffix(
|
58
60
|
opts[:arel_predicate], suffix
|
59
61
|
),
|
60
|
-
:
|
62
|
+
compound: true
|
61
63
|
)
|
62
64
|
)
|
63
65
|
end if compounds
|
@@ -99,6 +101,19 @@ module Ransack
|
|
99
101
|
self.options[:ignore_unknown_conditions] = boolean
|
100
102
|
end
|
101
103
|
|
104
|
+
# By default Ransack ignores empty predicates. Ransack can also fallback to
|
105
|
+
# a default predicate by setting it in an initializer file
|
106
|
+
# like `config/initializers/ransack.rb` as follows:
|
107
|
+
#
|
108
|
+
# Ransack.configure do |config|
|
109
|
+
# # Use the 'eq' predicate if an unknown predicate is passed
|
110
|
+
# config.default_predicate = 'eq'
|
111
|
+
# end
|
112
|
+
#
|
113
|
+
def default_predicate=(name)
|
114
|
+
self.options[:default_predicate] = name
|
115
|
+
end
|
116
|
+
|
102
117
|
# By default, Ransack displays sort order indicator arrows with HTML codes:
|
103
118
|
#
|
104
119
|
# up_arrow: '▼'
|
@@ -141,6 +156,21 @@ module Ransack
|
|
141
156
|
self.options[:sanitize_scope_args] = boolean
|
142
157
|
end
|
143
158
|
|
159
|
+
# The `NULLS FIRST` and `NULLS LAST` options can be used to determine
|
160
|
+
# whether nulls appear before or after non-null values in the sort ordering.
|
161
|
+
#
|
162
|
+
# User may want to configure it like this:
|
163
|
+
#
|
164
|
+
# Ransack.configure do |c|
|
165
|
+
# c.postgres_fields_sort_option = :nulls_first # or e.g. :nulls_always_last
|
166
|
+
# end
|
167
|
+
#
|
168
|
+
# See this feature: https://www.postgresql.org/docs/13/queries-order.html
|
169
|
+
#
|
170
|
+
def postgres_fields_sort_option=(setting)
|
171
|
+
self.options[:postgres_fields_sort_option] = setting
|
172
|
+
end
|
173
|
+
|
144
174
|
# By default, Ransack displays sort order indicator arrows in sort links.
|
145
175
|
# The default may be globally overridden in an initializer file like
|
146
176
|
# `config/initializers/ransack.rb` as follows:
|
@@ -154,6 +184,19 @@ module Ransack
|
|
154
184
|
self.options[:hide_sort_order_indicators] = boolean
|
155
185
|
end
|
156
186
|
|
187
|
+
# By default, Ransack displays strips all whitespace when searching for a string.
|
188
|
+
# The default may be globally changed in an initializer file like
|
189
|
+
# `config/initializers/ransack.rb` as follows:
|
190
|
+
#
|
191
|
+
# Ransack.configure do |config|
|
192
|
+
# # Enable whitespace stripping for string searches
|
193
|
+
# config.strip_whitespace = true
|
194
|
+
# end
|
195
|
+
#
|
196
|
+
def strip_whitespace=(boolean)
|
197
|
+
self.options[:strip_whitespace] = boolean
|
198
|
+
end
|
199
|
+
|
157
200
|
def arel_predicate_with_suffix(arel_predicate, suffix)
|
158
201
|
if arel_predicate === Proc
|
159
202
|
proc { |v| "#{arel_predicate.call(v)}#{suffix}" }
|