ransack 2.3.0 → 4.0.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/codeql.yml +72 -0
- data/.github/workflows/cronjob.yml +102 -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 +128 -0
- data/.nojekyll +0 -0
- data/.rubocop.yml +47 -0
- data/CHANGELOG.md +228 -1
- data/CONTRIBUTING.md +47 -22
- data/Gemfile +24 -10
- data/README.md +49 -917
- 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 +79 -0
- data/docs/docs/getting-started/using-predicates.md +282 -0
- data/docs/docs/going-further/_category_.json +4 -0
- data/docs/docs/going-further/acts-as-taggable-on.md +114 -0
- data/docs/docs/going-further/associations.md +70 -0
- data/docs/docs/going-further/custom-predicates.md +52 -0
- data/docs/docs/going-further/documentation.md +43 -0
- data/docs/docs/going-further/exporting-to-csv.md +49 -0
- data/docs/docs/going-further/external-guides.md +57 -0
- data/docs/docs/going-further/form-customisation.md +63 -0
- data/docs/docs/going-further/i18n.md +53 -0
- data/docs/docs/going-further/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 +40 -0
- data/docs/docs/going-further/ransackers.md +331 -0
- data/docs/docs/going-further/release_process.md +36 -0
- data/docs/docs/going-further/saving-queries.md +82 -0
- data/docs/docs/going-further/searching-postgres.md +57 -0
- data/docs/docs/going-further/wiki-contributors.md +82 -0
- data/docs/docs/intro.md +99 -0
- data/docs/docusaurus.config.js +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 +8790 -0
- data/lib/polyamorous/activerecord_6.1_ruby_2/join_association.rb +70 -0
- data/{polyamorous/lib/polyamorous/activerecord_6.0_ruby_2 → lib/polyamorous/activerecord_6.1_ruby_2}/join_dependency.rb +23 -12
- data/lib/polyamorous/activerecord_6.1_ruby_2/reflection.rb +11 -0
- 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/lib/polyamorous/activerecord_7.1_ruby_2/join_association.rb +1 -0
- data/lib/polyamorous/activerecord_7.1_ruby_2/join_dependency.rb +1 -0
- data/lib/polyamorous/activerecord_7.1_ruby_2/reflection.rb +1 -0
- data/{polyamorous/lib → lib/polyamorous}/polyamorous.rb +3 -8
- data/lib/ransack/adapters/active_record/base.rb +83 -10
- data/lib/ransack/adapters/active_record/context.rb +59 -115
- data/lib/ransack/configuration.rb +53 -10
- data/lib/ransack/constants.rb +126 -7
- data/lib/ransack/context.rb +34 -5
- data/lib/ransack/helpers/form_builder.rb +11 -17
- data/lib/ransack/helpers/form_helper.rb +14 -5
- data/lib/ransack/helpers.rb +1 -1
- data/lib/ransack/locale/sk.yml +70 -0
- data/lib/ransack/locale/sv.yml +70 -0
- data/lib/ransack/nodes/attribute.rb +3 -3
- data/lib/ransack/nodes/condition.rb +87 -8
- 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 +3 -2
- 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 +6 -10
- data/ransack.gemspec +9 -24
- 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 -17
- 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 +109 -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 +75 -2
- data/spec/ransack/search_spec.rb +239 -38
- data/spec/ransack/translate_spec.rb +1 -1
- data/spec/spec_helper.rb +9 -5
- data/spec/support/schema.rb +83 -12
- metadata +105 -195
- data/.travis.yml +0 -49
- data/lib/ransack/adapters/active_record/ransack/constants.rb +0 -116
- data/lib/ransack/adapters/active_record/ransack/context.rb +0 -60
- 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.0_ruby_2/join_association.rb +0 -2
- data/polyamorous/lib/polyamorous/activerecord_5.0_ruby_2/join_dependency.rb +0 -2
- data/polyamorous/lib/polyamorous/activerecord_5.1_ruby_2/join_association.rb +0 -31
- data/polyamorous/lib/polyamorous/activerecord_5.1_ruby_2/join_dependency.rb +0 -112
- data/polyamorous/lib/polyamorous/activerecord_5.2.0_ruby_2/join_association.rb +0 -31
- data/polyamorous/lib/polyamorous/activerecord_5.2.0_ruby_2/join_dependency.rb +0 -112
- data/polyamorous/lib/polyamorous/activerecord_5.2.0_ruby_2/reflection.rb +0 -12
- data/polyamorous/lib/polyamorous/activerecord_5.2.1_ruby_2/join_association.rb +0 -22
- data/polyamorous/lib/polyamorous/activerecord_5.2.1_ruby_2/join_dependency.rb +0 -81
- data/polyamorous/lib/polyamorous/activerecord_5.2.1_ruby_2/reflection.rb +0 -2
- 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 -35
- /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,23 @@ 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
|
+
node.reflection.chain.map { |reflection|
|
|
60
|
+
alias_tracker.aliased_table_for(reflection.klass.arel_table) do
|
|
61
|
+
root = reflection == node.reflection
|
|
62
|
+
name = reflection.alias_candidate(parent.table_name)
|
|
63
|
+
root ? name : "#{name}_join"
|
|
64
|
+
end
|
|
65
|
+
}
|
|
66
|
+
end
|
|
56
67
|
|
|
57
68
|
module ClassMethods
|
|
58
69
|
# Prepended before ActiveRecord::Associations::JoinDependency#walk_tree
|
|
@@ -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'
|
|
@@ -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'
|
|
@@ -11,17 +11,12 @@ 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]
|
|
15
|
-
|
|
16
|
-
ar_version = "5.2.1" if ::ActiveRecord::VERSION::STRING >= "5.2.1" && ::ActiveRecord.version < ::Gem::Version.new("6.0")
|
|
17
|
-
%w(join_association join_dependency).each do |file|
|
|
14
|
+
ar_version = ::ActiveRecord::VERSION::STRING[0, 3]
|
|
15
|
+
%w(join_association join_dependency reflection).each do |file|
|
|
18
16
|
require "polyamorous/activerecord_#{ar_version}_ruby_2/#{file}"
|
|
19
17
|
end
|
|
20
18
|
|
|
21
|
-
|
|
22
|
-
require "polyamorous/activerecord_#{ar_version}_ruby_2/reflection.rb"
|
|
23
|
-
::ActiveRecord::Reflection::AbstractReflection.send(:prepend, Polyamorous::ReflectionExtensions)
|
|
24
|
-
end
|
|
19
|
+
ActiveRecord::Reflection::AbstractReflection.send(:prepend, Polyamorous::ReflectionExtensions)
|
|
25
20
|
|
|
26
21
|
Polyamorous::JoinDependency.send(:prepend, Polyamorous::JoinDependencyExtensions)
|
|
27
22
|
Polyamorous::JoinDependency.singleton_class.send(:prepend, Polyamorous::JoinDependencyExtensions::ClassMethods)
|
|
@@ -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,18 +1,11 @@
|
|
|
1
1
|
require 'ransack/context'
|
|
2
|
-
require 'polyamorous'
|
|
2
|
+
require 'polyamorous/polyamorous'
|
|
3
3
|
|
|
4
4
|
module Ransack
|
|
5
5
|
module Adapters
|
|
6
6
|
module ActiveRecord
|
|
7
7
|
class Context < ::Ransack::Context
|
|
8
8
|
|
|
9
|
-
def initialize(object, options = {})
|
|
10
|
-
super
|
|
11
|
-
if ::ActiveRecord::VERSION::STRING < Constants::RAILS_5_2
|
|
12
|
-
@arel_visitor = @engine.connection.visitor
|
|
13
|
-
end
|
|
14
|
-
end
|
|
15
|
-
|
|
16
9
|
def relation_for(object)
|
|
17
10
|
object.all
|
|
18
11
|
end
|
|
@@ -49,6 +42,17 @@ module Ransack
|
|
|
49
42
|
if scope_or_sort.is_a?(Symbol)
|
|
50
43
|
relation = relation.send(scope_or_sort)
|
|
51
44
|
else
|
|
45
|
+
case Ransack.options[:postgres_fields_sort_option]
|
|
46
|
+
when :nulls_first
|
|
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
|
+
when :nulls_last
|
|
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")
|
|
54
|
+
end
|
|
55
|
+
|
|
52
56
|
relation = relation.order(scope_or_sort)
|
|
53
57
|
end
|
|
54
58
|
end
|
|
@@ -104,26 +108,15 @@ module Ransack
|
|
|
104
108
|
# JoinDependency to track table aliases.
|
|
105
109
|
#
|
|
106
110
|
def join_sources
|
|
107
|
-
base, joins =
|
|
108
|
-
if ::ActiveRecord::VERSION::STRING > Constants::RAILS_5_2_0
|
|
111
|
+
base, joins = begin
|
|
109
112
|
alias_tracker = ::ActiveRecord::Associations::AliasTracker.create(self.klass.connection, @object.table.name, [])
|
|
110
|
-
constraints =
|
|
111
|
-
@join_dependency.join_constraints(@object.joins_values, alias_tracker)
|
|
112
|
-
else
|
|
113
|
-
@join_dependency.join_constraints(@object.joins_values, @join_type, alias_tracker)
|
|
114
|
-
end
|
|
113
|
+
constraints = @join_dependency.join_constraints(@object.joins_values, alias_tracker, @object.references_values)
|
|
115
114
|
|
|
116
115
|
[
|
|
117
116
|
Arel::SelectManager.new(@object.table),
|
|
118
117
|
constraints
|
|
119
118
|
]
|
|
120
|
-
else
|
|
121
|
-
[
|
|
122
|
-
Arel::SelectManager.new(@object.table),
|
|
123
|
-
@join_dependency.join_constraints(@object.joins_values, @join_type)
|
|
124
|
-
]
|
|
125
119
|
end
|
|
126
|
-
joins = joins.collect(&:joins).flatten if ::ActiveRecord::VERSION::STRING < Constants::RAILS_5_2
|
|
127
120
|
joins.each do |aliased_join|
|
|
128
121
|
base.from(aliased_join)
|
|
129
122
|
end
|
|
@@ -144,6 +137,7 @@ module Ransack
|
|
|
144
137
|
stashed.eql?(association)
|
|
145
138
|
}
|
|
146
139
|
@object.joins_values.delete_if { |jd|
|
|
140
|
+
jd.instance_variables.include?(:@join_root) &&
|
|
147
141
|
jd.instance_variable_get(:@join_root).children.map(&:object_id) == [association.object_id]
|
|
148
142
|
}
|
|
149
143
|
end
|
|
@@ -186,16 +180,31 @@ module Ransack
|
|
|
186
180
|
private
|
|
187
181
|
|
|
188
182
|
def extract_correlated_key(join_root)
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
183
|
+
case join_root
|
|
184
|
+
when Arel::Nodes::OuterJoin
|
|
185
|
+
# one of join_root.right/join_root.left is expected to be Arel::Nodes::On
|
|
186
|
+
if join_root.right.is_a?(Arel::Nodes::On)
|
|
187
|
+
extract_correlated_key(join_root.right.expr)
|
|
188
|
+
elsif join_root.left.is_a?(Arel::Nodes::On)
|
|
189
|
+
extract_correlated_key(join_root.left.expr)
|
|
190
|
+
else
|
|
191
|
+
raise 'Ransack encountered an unexpected arel structure'
|
|
192
|
+
end
|
|
193
|
+
when Arel::Nodes::Equality
|
|
194
|
+
pk = primary_key
|
|
195
|
+
if join_root.left == pk
|
|
196
|
+
join_root.right
|
|
197
|
+
elsif join_root.right == pk
|
|
198
|
+
join_root.left
|
|
199
|
+
else
|
|
200
|
+
nil
|
|
201
|
+
end
|
|
202
|
+
when Arel::Nodes::And
|
|
203
|
+
extract_correlated_key(join_root.left) || extract_correlated_key(join_root.right)
|
|
197
204
|
else
|
|
198
|
-
|
|
205
|
+
# eg parent was Arel::Nodes::And and the evaluated side was one of
|
|
206
|
+
# Arel::Nodes::Grouping or MultiTenant::TenantEnforcementClause
|
|
207
|
+
nil
|
|
199
208
|
end
|
|
200
209
|
end
|
|
201
210
|
|
|
@@ -268,28 +277,11 @@ module Ransack
|
|
|
268
277
|
|
|
269
278
|
join_list = join_nodes + convert_join_strings_to_ast(relation.table, string_joins)
|
|
270
279
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
elsif ::ActiveRecord::VERSION::STRING == Constants::RAILS_5_2_0
|
|
277
|
-
alias_tracker = ::ActiveRecord::Associations::AliasTracker.create(self.klass.connection, relation.table.name, join_list)
|
|
278
|
-
join_dependency = Polyamorous::JoinDependency.new(relation.klass, relation.table, association_joins, alias_tracker)
|
|
279
|
-
join_nodes.each do |join|
|
|
280
|
-
join_dependency.send(:alias_tracker).aliases[join.left.name.downcase] = 1
|
|
281
|
-
end
|
|
282
|
-
else
|
|
283
|
-
alias_tracker = ::ActiveRecord::Associations::AliasTracker.create(self.klass.connection, relation.table.name, join_list)
|
|
284
|
-
join_dependency = if ::Gem::Version.new(::ActiveRecord::VERSION::STRING) >= ::Gem::Version.new(Constants::RAILS_6_0)
|
|
285
|
-
Polyamorous::JoinDependency.new(relation.klass, relation.table, association_joins, Arel::Nodes::OuterJoin)
|
|
286
|
-
else
|
|
287
|
-
Polyamorous::JoinDependency.new(relation.klass, relation.table, association_joins)
|
|
288
|
-
end
|
|
289
|
-
join_dependency.instance_variable_set(:@alias_tracker, alias_tracker)
|
|
290
|
-
join_nodes.each do |join|
|
|
291
|
-
join_dependency.send(:alias_tracker).aliases[join.left.name.downcase] = 1
|
|
292
|
-
end
|
|
280
|
+
alias_tracker = ::ActiveRecord::Associations::AliasTracker.create(self.klass.connection, relation.table.name, join_list)
|
|
281
|
+
join_dependency = Polyamorous::JoinDependency.new(relation.klass, relation.table, association_joins, Arel::Nodes::OuterJoin)
|
|
282
|
+
join_dependency.instance_variable_set(:@alias_tracker, alias_tracker)
|
|
283
|
+
join_nodes.each do |join|
|
|
284
|
+
join_dependency.send(:alias_tracker).aliases[join.left.name.downcase] = 1
|
|
293
285
|
end
|
|
294
286
|
join_dependency
|
|
295
287
|
end
|
|
@@ -313,38 +305,13 @@ module Ransack
|
|
|
313
305
|
end
|
|
314
306
|
|
|
315
307
|
def build_association(name, parent = @base, klass = nil)
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
found_association = jd.instance_variable_get(:@join_root).children.last
|
|
324
|
-
elsif ::Gem::Version.new(::ActiveRecord::VERSION::STRING) < ::Gem::Version.new(Constants::RAILS_5_2_0)
|
|
325
|
-
jd = Polyamorous::JoinDependency.new(
|
|
326
|
-
parent.base_klass,
|
|
327
|
-
Polyamorous::Join.new(name, @join_type, klass),
|
|
328
|
-
[]
|
|
329
|
-
)
|
|
330
|
-
found_association = jd.join_root.children.last
|
|
331
|
-
elsif ::ActiveRecord::VERSION::STRING == Constants::RAILS_5_2_0
|
|
332
|
-
alias_tracker = ::ActiveRecord::Associations::AliasTracker.create(self.klass.connection, parent.table.name, [])
|
|
333
|
-
jd = Polyamorous::JoinDependency.new(
|
|
334
|
-
parent.base_klass,
|
|
335
|
-
parent.table,
|
|
336
|
-
Polyamorous::Join.new(name, @join_type, klass),
|
|
337
|
-
alias_tracker
|
|
338
|
-
)
|
|
339
|
-
found_association = jd.instance_variable_get(:@join_root).children.last
|
|
340
|
-
else
|
|
341
|
-
jd = Polyamorous::JoinDependency.new(
|
|
342
|
-
parent.base_klass,
|
|
343
|
-
parent.table,
|
|
344
|
-
Polyamorous::Join.new(name, @join_type, klass)
|
|
345
|
-
)
|
|
346
|
-
found_association = jd.instance_variable_get(:@join_root).children.last
|
|
347
|
-
end
|
|
308
|
+
jd = Polyamorous::JoinDependency.new(
|
|
309
|
+
parent.base_klass,
|
|
310
|
+
parent.table,
|
|
311
|
+
Polyamorous::Join.new(name, @join_type, klass),
|
|
312
|
+
@join_type
|
|
313
|
+
)
|
|
314
|
+
found_association = jd.instance_variable_get(:@join_root).children.last
|
|
348
315
|
|
|
349
316
|
@associations_pot[found_association] = parent
|
|
350
317
|
|
|
@@ -353,11 +320,7 @@ module Ransack
|
|
|
353
320
|
@join_dependency.instance_variable_get(:@join_root).children.push found_association
|
|
354
321
|
|
|
355
322
|
# Builds the arel nodes properly for this association
|
|
356
|
-
|
|
357
|
-
@join_dependency.send(:construct_tables!, jd.instance_variable_get(:@join_root))
|
|
358
|
-
else
|
|
359
|
-
@join_dependency.send(:construct_tables!, jd.instance_variable_get(:@join_root), found_association)
|
|
360
|
-
end
|
|
323
|
+
@tables_pot[found_association] = @join_dependency.construct_tables_for_association!(jd.instance_variable_get(:@join_root), found_association)
|
|
361
324
|
|
|
362
325
|
# Leverage the stashed association functionality in AR
|
|
363
326
|
@object = @object.joins(jd)
|
|
@@ -367,32 +330,13 @@ module Ransack
|
|
|
367
330
|
def extract_joins(association)
|
|
368
331
|
parent = @join_dependency.instance_variable_get(:@join_root)
|
|
369
332
|
reflection = association.reflection
|
|
370
|
-
join_constraints =
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
reflection.scope_chain,
|
|
378
|
-
reflection.chain
|
|
379
|
-
)
|
|
380
|
-
elsif ::ActiveRecord::VERSION::STRING <= Constants::RAILS_5_2_0
|
|
381
|
-
association.join_constraints(
|
|
382
|
-
parent.table,
|
|
383
|
-
parent.base_klass,
|
|
384
|
-
Arel::Nodes::OuterJoin,
|
|
385
|
-
association.tables,
|
|
386
|
-
reflection.chain
|
|
387
|
-
)
|
|
388
|
-
else
|
|
389
|
-
association.join_constraints(
|
|
390
|
-
parent.table,
|
|
391
|
-
parent.base_klass,
|
|
392
|
-
Arel::Nodes::OuterJoin,
|
|
393
|
-
@join_dependency.instance_variable_get(:@alias_tracker)
|
|
394
|
-
)
|
|
395
|
-
end
|
|
333
|
+
join_constraints = association.join_constraints_with_tables(
|
|
334
|
+
parent.table,
|
|
335
|
+
parent.base_klass,
|
|
336
|
+
Arel::Nodes::OuterJoin,
|
|
337
|
+
@join_dependency.instance_variable_get(:@alias_tracker),
|
|
338
|
+
@tables_pot[association]
|
|
339
|
+
)
|
|
396
340
|
join_constraints.to_a.flatten
|
|
397
341
|
end
|
|
398
342
|
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}" }
|