ransack 2.3.2 → 4.1.1
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/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}" }
|