ransack 2.3.0 → 4.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (159) 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/codeql.yml +72 -0
  5. data/.github/workflows/cronjob.yml +99 -0
  6. data/.github/workflows/deploy.yml +35 -0
  7. data/.github/workflows/rubocop.yml +20 -0
  8. data/.github/workflows/test-deploy.yml +29 -0
  9. data/.github/workflows/test.yml +131 -0
  10. data/.nojekyll +0 -0
  11. data/.rubocop.yml +50 -0
  12. data/CHANGELOG.md +263 -1
  13. data/CONTRIBUTING.md +51 -29
  14. data/Gemfile +24 -10
  15. data/README.md +49 -917
  16. data/bug_report_templates/test-ransack-scope-and-column-same-name.rb +78 -0
  17. data/bug_report_templates/test-ransacker-arel-present-predicate.rb +75 -0
  18. data/docs/.gitignore +19 -0
  19. data/docs/.nojekyll +0 -0
  20. data/docs/babel.config.js +3 -0
  21. data/docs/blog/2022-03-27-ransack-3.0.0.md +20 -0
  22. data/docs/docs/getting-started/_category_.json +4 -0
  23. data/docs/docs/getting-started/advanced-mode.md +46 -0
  24. data/docs/docs/getting-started/configuration.md +47 -0
  25. data/docs/docs/getting-started/search-matches.md +67 -0
  26. data/docs/docs/getting-started/simple-mode.md +288 -0
  27. data/docs/docs/getting-started/sorting.md +71 -0
  28. data/docs/docs/getting-started/using-predicates.md +282 -0
  29. data/docs/docs/going-further/_category_.json +4 -0
  30. data/docs/docs/going-further/acts-as-taggable-on.md +114 -0
  31. data/docs/docs/going-further/associations.md +70 -0
  32. data/docs/docs/going-further/custom-predicates.md +52 -0
  33. data/docs/docs/going-further/documentation.md +43 -0
  34. data/docs/docs/going-further/exporting-to-csv.md +49 -0
  35. data/docs/docs/going-further/external-guides.md +57 -0
  36. data/docs/docs/going-further/form-customisation.md +63 -0
  37. data/docs/docs/going-further/i18n.md +53 -0
  38. data/docs/docs/going-further/img/create_release.png +0 -0
  39. data/docs/docs/going-further/merging-searches.md +41 -0
  40. data/docs/docs/going-further/other-notes.md +428 -0
  41. data/docs/docs/going-further/polymorphic-search.md +46 -0
  42. data/docs/docs/going-further/ransackers.md +331 -0
  43. data/docs/docs/going-further/release_process.md +36 -0
  44. data/docs/docs/going-further/saving-queries.md +82 -0
  45. data/docs/docs/going-further/searching-postgres.md +57 -0
  46. data/docs/docs/going-further/wiki-contributors.md +82 -0
  47. data/docs/docs/intro.md +99 -0
  48. data/docs/docusaurus.config.js +120 -0
  49. data/docs/package.json +42 -0
  50. data/docs/sidebars.js +31 -0
  51. data/docs/src/components/HomepageFeatures/index.js +64 -0
  52. data/docs/src/components/HomepageFeatures/styles.module.css +11 -0
  53. data/docs/src/css/custom.css +39 -0
  54. data/docs/src/pages/index.module.css +23 -0
  55. data/docs/src/pages/markdown-page.md +7 -0
  56. data/docs/static/.nojekyll +0 -0
  57. data/docs/static/img/docusaurus.png +0 -0
  58. data/docs/static/img/favicon.ico +0 -0
  59. data/docs/static/img/logo.svg +1 -0
  60. data/docs/static/img/tutorial/docsVersionDropdown.png +0 -0
  61. data/docs/static/img/tutorial/localeDropdown.png +0 -0
  62. data/docs/static/img/undraw_docusaurus_mountain.svg +171 -0
  63. data/docs/static/img/undraw_docusaurus_react.svg +170 -0
  64. data/docs/static/img/undraw_docusaurus_tree.svg +40 -0
  65. data/docs/yarn.lock +8879 -0
  66. data/lib/polyamorous/activerecord_6.1_ruby_2/join_association.rb +70 -0
  67. data/{polyamorous/lib/polyamorous/activerecord_6.0_ruby_2 → lib/polyamorous/activerecord_6.1_ruby_2}/join_dependency.rb +33 -12
  68. data/lib/polyamorous/activerecord_6.1_ruby_2/reflection.rb +11 -0
  69. data/lib/polyamorous/activerecord_7.0_ruby_2/join_association.rb +1 -0
  70. data/lib/polyamorous/activerecord_7.0_ruby_2/join_dependency.rb +1 -0
  71. data/lib/polyamorous/activerecord_7.0_ruby_2/reflection.rb +1 -0
  72. data/lib/polyamorous/activerecord_7.1_ruby_2/join_association.rb +1 -0
  73. data/lib/polyamorous/activerecord_7.1_ruby_2/join_dependency.rb +1 -0
  74. data/lib/polyamorous/activerecord_7.1_ruby_2/reflection.rb +1 -0
  75. data/{polyamorous/lib → lib/polyamorous}/polyamorous.rb +3 -8
  76. data/lib/ransack/adapters/active_record/base.rb +83 -10
  77. data/lib/ransack/adapters/active_record/context.rb +61 -116
  78. data/lib/ransack/configuration.rb +53 -10
  79. data/lib/ransack/constants.rb +126 -7
  80. data/lib/ransack/context.rb +34 -5
  81. data/lib/ransack/helpers/form_builder.rb +11 -17
  82. data/lib/ransack/helpers/form_helper.rb +14 -5
  83. data/lib/ransack/helpers.rb +1 -1
  84. data/lib/ransack/locale/sk.yml +70 -0
  85. data/lib/ransack/locale/sv.yml +70 -0
  86. data/lib/ransack/nodes/attribute.rb +3 -3
  87. data/lib/ransack/nodes/condition.rb +87 -8
  88. data/lib/ransack/nodes/grouping.rb +4 -4
  89. data/lib/ransack/nodes/node.rb +1 -1
  90. data/lib/ransack/nodes/sort.rb +3 -3
  91. data/lib/ransack/nodes/value.rb +3 -3
  92. data/lib/ransack/predicate.rb +3 -2
  93. data/lib/ransack/ransacker.rb +1 -1
  94. data/lib/ransack/search.rb +15 -7
  95. data/lib/ransack/translate.rb +6 -6
  96. data/lib/ransack/version.rb +1 -1
  97. data/lib/ransack/visitor.rb +38 -2
  98. data/lib/ransack.rb +6 -10
  99. data/ransack.gemspec +9 -24
  100. data/spec/blueprints/articles.rb +1 -1
  101. data/spec/blueprints/comments.rb +1 -1
  102. data/spec/blueprints/notes.rb +1 -1
  103. data/spec/blueprints/tags.rb +1 -1
  104. data/spec/console.rb +5 -5
  105. data/spec/helpers/polyamorous_helper.rb +2 -17
  106. data/spec/helpers/ransack_helper.rb +1 -1
  107. data/spec/polyamorous/activerecord_compatibility_spec.rb +15 -0
  108. data/spec/{ransack → polyamorous}/join_association_spec.rb +3 -1
  109. data/spec/{ransack → polyamorous}/join_dependency_spec.rb +0 -16
  110. data/spec/ransack/adapters/active_record/base_spec.rb +125 -16
  111. data/spec/ransack/adapters/active_record/context_spec.rb +19 -18
  112. data/spec/ransack/configuration_spec.rb +33 -9
  113. data/spec/ransack/helpers/form_builder_spec.rb +8 -8
  114. data/spec/ransack/helpers/form_helper_spec.rb +109 -20
  115. data/spec/ransack/nodes/condition_spec.rb +37 -0
  116. data/spec/ransack/nodes/grouping_spec.rb +2 -2
  117. data/spec/ransack/nodes/value_spec.rb +115 -0
  118. data/spec/ransack/predicate_spec.rb +75 -2
  119. data/spec/ransack/search_spec.rb +239 -38
  120. data/spec/ransack/translate_spec.rb +1 -1
  121. data/spec/spec_helper.rb +9 -5
  122. data/spec/support/schema.rb +111 -12
  123. metadata +105 -195
  124. data/.travis.yml +0 -49
  125. data/lib/ransack/adapters/active_record/ransack/constants.rb +0 -116
  126. data/lib/ransack/adapters/active_record/ransack/context.rb +0 -60
  127. data/lib/ransack/adapters/active_record/ransack/nodes/condition.rb +0 -61
  128. data/lib/ransack/adapters/active_record/ransack/translate.rb +0 -8
  129. data/lib/ransack/adapters/active_record/ransack/visitor.rb +0 -47
  130. data/lib/ransack/adapters.rb +0 -64
  131. data/lib/ransack/nodes.rb +0 -8
  132. data/polyamorous/lib/polyamorous/activerecord_5.0_ruby_2/join_association.rb +0 -2
  133. data/polyamorous/lib/polyamorous/activerecord_5.0_ruby_2/join_dependency.rb +0 -2
  134. data/polyamorous/lib/polyamorous/activerecord_5.1_ruby_2/join_association.rb +0 -31
  135. data/polyamorous/lib/polyamorous/activerecord_5.1_ruby_2/join_dependency.rb +0 -112
  136. data/polyamorous/lib/polyamorous/activerecord_5.2.0_ruby_2/join_association.rb +0 -31
  137. data/polyamorous/lib/polyamorous/activerecord_5.2.0_ruby_2/join_dependency.rb +0 -112
  138. data/polyamorous/lib/polyamorous/activerecord_5.2.0_ruby_2/reflection.rb +0 -12
  139. data/polyamorous/lib/polyamorous/activerecord_5.2.1_ruby_2/join_association.rb +0 -22
  140. data/polyamorous/lib/polyamorous/activerecord_5.2.1_ruby_2/join_dependency.rb +0 -81
  141. data/polyamorous/lib/polyamorous/activerecord_5.2.1_ruby_2/reflection.rb +0 -2
  142. data/polyamorous/lib/polyamorous/activerecord_6.0_ruby_2/join_association.rb +0 -2
  143. data/polyamorous/lib/polyamorous/activerecord_6.0_ruby_2/reflection.rb +0 -2
  144. data/polyamorous/lib/polyamorous/activerecord_6.1_ruby_2/join_association.rb +0 -2
  145. data/polyamorous/lib/polyamorous/activerecord_6.1_ruby_2/join_dependency.rb +0 -2
  146. data/polyamorous/lib/polyamorous/activerecord_6.1_ruby_2/reflection.rb +0 -2
  147. data/polyamorous/lib/polyamorous/version.rb +0 -3
  148. data/polyamorous/polyamorous.gemspec +0 -35
  149. /data/{logo → docs/static/logo}/ransack-h.png +0 -0
  150. /data/{logo → docs/static/logo}/ransack-h.svg +0 -0
  151. /data/{logo → docs/static/logo}/ransack-v.png +0 -0
  152. /data/{logo → docs/static/logo}/ransack-v.svg +0 -0
  153. /data/{logo → docs/static/logo}/ransack.png +0 -0
  154. /data/{logo → docs/static/logo}/ransack.svg +0 -0
  155. /data/{polyamorous/lib → lib}/polyamorous/join.rb +0 -0
  156. /data/{polyamorous/lib → lib}/polyamorous/swapping_reflection_class.rb +0 -0
  157. /data/{polyamorous/lib → lib}/polyamorous/tree_node.rb +0 -0
  158. /data/lib/ransack/{adapters/active_record.rb → active_record.rb} +0 -0
  159. /data/spec/{ransack → polyamorous}/join_spec.rb +0 -0
@@ -0,0 +1,70 @@
1
+ module Polyamorous
2
+ module JoinAssociationExtensions
3
+ include SwappingReflectionClass
4
+ def self.prepended(base)
5
+ base.class_eval { attr_reader :join_type }
6
+ end
7
+
8
+ def initialize(reflection, children, polymorphic_class = nil, join_type = Arel::Nodes::InnerJoin)
9
+ @join_type = join_type
10
+ if polymorphic_class && ::ActiveRecord::Base > polymorphic_class
11
+ swapping_reflection_klass(reflection, polymorphic_class) do |reflection|
12
+ super(reflection, children)
13
+ self.reflection.options[:polymorphic] = true
14
+ end
15
+ else
16
+ super(reflection, children)
17
+ end
18
+ end
19
+
20
+ # Same as #join_constraints, but instead of constructing tables from the
21
+ # given block, uses the ones passed
22
+ def join_constraints_with_tables(foreign_table, foreign_klass, join_type, alias_tracker, tables)
23
+ joins = []
24
+ chain = []
25
+
26
+ reflection.chain.each.with_index do |reflection, i|
27
+ table = tables[i]
28
+
29
+ @table ||= table
30
+ chain << [reflection, table]
31
+ end
32
+
33
+ # The chain starts with the target table, but we want to end with it here (makes
34
+ # more sense in this context), so we reverse
35
+ chain.reverse_each do |reflection, table|
36
+ klass = reflection.klass
37
+
38
+ join_scope = reflection.join_scope(table, foreign_table, foreign_klass)
39
+
40
+ unless join_scope.references_values.empty?
41
+ join_dependency = join_scope.construct_join_dependency(
42
+ join_scope.eager_load_values | join_scope.includes_values, Arel::Nodes::OuterJoin
43
+ )
44
+ join_scope.joins!(join_dependency)
45
+ end
46
+
47
+ arel = join_scope.arel(alias_tracker.aliases)
48
+ nodes = arel.constraints.first
49
+
50
+ if nodes.is_a?(Arel::Nodes::And)
51
+ others = nodes.children.extract! do |node|
52
+ !Arel.fetch_attribute(node) { |attr| attr.relation.name == table.name }
53
+ end
54
+ end
55
+
56
+ joins << table.create_join(table, table.create_on(nodes), join_type)
57
+
58
+ if others && !others.empty?
59
+ joins.concat arel.join_sources
60
+ append_constraints(joins.last, others)
61
+ end
62
+
63
+ # The current table in this iteration becomes the foreign table in the next
64
+ foreign_table, foreign_klass = table, klass
65
+ end
66
+
67
+ joins
68
+ end
69
+ end
70
+ end
@@ -1,5 +1,3 @@
1
- # active_record_6.0_ruby_2/join_dependency.rb
2
-
3
1
  module Polyamorous
4
2
  module JoinDependencyExtensions
5
3
  # Replaces ActiveRecord::Associations::JoinDependency#build
@@ -29,14 +27,18 @@ module Polyamorous
29
27
  end
30
28
  end
31
29
 
32
- def join_constraints(joins_to_add, alias_tracker)
30
+ def join_constraints(joins_to_add, alias_tracker, references)
33
31
  @alias_tracker = alias_tracker
32
+ @joined_tables = {}
33
+ @references = {}
34
+
35
+ references.each do |table_name|
36
+ @references[table_name.to_sym] = table_name if table_name.is_a?(String)
37
+ end
34
38
 
35
- construct_tables!(join_root)
36
39
  joins = make_join_constraints(join_root, join_type)
37
40
 
38
41
  joins.concat joins_to_add.flat_map { |oj|
39
- construct_tables!(oj.join_root)
40
42
  if join_root.match?(oj.join_root) && join_root.table.name == oj.join_root.table.name
41
43
  walk join_root, oj.join_root, oj.join_type
42
44
  else
@@ -45,14 +47,33 @@ module Polyamorous
45
47
  }
46
48
  end
47
49
 
50
+ def construct_tables_for_association!(join_root, association)
51
+ tables = table_aliases_for(join_root, association)
52
+ association.table = tables.first
53
+ tables
54
+ end
55
+
48
56
  private
49
- def make_constraints(parent, child, join_type = Arel::Nodes::OuterJoin)
50
- foreign_table = parent.table
51
- foreign_klass = parent.base_klass
52
- join_type = child.join_type || join_type if join_type == Arel::Nodes::InnerJoin
53
- joins = child.join_constraints(foreign_table, foreign_klass, join_type, alias_tracker)
54
- joins.concat child.children.flat_map { |c| make_constraints(child, c, join_type) }
55
- end
57
+
58
+ def table_aliases_for(parent, node)
59
+ @joined_tables ||= {}
60
+ node.reflection.chain.map { |reflection|
61
+ table, terminated = @joined_tables[reflection]
62
+ root = reflection == node.reflection
63
+
64
+ if table && (!root || !terminated)
65
+ @joined_tables[reflection] = [table, true] if root
66
+ table
67
+ else
68
+ table = alias_tracker.aliased_table_for(reflection.klass.arel_table) do
69
+ name = reflection.alias_candidate(parent.table_name)
70
+ root ? name : "#{name}_join"
71
+ end
72
+ @joined_tables[reflection] ||= [table, root] if join_type == Arel::Nodes::OuterJoin
73
+ table
74
+ end
75
+ }
76
+ end
56
77
 
57
78
  module ClassMethods
58
79
  # Prepended before ActiveRecord::Associations::JoinDependency#walk_tree
@@ -0,0 +1,11 @@
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'
@@ -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
- ar_version = ::ActiveRecord::VERSION::STRING[0,5] if ar_version >= "5.2" && ::ActiveRecord.version < ::Gem::Version.new("6.0")
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
- if ar_version >= "5.2.0"
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 ||= if Ransack::SUPPORTS_ATTRIBUTE_ALIAS
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 ||= reflect_on_all_associations.map { |a| a.name.to_s }
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 determin if args should be converted.
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,26 +1,20 @@
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
19
12
 
20
13
  def type_for(attr)
21
14
  return nil unless attr && attr.valid?
15
+ relation = attr.arel_attribute.relation
22
16
  name = attr.arel_attribute.name.to_s
23
- table = attr.arel_attribute.relation.table_name
17
+ table = relation.respond_to?(:table_name) ? relation.table_name : relation.name
24
18
  schema_cache = self.klass.connection.schema_cache
25
19
  unless schema_cache.send(:data_source_exists?, table)
26
20
  raise "No table named #{table} exists."
@@ -49,6 +43,17 @@ module Ransack
49
43
  if scope_or_sort.is_a?(Symbol)
50
44
  relation = relation.send(scope_or_sort)
51
45
  else
46
+ case Ransack.options[:postgres_fields_sort_option]
47
+ when :nulls_first
48
+ scope_or_sort = scope_or_sort.direction == :asc ? Arel.sql("#{scope_or_sort.to_sql} NULLS FIRST") : Arel.sql("#{scope_or_sort.to_sql} NULLS LAST")
49
+ when :nulls_last
50
+ scope_or_sort = scope_or_sort.direction == :asc ? Arel.sql("#{scope_or_sort.to_sql} NULLS LAST") : Arel.sql("#{scope_or_sort.to_sql} NULLS FIRST")
51
+ when :nulls_always_first
52
+ scope_or_sort = Arel.sql("#{scope_or_sort.to_sql} NULLS FIRST")
53
+ when :nulls_always_last
54
+ scope_or_sort = Arel.sql("#{scope_or_sort.to_sql} NULLS LAST")
55
+ end
56
+
52
57
  relation = relation.order(scope_or_sort)
53
58
  end
54
59
  end
@@ -104,26 +109,15 @@ module Ransack
104
109
  # JoinDependency to track table aliases.
105
110
  #
106
111
  def join_sources
107
- base, joins =
108
- if ::ActiveRecord::VERSION::STRING > Constants::RAILS_5_2_0
112
+ base, joins = begin
109
113
  alias_tracker = ::ActiveRecord::Associations::AliasTracker.create(self.klass.connection, @object.table.name, [])
110
- constraints = if ::Gem::Version.new(::ActiveRecord::VERSION::STRING) >= ::Gem::Version.new(Constants::RAILS_6_0)
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
114
+ constraints = @join_dependency.join_constraints(@object.joins_values, alias_tracker, @object.references_values)
115
115
 
116
116
  [
117
117
  Arel::SelectManager.new(@object.table),
118
118
  constraints
119
119
  ]
120
- else
121
- [
122
- Arel::SelectManager.new(@object.table),
123
- @join_dependency.join_constraints(@object.joins_values, @join_type)
124
- ]
125
120
  end
126
- joins = joins.collect(&:joins).flatten if ::ActiveRecord::VERSION::STRING < Constants::RAILS_5_2
127
121
  joins.each do |aliased_join|
128
122
  base.from(aliased_join)
129
123
  end
@@ -144,6 +138,7 @@ module Ransack
144
138
  stashed.eql?(association)
145
139
  }
146
140
  @object.joins_values.delete_if { |jd|
141
+ jd.instance_variables.include?(:@join_root) &&
147
142
  jd.instance_variable_get(:@join_root).children.map(&:object_id) == [association.object_id]
148
143
  }
149
144
  end
@@ -186,16 +181,31 @@ module Ransack
186
181
  private
187
182
 
188
183
  def extract_correlated_key(join_root)
189
- correlated_key = join_root.right.expr.left
190
-
191
- if correlated_key.is_a? Arel::Nodes::And
192
- correlated_key = correlated_key.left.left
193
- elsif correlated_key.is_a? Arel::Nodes::Equality
194
- correlated_key = correlated_key.left
195
- elsif correlated_key.is_a? Arel::Nodes::Grouping
196
- correlated_key = join_root.right.expr.right.left
184
+ case join_root
185
+ when Arel::Nodes::OuterJoin
186
+ # one of join_root.right/join_root.left is expected to be Arel::Nodes::On
187
+ if join_root.right.is_a?(Arel::Nodes::On)
188
+ extract_correlated_key(join_root.right.expr)
189
+ elsif join_root.left.is_a?(Arel::Nodes::On)
190
+ extract_correlated_key(join_root.left.expr)
191
+ else
192
+ raise 'Ransack encountered an unexpected arel structure'
193
+ end
194
+ when Arel::Nodes::Equality
195
+ pk = primary_key
196
+ if join_root.left == pk
197
+ join_root.right
198
+ elsif join_root.right == pk
199
+ join_root.left
200
+ else
201
+ nil
202
+ end
203
+ when Arel::Nodes::And
204
+ extract_correlated_key(join_root.left) || extract_correlated_key(join_root.right)
197
205
  else
198
- correlated_key
206
+ # eg parent was Arel::Nodes::And and the evaluated side was one of
207
+ # Arel::Nodes::Grouping or MultiTenant::TenantEnforcementClause
208
+ nil
199
209
  end
200
210
  end
201
211
 
@@ -268,28 +278,11 @@ module Ransack
268
278
 
269
279
  join_list = join_nodes + convert_join_strings_to_ast(relation.table, string_joins)
270
280
 
271
- if ::ActiveRecord::VERSION::STRING < Constants::RAILS_5_2_0
272
- join_dependency = Polyamorous::JoinDependency.new(relation.klass, association_joins, join_list)
273
- join_nodes.each do |join|
274
- join_dependency.send(:alias_tracker).aliases[join.left.name.downcase] = 1
275
- end
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
281
+ alias_tracker = ::ActiveRecord::Associations::AliasTracker.create(self.klass.connection, relation.table.name, join_list)
282
+ join_dependency = Polyamorous::JoinDependency.new(relation.klass, relation.table, association_joins, Arel::Nodes::OuterJoin)
283
+ join_dependency.instance_variable_set(:@alias_tracker, alias_tracker)
284
+ join_nodes.each do |join|
285
+ join_dependency.send(:alias_tracker).aliases[join.left.name.downcase] = 1
293
286
  end
294
287
  join_dependency
295
288
  end
@@ -313,38 +306,13 @@ module Ransack
313
306
  end
314
307
 
315
308
  def build_association(name, parent = @base, klass = nil)
316
- if ::Gem::Version.new(::ActiveRecord::VERSION::STRING) >= ::Gem::Version.new(Constants::RAILS_6_0)
317
- jd = Polyamorous::JoinDependency.new(
318
- parent.base_klass,
319
- parent.table,
320
- Polyamorous::Join.new(name, @join_type, klass),
321
- @join_type
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
309
+ jd = Polyamorous::JoinDependency.new(
310
+ parent.base_klass,
311
+ parent.table,
312
+ Polyamorous::Join.new(name, @join_type, klass),
313
+ @join_type
314
+ )
315
+ found_association = jd.instance_variable_get(:@join_root).children.last
348
316
 
349
317
  @associations_pot[found_association] = parent
350
318
 
@@ -353,11 +321,7 @@ module Ransack
353
321
  @join_dependency.instance_variable_get(:@join_root).children.push found_association
354
322
 
355
323
  # Builds the arel nodes properly for this association
356
- if ::ActiveRecord::VERSION::STRING > Constants::RAILS_5_2_0
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
324
+ @tables_pot[found_association] = @join_dependency.construct_tables_for_association!(jd.instance_variable_get(:@join_root), found_association)
361
325
 
362
326
  # Leverage the stashed association functionality in AR
363
327
  @object = @object.joins(jd)
@@ -367,32 +331,13 @@ module Ransack
367
331
  def extract_joins(association)
368
332
  parent = @join_dependency.instance_variable_get(:@join_root)
369
333
  reflection = association.reflection
370
- join_constraints = if ::ActiveRecord::VERSION::STRING < Constants::RAILS_5_1
371
- association.join_constraints(
372
- parent.table,
373
- parent.base_klass,
374
- association,
375
- Arel::Nodes::OuterJoin,
376
- association.tables,
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
334
+ join_constraints = association.join_constraints_with_tables(
335
+ parent.table,
336
+ parent.base_klass,
337
+ Arel::Nodes::OuterJoin,
338
+ @join_dependency.instance_variable_get(:@alias_tracker),
339
+ @tables_pot[association]
340
+ )
396
341
  join_constraints.to_a.flatten
397
342
  end
398
343
  end