ransack 1.8.4 → 3.2.1

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 (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