ransack 1.8.4 → 3.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (184) hide show
  1. checksums.yaml +5 -5
  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/deploy.yml +35 -0
  6. data/.github/workflows/rubocop.yml +20 -0
  7. data/.github/workflows/test-deploy.yml +29 -0
  8. data/.github/workflows/test.yml +130 -0
  9. data/.gitignore +3 -0
  10. data/{lib/ransack/adapters/mongoid/3.2/.gitkeep → .nojekyll} +0 -0
  11. data/.rubocop.yml +44 -0
  12. data/CHANGELOG.md +352 -0
  13. data/CONTRIBUTING.md +25 -13
  14. data/Gemfile +26 -27
  15. data/README.md +65 -815
  16. data/Rakefile +1 -22
  17. data/bug_report_templates/test-ransack-scope-and-column-same-name.rb +78 -0
  18. data/bug_report_templates/test-ransacker-arel-present-predicate.rb +71 -0
  19. data/docs/.gitignore +19 -0
  20. data/docs/.nojekyll +0 -0
  21. data/docs/babel.config.js +3 -0
  22. data/docs/blog/2022-03-27-ransack-3.0.0.md +20 -0
  23. data/docs/docs/getting-started/_category_.json +4 -0
  24. data/docs/docs/getting-started/advanced-mode.md +46 -0
  25. data/docs/docs/getting-started/configuration.md +47 -0
  26. data/docs/docs/getting-started/search-matches.md +67 -0
  27. data/docs/docs/getting-started/simple-mode.md +284 -0
  28. data/docs/docs/getting-started/sorting.md +79 -0
  29. data/docs/docs/getting-started/using-predicates.md +282 -0
  30. data/docs/docs/going-further/_category_.json +4 -0
  31. data/docs/docs/going-further/acts-as-taggable-on.md +114 -0
  32. data/docs/docs/going-further/associations.md +70 -0
  33. data/docs/docs/going-further/custom-predicates.md +52 -0
  34. data/docs/docs/going-further/documentation.md +43 -0
  35. data/docs/docs/going-further/exporting-to-csv.md +49 -0
  36. data/docs/docs/going-further/external-guides.md +57 -0
  37. data/docs/docs/going-further/form-customisation.md +63 -0
  38. data/docs/docs/going-further/i18n.md +53 -0
  39. data/docs/docs/going-further/img/create_release.png +0 -0
  40. data/docs/docs/going-further/merging-searches.md +41 -0
  41. data/docs/docs/going-further/other-notes.md +428 -0
  42. data/docs/docs/going-further/polymorphic-search.md +40 -0
  43. data/docs/docs/going-further/ransackers.md +331 -0
  44. data/docs/docs/going-further/release_process.md +36 -0
  45. data/docs/docs/going-further/saving-queries.md +82 -0
  46. data/docs/docs/going-further/searching-postgres.md +57 -0
  47. data/docs/docs/going-further/wiki-contributors.md +82 -0
  48. data/docs/docs/intro.md +99 -0
  49. data/docs/docusaurus.config.js +120 -0
  50. data/docs/package.json +38 -0
  51. data/docs/sidebars.js +31 -0
  52. data/docs/src/components/HomepageFeatures/index.js +64 -0
  53. data/docs/src/components/HomepageFeatures/styles.module.css +11 -0
  54. data/docs/src/css/custom.css +39 -0
  55. data/docs/src/pages/index.module.css +23 -0
  56. data/docs/src/pages/markdown-page.md +7 -0
  57. data/docs/static/.nojekyll +0 -0
  58. data/docs/static/img/docusaurus.png +0 -0
  59. data/docs/static/img/favicon.ico +0 -0
  60. data/docs/static/img/logo.svg +1 -0
  61. data/docs/static/img/tutorial/docsVersionDropdown.png +0 -0
  62. data/docs/static/img/tutorial/localeDropdown.png +0 -0
  63. data/docs/static/img/undraw_docusaurus_mountain.svg +171 -0
  64. data/docs/static/img/undraw_docusaurus_react.svg +170 -0
  65. data/docs/static/img/undraw_docusaurus_tree.svg +40 -0
  66. data/docs/static/logo/ransack-h.png +0 -0
  67. data/docs/static/logo/ransack-h.svg +34 -0
  68. data/docs/static/logo/ransack-v.png +0 -0
  69. data/docs/static/logo/ransack-v.svg +34 -0
  70. data/docs/static/logo/ransack.png +0 -0
  71. data/docs/static/logo/ransack.svg +21 -0
  72. data/docs/yarn.lock +8436 -0
  73. data/lib/polyamorous/activerecord_6.1_ruby_2/join_association.rb +70 -0
  74. data/lib/polyamorous/activerecord_6.1_ruby_2/join_dependency.rb +92 -0
  75. data/lib/polyamorous/activerecord_6.1_ruby_2/reflection.rb +11 -0
  76. data/lib/polyamorous/activerecord_7.0_ruby_2/join_association.rb +1 -0
  77. data/lib/polyamorous/activerecord_7.0_ruby_2/join_dependency.rb +1 -0
  78. data/lib/polyamorous/activerecord_7.0_ruby_2/reflection.rb +1 -0
  79. data/lib/polyamorous/activerecord_7.1_ruby_2/join_association.rb +1 -0
  80. data/lib/polyamorous/activerecord_7.1_ruby_2/join_dependency.rb +1 -0
  81. data/lib/polyamorous/activerecord_7.1_ruby_2/reflection.rb +1 -0
  82. data/lib/polyamorous/join.rb +70 -0
  83. data/lib/polyamorous/polyamorous.rb +24 -0
  84. data/lib/polyamorous/swapping_reflection_class.rb +11 -0
  85. data/lib/polyamorous/tree_node.rb +7 -0
  86. data/lib/polyamorous.rb +1 -0
  87. data/lib/ransack/adapters/active_record/base.rb +14 -3
  88. data/lib/ransack/adapters/active_record/context.rb +140 -196
  89. data/lib/ransack/adapters/active_record/ransack/constants.rb +19 -4
  90. data/lib/ransack/adapters/active_record/ransack/context.rb +9 -19
  91. data/lib/ransack/adapters/active_record/ransack/nodes/condition.rb +7 -7
  92. data/lib/ransack/adapters/active_record/ransack/translate.rb +1 -5
  93. data/lib/ransack/adapters/active_record/ransack/visitor.rb +23 -0
  94. data/lib/ransack/adapters/active_record.rb +0 -9
  95. data/lib/ransack/adapters.rb +2 -0
  96. data/lib/ransack/configuration.rb +52 -2
  97. data/lib/ransack/constants.rb +1 -5
  98. data/lib/ransack/context.rb +29 -24
  99. data/lib/ransack/helpers/form_builder.rb +12 -6
  100. data/lib/ransack/helpers/form_helper.rb +11 -3
  101. data/lib/ransack/helpers.rb +1 -1
  102. data/lib/ransack/locale/ar.yml +70 -0
  103. data/lib/ransack/locale/az.yml +70 -0
  104. data/lib/ransack/locale/bg.yml +70 -0
  105. data/lib/ransack/locale/ca.yml +70 -0
  106. data/lib/ransack/locale/el.yml +70 -0
  107. data/lib/ransack/locale/es.yml +22 -22
  108. data/lib/ransack/locale/fa.yml +70 -0
  109. data/lib/ransack/locale/fi.yml +71 -0
  110. data/lib/ransack/locale/nl.yml +4 -4
  111. data/lib/ransack/locale/ru.yml +70 -0
  112. data/lib/ransack/locale/sk.yml +70 -0
  113. data/lib/ransack/locale/sv.yml +70 -0
  114. data/lib/ransack/locale/tr.yml +70 -0
  115. data/lib/ransack/locale/zh-CN.yml +12 -12
  116. data/lib/ransack/nodes/attribute.rb +2 -2
  117. data/lib/ransack/nodes/condition.rb +7 -1
  118. data/lib/ransack/nodes/grouping.rb +3 -8
  119. data/lib/ransack/nodes/sort.rb +3 -3
  120. data/lib/ransack/nodes/value.rb +3 -3
  121. data/lib/ransack/predicate.rb +13 -20
  122. data/lib/ransack/search.rb +7 -4
  123. data/lib/ransack/translate.rb +115 -115
  124. data/lib/ransack/version.rb +1 -1
  125. data/lib/ransack/visitor.rb +1 -12
  126. data/lib/ransack.rb +7 -5
  127. data/ransack.gemspec +9 -25
  128. data/spec/blueprints/articles.rb +1 -1
  129. data/spec/blueprints/comments.rb +1 -1
  130. data/spec/blueprints/notes.rb +1 -1
  131. data/spec/blueprints/tags.rb +1 -1
  132. data/spec/console.rb +5 -5
  133. data/spec/helpers/polyamorous_helper.rb +13 -0
  134. data/spec/helpers/ransack_helper.rb +1 -1
  135. data/spec/polyamorous/activerecord_compatibility_spec.rb +15 -0
  136. data/spec/polyamorous/join_association_spec.rb +30 -0
  137. data/spec/polyamorous/join_dependency_spec.rb +81 -0
  138. data/spec/polyamorous/join_spec.rb +19 -0
  139. data/spec/ransack/adapters/active_record/base_spec.rb +105 -11
  140. data/spec/ransack/adapters/active_record/context_spec.rb +63 -24
  141. data/spec/ransack/configuration_spec.rb +24 -0
  142. data/spec/ransack/helpers/form_builder_spec.rb +3 -15
  143. data/spec/ransack/helpers/form_helper_spec.rb +135 -168
  144. data/spec/ransack/nodes/condition_spec.rb +13 -0
  145. data/spec/ransack/nodes/grouping_spec.rb +2 -2
  146. data/spec/ransack/nodes/value_spec.rb +115 -0
  147. data/spec/ransack/predicate_spec.rb +54 -2
  148. data/spec/ransack/search_spec.rb +266 -36
  149. data/spec/spec_helper.rb +14 -5
  150. data/spec/support/schema.rb +99 -21
  151. metadata +117 -187
  152. data/.travis.yml +0 -86
  153. data/lib/ransack/adapters/active_record/3.0/compat.rb +0 -179
  154. data/lib/ransack/adapters/active_record/3.0/context.rb +0 -203
  155. data/lib/ransack/adapters/active_record/3.1/context.rb +0 -212
  156. data/lib/ransack/adapters/active_record/3.2/context.rb +0 -44
  157. data/lib/ransack/adapters/active_record/compat.rb +0 -14
  158. data/lib/ransack/adapters/mongoid/attributes/attribute.rb +0 -37
  159. data/lib/ransack/adapters/mongoid/attributes/order_predications.rb +0 -17
  160. data/lib/ransack/adapters/mongoid/attributes/predications.rb +0 -141
  161. data/lib/ransack/adapters/mongoid/base.rb +0 -134
  162. data/lib/ransack/adapters/mongoid/context.rb +0 -212
  163. data/lib/ransack/adapters/mongoid/inquiry_hash.rb +0 -23
  164. data/lib/ransack/adapters/mongoid/ransack/constants.rb +0 -88
  165. data/lib/ransack/adapters/mongoid/ransack/context.rb +0 -60
  166. data/lib/ransack/adapters/mongoid/ransack/nodes/condition.rb +0 -27
  167. data/lib/ransack/adapters/mongoid/ransack/translate.rb +0 -13
  168. data/lib/ransack/adapters/mongoid/ransack/visitor.rb +0 -24
  169. data/lib/ransack/adapters/mongoid/table.rb +0 -35
  170. data/lib/ransack/adapters/mongoid.rb +0 -15
  171. data/spec/mongoid/adapters/mongoid/base_spec.rb +0 -314
  172. data/spec/mongoid/adapters/mongoid/context_spec.rb +0 -56
  173. data/spec/mongoid/configuration_spec.rb +0 -162
  174. data/spec/mongoid/dependencies_spec.rb +0 -8
  175. data/spec/mongoid/helpers/ransack_helper.rb +0 -11
  176. data/spec/mongoid/nodes/condition_spec.rb +0 -49
  177. data/spec/mongoid/nodes/grouping_spec.rb +0 -13
  178. data/spec/mongoid/predicate_spec.rb +0 -155
  179. data/spec/mongoid/search_spec.rb +0 -445
  180. data/spec/mongoid/support/mongoid.yml +0 -11
  181. data/spec/mongoid/support/schema.rb +0 -135
  182. data/spec/mongoid/translate_spec.rb +0 -14
  183. data/spec/mongoid_spec_helper.rb +0 -63
  184. data/spec/ransack/dependencies_spec.rb +0 -12
@@ -1,22 +1,11 @@
1
1
  require 'ransack/context'
2
- require 'ransack/adapters/active_record/compat'
3
- require 'polyamorous'
2
+ require 'polyamorous/polyamorous'
4
3
 
5
4
  module Ransack
6
5
  module Adapters
7
6
  module ActiveRecord
8
7
  class Context < ::Ransack::Context
9
8
 
10
- # Because the AR::Associations namespace is insane
11
- if defined? ::ActiveRecord::Associations::JoinDependency
12
- JoinDependency = ::ActiveRecord::Associations::JoinDependency
13
- end
14
-
15
- def initialize(object, options = {})
16
- super
17
- @arel_visitor = @engine.connection.visitor
18
- end
19
-
20
9
  def relation_for(object)
21
10
  object.all
22
11
  end
@@ -25,19 +14,50 @@ module Ransack
25
14
  return nil unless attr && attr.valid?
26
15
  name = attr.arel_attribute.name.to_s
27
16
  table = attr.arel_attribute.relation.table_name
28
- schema_cache = @engine.connection.schema_cache
29
- unless schema_cache.send(database_table_exists?, table)
17
+ schema_cache = self.klass.connection.schema_cache
18
+ unless schema_cache.send(:data_source_exists?, table)
30
19
  raise "No table named #{table} exists."
31
20
  end
32
- schema_cache.columns_hash(table)[name].type
21
+ attr.klass.columns.find { |column| column.name == name }.type
33
22
  end
34
23
 
35
24
  def evaluate(search, opts = {})
36
25
  viz = Visitor.new
37
26
  relation = @object.where(viz.accept(search.base))
27
+
38
28
  if search.sorts.any?
39
- relation = relation.except(:order).reorder(viz.accept(search.sorts))
29
+ relation = relation.except(:order)
30
+ # Rather than applying all of the search's sorts in one fell swoop,
31
+ # as the original implementation does, we apply one at a time.
32
+ #
33
+ # If the sort (returned by the Visitor above) is a symbol, we know
34
+ # that it represents a scope on the model and we can apply that
35
+ # scope.
36
+ #
37
+ # Otherwise, we fall back to the applying the sort with the "order"
38
+ # method as the original implementation did. Actually the original
39
+ # implementation used "reorder," which was overkill since we already
40
+ # have a clean slate after "relation.except(:order)" above.
41
+ viz.accept(search.sorts).each do |scope_or_sort|
42
+ if scope_or_sort.is_a?(Symbol)
43
+ relation = relation.send(scope_or_sort)
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
+
56
+ relation = relation.order(scope_or_sort)
57
+ end
58
+ end
40
59
  end
60
+
41
61
  opts[:distinct] ? relation.distinct : relation
42
62
  end
43
63
 
@@ -45,7 +65,7 @@ module Ransack
45
65
  exists = false
46
66
  if ransackable_attribute?(str, klass)
47
67
  exists = true
48
- elsif (segments = str.split(/_/)).size > 1
68
+ elsif (segments = str.split(Constants::UNDERSCORE)).size > 1
49
69
  remainder = []
50
70
  found_assoc = nil
51
71
  while !found_assoc && remainder.unshift(segments.pop) &&
@@ -80,84 +100,46 @@ module Ransack
80
100
  end
81
101
  end
82
102
 
83
- if ::ActiveRecord::VERSION::STRING >= Constants::RAILS_4_1
84
-
85
- def join_associations
86
- raise NotImplementedError,
87
- "ActiveRecord 4.1 and later does not use join_associations. Use join_sources."
88
- end
89
-
90
- # All dependent Arel::Join nodes used in the search query.
91
- #
92
- # This could otherwise be done as `@object.arel.join_sources`, except
93
- # that ActiveRecord's build_joins sets up its own JoinDependency.
94
- # This extracts what we need to access the joins using our existing
95
- # JoinDependency to track table aliases.
96
- #
97
- def join_sources
98
- base, joins =
99
- if ::ActiveRecord::VERSION::MAJOR >= 5
100
- [
101
- Arel::SelectManager.new(@object.table),
102
- @join_dependency.join_constraints(@object.joins_values, @join_type)
103
- ]
104
- else
105
- [
106
- Arel::SelectManager.new(@object.engine, @object.table),
107
- @join_dependency.join_constraints(@object.joins_values)
108
- ]
109
- end
110
- joins.each do |aliased_join|
111
- base.from(aliased_join)
112
- end
113
- base.join_sources
114
- end
115
-
116
- else
117
-
118
- # All dependent JoinAssociation items used in the search query.
119
- #
120
- # Deprecated: this goes away in ActiveRecord 4.1. Use join_sources.
121
- #
122
- def join_associations
123
- @join_dependency.join_associations
103
+ # All dependent Arel::Join nodes used in the search query.
104
+ #
105
+ # This could otherwise be done as `@object.arel.join_sources`, except
106
+ # that ActiveRecord's build_joins sets up its own JoinDependency.
107
+ # This extracts what we need to access the joins using our existing
108
+ # JoinDependency to track table aliases.
109
+ #
110
+ def join_sources
111
+ base, joins = begin
112
+ alias_tracker = ::ActiveRecord::Associations::AliasTracker.create(self.klass.connection, @object.table.name, [])
113
+ constraints = @join_dependency.join_constraints(@object.joins_values, alias_tracker, @object.references_values)
114
+
115
+ [
116
+ Arel::SelectManager.new(@object.table),
117
+ constraints
118
+ ]
124
119
  end
125
-
126
- def join_sources
127
- base = Arel::SelectManager.new(@object.engine, @object.table)
128
- joins = @object.joins_values
129
- joins.each do |assoc|
130
- assoc.join_to(base)
131
- end
132
- base.join_sources
120
+ joins.each do |aliased_join|
121
+ base.from(aliased_join)
133
122
  end
134
-
123
+ base.join_sources
135
124
  end
136
125
 
137
126
  def alias_tracker
138
- @join_dependency.alias_tracker
127
+ @join_dependency.send(:alias_tracker)
139
128
  end
140
129
 
141
130
  def lock_association(association)
142
131
  @lock_associations << association
143
132
  end
144
133
 
145
- if ::ActiveRecord::VERSION::STRING >= Constants::RAILS_4_1
146
- def remove_association(association)
147
- return if @lock_associations.include?(association)
148
- @join_dependency.join_root.children.delete_if { |stashed|
149
- stashed.eql?(association)
150
- }
151
- @object.joins_values.delete_if { |jd|
152
- jd.join_root.children.map(&:object_id) == [association.object_id]
153
- }
154
- end
155
- else
156
- def remove_association(association)
157
- return if @lock_associations.include?(association)
158
- @join_dependency.join_parts.delete(association)
159
- @object.joins_values.delete(association)
160
- end
134
+ def remove_association(association)
135
+ return if @lock_associations.include?(association)
136
+ @join_dependency.instance_variable_get(:@join_root).children.delete_if { |stashed|
137
+ stashed.eql?(association)
138
+ }
139
+ @object.joins_values.delete_if { |jd|
140
+ jd.instance_variables.include?(:@join_root) &&
141
+ jd.instance_variable_get(:@join_root).children.map(&:object_id) == [association.object_id]
142
+ }
161
143
  end
162
144
 
163
145
  # Build an Arel subquery that selects keys for the top query,
@@ -181,8 +163,7 @@ module Ransack
181
163
  def build_correlated_subquery(association)
182
164
  join_constraints = extract_joins(association)
183
165
  join_root = join_constraints.shift
184
- join_table = join_root.left
185
- correlated_key = join_root.right.expr.left
166
+ correlated_key = extract_correlated_key(join_root)
186
167
  subquery = Arel::SelectManager.new(association.base_klass)
187
168
  subquery.from(join_root.left)
188
169
  subquery.project(correlated_key)
@@ -198,11 +179,32 @@ module Ransack
198
179
 
199
180
  private
200
181
 
201
- def database_table_exists?
202
- if ::ActiveRecord::VERSION::MAJOR >= 5
203
- :data_source_exists?
182
+ def extract_correlated_key(join_root)
183
+ case join_root
184
+ when Arel::Nodes::OuterJoin
185
+ # one of join_root.right/join_root.left is expected to be Arel::Nodes::On
186
+ if join_root.right.is_a?(Arel::Nodes::On)
187
+ extract_correlated_key(join_root.right.expr)
188
+ elsif join_root.left.is_a?(Arel::Nodes::On)
189
+ extract_correlated_key(join_root.left.expr)
190
+ else
191
+ raise 'Ransack encountered an unexpected arel structure'
192
+ end
193
+ when Arel::Nodes::Equality
194
+ pk = primary_key
195
+ if join_root.left == pk
196
+ join_root.right
197
+ elsif join_root.right == pk
198
+ join_root.left
199
+ else
200
+ nil
201
+ end
202
+ when Arel::Nodes::And
203
+ extract_correlated_key(join_root.left) || extract_correlated_key(join_root.right)
204
204
  else
205
- :table_exists?
205
+ # eg parent was Arel::Nodes::And and the evaluated side was one of
206
+ # Arel::Nodes::Grouping or MultiTenant::TenantEnforcementClause
207
+ nil
206
208
  end
207
209
  end
208
210
 
@@ -250,7 +252,9 @@ module Ransack
250
252
  # Checkout active_record/relation/query_methods.rb +build_joins+ for
251
253
  # reference. Lots of duplicated code maybe we can avoid it
252
254
  def build_joins(relation)
253
- buckets = relation.joins_values.group_by do |join|
255
+ buckets = relation.joins_values + relation.left_outer_joins_values
256
+
257
+ buckets = buckets.group_by do |join|
254
258
  case join
255
259
  when String
256
260
  :string_join
@@ -268,133 +272,73 @@ module Ransack
268
272
  association_joins = buckets[:association_join]
269
273
  stashed_association_joins = buckets[:stashed_join]
270
274
  join_nodes = buckets[:join_node].uniq
271
- string_joins = buckets[:string_join].map(&:strip).uniq
275
+ string_joins = buckets[:string_join].map(&:strip)
276
+ string_joins.uniq!
272
277
 
273
- join_list =
274
- if ::ActiveRecord::VERSION::MAJOR >= 5
275
- join_nodes +
276
- convert_join_strings_to_ast(relation.table, string_joins)
277
- else
278
- relation.send :custom_join_ast,
279
- relation.table.from(relation.table), string_joins
280
- end
281
-
282
- join_dependency = JoinDependency.new(
283
- relation.klass, association_joins, join_list
284
- )
278
+ join_list = join_nodes + convert_join_strings_to_ast(relation.table, string_joins)
285
279
 
280
+ alias_tracker = ::ActiveRecord::Associations::AliasTracker.create(self.klass.connection, relation.table.name, join_list)
281
+ join_dependency = Polyamorous::JoinDependency.new(relation.klass, relation.table, association_joins, Arel::Nodes::OuterJoin)
282
+ join_dependency.instance_variable_set(:@alias_tracker, alias_tracker)
286
283
  join_nodes.each do |join|
287
- join_dependency.alias_tracker.aliases[join.left.name.downcase] = 1
288
- end
289
-
290
- if ::ActiveRecord::VERSION::STRING >= Constants::RAILS_4_1
291
- join_dependency
292
- else
293
- join_dependency.graft(*stashed_association_joins)
284
+ join_dependency.send(:alias_tracker).aliases[join.left.name.downcase] = 1
294
285
  end
286
+ join_dependency
295
287
  end
296
288
 
297
289
  def convert_join_strings_to_ast(table, joins)
290
+ joins.map! { |join| table.create_string_join(Arel.sql(join)) unless join.blank? }
291
+ joins.compact!
298
292
  joins
299
- .flatten
300
- .reject(&:blank?)
301
- .map { |join| table.create_string_join(Arel.sql(join)) }
302
293
  end
303
294
 
304
295
  def build_or_find_association(name, parent = @base, klass = nil)
305
296
  find_association(name, parent, klass) or build_association(name, parent, klass)
306
297
  end
307
298
 
308
- if ::ActiveRecord::VERSION::STRING >= Constants::RAILS_4_1
309
-
310
- def find_association(name, parent = @base, klass = nil)
311
- @join_dependency.join_root.children.detect do |assoc|
312
- assoc.reflection.name == name &&
313
- (@associations_pot.empty? || @associations_pot[assoc] == parent) &&
314
- (!klass || assoc.reflection.klass == klass)
315
- end
299
+ def find_association(name, parent = @base, klass = nil)
300
+ @join_dependency.instance_variable_get(:@join_root).children.detect do |assoc|
301
+ assoc.reflection.name == name && assoc.table &&
302
+ (@associations_pot.empty? || @associations_pot[assoc] == parent || !@associations_pot.key?(assoc)) &&
303
+ (!klass || assoc.reflection.klass == klass)
316
304
  end
305
+ end
317
306
 
318
- def build_association(name, parent = @base, klass = nil)
319
- jd = JoinDependency.new(
320
- parent.base_klass,
321
- Polyamorous::Join.new(name, @join_type, klass),
322
- []
323
- )
324
- found_association = jd.join_root.children.last
325
- @associations_pot[found_association] = parent
326
-
327
- # TODO maybe we dont need to push associations here, we could loop
328
- # through the @associations_pot instead
329
- @join_dependency.join_root.children.push found_association
330
-
331
- # Builds the arel nodes properly for this association
332
- @join_dependency.send(
333
- :construct_tables!, jd.join_root, found_association
334
- )
335
-
336
- # Leverage the stashed association functionality in AR
337
- @object = @object.joins(jd)
338
-
339
- found_association
340
- end
341
-
342
- def extract_joins(association)
343
- parent = @join_dependency.join_root
344
- reflection = association.reflection
345
- join_constraints = if ::ActiveRecord::VERSION::STRING < Constants::RAILS_5_1
346
- association.join_constraints(
347
- parent.table,
348
- parent.base_klass,
349
- association,
350
- Arel::Nodes::OuterJoin,
351
- association.tables,
352
- reflection.scope_chain,
353
- reflection.chain
354
- )
355
- else
356
- association.join_constraints(
357
- parent.table,
358
- parent.base_klass,
359
- Arel::Nodes::OuterJoin,
360
- association.tables,
361
- reflection.chain
362
- )
363
- end
364
- join_constraints.to_a.flatten
365
- end
366
-
367
- else
368
-
369
- def build_association(name, parent = @base, klass = nil)
370
- @join_dependency.send(
371
- :build,
372
- Polyamorous::Join.new(name, @join_type, klass),
373
- parent
374
- )
375
- found_association = @join_dependency.join_associations.last
376
- # Leverage the stashed association functionality in AR
377
- @object = @object.joins(found_association)
307
+ def build_association(name, parent = @base, klass = nil)
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
378
315
 
379
- found_association
380
- end
316
+ @associations_pot[found_association] = parent
381
317
 
382
- def extract_joins(association)
383
- query = Arel::SelectManager.new(association.base_klass, association.table)
384
- association.join_to(query).join_sources
385
- end
318
+ # TODO maybe we dont need to push associations here, we could loop
319
+ # through the @associations_pot instead
320
+ @join_dependency.instance_variable_get(:@join_root).children.push found_association
386
321
 
387
- def find_association(name, parent = @base, klass = nil)
388
- found_association = @join_dependency.join_associations
389
- .detect do |assoc|
390
- assoc.reflection.name == name &&
391
- assoc.parent == parent &&
392
- (!klass || assoc.reflection.klass == klass)
393
- end
394
- end
322
+ # Builds the arel nodes properly for this association
323
+ @tables_pot[found_association] = @join_dependency.construct_tables_for_association!(jd.instance_variable_get(:@join_root), found_association)
395
324
 
325
+ # Leverage the stashed association functionality in AR
326
+ @object = @object.joins(jd)
327
+ found_association
396
328
  end
397
329
 
330
+ def extract_joins(association)
331
+ parent = @join_dependency.instance_variable_get(:@join_root)
332
+ reflection = association.reflection
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
+ )
340
+ join_constraints.to_a.flatten
341
+ end
398
342
  end
399
343
  end
400
344
  end
@@ -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,9 +114,12 @@ 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
107
- unescaped.to_s.gsub(/([\\|\%|_|.])/, '\\\\\\1')
117
+ when "Mysql2".freeze
118
+ # Necessary for MySQL
119
+ unescaped.to_s.gsub(/([\\%_])/, '\\\\\\1')
120
+ when "PostgreSQL".freeze
121
+ # Necessary for PostgreSQL
122
+ unescaped.to_s.gsub(/([\\%_.])/, '\\\\\\1')
108
123
  else
109
124
  unescaped
110
125
  end
@@ -28,24 +28,18 @@ 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_4_1
34
- @base = @join_dependency.join_root
35
- @engine = @base.base_klass.arel_engine
36
- else
37
- @base = @join_dependency.join_base
38
- @engine = @base.arel_engine
39
- end
34
+ @base = @join_dependency.instance_variable_get(:@join_root)
35
+ end
40
36
 
41
- @default_table = Arel::Table.new(
42
- @base.table_name, as: @base.aliased_table_name, type_caster: self
43
- )
44
- @bind_pairs = Hash.new do |hash, key|
45
- parent, attr_name = get_parent_and_attribute_name(key)
46
- if parent && attr_name
47
- hash[key] = [parent, attr_name]
48
- end
37
+ def bind_pair_for(key)
38
+ @bind_pairs ||= {}
39
+
40
+ @bind_pairs[key] ||= begin
41
+ parent, attr_name = get_parent_and_attribute_name(key.to_s)
42
+ [parent, attr_name] if parent && attr_name
49
43
  end
50
44
  end
51
45
 
@@ -54,10 +48,6 @@ module Ransack
54
48
  obj
55
49
  elsif obj.respond_to? :klass
56
50
  obj.klass
57
- elsif obj.respond_to? :active_record # Rails 3
58
- obj.active_record
59
- elsif obj.respond_to? :base_klass # Rails 4
60
- obj.base_klass
61
51
  else
62
52
  raise ArgumentError, "Don't know how to klassify #{obj.inspect}"
63
53
  end
@@ -30,11 +30,11 @@ 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
- predicate.right = predicate.right.map do |predicate|
37
- casted_array?(predicate) ? format_values_for(predicate) : predicate
36
+ predicate.right = predicate.right.map do |pr|
37
+ casted_array?(pr) ? format_values_for(pr) : pr
38
38
  end
39
39
  end
40
40
 
@@ -43,16 +43,16 @@ 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
+ predicate.value.is_a?(Array) && predicate.is_a?(Arel::Nodes::Casted)
51
51
  end
52
52
 
53
53
  def format_values_for(predicate)
54
- predicate.val.map do |value|
55
- value.is_a?(String) ? Arel::Nodes.build_quoted(value) : value
54
+ predicate.value.map do |val|
55
+ val.is_a?(String) ? Arel::Nodes.build_quoted(val) : val
56
56
  end
57
57
  end
58
58
 
@@ -2,11 +2,7 @@ module Ransack
2
2
  module Translate
3
3
 
4
4
  def self.i18n_key(klass)
5
- if ActiveRecord::VERSION::MAJOR == 3 && ActiveRecord::VERSION::MINOR == 0
6
- klass.model_name.i18n_key.to_s.tr('.'.freeze, '/'.freeze)
7
- else
8
- klass.model_name.i18n_key.to_s.freeze
9
- end
5
+ klass.model_name.i18n_key
10
6
  end
11
7
  end
12
8
  end
@@ -20,5 +20,28 @@ module Ransack
20
20
  end
21
21
  end
22
22
 
23
+ def visit_Ransack_Nodes_Sort(object)
24
+ if object.valid?
25
+ if object.attr.is_a?(Arel::Attributes::Attribute)
26
+ object.attr.send(object.dir)
27
+ else
28
+ ordered(object)
29
+ end
30
+ else
31
+ scope_name = :"sort_by_#{object.name}_#{object.dir}"
32
+ scope_name if object.context.object.respond_to?(scope_name)
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def ordered(object)
39
+ case object.dir
40
+ when 'asc'.freeze
41
+ Arel::Nodes::Ascending.new(object.attr)
42
+ when 'desc'.freeze
43
+ Arel::Nodes::Descending.new(object.attr)
44
+ end
45
+ end
23
46
  end
24
47
  end
@@ -12,12 +12,3 @@ ActiveSupport.on_load(:active_record) do
12
12
  end
13
13
 
14
14
  require 'ransack/adapters/active_record/context'
15
-
16
- case ActiveRecord::VERSION::STRING
17
- when /^3\.0\./
18
- require 'ransack/adapters/active_record/3.0/context'
19
- when /^3\.1\./
20
- require 'ransack/adapters/active_record/3.1/context'
21
- when /^3\.2\./
22
- require 'ransack/adapters/active_record/3.2/context'
23
- end
@@ -10,6 +10,8 @@ module Ransack
10
10
  ActiveRecordAdapter.new
11
11
  elsif defined?(::Mongoid)
12
12
  MongoidAdapter.new
13
+ else
14
+ raise "Unsupported adapter"
13
15
  end
14
16
  end
15
17