ransack 2.4.2 → 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/workflows/codeql.yml +72 -0
- data/.github/workflows/cronjob.yml +6 -9
- data/.github/workflows/deploy.yml +35 -0
- data/.github/workflows/rubocop.yml +1 -1
- data/.github/workflows/test-deploy.yml +29 -0
- data/.github/workflows/test.yml +22 -48
- data/.nojekyll +0 -0
- data/.rubocop.yml +3 -0
- data/CHANGELOG.md +208 -11
- data/CONTRIBUTING.md +41 -18
- data/Gemfile +10 -10
- data/README.md +44 -977
- data/bug_report_templates/test-ransacker-arel-present-predicate.rb +4 -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/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 +0 -4
- data/lib/polyamorous/activerecord_6.1_ruby_2/join_dependency.rb +0 -1
- data/lib/polyamorous/activerecord_6.1_ruby_2/reflection.rb +11 -1
- 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/lib/ransack/adapters/active_record/base.rb +79 -10
- data/lib/ransack/adapters/active_record/context.rb +24 -51
- data/lib/ransack/configuration.rb +39 -12
- data/lib/ransack/constants.rb +125 -3
- data/lib/ransack/context.rb +34 -5
- data/lib/ransack/helpers/form_builder.rb +3 -3
- data/lib/ransack/helpers/form_helper.rb +14 -5
- data/lib/ransack/locale/sv.yml +70 -0
- data/lib/ransack/nodes/attribute.rb +2 -2
- data/lib/ransack/nodes/condition.rb +80 -7
- data/lib/ransack/nodes/grouping.rb +3 -3
- data/lib/ransack/nodes/node.rb +1 -1
- data/lib/ransack/nodes/sort.rb +2 -2
- data/lib/ransack/nodes/value.rb +2 -2
- data/lib/ransack/predicate.rb +1 -1
- data/lib/ransack/ransacker.rb +1 -1
- data/lib/ransack/search.rb +13 -7
- data/lib/ransack/translate.rb +3 -3
- data/lib/ransack/version.rb +1 -1
- data/lib/ransack/visitor.rb +38 -2
- data/lib/ransack.rb +3 -6
- data/ransack.gemspec +5 -5
- data/spec/helpers/polyamorous_helper.rb +2 -8
- data/spec/polyamorous/activerecord_compatibility_spec.rb +15 -0
- data/spec/polyamorous/join_association_spec.rb +1 -6
- data/spec/polyamorous/join_dependency_spec.rb +0 -16
- data/spec/ransack/adapters/active_record/base_spec.rb +101 -11
- data/spec/ransack/configuration_spec.rb +23 -9
- data/spec/ransack/helpers/form_builder_spec.rb +8 -8
- data/spec/ransack/helpers/form_helper_spec.rb +93 -4
- data/spec/ransack/nodes/condition_spec.rb +37 -0
- data/spec/ransack/nodes/value_spec.rb +115 -0
- data/spec/ransack/predicate_spec.rb +36 -1
- data/spec/ransack/search_spec.rb +140 -27
- data/spec/ransack/translate_spec.rb +1 -1
- data/spec/support/schema.rb +75 -9
- metadata +83 -37
- data/docs/release_process.md +0 -20
- data/lib/polyamorous/activerecord_5.2_ruby_2/join_association.rb +0 -24
- data/lib/polyamorous/activerecord_5.2_ruby_2/join_dependency.rb +0 -79
- data/lib/polyamorous/activerecord_5.2_ruby_2/reflection.rb +0 -11
- data/lib/polyamorous/activerecord_6.0_ruby_2/join_association.rb +0 -1
- data/lib/polyamorous/activerecord_6.0_ruby_2/join_dependency.rb +0 -80
- data/lib/polyamorous/activerecord_6.0_ruby_2/reflection.rb +0 -1
- data/lib/ransack/adapters/active_record/ransack/constants.rb +0 -128
- data/lib/ransack/adapters/active_record/ransack/context.rb +0 -56
- data/lib/ransack/adapters/active_record/ransack/nodes/condition.rb +0 -68
- 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/docs/{img → docs/going-further/img}/create_release.png +0 -0
- /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/lib/polyamorous/{activerecord_6.2_ruby_2 → activerecord_7.0_ruby_2}/join_association.rb +0 -0
- /data/lib/polyamorous/{activerecord_6.2_ruby_2 → activerecord_7.0_ruby_2}/join_dependency.rb +0 -0
- /data/lib/polyamorous/{activerecord_6.2_ruby_2 → activerecord_7.0_ruby_2}/reflection.rb +0 -0
- /data/lib/ransack/{adapters/active_record.rb → active_record.rb} +0 -0
|
@@ -1 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
module Polyamorous
|
|
2
|
+
module ReflectionExtensions
|
|
3
|
+
def join_scope(table, foreign_table, foreign_klass)
|
|
4
|
+
if respond_to?(:polymorphic?) && polymorphic?
|
|
5
|
+
super.where!(foreign_table[foreign_type].eq(klass.name))
|
|
6
|
+
else
|
|
7
|
+
super
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require 'polyamorous/activerecord_6.1_ruby_2/join_association'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require 'polyamorous/activerecord_6.1_ruby_2/join_dependency'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require 'polyamorous/activerecord_6.1_ruby_2/reflection'
|
|
@@ -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,7 +13,6 @@ 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
|
|
|
@@ -37,12 +35,7 @@ module Ransack
|
|
|
37
35
|
# For overriding with a whitelist array of strings.
|
|
38
36
|
#
|
|
39
37
|
def ransackable_attributes(auth_object = nil)
|
|
40
|
-
@ransackable_attributes ||=
|
|
41
|
-
column_names + _ransackers.keys + _ransack_aliases.keys +
|
|
42
|
-
attribute_aliases.keys
|
|
43
|
-
else
|
|
44
|
-
column_names + _ransackers.keys + _ransack_aliases.keys
|
|
45
|
-
end
|
|
38
|
+
@ransackable_attributes ||= deprecated_ransackable_list(:ransackable_attributes)
|
|
46
39
|
end
|
|
47
40
|
|
|
48
41
|
# Ransackable_associations, by default, returns the names
|
|
@@ -50,7 +43,7 @@ module Ransack
|
|
|
50
43
|
# For overriding with a whitelist array of strings.
|
|
51
44
|
#
|
|
52
45
|
def ransackable_associations(auth_object = nil)
|
|
53
|
-
@ransackable_associations ||=
|
|
46
|
+
@ransackable_associations ||= deprecated_ransackable_list(:ransackable_associations)
|
|
54
47
|
end
|
|
55
48
|
|
|
56
49
|
# Ransortable_attributes, by default, returns the names
|
|
@@ -70,13 +63,89 @@ module Ransack
|
|
|
70
63
|
end
|
|
71
64
|
|
|
72
65
|
# ransack_scope_skip_sanitize_args, by default, returns an empty array.
|
|
73
|
-
# 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.
|
|
74
67
|
# For overriding with a list of scopes which should be passed the args as-is.
|
|
75
68
|
#
|
|
76
69
|
def ransackable_scopes_skip_sanitize_args
|
|
77
70
|
[]
|
|
78
71
|
end
|
|
79
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
|
|
80
149
|
end
|
|
81
150
|
end
|
|
82
151
|
end
|
|
@@ -44,9 +44,13 @@ module Ransack
|
|
|
44
44
|
else
|
|
45
45
|
case Ransack.options[:postgres_fields_sort_option]
|
|
46
46
|
when :nulls_first
|
|
47
|
-
scope_or_sort = scope_or_sort.direction == :asc ? "#{scope_or_sort.to_sql} NULLS FIRST" : "#{scope_or_sort.to_sql} NULLS LAST"
|
|
47
|
+
scope_or_sort = scope_or_sort.direction == :asc ? Arel.sql("#{scope_or_sort.to_sql} NULLS FIRST") : Arel.sql("#{scope_or_sort.to_sql} NULLS LAST")
|
|
48
48
|
when :nulls_last
|
|
49
|
-
scope_or_sort = scope_or_sort.direction == :asc ? "#{scope_or_sort.to_sql} NULLS LAST" : "#{scope_or_sort.to_sql} NULLS FIRST"
|
|
49
|
+
scope_or_sort = scope_or_sort.direction == :asc ? Arel.sql("#{scope_or_sort.to_sql} NULLS LAST") : Arel.sql("#{scope_or_sort.to_sql} NULLS FIRST")
|
|
50
|
+
when :nulls_always_first
|
|
51
|
+
scope_or_sort = Arel.sql("#{scope_or_sort.to_sql} NULLS FIRST")
|
|
52
|
+
when :nulls_always_last
|
|
53
|
+
scope_or_sort = Arel.sql("#{scope_or_sort.to_sql} NULLS LAST")
|
|
50
54
|
end
|
|
51
55
|
|
|
52
56
|
relation = relation.order(scope_or_sort)
|
|
@@ -106,13 +110,7 @@ module Ransack
|
|
|
106
110
|
def join_sources
|
|
107
111
|
base, joins = begin
|
|
108
112
|
alias_tracker = ::ActiveRecord::Associations::AliasTracker.create(self.klass.connection, @object.table.name, [])
|
|
109
|
-
constraints =
|
|
110
|
-
@join_dependency.join_constraints(@object.joins_values, alias_tracker, @object.references_values)
|
|
111
|
-
elsif ::Gem::Version.new(::ActiveRecord::VERSION::STRING) >= ::Gem::Version.new(Constants::RAILS_6_0)
|
|
112
|
-
@join_dependency.join_constraints(@object.joins_values, alias_tracker)
|
|
113
|
-
else
|
|
114
|
-
@join_dependency.join_constraints(@object.joins_values, @join_type, alias_tracker)
|
|
115
|
-
end
|
|
113
|
+
constraints = @join_dependency.join_constraints(@object.joins_values, alias_tracker, @object.references_values)
|
|
116
114
|
|
|
117
115
|
[
|
|
118
116
|
Arel::SelectManager.new(@object.table),
|
|
@@ -139,6 +137,7 @@ module Ransack
|
|
|
139
137
|
stashed.eql?(association)
|
|
140
138
|
}
|
|
141
139
|
@object.joins_values.delete_if { |jd|
|
|
140
|
+
jd.instance_variables.include?(:@join_root) &&
|
|
142
141
|
jd.instance_variable_get(:@join_root).children.map(&:object_id) == [association.object_id]
|
|
143
142
|
}
|
|
144
143
|
end
|
|
@@ -279,11 +278,7 @@ module Ransack
|
|
|
279
278
|
join_list = join_nodes + convert_join_strings_to_ast(relation.table, string_joins)
|
|
280
279
|
|
|
281
280
|
alias_tracker = ::ActiveRecord::Associations::AliasTracker.create(self.klass.connection, relation.table.name, join_list)
|
|
282
|
-
join_dependency =
|
|
283
|
-
Polyamorous::JoinDependency.new(relation.klass, relation.table, association_joins, Arel::Nodes::OuterJoin)
|
|
284
|
-
else
|
|
285
|
-
Polyamorous::JoinDependency.new(relation.klass, relation.table, association_joins)
|
|
286
|
-
end
|
|
281
|
+
join_dependency = Polyamorous::JoinDependency.new(relation.klass, relation.table, association_joins, Arel::Nodes::OuterJoin)
|
|
287
282
|
join_dependency.instance_variable_set(:@alias_tracker, alias_tracker)
|
|
288
283
|
join_nodes.each do |join|
|
|
289
284
|
join_dependency.send(:alias_tracker).aliases[join.left.name.downcase] = 1
|
|
@@ -310,22 +305,13 @@ module Ransack
|
|
|
310
305
|
end
|
|
311
306
|
|
|
312
307
|
def build_association(name, parent = @base, klass = nil)
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
found_association = jd.instance_variable_get(:@join_root).children.last
|
|
321
|
-
else
|
|
322
|
-
jd = Polyamorous::JoinDependency.new(
|
|
323
|
-
parent.base_klass,
|
|
324
|
-
parent.table,
|
|
325
|
-
Polyamorous::Join.new(name, @join_type, klass)
|
|
326
|
-
)
|
|
327
|
-
found_association = jd.instance_variable_get(:@join_root).children.last
|
|
328
|
-
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
|
|
329
315
|
|
|
330
316
|
@associations_pot[found_association] = parent
|
|
331
317
|
|
|
@@ -334,11 +320,7 @@ module Ransack
|
|
|
334
320
|
@join_dependency.instance_variable_get(:@join_root).children.push found_association
|
|
335
321
|
|
|
336
322
|
# Builds the arel nodes properly for this association
|
|
337
|
-
|
|
338
|
-
@tables_pot[found_association] = @join_dependency.construct_tables_for_association!(jd.instance_variable_get(:@join_root), found_association)
|
|
339
|
-
else
|
|
340
|
-
@join_dependency.send(:construct_tables!, jd.instance_variable_get(:@join_root))
|
|
341
|
-
end
|
|
323
|
+
@tables_pot[found_association] = @join_dependency.construct_tables_for_association!(jd.instance_variable_get(:@join_root), found_association)
|
|
342
324
|
|
|
343
325
|
# Leverage the stashed association functionality in AR
|
|
344
326
|
@object = @object.joins(jd)
|
|
@@ -348,22 +330,13 @@ module Ransack
|
|
|
348
330
|
def extract_joins(association)
|
|
349
331
|
parent = @join_dependency.instance_variable_get(:@join_root)
|
|
350
332
|
reflection = association.reflection
|
|
351
|
-
join_constraints =
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
)
|
|
359
|
-
else
|
|
360
|
-
association.join_constraints(
|
|
361
|
-
parent.table,
|
|
362
|
-
parent.base_klass,
|
|
363
|
-
Arel::Nodes::OuterJoin,
|
|
364
|
-
@join_dependency.instance_variable_get(:@alias_tracker)
|
|
365
|
-
)
|
|
366
|
-
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
|
+
)
|
|
367
340
|
join_constraints.to_a.flatten
|
|
368
341
|
end
|
|
369
342
|
end
|
|
@@ -27,14 +27,15 @@ module Ransack
|
|
|
27
27
|
self.predicates = PredicateCollection.new
|
|
28
28
|
|
|
29
29
|
self.options = {
|
|
30
|
-
:
|
|
31
|
-
:
|
|
32
|
-
:
|
|
33
|
-
:
|
|
34
|
-
:
|
|
35
|
-
:
|
|
36
|
-
:
|
|
37
|
-
:
|
|
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
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
def configure
|
|
@@ -54,11 +55,11 @@ module Ransack
|
|
|
54
55
|
compound_name = name + suffix
|
|
55
56
|
self.predicates[compound_name] = Predicate.new(
|
|
56
57
|
opts.merge(
|
|
57
|
-
:
|
|
58
|
-
:
|
|
58
|
+
name: compound_name,
|
|
59
|
+
arel_predicate: arel_predicate_with_suffix(
|
|
59
60
|
opts[:arel_predicate], suffix
|
|
60
61
|
),
|
|
61
|
-
:
|
|
62
|
+
compound: true
|
|
62
63
|
)
|
|
63
64
|
)
|
|
64
65
|
end if compounds
|
|
@@ -100,6 +101,19 @@ module Ransack
|
|
|
100
101
|
self.options[:ignore_unknown_conditions] = boolean
|
|
101
102
|
end
|
|
102
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
|
+
|
|
103
117
|
# By default, Ransack displays sort order indicator arrows with HTML codes:
|
|
104
118
|
#
|
|
105
119
|
# up_arrow: '▼'
|
|
@@ -148,7 +162,7 @@ module Ransack
|
|
|
148
162
|
# User may want to configure it like this:
|
|
149
163
|
#
|
|
150
164
|
# Ransack.configure do |c|
|
|
151
|
-
# c.postgres_fields_sort_option = :nulls_first # or :
|
|
165
|
+
# c.postgres_fields_sort_option = :nulls_first # or e.g. :nulls_always_last
|
|
152
166
|
# end
|
|
153
167
|
#
|
|
154
168
|
# See this feature: https://www.postgresql.org/docs/13/queries-order.html
|
|
@@ -170,6 +184,19 @@ module Ransack
|
|
|
170
184
|
self.options[:hide_sort_order_indicators] = boolean
|
|
171
185
|
end
|
|
172
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
|
+
|
|
173
200
|
def arel_predicate_with_suffix(arel_predicate, suffix)
|
|
174
201
|
if arel_predicate === Proc
|
|
175
202
|
proc { |v| "#{arel_predicate.call(v)}#{suffix}" }
|
data/lib/ransack/constants.rb
CHANGED
|
@@ -45,10 +45,132 @@ module Ransack
|
|
|
45
45
|
NOT_EQ_ALL = 'not_eq_all'.freeze
|
|
46
46
|
CONT = 'cont'.freeze
|
|
47
47
|
|
|
48
|
-
RAILS_6_0 = '6.0.0'.freeze
|
|
49
|
-
RAILS_6_1 = '6.1.0'.freeze
|
|
50
|
-
|
|
51
48
|
RANSACK_SLASH_SEARCHES = 'ransack/searches'.freeze
|
|
52
49
|
RANSACK_SLASH_SEARCHES_SLASH_SEARCH = 'ransack/searches/search'.freeze
|
|
50
|
+
|
|
51
|
+
DISTINCT = 'DISTINCT '.freeze
|
|
52
|
+
|
|
53
|
+
DERIVED_PREDICATES = [
|
|
54
|
+
[CONT, {
|
|
55
|
+
arel_predicate: 'matches'.freeze,
|
|
56
|
+
formatter: proc { |v| "%#{escape_wildcards(v)}%" }
|
|
57
|
+
}
|
|
58
|
+
],
|
|
59
|
+
['not_cont'.freeze, {
|
|
60
|
+
arel_predicate: 'does_not_match'.freeze,
|
|
61
|
+
formatter: proc { |v| "%#{escape_wildcards(v)}%" }
|
|
62
|
+
}
|
|
63
|
+
],
|
|
64
|
+
['i_cont'.freeze, {
|
|
65
|
+
arel_predicate: 'matches'.freeze,
|
|
66
|
+
formatter: proc { |v| "%#{escape_wildcards(v.downcase)}%" },
|
|
67
|
+
case_insensitive: true
|
|
68
|
+
}
|
|
69
|
+
],
|
|
70
|
+
['not_i_cont'.freeze, {
|
|
71
|
+
arel_predicate: 'does_not_match'.freeze,
|
|
72
|
+
formatter: proc { |v| "%#{escape_wildcards(v.downcase)}%" },
|
|
73
|
+
case_insensitive: true
|
|
74
|
+
}
|
|
75
|
+
],
|
|
76
|
+
['start'.freeze, {
|
|
77
|
+
arel_predicate: 'matches'.freeze,
|
|
78
|
+
formatter: proc { |v| "#{escape_wildcards(v)}%" }
|
|
79
|
+
}
|
|
80
|
+
],
|
|
81
|
+
['not_start'.freeze, {
|
|
82
|
+
arel_predicate: 'does_not_match'.freeze,
|
|
83
|
+
formatter: proc { |v| "#{escape_wildcards(v)}%" }
|
|
84
|
+
}
|
|
85
|
+
],
|
|
86
|
+
['end'.freeze, {
|
|
87
|
+
arel_predicate: 'matches'.freeze,
|
|
88
|
+
formatter: proc { |v| "%#{escape_wildcards(v)}" }
|
|
89
|
+
}
|
|
90
|
+
],
|
|
91
|
+
['not_end'.freeze, {
|
|
92
|
+
arel_predicate: 'does_not_match'.freeze,
|
|
93
|
+
formatter: proc { |v| "%#{escape_wildcards(v)}" }
|
|
94
|
+
}
|
|
95
|
+
],
|
|
96
|
+
['true'.freeze, {
|
|
97
|
+
arel_predicate: proc { |v| v ? EQ : NOT_EQ },
|
|
98
|
+
compounds: false,
|
|
99
|
+
type: :boolean,
|
|
100
|
+
validator: proc { |v| BOOLEAN_VALUES.include?(v) },
|
|
101
|
+
formatter: proc { |v| true }
|
|
102
|
+
}
|
|
103
|
+
],
|
|
104
|
+
['not_true'.freeze, {
|
|
105
|
+
arel_predicate: proc { |v| v ? NOT_EQ : EQ },
|
|
106
|
+
compounds: false,
|
|
107
|
+
type: :boolean,
|
|
108
|
+
validator: proc { |v| BOOLEAN_VALUES.include?(v) },
|
|
109
|
+
formatter: proc { |v| true }
|
|
110
|
+
}
|
|
111
|
+
],
|
|
112
|
+
['false'.freeze, {
|
|
113
|
+
arel_predicate: proc { |v| v ? EQ : NOT_EQ },
|
|
114
|
+
compounds: false,
|
|
115
|
+
type: :boolean,
|
|
116
|
+
validator: proc { |v| BOOLEAN_VALUES.include?(v) },
|
|
117
|
+
formatter: proc { |v| false }
|
|
118
|
+
}
|
|
119
|
+
],
|
|
120
|
+
['not_false'.freeze, {
|
|
121
|
+
arel_predicate: proc { |v| v ? NOT_EQ : EQ },
|
|
122
|
+
compounds: false,
|
|
123
|
+
type: :boolean,
|
|
124
|
+
validator: proc { |v| BOOLEAN_VALUES.include?(v) },
|
|
125
|
+
formatter: proc { |v| false }
|
|
126
|
+
}
|
|
127
|
+
],
|
|
128
|
+
['present'.freeze, {
|
|
129
|
+
arel_predicate: proc { |v| v ? NOT_EQ_ALL : EQ_ANY },
|
|
130
|
+
compounds: false,
|
|
131
|
+
type: :boolean,
|
|
132
|
+
validator: proc { |v| BOOLEAN_VALUES.include?(v) },
|
|
133
|
+
formatter: proc { |v| [nil, ''.freeze].freeze }
|
|
134
|
+
}
|
|
135
|
+
],
|
|
136
|
+
['blank'.freeze, {
|
|
137
|
+
arel_predicate: proc { |v| v ? EQ_ANY : NOT_EQ_ALL },
|
|
138
|
+
compounds: false,
|
|
139
|
+
type: :boolean,
|
|
140
|
+
validator: proc { |v| BOOLEAN_VALUES.include?(v) },
|
|
141
|
+
formatter: proc { |v| [nil, ''.freeze].freeze }
|
|
142
|
+
}
|
|
143
|
+
],
|
|
144
|
+
['null'.freeze, {
|
|
145
|
+
arel_predicate: proc { |v| v ? EQ : NOT_EQ },
|
|
146
|
+
compounds: false,
|
|
147
|
+
type: :boolean,
|
|
148
|
+
validator: proc { |v| BOOLEAN_VALUES.include?(v) },
|
|
149
|
+
formatter: proc { |v| nil }
|
|
150
|
+
}
|
|
151
|
+
],
|
|
152
|
+
['not_null'.freeze, {
|
|
153
|
+
arel_predicate: proc { |v| v ? NOT_EQ : EQ },
|
|
154
|
+
compounds: false,
|
|
155
|
+
type: :boolean,
|
|
156
|
+
validator: proc { |v| BOOLEAN_VALUES.include?(v) },
|
|
157
|
+
formatter: proc { |v| nil } }
|
|
158
|
+
]
|
|
159
|
+
].freeze
|
|
160
|
+
|
|
161
|
+
module_function
|
|
162
|
+
# replace % \ to \% \\
|
|
163
|
+
def escape_wildcards(unescaped)
|
|
164
|
+
case ActiveRecord::Base.connection.adapter_name
|
|
165
|
+
when "Mysql2".freeze
|
|
166
|
+
# Necessary for MySQL
|
|
167
|
+
unescaped.to_s.gsub(/([\\%_])/, '\\\\\\1')
|
|
168
|
+
when "PostgreSQL".freeze
|
|
169
|
+
# Necessary for PostgreSQL
|
|
170
|
+
unescaped.to_s.gsub(/([\\%_.])/, '\\\\\\1')
|
|
171
|
+
else
|
|
172
|
+
unescaped
|
|
173
|
+
end
|
|
174
|
+
end
|
|
53
175
|
end
|
|
54
176
|
end
|
data/lib/ransack/context.rb
CHANGED
|
@@ -1,19 +1,24 @@
|
|
|
1
1
|
require 'ransack/visitor'
|
|
2
|
-
Ransack::Adapters.object_mapper.require_context
|
|
3
2
|
|
|
4
3
|
module Ransack
|
|
5
4
|
class Context
|
|
6
5
|
attr_reader :search, :object, :klass, :base, :engine, :arel_visitor
|
|
7
6
|
attr_accessor :auth_object, :search_key
|
|
7
|
+
attr_reader :arel_visitor
|
|
8
8
|
|
|
9
9
|
class << self
|
|
10
10
|
|
|
11
11
|
def for_class(klass, options = {})
|
|
12
|
-
|
|
12
|
+
if klass < ActiveRecord::Base
|
|
13
|
+
Adapters::ActiveRecord::Context.new(klass, options)
|
|
14
|
+
end
|
|
13
15
|
end
|
|
14
16
|
|
|
15
17
|
def for_object(object, options = {})
|
|
16
|
-
|
|
18
|
+
case object
|
|
19
|
+
when ActiveRecord::Relation
|
|
20
|
+
Adapters::ActiveRecord::Context.new(object.klass, options)
|
|
21
|
+
end
|
|
17
22
|
end
|
|
18
23
|
|
|
19
24
|
def for(object, options = {})
|
|
@@ -30,11 +35,35 @@ module Ransack
|
|
|
30
35
|
end # << self
|
|
31
36
|
|
|
32
37
|
def initialize(object, options = {})
|
|
33
|
-
|
|
38
|
+
@object = relation_for(object)
|
|
39
|
+
@klass = @object.klass
|
|
40
|
+
@join_dependency = join_dependency(@object)
|
|
41
|
+
@join_type = options[:join_type] || Polyamorous::OuterJoin
|
|
42
|
+
@search_key = options[:search_key] || Ransack.options[:search_key]
|
|
43
|
+
@associations_pot = {}
|
|
44
|
+
@tables_pot = {}
|
|
45
|
+
@lock_associations = []
|
|
46
|
+
|
|
47
|
+
@base = @join_dependency.instance_variable_get(:@join_root)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def bind_pair_for(key)
|
|
51
|
+
@bind_pairs ||= {}
|
|
52
|
+
|
|
53
|
+
@bind_pairs[key] ||= begin
|
|
54
|
+
parent, attr_name = get_parent_and_attribute_name(key.to_s)
|
|
55
|
+
[parent, attr_name] if parent && attr_name
|
|
56
|
+
end
|
|
34
57
|
end
|
|
35
58
|
|
|
36
59
|
def klassify(obj)
|
|
37
|
-
|
|
60
|
+
if Class === obj && ::ActiveRecord::Base > obj
|
|
61
|
+
obj
|
|
62
|
+
elsif obj.respond_to? :klass
|
|
63
|
+
obj.klass
|
|
64
|
+
else
|
|
65
|
+
raise ArgumentError, "Don't know how to klassify #{obj.inspect}"
|
|
66
|
+
end
|
|
38
67
|
end
|
|
39
68
|
|
|
40
69
|
# Convert a string representing a chain of associations and an attribute
|
|
@@ -33,7 +33,7 @@ module Ransack
|
|
|
33
33
|
text = args.first
|
|
34
34
|
i18n = options[:i18n] || {}
|
|
35
35
|
text ||= object.translate(
|
|
36
|
-
method, i18n.reverse_merge(:
|
|
36
|
+
method, i18n.reverse_merge(include_associations: true)
|
|
37
37
|
) if object.respond_to? :translate
|
|
38
38
|
super(method, text, options, &block)
|
|
39
39
|
end
|
|
@@ -240,7 +240,7 @@ module Ransack
|
|
|
240
240
|
def get_attribute_element(action, base)
|
|
241
241
|
begin
|
|
242
242
|
[
|
|
243
|
-
Translate.association(base, :
|
|
243
|
+
Translate.association(base, context: object.context),
|
|
244
244
|
collection_for_base(action, base)
|
|
245
245
|
]
|
|
246
246
|
rescue UntraversableAssociationError
|
|
@@ -253,7 +253,7 @@ module Ransack
|
|
|
253
253
|
[
|
|
254
254
|
attr_from_base_and_column(base, c),
|
|
255
255
|
Translate.attribute(
|
|
256
|
-
attr_from_base_and_column(base, c), :
|
|
256
|
+
attr_from_base_and_column(base, c), context: object.context
|
|
257
257
|
)
|
|
258
258
|
]
|
|
259
259
|
end
|
|
@@ -129,13 +129,21 @@ module Ransack
|
|
|
129
129
|
end
|
|
130
130
|
|
|
131
131
|
def url_options
|
|
132
|
-
@params.merge(
|
|
133
|
-
@options.merge(
|
|
132
|
+
@params.except(:host).merge(
|
|
133
|
+
@options.except(:class, :data, :host).merge(
|
|
134
134
|
@search.context.search_key => search_and_sort_params))
|
|
135
135
|
end
|
|
136
136
|
|
|
137
137
|
def html_options(args)
|
|
138
|
-
|
|
138
|
+
if args.empty?
|
|
139
|
+
html_options = @options
|
|
140
|
+
else
|
|
141
|
+
deprecation_message = "Passing two trailing hashes to `sort_link` is deprecated, merge the trailing hashes into a single one."
|
|
142
|
+
caller_location = caller_locations(2, 2).first
|
|
143
|
+
warn "#{deprecation_message} (called at #{caller_location.path}:#{caller_location.lineno})"
|
|
144
|
+
html_options = extract_options_and_mutate_args!(args)
|
|
145
|
+
end
|
|
146
|
+
|
|
139
147
|
html_options.merge(
|
|
140
148
|
class: [['sort_link'.freeze, @current_dir], html_options[:class]]
|
|
141
149
|
.compact.join(' '.freeze)
|
|
@@ -145,7 +153,7 @@ module Ransack
|
|
|
145
153
|
private
|
|
146
154
|
|
|
147
155
|
def parameters_hash(params)
|
|
148
|
-
if
|
|
156
|
+
if params.respond_to?(:to_unsafe_h)
|
|
149
157
|
params.to_unsafe_h
|
|
150
158
|
else
|
|
151
159
|
params
|
|
@@ -172,7 +180,8 @@ module Ransack
|
|
|
172
180
|
end
|
|
173
181
|
|
|
174
182
|
def search_params
|
|
175
|
-
@params[@search.context.search_key]
|
|
183
|
+
query_params = @params[@search.context.search_key]
|
|
184
|
+
query_params.is_a?(Hash) ? query_params : {}
|
|
176
185
|
end
|
|
177
186
|
|
|
178
187
|
def sort_params
|