ransack 2.1.1 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. checksums.yaml +4 -4
  2. data/.github/FUNDING.yml +3 -0
  3. data/.github/SECURITY.md +12 -0
  4. data/.github/workflows/cronjob.yml +102 -0
  5. data/.github/workflows/rubocop.yml +20 -0
  6. data/.github/workflows/test.yml +163 -0
  7. data/.gitignore +1 -0
  8. data/.rubocop.yml +44 -0
  9. data/CHANGELOG.md +64 -1
  10. data/CONTRIBUTING.md +16 -11
  11. data/Gemfile +23 -17
  12. data/README.md +190 -57
  13. data/bug_report_templates/test-ransack-scope-and-column-same-name.rb +78 -0
  14. data/bug_report_templates/test-ransacker-arel-present-predicate.rb +71 -0
  15. data/docs/img/create_release.png +0 -0
  16. data/docs/release_process.md +17 -0
  17. data/lib/polyamorous/{activerecord_5.2.1_ruby_2 → activerecord_5.2_ruby_2}/join_association.rb +2 -9
  18. data/lib/polyamorous/{activerecord_5.2.1_ruby_2 → activerecord_5.2_ruby_2}/join_dependency.rb +25 -3
  19. data/lib/polyamorous/activerecord_5.2_ruby_2/reflection.rb +11 -0
  20. data/lib/polyamorous/activerecord_6.0_ruby_2/join_association.rb +1 -0
  21. data/lib/polyamorous/activerecord_6.0_ruby_2/join_dependency.rb +80 -0
  22. data/lib/polyamorous/activerecord_6.0_ruby_2/reflection.rb +1 -0
  23. data/lib/polyamorous/activerecord_6.1_ruby_2/join_association.rb +74 -0
  24. data/lib/polyamorous/activerecord_6.1_ruby_2/join_dependency.rb +93 -0
  25. data/lib/polyamorous/activerecord_6.1_ruby_2/reflection.rb +1 -0
  26. data/lib/polyamorous/activerecord_7.0_ruby_2/join_association.rb +1 -0
  27. data/lib/polyamorous/activerecord_7.0_ruby_2/join_dependency.rb +1 -0
  28. data/lib/polyamorous/activerecord_7.0_ruby_2/reflection.rb +1 -0
  29. data/lib/polyamorous/polyamorous.rb +24 -0
  30. data/lib/polyamorous.rb +1 -25
  31. data/lib/ransack/adapters/active_record/base.rb +5 -1
  32. data/lib/ransack/adapters/active_record/context.rb +71 -68
  33. data/lib/ransack/adapters/active_record/ransack/constants.rb +18 -3
  34. data/lib/ransack/adapters/active_record/ransack/context.rb +2 -6
  35. data/lib/ransack/adapters/active_record/ransack/nodes/condition.rb +13 -5
  36. data/lib/ransack/adapters/active_record/ransack/translate.rb +1 -1
  37. data/lib/ransack/configuration.rb +31 -1
  38. data/lib/ransack/constants.rb +3 -5
  39. data/lib/ransack/context.rb +19 -18
  40. data/lib/ransack/helpers/form_builder.rb +8 -14
  41. data/lib/ransack/helpers/form_helper.rb +1 -1
  42. data/lib/ransack/helpers.rb +1 -1
  43. data/lib/ransack/locale/az.yml +1 -1
  44. data/lib/ransack/locale/ca.yml +70 -0
  45. data/lib/ransack/locale/es.yml +22 -22
  46. data/lib/ransack/locale/fa.yml +70 -0
  47. data/lib/ransack/locale/fi.yml +71 -0
  48. data/lib/ransack/locale/sk.yml +70 -0
  49. data/lib/ransack/locale/sv.yml +70 -0
  50. data/lib/ransack/nodes/attribute.rb +1 -1
  51. data/lib/ransack/nodes/condition.rb +7 -1
  52. data/lib/ransack/nodes/grouping.rb +1 -1
  53. data/lib/ransack/nodes/sort.rb +3 -3
  54. data/lib/ransack/nodes/value.rb +1 -1
  55. data/lib/ransack/predicate.rb +2 -1
  56. data/lib/ransack/search.rb +4 -1
  57. data/lib/ransack/translate.rb +115 -115
  58. data/lib/ransack/version.rb +1 -1
  59. data/lib/ransack.rb +3 -3
  60. data/ransack.gemspec +8 -23
  61. data/spec/blueprints/articles.rb +1 -1
  62. data/spec/blueprints/comments.rb +1 -1
  63. data/spec/blueprints/notes.rb +1 -1
  64. data/spec/blueprints/tags.rb +1 -1
  65. data/spec/console.rb +5 -5
  66. data/spec/helpers/polyamorous_helper.rb +3 -8
  67. data/spec/helpers/ransack_helper.rb +1 -1
  68. data/spec/{ransack → polyamorous}/join_association_spec.rb +8 -1
  69. data/spec/{ransack → polyamorous}/join_dependency_spec.rb +18 -7
  70. data/spec/{ransack → polyamorous}/join_spec.rb +0 -0
  71. data/spec/ransack/adapters/active_record/base_spec.rb +26 -15
  72. data/spec/ransack/adapters/active_record/context_spec.rb +60 -18
  73. data/spec/ransack/configuration_spec.rb +24 -0
  74. data/spec/ransack/helpers/form_helper_spec.rb +16 -16
  75. data/spec/ransack/nodes/condition_spec.rb +13 -0
  76. data/spec/ransack/nodes/grouping_spec.rb +2 -2
  77. data/spec/ransack/predicate_spec.rb +54 -2
  78. data/spec/ransack/search_spec.rb +238 -36
  79. data/spec/spec_helper.rb +10 -5
  80. data/spec/support/schema.rb +37 -3
  81. metadata +45 -139
  82. data/.travis.yml +0 -37
  83. data/lib/polyamorous/activerecord_5.0_ruby_2/join_association.rb +0 -2
  84. data/lib/polyamorous/activerecord_5.0_ruby_2/join_dependency.rb +0 -2
  85. data/lib/polyamorous/activerecord_5.1_ruby_2/join_association.rb +0 -32
  86. data/lib/polyamorous/activerecord_5.1_ruby_2/join_dependency.rb +0 -112
  87. data/lib/polyamorous/activerecord_5.2.0_ruby_2/join_association.rb +0 -32
  88. data/lib/polyamorous/activerecord_5.2.0_ruby_2/join_dependency.rb +0 -113
@@ -0,0 +1,74 @@
1
+ module Polyamorous
2
+ module JoinAssociationExtensions
3
+ include SwappingReflectionClass
4
+ def self.prepended(base)
5
+ base.class_eval { attr_reader :join_type }
6
+ end
7
+
8
+ def initialize(reflection, children, polymorphic_class = nil, join_type = Arel::Nodes::InnerJoin)
9
+ @join_type = join_type
10
+ if polymorphic_class && ::ActiveRecord::Base > polymorphic_class
11
+ swapping_reflection_klass(reflection, polymorphic_class) do |reflection|
12
+ super(reflection, children)
13
+ self.reflection.options[:polymorphic] = true
14
+ end
15
+ else
16
+ super(reflection, children)
17
+ end
18
+ end
19
+
20
+ # Same as #join_constraints, but instead of constructing tables from the
21
+ # given block, uses the ones passed
22
+ def join_constraints_with_tables(foreign_table, foreign_klass, join_type, alias_tracker, tables)
23
+ joins = []
24
+ chain = []
25
+
26
+ reflection.chain.each.with_index do |reflection, i|
27
+ table = tables[i]
28
+
29
+ @table ||= table
30
+ chain << [reflection, table]
31
+ end
32
+
33
+ # The chain starts with the target table, but we want to end with it here (makes
34
+ # more sense in this context), so we reverse
35
+ chain.reverse_each do |reflection, table|
36
+ klass = reflection.klass
37
+
38
+ join_scope = reflection.join_scope(table, foreign_table, foreign_klass)
39
+
40
+ unless join_scope.references_values.empty?
41
+ join_dependency = join_scope.construct_join_dependency(
42
+ join_scope.eager_load_values | join_scope.includes_values, Arel::Nodes::OuterJoin
43
+ )
44
+ join_scope.joins!(join_dependency)
45
+ end
46
+
47
+ arel = join_scope.arel(alias_tracker.aliases)
48
+ nodes = arel.constraints.first
49
+
50
+ if nodes.is_a?(Arel::Nodes::And)
51
+ others = nodes.children.extract! do |node|
52
+ !Arel.fetch_attribute(node) { |attr| attr.relation.name == table.name }
53
+ end
54
+ end
55
+
56
+ joins << table.create_join(table, table.create_on(nodes), join_type)
57
+
58
+ if others && !others.empty?
59
+ joins.concat arel.join_sources
60
+ append_constraints(joins.last, others)
61
+ end
62
+
63
+ # The current table in this iteration becomes the foreign table in the next
64
+ foreign_table, foreign_klass = table, klass
65
+ end
66
+
67
+ joins
68
+ end
69
+
70
+ def ==(other)
71
+ base_klass == other.base_klass
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,93 @@
1
+ # active_record_6.1_ruby_2/join_dependency.rb
2
+ module Polyamorous
3
+ module JoinDependencyExtensions
4
+ # Replaces ActiveRecord::Associations::JoinDependency#build
5
+ def build(associations, base_klass)
6
+ associations.map do |name, right|
7
+ if name.is_a? Join
8
+ reflection = find_reflection base_klass, name.name
9
+ reflection.check_validity!
10
+ reflection.check_eager_loadable!
11
+
12
+ klass = if reflection.polymorphic?
13
+ name.klass || base_klass
14
+ else
15
+ reflection.klass
16
+ end
17
+ JoinAssociation.new(reflection, build(right, klass), name.klass, name.type)
18
+ else
19
+ reflection = find_reflection base_klass, name
20
+ reflection.check_validity!
21
+ reflection.check_eager_loadable!
22
+
23
+ if reflection.polymorphic?
24
+ raise ActiveRecord::EagerLoadPolymorphicError.new(reflection)
25
+ end
26
+ JoinAssociation.new(reflection, build(right, reflection.klass))
27
+ end
28
+ end
29
+ end
30
+
31
+ def join_constraints(joins_to_add, alias_tracker, references)
32
+ @alias_tracker = alias_tracker
33
+ @joined_tables = {}
34
+ @references = {}
35
+
36
+ references.each do |table_name|
37
+ @references[table_name.to_sym] = table_name if table_name.is_a?(String)
38
+ end
39
+
40
+ joins = make_join_constraints(join_root, join_type)
41
+
42
+ joins.concat joins_to_add.flat_map { |oj|
43
+ if join_root.match?(oj.join_root) && join_root.table.name == oj.join_root.table.name
44
+ walk join_root, oj.join_root, oj.join_type
45
+ else
46
+ make_join_constraints(oj.join_root, oj.join_type)
47
+ end
48
+ }
49
+ end
50
+
51
+ def construct_tables_for_association!(join_root, association)
52
+ tables = table_aliases_for(join_root, association)
53
+ association.table = tables.first
54
+ tables
55
+ end
56
+
57
+ private
58
+
59
+ def table_aliases_for(parent, node)
60
+ node.reflection.chain.map { |reflection|
61
+ alias_tracker.aliased_table_for(reflection.klass.arel_table) do
62
+ root = reflection == node.reflection
63
+ name = reflection.alias_candidate(parent.table_name)
64
+ root ? name : "#{name}_join"
65
+ end
66
+ }
67
+ end
68
+
69
+ module ClassMethods
70
+ # Prepended before ActiveRecord::Associations::JoinDependency#walk_tree
71
+ #
72
+ def walk_tree(associations, hash)
73
+ case associations
74
+ when TreeNode
75
+ associations.add_to_tree(hash)
76
+ when Hash
77
+ associations.each do |k, v|
78
+ cache =
79
+ if TreeNode === k
80
+ k.add_to_tree(hash)
81
+ else
82
+ hash[k] ||= {}
83
+ end
84
+ walk_tree(v, cache)
85
+ end
86
+ else
87
+ super(associations, hash)
88
+ end
89
+ end
90
+ end
91
+
92
+ end
93
+ end
@@ -0,0 +1 @@
1
+ require 'polyamorous/activerecord_6.0_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'
@@ -0,0 +1,24 @@
1
+ if defined?(::ActiveRecord)
2
+ module Polyamorous
3
+ InnerJoin = Arel::Nodes::InnerJoin
4
+ OuterJoin = Arel::Nodes::OuterJoin
5
+
6
+ JoinDependency = ::ActiveRecord::Associations::JoinDependency
7
+ JoinAssociation = ::ActiveRecord::Associations::JoinDependency::JoinAssociation
8
+ end
9
+
10
+ require 'polyamorous/tree_node'
11
+ require 'polyamorous/join'
12
+ require 'polyamorous/swapping_reflection_class'
13
+
14
+ ar_version = ::ActiveRecord::VERSION::STRING[0, 3]
15
+ %w(join_association join_dependency reflection).each do |file|
16
+ require "polyamorous/activerecord_#{ar_version}_ruby_2/#{file}"
17
+ end
18
+
19
+ ActiveRecord::Reflection::AbstractReflection.send(:prepend, Polyamorous::ReflectionExtensions)
20
+
21
+ Polyamorous::JoinDependency.send(:prepend, Polyamorous::JoinDependencyExtensions)
22
+ Polyamorous::JoinDependency.singleton_class.send(:prepend, Polyamorous::JoinDependencyExtensions::ClassMethods)
23
+ Polyamorous::JoinAssociation.send(:prepend, Polyamorous::JoinAssociationExtensions)
24
+ end
data/lib/polyamorous.rb CHANGED
@@ -1,25 +1 @@
1
- if defined?(::ActiveRecord)
2
- module Polyamorous
3
- InnerJoin = Arel::Nodes::InnerJoin
4
- OuterJoin = Arel::Nodes::OuterJoin
5
-
6
- JoinDependency = ::ActiveRecord::Associations::JoinDependency
7
- JoinAssociation = ::ActiveRecord::Associations::JoinDependency::JoinAssociation
8
- end
9
-
10
- require 'polyamorous/tree_node'
11
- require 'polyamorous/join'
12
- require 'polyamorous/swapping_reflection_class'
13
-
14
- ar_version = ::ActiveRecord::VERSION::STRING[0,3]
15
- ar_version = ::ActiveRecord::VERSION::STRING[0,5] if ar_version >= "5.2"
16
- ar_version = "5.2.1" if ::ActiveRecord::VERSION::STRING >= "5.2.1"
17
-
18
- %w(join_association join_dependency).each do |file|
19
- require "polyamorous/activerecord_#{ar_version}_ruby_2/#{file}"
20
- end
21
-
22
- Polyamorous::JoinDependency.send(:prepend, Polyamorous::JoinDependencyExtensions)
23
- Polyamorous::JoinDependency.singleton_class.send(:prepend, Polyamorous::JoinDependencyExtensions::ClassMethods)
24
- Polyamorous::JoinAssociation.send(:prepend, Polyamorous::JoinAssociationExtensions)
25
- end
1
+ require 'polyamorous/polyamorous'
@@ -18,6 +18,10 @@ module Ransack
18
18
  Search.new(self, params, options)
19
19
  end
20
20
 
21
+ def ransack!(params = {}, options = {})
22
+ ransack(params, options.merge(ignore_unknown_conditions: false))
23
+ end
24
+
21
25
  def ransacker(name, opts = {}, &block)
22
26
  self._ransackers = _ransackers.merge name.to_s => Ransacker
23
27
  .new(self, name, opts, &block)
@@ -66,7 +70,7 @@ module Ransack
66
70
  end
67
71
 
68
72
  # ransack_scope_skip_sanitize_args, by default, returns an empty array.
69
- # i.e. use the sanitize_scope_args setting to determin if args should be converted.
73
+ # i.e. use the sanitize_scope_args setting to determine if args should be converted.
70
74
  # For overriding with a list of scopes which should be passed the args as-is.
71
75
  #
72
76
  def ransackable_scopes_skip_sanitize_args
@@ -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,20 +108,21 @@ 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
- [
111
- Arel::SelectManager.new(@object.table),
113
+ constraints = if ::Gem::Version.new(::ActiveRecord::VERSION::STRING) >= ::Gem::Version.new(Constants::RAILS_6_1)
114
+ @join_dependency.join_constraints(@object.joins_values, alias_tracker, @object.references_values)
115
+ elsif ::Gem::Version.new(::ActiveRecord::VERSION::STRING) >= ::Gem::Version.new(Constants::RAILS_6_0)
116
+ @join_dependency.join_constraints(@object.joins_values, alias_tracker)
117
+ else
112
118
  @join_dependency.join_constraints(@object.joins_values, @join_type, alias_tracker)
113
- ]
114
- else
119
+ end
120
+
115
121
  [
116
122
  Arel::SelectManager.new(@object.table),
117
- @join_dependency.join_constraints(@object.joins_values, @join_type)
123
+ constraints
118
124
  ]
119
125
  end
120
- joins = joins.collect(&:joins).flatten if ::ActiveRecord::VERSION::STRING < Constants::RAILS_5_2
121
126
  joins.each do |aliased_join|
122
127
  base.from(aliased_join)
123
128
  end
@@ -163,7 +168,7 @@ module Ransack
163
168
  def build_correlated_subquery(association)
164
169
  join_constraints = extract_joins(association)
165
170
  join_root = join_constraints.shift
166
- correlated_key = join_root.right.expr.left
171
+ correlated_key = extract_correlated_key(join_root)
167
172
  subquery = Arel::SelectManager.new(association.base_klass)
168
173
  subquery.from(join_root.left)
169
174
  subquery.project(correlated_key)
@@ -179,6 +184,35 @@ module Ransack
179
184
 
180
185
  private
181
186
 
187
+ def extract_correlated_key(join_root)
188
+ case join_root
189
+ when Arel::Nodes::OuterJoin
190
+ # one of join_root.right/join_root.left is expected to be Arel::Nodes::On
191
+ if join_root.right.is_a?(Arel::Nodes::On)
192
+ extract_correlated_key(join_root.right.expr)
193
+ elsif join_root.left.is_a?(Arel::Nodes::On)
194
+ extract_correlated_key(join_root.left.expr)
195
+ else
196
+ raise 'Ransack encountered an unexpected arel structure'
197
+ end
198
+ when Arel::Nodes::Equality
199
+ pk = primary_key
200
+ if join_root.left == pk
201
+ join_root.right
202
+ elsif join_root.right == pk
203
+ join_root.left
204
+ else
205
+ nil
206
+ end
207
+ when Arel::Nodes::And
208
+ extract_correlated_key(join_root.left) || extract_correlated_key(join_root.right)
209
+ else
210
+ # eg parent was Arel::Nodes::And and the evaluated side was one of
211
+ # Arel::Nodes::Grouping or MultiTenant::TenantEnforcementClause
212
+ nil
213
+ end
214
+ end
215
+
182
216
  def get_parent_and_attribute_name(str, parent = @base)
183
217
  attr_name = nil
184
218
 
@@ -248,24 +282,15 @@ module Ransack
248
282
 
249
283
  join_list = join_nodes + convert_join_strings_to_ast(relation.table, string_joins)
250
284
 
251
- if ::ActiveRecord::VERSION::STRING < Constants::RAILS_5_2_0
252
- join_dependency = Polyamorous::JoinDependency.new(relation.klass, association_joins, join_list)
253
- join_nodes.each do |join|
254
- join_dependency.send(:alias_tracker).aliases[join.left.name.downcase] = 1
255
- end
256
- elsif ::ActiveRecord::VERSION::STRING == Constants::RAILS_5_2_0
257
- alias_tracker = ::ActiveRecord::Associations::AliasTracker.create(self.klass.connection, relation.table.name, join_list)
258
- join_dependency = Polyamorous::JoinDependency.new(relation.klass, relation.table, association_joins, alias_tracker)
259
- join_nodes.each do |join|
260
- join_dependency.send(:alias_tracker).aliases[join.left.name.downcase] = 1
261
- end
285
+ alias_tracker = ::ActiveRecord::Associations::AliasTracker.create(self.klass.connection, relation.table.name, join_list)
286
+ join_dependency = if ::Gem::Version.new(::ActiveRecord::VERSION::STRING) >= ::Gem::Version.new(Constants::RAILS_6_0)
287
+ Polyamorous::JoinDependency.new(relation.klass, relation.table, association_joins, Arel::Nodes::OuterJoin)
262
288
  else
263
- alias_tracker = ::ActiveRecord::Associations::AliasTracker.create(self.klass.connection, relation.table.name, join_list)
264
- join_dependency = Polyamorous::JoinDependency.new(relation.klass, relation.table, association_joins)
265
- join_dependency.instance_variable_set(:@alias_tracker, alias_tracker)
266
- join_nodes.each do |join|
267
- join_dependency.send(:alias_tracker).aliases[join.left.name.downcase] = 1
268
- end
289
+ Polyamorous::JoinDependency.new(relation.klass, relation.table, association_joins)
290
+ end
291
+ join_dependency.instance_variable_set(:@alias_tracker, alias_tracker)
292
+ join_nodes.each do |join|
293
+ join_dependency.send(:alias_tracker).aliases[join.left.name.downcase] = 1
269
294
  end
270
295
  join_dependency
271
296
  end
@@ -289,32 +314,23 @@ module Ransack
289
314
  end
290
315
 
291
316
  def build_association(name, parent = @base, klass = nil)
292
- if ::ActiveRecord::VERSION::STRING < Constants::RAILS_5_2_0
293
- jd = Polyamorous::JoinDependency.new(
294
- parent.base_klass,
295
- Polyamorous::Join.new(name, @join_type, klass),
296
- []
297
- )
298
- found_association = jd.join_root.children.last
299
- elsif ::ActiveRecord::VERSION::STRING == Constants::RAILS_5_2_0
300
- alias_tracker = ::ActiveRecord::Associations::AliasTracker.create(self.klass.connection, parent.table.name, [])
317
+ if ::Gem::Version.new(::ActiveRecord::VERSION::STRING) >= ::Gem::Version.new(Constants::RAILS_6_0)
301
318
  jd = Polyamorous::JoinDependency.new(
302
319
  parent.base_klass,
303
- parent.base_klass.arel_table,
320
+ parent.table,
304
321
  Polyamorous::Join.new(name, @join_type, klass),
305
- alias_tracker
322
+ @join_type
306
323
  )
307
324
  found_association = jd.instance_variable_get(:@join_root).children.last
308
325
  else
309
326
  jd = Polyamorous::JoinDependency.new(
310
327
  parent.base_klass,
311
- parent.base_klass.arel_table,
312
- Polyamorous::Join.new(name, @join_type, klass),
328
+ parent.table,
329
+ Polyamorous::Join.new(name, @join_type, klass)
313
330
  )
314
331
  found_association = jd.instance_variable_get(:@join_root).children.last
315
332
  end
316
333
 
317
-
318
334
  @associations_pot[found_association] = parent
319
335
 
320
336
  # TODO maybe we dont need to push associations here, we could loop
@@ -322,40 +338,27 @@ module Ransack
322
338
  @join_dependency.instance_variable_get(:@join_root).children.push found_association
323
339
 
324
340
  # Builds the arel nodes properly for this association
325
- if ::ActiveRecord::VERSION::STRING > Constants::RAILS_5_2_0
326
- @join_dependency.send(:construct_tables!, jd.instance_variable_get(:@join_root))
341
+ if ::Gem::Version.new(::ActiveRecord::VERSION::STRING) >= ::Gem::Version.new(Constants::RAILS_6_1)
342
+ @tables_pot[found_association] = @join_dependency.construct_tables_for_association!(jd.instance_variable_get(:@join_root), found_association)
327
343
  else
328
- @join_dependency.send(
329
- :construct_tables!, jd.instance_variable_get(:@join_root), found_association
330
- )
344
+ @join_dependency.send(:construct_tables!, jd.instance_variable_get(:@join_root))
331
345
  end
332
346
 
333
347
  # Leverage the stashed association functionality in AR
334
348
  @object = @object.joins(jd)
335
-
336
349
  found_association
337
350
  end
338
351
 
339
352
  def extract_joins(association)
340
353
  parent = @join_dependency.instance_variable_get(:@join_root)
341
354
  reflection = association.reflection
342
- join_constraints = if ::ActiveRecord::VERSION::STRING < Constants::RAILS_5_1
343
- association.join_constraints(
344
- parent.table,
345
- parent.base_klass,
346
- association,
347
- Arel::Nodes::OuterJoin,
348
- association.tables,
349
- reflection.scope_chain,
350
- reflection.chain
351
- )
352
- elsif ::ActiveRecord::VERSION::STRING <= Constants::RAILS_5_2_0
353
- association.join_constraints(
355
+ join_constraints = if ::Gem::Version.new(::ActiveRecord::VERSION::STRING) >= ::Gem::Version.new(Constants::RAILS_6_1)
356
+ association.join_constraints_with_tables(
354
357
  parent.table,
355
358
  parent.base_klass,
356
359
  Arel::Nodes::OuterJoin,
357
- association.tables,
358
- reflection.chain
360
+ @join_dependency.instance_variable_get(:@alias_tracker),
361
+ @tables_pot[association]
359
362
  )
360
363
  else
361
364
  association.join_constraints(
@@ -13,6 +13,18 @@ module Ransack
13
13
  formatter: proc { |v| "%#{escape_wildcards(v)}%" }
14
14
  }
15
15
  ],
16
+ ['i_cont'.freeze, {
17
+ arel_predicate: 'matches'.freeze,
18
+ formatter: proc { |v| "%#{escape_wildcards(v.downcase)}%" },
19
+ case_insensitive: true
20
+ }
21
+ ],
22
+ ['not_i_cont'.freeze, {
23
+ arel_predicate: 'does_not_match'.freeze,
24
+ formatter: proc { |v| "%#{escape_wildcards(v.downcase)}%" },
25
+ case_insensitive: true
26
+ }
27
+ ],
16
28
  ['start'.freeze, {
17
29
  arel_predicate: 'matches'.freeze,
18
30
  formatter: proc { |v| "#{escape_wildcards(v)}%" }
@@ -85,7 +97,7 @@ module Ransack
85
97
  arel_predicate: proc { |v| v ? EQ : NOT_EQ },
86
98
  compounds: false,
87
99
  type: :boolean,
88
- validator: proc { |v| BOOLEAN_VALUES.include?(v)},
100
+ validator: proc { |v| BOOLEAN_VALUES.include?(v) },
89
101
  formatter: proc { |v| nil }
90
102
  }
91
103
  ],
@@ -102,8 +114,11 @@ module Ransack
102
114
  # replace % \ to \% \\
103
115
  def escape_wildcards(unescaped)
104
116
  case ActiveRecord::Base.connection.adapter_name
105
- when "Mysql2".freeze, "PostgreSQL".freeze
106
- # Necessary for PostgreSQL and MySQL
117
+ when "Mysql2".freeze
118
+ # Necessary for MySQL
119
+ unescaped.to_s.gsub(/([\\%_])/, '\\\\\\1')
120
+ when "PostgreSQL".freeze
121
+ # Necessary for PostgreSQL
107
122
  unescaped.to_s.gsub(/([\\%_.])/, '\\\\\\1')
108
123
  else
109
124
  unescaped
@@ -28,14 +28,10 @@ module Ransack
28
28
  @join_type = options[:join_type] || Polyamorous::OuterJoin
29
29
  @search_key = options[:search_key] || Ransack.options[:search_key]
30
30
  @associations_pot = {}
31
+ @tables_pot = {}
31
32
  @lock_associations = []
32
33
 
33
- if ::ActiveRecord::VERSION::STRING >= Constants::RAILS_5_2
34
- @base = @join_dependency.instance_variable_get(:@join_root)
35
- else
36
- @base = @join_dependency.join_root
37
- @engine = @base.base_klass.arel_engine
38
- end
34
+ @base = @join_dependency.instance_variable_get(:@join_root)
39
35
  end
40
36
 
41
37
  def bind_pair_for(key)
@@ -30,7 +30,7 @@ module Ransack
30
30
  def format_predicate(attribute)
31
31
  arel_pred = arel_predicate_for_attribute(attribute)
32
32
  arel_values = formatted_values_for_attribute(attribute)
33
- predicate = attribute.attr.public_send(arel_pred, arel_values)
33
+ predicate = attr_value_for_attribute(attribute).public_send(arel_pred, arel_values)
34
34
 
35
35
  if in_predicate?(predicate)
36
36
  predicate.right = predicate.right.map do |pr|
@@ -43,16 +43,24 @@ module Ransack
43
43
 
44
44
  def in_predicate?(predicate)
45
45
  return unless defined?(Arel::Nodes::Casted)
46
- predicate.class == Arel::Nodes::In
46
+ predicate.class == Arel::Nodes::In || predicate.class == Arel::Nodes::NotIn
47
47
  end
48
48
 
49
49
  def casted_array?(predicate)
50
- predicate.respond_to?(:val) && predicate.val.is_a?(Array)
50
+ value_from(predicate).is_a?(Array) && predicate.is_a?(Arel::Nodes::Casted)
51
+ end
52
+
53
+ def value_from(predicate)
54
+ if predicate.respond_to?(:value)
55
+ predicate.value # Rails 6.1
56
+ elsif predicate.respond_to?(:val)
57
+ predicate.val # Rails 5.2, 6.0
58
+ end
51
59
  end
52
60
 
53
61
  def format_values_for(predicate)
54
- predicate.val.map do |value|
55
- value.is_a?(String) ? Arel::Nodes.build_quoted(value) : value
62
+ value_from(predicate).map do |val|
63
+ val.is_a?(String) ? Arel::Nodes.build_quoted(val) : val
56
64
  end
57
65
  end
58
66
 
@@ -2,7 +2,7 @@ module Ransack
2
2
  module Translate
3
3
 
4
4
  def self.i18n_key(klass)
5
- klass.model_name.i18n_key.to_s.freeze
5
+ klass.model_name.i18n_key
6
6
  end
7
7
  end
8
8
  end
@@ -33,7 +33,9 @@ module Ransack
33
33
  :up_arrow => '&#9660;'.freeze,
34
34
  :down_arrow => '&#9650;'.freeze,
35
35
  :default_arrow => nil,
36
- :sanitize_scope_args => true
36
+ :sanitize_scope_args => true,
37
+ :postgres_fields_sort_option => nil,
38
+ :strip_whitespace => true
37
39
  }
38
40
 
39
41
  def configure
@@ -141,6 +143,21 @@ module Ransack
141
143
  self.options[:sanitize_scope_args] = boolean
142
144
  end
143
145
 
146
+ # The `NULLS FIRST` and `NULLS LAST` options can be used to determine
147
+ # whether nulls appear before or after non-null values in the sort ordering.
148
+ #
149
+ # User may want to configure it like this:
150
+ #
151
+ # Ransack.configure do |c|
152
+ # c.postgres_fields_sort_option = :nulls_first # or e.g. :nulls_always_last
153
+ # end
154
+ #
155
+ # See this feature: https://www.postgresql.org/docs/13/queries-order.html
156
+ #
157
+ def postgres_fields_sort_option=(setting)
158
+ self.options[:postgres_fields_sort_option] = setting
159
+ end
160
+
144
161
  # By default, Ransack displays sort order indicator arrows in sort links.
145
162
  # The default may be globally overridden in an initializer file like
146
163
  # `config/initializers/ransack.rb` as follows:
@@ -154,6 +171,19 @@ module Ransack
154
171
  self.options[:hide_sort_order_indicators] = boolean
155
172
  end
156
173
 
174
+ # By default, Ransack displays strips all whitespace when searching for a string.
175
+ # The default may be globally changed in an initializer file like
176
+ # `config/initializers/ransack.rb` as follows:
177
+ #
178
+ # Ransack.configure do |config|
179
+ # # Enable whitespace stripping for string searches
180
+ # config.strip_whitespace = true
181
+ # end
182
+ #
183
+ def strip_whitespace=(boolean)
184
+ self.options[:strip_whitespace] = boolean
185
+ end
186
+
157
187
  def arel_predicate_with_suffix(arel_predicate, suffix)
158
188
  if arel_predicate === Proc
159
189
  proc { |v| "#{arel_predicate.call(v)}#{suffix}" }
@@ -36,7 +36,7 @@ module Ransack
36
36
  'lt'.freeze, 'lteq'.freeze,
37
37
  'gt'.freeze, 'gteq'.freeze,
38
38
  'in'.freeze, 'not_in'.freeze
39
- ].freeze
39
+ ].freeze
40
40
  A_S_I = ['a'.freeze, 's'.freeze, 'i'.freeze].freeze
41
41
 
42
42
  EQ = 'eq'.freeze
@@ -45,12 +45,10 @@ module Ransack
45
45
  NOT_EQ_ALL = 'not_eq_all'.freeze
46
46
  CONT = 'cont'.freeze
47
47
 
48
- RAILS_5_1 = '5.1'.freeze
49
- RAILS_5_2 = '5.2'.freeze
50
- RAILS_5_2_0 = '5.2.0'.freeze
48
+ RAILS_6_0 = '6.0.0'.freeze
49
+ RAILS_6_1 = '6.1.0'.freeze
51
50
 
52
51
  RANSACK_SLASH_SEARCHES = 'ransack/searches'.freeze
53
52
  RANSACK_SLASH_SEARCHES_SLASH_SEARCH = 'ransack/searches/search'.freeze
54
53
  end
55
54
  end
56
-