ransack 1.7.0 → 2.4.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.
- checksums.yaml +5 -5
- data/.github/FUNDING.yml +3 -0
- data/.github/SECURITY.md +12 -0
- data/.github/workflows/test.yml +120 -0
- data/.gitignore +3 -0
- data/CHANGELOG.md +463 -27
- data/CONTRIBUTING.md +52 -22
- data/Gemfile +24 -24
- data/README.md +453 -126
- data/Rakefile +6 -25
- data/lib/polyamorous/activerecord_5.2_ruby_2/join_association.rb +24 -0
- data/lib/polyamorous/activerecord_5.2_ruby_2/join_dependency.rb +79 -0
- data/lib/polyamorous/activerecord_5.2_ruby_2/reflection.rb +11 -0
- data/lib/polyamorous/activerecord_6.0_ruby_2/join_association.rb +1 -0
- data/lib/polyamorous/activerecord_6.0_ruby_2/join_dependency.rb +80 -0
- data/lib/polyamorous/activerecord_6.0_ruby_2/reflection.rb +1 -0
- data/lib/polyamorous/activerecord_6.1_ruby_2/join_association.rb +74 -0
- data/lib/polyamorous/activerecord_6.1_ruby_2/join_dependency.rb +93 -0
- data/lib/polyamorous/activerecord_6.1_ruby_2/reflection.rb +1 -0
- data/lib/polyamorous/join.rb +70 -0
- data/lib/polyamorous/polyamorous.rb +24 -0
- data/lib/polyamorous/swapping_reflection_class.rb +11 -0
- data/lib/polyamorous/tree_node.rb +7 -0
- data/lib/ransack/adapters/active_record/base.rb +27 -2
- data/lib/ransack/adapters/active_record/context.rb +213 -139
- data/lib/ransack/adapters/active_record/ransack/constants.rb +70 -55
- data/lib/ransack/adapters/active_record/ransack/context.rb +10 -18
- data/lib/ransack/adapters/active_record/ransack/nodes/condition.rb +42 -32
- data/lib/ransack/adapters/active_record/ransack/translate.rb +1 -5
- data/lib/ransack/adapters/active_record/ransack/visitor.rb +23 -0
- data/lib/ransack/adapters/active_record.rb +11 -10
- data/lib/ransack/adapters.rb +45 -23
- data/lib/ransack/configuration.rb +107 -4
- data/lib/ransack/constants.rb +13 -26
- data/lib/ransack/context.rb +45 -33
- data/lib/ransack/helpers/form_builder.rb +21 -12
- data/lib/ransack/helpers/form_helper.rb +75 -70
- data/lib/ransack/locale/ar.yml +70 -0
- data/lib/ransack/locale/az.yml +70 -0
- data/lib/ransack/locale/bg.yml +70 -0
- data/lib/ransack/locale/ca.yml +70 -0
- data/lib/ransack/locale/da.yml +70 -0
- data/lib/ransack/locale/el.yml +70 -0
- data/lib/ransack/locale/es.yml +22 -22
- data/lib/ransack/locale/fa.yml +70 -0
- data/lib/ransack/locale/fi.yml +71 -0
- data/lib/ransack/locale/id.yml +70 -0
- data/lib/ransack/locale/it.yml +70 -0
- data/lib/ransack/locale/ja.yml +70 -0
- data/lib/ransack/locale/nl.yml +4 -4
- data/lib/ransack/locale/pt-BR.yml +70 -0
- data/lib/ransack/locale/ru.yml +70 -0
- data/lib/ransack/locale/sk.yml +70 -0
- data/lib/ransack/locale/tr.yml +70 -0
- data/lib/ransack/locale/{zh.yml → zh-CN.yml} +13 -13
- data/lib/ransack/locale/zh-TW.yml +70 -0
- data/lib/ransack/nodes/attribute.rb +5 -2
- data/lib/ransack/nodes/bindable.rb +18 -6
- data/lib/ransack/nodes/condition.rb +85 -28
- data/lib/ransack/nodes/grouping.rb +17 -11
- data/lib/ransack/nodes/sort.rb +9 -5
- data/lib/ransack/nodes/value.rb +74 -68
- data/lib/ransack/nodes.rb +1 -1
- data/lib/ransack/predicate.rb +17 -20
- data/lib/ransack/search.rb +17 -8
- data/lib/ransack/translate.rb +115 -115
- data/lib/ransack/version.rb +1 -1
- data/lib/ransack/visitor.rb +1 -12
- data/lib/ransack.rb +9 -9
- data/logo/ransack-h.png +0 -0
- data/logo/ransack-h.svg +34 -0
- data/logo/ransack-v.png +0 -0
- data/logo/ransack-v.svg +34 -0
- data/logo/ransack.png +0 -0
- data/logo/ransack.svg +21 -0
- data/ransack.gemspec +7 -24
- data/spec/console.rb +4 -0
- data/spec/helpers/polyamorous_helper.rb +19 -0
- data/spec/polyamorous/join_association_spec.rb +35 -0
- data/spec/polyamorous/join_dependency_spec.rb +97 -0
- data/spec/polyamorous/join_spec.rb +19 -0
- data/spec/ransack/adapters/active_record/base_spec.rb +370 -75
- data/spec/ransack/adapters/active_record/context_spec.rb +72 -34
- data/spec/ransack/configuration_spec.rb +97 -14
- data/spec/ransack/helpers/form_builder_spec.rb +2 -11
- data/spec/ransack/helpers/form_helper_spec.rb +481 -113
- data/spec/ransack/nodes/condition_spec.rb +24 -0
- data/spec/ransack/nodes/grouping_spec.rb +56 -0
- data/spec/ransack/predicate_spec.rb +79 -5
- data/spec/ransack/search_spec.rb +207 -81
- data/spec/spec_helper.rb +8 -0
- data/spec/support/schema.rb +100 -42
- metadata +57 -184
- data/.travis.yml +0 -69
- data/lib/ransack/adapters/active_record/3.0/compat.rb +0 -179
- data/lib/ransack/adapters/active_record/3.0/context.rb +0 -201
- data/lib/ransack/adapters/active_record/3.1/context.rb +0 -215
- data/lib/ransack/adapters/active_record/3.2/context.rb +0 -44
- data/lib/ransack/adapters/active_record/compat.rb +0 -14
- data/lib/ransack/adapters/mongoid/3.2/.gitkeep +0 -0
- data/lib/ransack/adapters/mongoid/attributes/attribute.rb +0 -37
- data/lib/ransack/adapters/mongoid/attributes/order_predications.rb +0 -17
- data/lib/ransack/adapters/mongoid/attributes/predications.rb +0 -141
- data/lib/ransack/adapters/mongoid/base.rb +0 -130
- data/lib/ransack/adapters/mongoid/context.rb +0 -208
- data/lib/ransack/adapters/mongoid/inquiry_hash.rb +0 -23
- data/lib/ransack/adapters/mongoid/ransack/constants.rb +0 -88
- data/lib/ransack/adapters/mongoid/ransack/context.rb +0 -60
- data/lib/ransack/adapters/mongoid/ransack/nodes/condition.rb +0 -27
- data/lib/ransack/adapters/mongoid/ransack/translate.rb +0 -13
- data/lib/ransack/adapters/mongoid/ransack/visitor.rb +0 -24
- data/lib/ransack/adapters/mongoid/table.rb +0 -35
- data/lib/ransack/adapters/mongoid.rb +0 -13
- data/spec/mongoid/adapters/mongoid/base_spec.rb +0 -276
- data/spec/mongoid/adapters/mongoid/context_spec.rb +0 -56
- data/spec/mongoid/configuration_spec.rb +0 -102
- data/spec/mongoid/dependencies_spec.rb +0 -8
- data/spec/mongoid/helpers/ransack_helper.rb +0 -11
- data/spec/mongoid/nodes/condition_spec.rb +0 -34
- data/spec/mongoid/nodes/grouping_spec.rb +0 -13
- data/spec/mongoid/predicate_spec.rb +0 -155
- data/spec/mongoid/search_spec.rb +0 -446
- data/spec/mongoid/support/mongoid.yml +0 -6
- data/spec/mongoid/support/schema.rb +0 -128
- data/spec/mongoid/translate_spec.rb +0 -14
- data/spec/mongoid_spec_helper.rb +0 -59
- data/spec/ransack/dependencies_spec.rb +0 -12
|
@@ -1,42 +1,59 @@
|
|
|
1
1
|
require 'ransack/context'
|
|
2
|
-
require '
|
|
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
|
-
JoinDependency = ::ActiveRecord::Associations::JoinDependency
|
|
12
|
-
JoinPart = JoinDependency::JoinPart
|
|
13
|
-
|
|
14
|
-
def initialize(object, options = {})
|
|
15
|
-
super
|
|
16
|
-
@arel_visitor = @engine.connection.visitor
|
|
17
|
-
end
|
|
18
|
-
|
|
19
9
|
def relation_for(object)
|
|
20
10
|
object.all
|
|
21
11
|
end
|
|
22
12
|
|
|
23
13
|
def type_for(attr)
|
|
24
14
|
return nil unless attr && attr.valid?
|
|
25
|
-
name
|
|
26
|
-
table
|
|
27
|
-
|
|
28
|
-
unless
|
|
29
|
-
raise "No table named #{table} exists"
|
|
15
|
+
name = attr.arel_attribute.name.to_s
|
|
16
|
+
table = attr.arel_attribute.relation.table_name
|
|
17
|
+
schema_cache = self.klass.connection.schema_cache
|
|
18
|
+
unless schema_cache.send(:data_source_exists?, table)
|
|
19
|
+
raise "No table named #{table} exists."
|
|
30
20
|
end
|
|
31
|
-
|
|
21
|
+
attr.klass.columns.find { |column| column.name == name }.type
|
|
32
22
|
end
|
|
33
23
|
|
|
34
24
|
def evaluate(search, opts = {})
|
|
35
25
|
viz = Visitor.new
|
|
36
26
|
relation = @object.where(viz.accept(search.base))
|
|
27
|
+
|
|
37
28
|
if search.sorts.any?
|
|
38
|
-
relation = relation.except(:order)
|
|
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 ? "#{scope_or_sort.to_sql} NULLS FIRST" : "#{scope_or_sort.to_sql} NULLS LAST"
|
|
48
|
+
when :nulls_last
|
|
49
|
+
scope_or_sort = scope_or_sort.direction == :asc ? "#{scope_or_sort.to_sql} NULLS LAST" : "#{scope_or_sort.to_sql} NULLS FIRST"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
relation = relation.order(scope_or_sort)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
39
55
|
end
|
|
56
|
+
|
|
40
57
|
opts[:distinct] ? relation.distinct : relation
|
|
41
58
|
end
|
|
42
59
|
|
|
@@ -44,7 +61,7 @@ module Ransack
|
|
|
44
61
|
exists = false
|
|
45
62
|
if ransackable_attribute?(str, klass)
|
|
46
63
|
exists = true
|
|
47
|
-
elsif (segments = str.split(
|
|
64
|
+
elsif (segments = str.split(Constants::UNDERSCORE)).size > 1
|
|
48
65
|
remainder = []
|
|
49
66
|
found_assoc = nil
|
|
50
67
|
while !found_assoc && remainder.unshift(segments.pop) &&
|
|
@@ -79,61 +96,119 @@ module Ransack
|
|
|
79
96
|
end
|
|
80
97
|
end
|
|
81
98
|
|
|
82
|
-
|
|
99
|
+
# All dependent Arel::Join nodes used in the search query.
|
|
100
|
+
#
|
|
101
|
+
# This could otherwise be done as `@object.arel.join_sources`, except
|
|
102
|
+
# that ActiveRecord's build_joins sets up its own JoinDependency.
|
|
103
|
+
# This extracts what we need to access the joins using our existing
|
|
104
|
+
# JoinDependency to track table aliases.
|
|
105
|
+
#
|
|
106
|
+
def join_sources
|
|
107
|
+
base, joins = begin
|
|
108
|
+
alias_tracker = ::ActiveRecord::Associations::AliasTracker.create(self.klass.connection, @object.table.name, [])
|
|
109
|
+
constraints = if ::Gem::Version.new(::ActiveRecord::VERSION::STRING) >= ::Gem::Version.new(Constants::RAILS_6_1_ALPHA)
|
|
110
|
+
@join_dependency.join_constraints(@object.joins_values, alias_tracker, @object.references_values)
|
|
111
|
+
elsif ::Gem::Version.new(::ActiveRecord::VERSION::STRING) >= ::Gem::Version.new(Constants::RAILS_6_0)
|
|
112
|
+
@join_dependency.join_constraints(@object.joins_values, alias_tracker)
|
|
113
|
+
else
|
|
114
|
+
@join_dependency.join_constraints(@object.joins_values, @join_type, alias_tracker)
|
|
115
|
+
end
|
|
83
116
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
117
|
+
[
|
|
118
|
+
Arel::SelectManager.new(@object.table),
|
|
119
|
+
constraints
|
|
120
|
+
]
|
|
87
121
|
end
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
#
|
|
91
|
-
# This could otherwise be done as `@object.arel.join_sources`, except
|
|
92
|
-
# that ActiveRecord's build_joins sets up its own JoinDependency.
|
|
93
|
-
# This extracts what we need to access the joins using our existing
|
|
94
|
-
# JoinDependency to track table aliases.
|
|
95
|
-
#
|
|
96
|
-
def join_sources
|
|
97
|
-
base =
|
|
98
|
-
if ::ActiveRecord::VERSION::MAJOR >= 5
|
|
99
|
-
Arel::SelectManager.new(@object.table)
|
|
100
|
-
else
|
|
101
|
-
Arel::SelectManager.new(@object.engine, @object.table)
|
|
102
|
-
end
|
|
103
|
-
joins = @join_dependency.join_constraints(@object.joins_values)
|
|
104
|
-
joins.each do |aliased_join|
|
|
105
|
-
base.from(aliased_join)
|
|
106
|
-
end
|
|
107
|
-
base.join_sources
|
|
122
|
+
joins.each do |aliased_join|
|
|
123
|
+
base.from(aliased_join)
|
|
108
124
|
end
|
|
125
|
+
base.join_sources
|
|
126
|
+
end
|
|
109
127
|
|
|
110
|
-
|
|
128
|
+
def alias_tracker
|
|
129
|
+
@join_dependency.send(:alias_tracker)
|
|
130
|
+
end
|
|
111
131
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
#
|
|
116
|
-
def join_associations
|
|
117
|
-
@join_dependency.join_associations
|
|
118
|
-
end
|
|
132
|
+
def lock_association(association)
|
|
133
|
+
@lock_associations << association
|
|
134
|
+
end
|
|
119
135
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
136
|
+
def remove_association(association)
|
|
137
|
+
return if @lock_associations.include?(association)
|
|
138
|
+
@join_dependency.instance_variable_get(:@join_root).children.delete_if { |stashed|
|
|
139
|
+
stashed.eql?(association)
|
|
140
|
+
}
|
|
141
|
+
@object.joins_values.delete_if { |jd|
|
|
142
|
+
jd.instance_variable_get(:@join_root).children.map(&:object_id) == [association.object_id]
|
|
143
|
+
}
|
|
144
|
+
end
|
|
128
145
|
|
|
146
|
+
# Build an Arel subquery that selects keys for the top query,
|
|
147
|
+
# drawn from the first join association's foreign_key.
|
|
148
|
+
#
|
|
149
|
+
# Example: for an Article that has_and_belongs_to_many Tags
|
|
150
|
+
#
|
|
151
|
+
# context = Article.search.context
|
|
152
|
+
# attribute = Attribute.new(context, "tags_name").tap do |a|
|
|
153
|
+
# context.bind(a, a.name)
|
|
154
|
+
# end
|
|
155
|
+
# context.build_correlated_subquery(attribute.parent).to_sql
|
|
156
|
+
#
|
|
157
|
+
# # SELECT "articles_tags"."article_id" FROM "articles_tags"
|
|
158
|
+
# # INNER JOIN "tags" ON "tags"."id" = "articles_tags"."tag_id"
|
|
159
|
+
# # WHERE "articles_tags"."article_id" = "articles"."id"
|
|
160
|
+
#
|
|
161
|
+
# The WHERE condition on this query makes it invalid by itself,
|
|
162
|
+
# because it is correlated to the primary key on the outer query.
|
|
163
|
+
#
|
|
164
|
+
def build_correlated_subquery(association)
|
|
165
|
+
join_constraints = extract_joins(association)
|
|
166
|
+
join_root = join_constraints.shift
|
|
167
|
+
correlated_key = extract_correlated_key(join_root)
|
|
168
|
+
subquery = Arel::SelectManager.new(association.base_klass)
|
|
169
|
+
subquery.from(join_root.left)
|
|
170
|
+
subquery.project(correlated_key)
|
|
171
|
+
join_constraints.each do |j|
|
|
172
|
+
subquery.join_sources << Arel::Nodes::InnerJoin.new(j.left, j.right)
|
|
173
|
+
end
|
|
174
|
+
subquery.where(correlated_key.eq(primary_key))
|
|
129
175
|
end
|
|
130
176
|
|
|
131
|
-
def
|
|
132
|
-
@
|
|
177
|
+
def primary_key
|
|
178
|
+
@object.table[@object.primary_key]
|
|
133
179
|
end
|
|
134
180
|
|
|
135
181
|
private
|
|
136
182
|
|
|
183
|
+
def extract_correlated_key(join_root)
|
|
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)
|
|
205
|
+
else
|
|
206
|
+
# eg parent was Arel::Nodes::And and the evaluated side was one of
|
|
207
|
+
# Arel::Nodes::Grouping or MultiTenant::TenantEnforcementClause
|
|
208
|
+
nil
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
137
212
|
def get_parent_and_attribute_name(str, parent = @base)
|
|
138
213
|
attr_name = nil
|
|
139
214
|
|
|
@@ -168,7 +243,7 @@ module Ransack
|
|
|
168
243
|
end
|
|
169
244
|
|
|
170
245
|
def join_dependency(relation)
|
|
171
|
-
if relation.respond_to?(:join_dependency) #
|
|
246
|
+
if relation.respond_to?(:join_dependency) # Polyamorous enables this
|
|
172
247
|
relation.join_dependency
|
|
173
248
|
else
|
|
174
249
|
build_joins(relation)
|
|
@@ -178,7 +253,9 @@ module Ransack
|
|
|
178
253
|
# Checkout active_record/relation/query_methods.rb +build_joins+ for
|
|
179
254
|
# reference. Lots of duplicated code maybe we can avoid it
|
|
180
255
|
def build_joins(relation)
|
|
181
|
-
buckets = relation.joins_values
|
|
256
|
+
buckets = relation.joins_values + relation.left_outer_joins_values
|
|
257
|
+
|
|
258
|
+
buckets = buckets.group_by do |join|
|
|
182
259
|
case join
|
|
183
260
|
when String
|
|
184
261
|
:string_join
|
|
@@ -196,102 +273,99 @@ module Ransack
|
|
|
196
273
|
association_joins = buckets[:association_join]
|
|
197
274
|
stashed_association_joins = buckets[:stashed_join]
|
|
198
275
|
join_nodes = buckets[:join_node].uniq
|
|
199
|
-
string_joins = buckets[:string_join].map(&:strip)
|
|
200
|
-
|
|
201
|
-
join_list =
|
|
202
|
-
if ::ActiveRecord::VERSION::MAJOR >= 5
|
|
203
|
-
join_nodes +
|
|
204
|
-
convert_join_strings_to_ast(relation.table, string_joins)
|
|
205
|
-
else
|
|
206
|
-
relation.send :custom_join_ast,
|
|
207
|
-
relation.table.from(relation.table), string_joins
|
|
208
|
-
end
|
|
276
|
+
string_joins = buckets[:string_join].map(&:strip)
|
|
277
|
+
string_joins.uniq!
|
|
209
278
|
|
|
210
|
-
|
|
211
|
-
relation.klass, association_joins, join_list
|
|
212
|
-
)
|
|
279
|
+
join_list = join_nodes + convert_join_strings_to_ast(relation.table, string_joins)
|
|
213
280
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
if ::ActiveRecord::VERSION::STRING >= Constants::RAILS_4_1
|
|
219
|
-
join_dependency
|
|
281
|
+
alias_tracker = ::ActiveRecord::Associations::AliasTracker.create(self.klass.connection, relation.table.name, join_list)
|
|
282
|
+
join_dependency = if ::Gem::Version.new(::ActiveRecord::VERSION::STRING) >= ::Gem::Version.new(Constants::RAILS_6_0)
|
|
283
|
+
Polyamorous::JoinDependency.new(relation.klass, relation.table, association_joins, Arel::Nodes::OuterJoin)
|
|
220
284
|
else
|
|
221
|
-
|
|
285
|
+
Polyamorous::JoinDependency.new(relation.klass, relation.table, association_joins)
|
|
286
|
+
end
|
|
287
|
+
join_dependency.instance_variable_set(:@alias_tracker, alias_tracker)
|
|
288
|
+
join_nodes.each do |join|
|
|
289
|
+
join_dependency.send(:alias_tracker).aliases[join.left.name.downcase] = 1
|
|
222
290
|
end
|
|
291
|
+
join_dependency
|
|
223
292
|
end
|
|
224
293
|
|
|
225
294
|
def convert_join_strings_to_ast(table, joins)
|
|
295
|
+
joins.map! { |join| table.create_string_join(Arel.sql(join)) unless join.blank? }
|
|
296
|
+
joins.compact!
|
|
226
297
|
joins
|
|
227
|
-
.flatten
|
|
228
|
-
.reject(&:blank?)
|
|
229
|
-
.map { |join| table.create_string_join(Arel.sql(join)) }
|
|
230
298
|
end
|
|
231
299
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
found_association = @join_dependency.join_root.children
|
|
236
|
-
.detect do |assoc|
|
|
237
|
-
assoc.reflection.name == name &&
|
|
238
|
-
(@associations_pot.nil? || @associations_pot[assoc] == parent) &&
|
|
239
|
-
(!klass || assoc.reflection.klass == klass)
|
|
240
|
-
end
|
|
241
|
-
|
|
242
|
-
unless found_association
|
|
243
|
-
jd = JoinDependency.new(
|
|
244
|
-
parent.base_klass,
|
|
245
|
-
Polyamorous::Join.new(name, @join_type, klass),
|
|
246
|
-
[]
|
|
247
|
-
)
|
|
248
|
-
found_association = jd.join_root.children.last
|
|
249
|
-
associations found_association, parent
|
|
250
|
-
|
|
251
|
-
# TODO maybe we dont need to push associations here, we could loop
|
|
252
|
-
# through the @associations_pot instead
|
|
253
|
-
@join_dependency.join_root.children.push found_association
|
|
254
|
-
|
|
255
|
-
# Builds the arel nodes properly for this association
|
|
256
|
-
@join_dependency.send(
|
|
257
|
-
:construct_tables!, jd.join_root, found_association
|
|
258
|
-
)
|
|
300
|
+
def build_or_find_association(name, parent = @base, klass = nil)
|
|
301
|
+
find_association(name, parent, klass) or build_association(name, parent, klass)
|
|
302
|
+
end
|
|
259
303
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
304
|
+
def find_association(name, parent = @base, klass = nil)
|
|
305
|
+
@join_dependency.instance_variable_get(:@join_root).children.detect do |assoc|
|
|
306
|
+
assoc.reflection.name == name && assoc.table &&
|
|
307
|
+
(@associations_pot.empty? || @associations_pot[assoc] == parent || !@associations_pot.key?(assoc)) &&
|
|
308
|
+
(!klass || assoc.reflection.klass == klass)
|
|
264
309
|
end
|
|
310
|
+
end
|
|
265
311
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
312
|
+
def build_association(name, parent = @base, klass = nil)
|
|
313
|
+
if ::Gem::Version.new(::ActiveRecord::VERSION::STRING) >= ::Gem::Version.new(Constants::RAILS_6_0)
|
|
314
|
+
jd = Polyamorous::JoinDependency.new(
|
|
315
|
+
parent.base_klass,
|
|
316
|
+
parent.table,
|
|
317
|
+
Polyamorous::Join.new(name, @join_type, klass),
|
|
318
|
+
@join_type
|
|
319
|
+
)
|
|
320
|
+
found_association = jd.instance_variable_get(:@join_root).children.last
|
|
321
|
+
else
|
|
322
|
+
jd = Polyamorous::JoinDependency.new(
|
|
323
|
+
parent.base_klass,
|
|
324
|
+
parent.table,
|
|
325
|
+
Polyamorous::Join.new(name, @join_type, klass)
|
|
326
|
+
)
|
|
327
|
+
found_association = jd.instance_variable_get(:@join_root).children.last
|
|
269
328
|
end
|
|
270
329
|
|
|
271
|
-
|
|
330
|
+
@associations_pot[found_association] = parent
|
|
272
331
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
:build,
|
|
283
|
-
Polyamorous::Join.new(name, @join_type, klass),
|
|
284
|
-
parent
|
|
285
|
-
)
|
|
286
|
-
found_association = @join_dependency.join_associations.last
|
|
287
|
-
# Leverage the stashed association functionality in AR
|
|
288
|
-
@object = @object.joins(found_association)
|
|
289
|
-
end
|
|
290
|
-
found_association
|
|
332
|
+
# TODO maybe we dont need to push associations here, we could loop
|
|
333
|
+
# through the @associations_pot instead
|
|
334
|
+
@join_dependency.instance_variable_get(:@join_root).children.push found_association
|
|
335
|
+
|
|
336
|
+
# Builds the arel nodes properly for this association
|
|
337
|
+
if ::Gem::Version.new(::ActiveRecord::VERSION::STRING) >= ::Gem::Version.new(Constants::RAILS_6_1_ALPHA)
|
|
338
|
+
@tables_pot[found_association] = @join_dependency.construct_tables_for_association!(jd.instance_variable_get(:@join_root), found_association)
|
|
339
|
+
else
|
|
340
|
+
@join_dependency.send(:construct_tables!, jd.instance_variable_get(:@join_root))
|
|
291
341
|
end
|
|
292
342
|
|
|
343
|
+
# Leverage the stashed association functionality in AR
|
|
344
|
+
@object = @object.joins(jd)
|
|
345
|
+
found_association
|
|
293
346
|
end
|
|
294
347
|
|
|
348
|
+
def extract_joins(association)
|
|
349
|
+
parent = @join_dependency.instance_variable_get(:@join_root)
|
|
350
|
+
reflection = association.reflection
|
|
351
|
+
join_constraints = if ::Gem::Version.new(::ActiveRecord::VERSION::STRING) >= ::Gem::Version.new(Constants::RAILS_6_1_ALPHA)
|
|
352
|
+
association.join_constraints_with_tables(
|
|
353
|
+
parent.table,
|
|
354
|
+
parent.base_klass,
|
|
355
|
+
Arel::Nodes::OuterJoin,
|
|
356
|
+
@join_dependency.instance_variable_get(:@alias_tracker),
|
|
357
|
+
@tables_pot[association]
|
|
358
|
+
)
|
|
359
|
+
else
|
|
360
|
+
association.join_constraints(
|
|
361
|
+
parent.table,
|
|
362
|
+
parent.base_klass,
|
|
363
|
+
Arel::Nodes::OuterJoin,
|
|
364
|
+
@join_dependency.instance_variable_get(:@alias_tracker)
|
|
365
|
+
)
|
|
366
|
+
end
|
|
367
|
+
join_constraints.to_a.flatten
|
|
368
|
+
end
|
|
295
369
|
end
|
|
296
370
|
end
|
|
297
371
|
end
|
|
@@ -4,97 +4,109 @@ module Ransack
|
|
|
4
4
|
|
|
5
5
|
DERIVED_PREDICATES = [
|
|
6
6
|
[CONT, {
|
|
7
|
-
:
|
|
8
|
-
:
|
|
7
|
+
arel_predicate: 'matches'.freeze,
|
|
8
|
+
formatter: proc { |v| "%#{escape_wildcards(v)}%" }
|
|
9
9
|
}
|
|
10
10
|
],
|
|
11
11
|
['not_cont'.freeze, {
|
|
12
|
-
:
|
|
13
|
-
:
|
|
12
|
+
arel_predicate: 'does_not_match'.freeze,
|
|
13
|
+
formatter: proc { |v| "%#{escape_wildcards(v)}%" }
|
|
14
|
+
}
|
|
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
|
|
14
26
|
}
|
|
15
27
|
],
|
|
16
28
|
['start'.freeze, {
|
|
17
|
-
:
|
|
18
|
-
:
|
|
29
|
+
arel_predicate: 'matches'.freeze,
|
|
30
|
+
formatter: proc { |v| "#{escape_wildcards(v)}%" }
|
|
19
31
|
}
|
|
20
32
|
],
|
|
21
33
|
['not_start'.freeze, {
|
|
22
|
-
:
|
|
23
|
-
:
|
|
34
|
+
arel_predicate: 'does_not_match'.freeze,
|
|
35
|
+
formatter: proc { |v| "#{escape_wildcards(v)}%" }
|
|
24
36
|
}
|
|
25
37
|
],
|
|
26
38
|
['end'.freeze, {
|
|
27
|
-
:
|
|
28
|
-
:
|
|
39
|
+
arel_predicate: 'matches'.freeze,
|
|
40
|
+
formatter: proc { |v| "%#{escape_wildcards(v)}" }
|
|
29
41
|
}
|
|
30
42
|
],
|
|
31
43
|
['not_end'.freeze, {
|
|
32
|
-
:
|
|
33
|
-
:
|
|
44
|
+
arel_predicate: 'does_not_match'.freeze,
|
|
45
|
+
formatter: proc { |v| "%#{escape_wildcards(v)}" }
|
|
34
46
|
}
|
|
35
47
|
],
|
|
36
48
|
['true'.freeze, {
|
|
37
|
-
:
|
|
38
|
-
:
|
|
39
|
-
:
|
|
40
|
-
:
|
|
41
|
-
:
|
|
49
|
+
arel_predicate: proc { |v| v ? EQ : NOT_EQ },
|
|
50
|
+
compounds: false,
|
|
51
|
+
type: :boolean,
|
|
52
|
+
validator: proc { |v| BOOLEAN_VALUES.include?(v) },
|
|
53
|
+
formatter: proc { |v| true }
|
|
42
54
|
}
|
|
43
55
|
],
|
|
44
56
|
['not_true'.freeze, {
|
|
45
|
-
:
|
|
46
|
-
:
|
|
47
|
-
:
|
|
48
|
-
:
|
|
49
|
-
:
|
|
57
|
+
arel_predicate: proc { |v| v ? NOT_EQ : EQ },
|
|
58
|
+
compounds: false,
|
|
59
|
+
type: :boolean,
|
|
60
|
+
validator: proc { |v| BOOLEAN_VALUES.include?(v) },
|
|
61
|
+
formatter: proc { |v| true }
|
|
50
62
|
}
|
|
51
63
|
],
|
|
52
64
|
['false'.freeze, {
|
|
53
|
-
:
|
|
54
|
-
:
|
|
55
|
-
:
|
|
56
|
-
:
|
|
57
|
-
:
|
|
65
|
+
arel_predicate: proc { |v| v ? EQ : NOT_EQ },
|
|
66
|
+
compounds: false,
|
|
67
|
+
type: :boolean,
|
|
68
|
+
validator: proc { |v| BOOLEAN_VALUES.include?(v) },
|
|
69
|
+
formatter: proc { |v| false }
|
|
58
70
|
}
|
|
59
71
|
],
|
|
60
72
|
['not_false'.freeze, {
|
|
61
|
-
:
|
|
62
|
-
:
|
|
63
|
-
:
|
|
64
|
-
:
|
|
65
|
-
:
|
|
73
|
+
arel_predicate: proc { |v| v ? NOT_EQ : EQ },
|
|
74
|
+
compounds: false,
|
|
75
|
+
type: :boolean,
|
|
76
|
+
validator: proc { |v| BOOLEAN_VALUES.include?(v) },
|
|
77
|
+
formatter: proc { |v| false }
|
|
66
78
|
}
|
|
67
79
|
],
|
|
68
80
|
['present'.freeze, {
|
|
69
|
-
:
|
|
70
|
-
:
|
|
71
|
-
:
|
|
72
|
-
:
|
|
73
|
-
:
|
|
81
|
+
arel_predicate: proc { |v| v ? NOT_EQ_ALL : EQ_ANY },
|
|
82
|
+
compounds: false,
|
|
83
|
+
type: :boolean,
|
|
84
|
+
validator: proc { |v| BOOLEAN_VALUES.include?(v) },
|
|
85
|
+
formatter: proc { |v| [nil, ''.freeze].freeze }
|
|
74
86
|
}
|
|
75
87
|
],
|
|
76
88
|
['blank'.freeze, {
|
|
77
|
-
:
|
|
78
|
-
:
|
|
79
|
-
:
|
|
80
|
-
:
|
|
81
|
-
:
|
|
89
|
+
arel_predicate: proc { |v| v ? EQ_ANY : NOT_EQ_ALL },
|
|
90
|
+
compounds: false,
|
|
91
|
+
type: :boolean,
|
|
92
|
+
validator: proc { |v| BOOLEAN_VALUES.include?(v) },
|
|
93
|
+
formatter: proc { |v| [nil, ''.freeze].freeze }
|
|
82
94
|
}
|
|
83
95
|
],
|
|
84
96
|
['null'.freeze, {
|
|
85
|
-
:
|
|
86
|
-
:
|
|
87
|
-
:
|
|
88
|
-
:
|
|
89
|
-
:
|
|
97
|
+
arel_predicate: proc { |v| v ? EQ : NOT_EQ },
|
|
98
|
+
compounds: false,
|
|
99
|
+
type: :boolean,
|
|
100
|
+
validator: proc { |v| BOOLEAN_VALUES.include?(v)},
|
|
101
|
+
formatter: proc { |v| nil }
|
|
90
102
|
}
|
|
91
103
|
],
|
|
92
104
|
['not_null'.freeze, {
|
|
93
|
-
:
|
|
94
|
-
:
|
|
95
|
-
:
|
|
96
|
-
:
|
|
97
|
-
:
|
|
105
|
+
arel_predicate: proc { |v| v ? NOT_EQ : EQ },
|
|
106
|
+
compounds: false,
|
|
107
|
+
type: :boolean,
|
|
108
|
+
validator: proc { |v| BOOLEAN_VALUES.include?(v) },
|
|
109
|
+
formatter: proc { |v| nil } }
|
|
98
110
|
]
|
|
99
111
|
].freeze
|
|
100
112
|
|
|
@@ -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
|
|
106
|
-
# Necessary for
|
|
107
|
-
unescaped.to_s.gsub(/([
|
|
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
|
|
@@ -27,23 +27,19 @@ module Ransack
|
|
|
27
27
|
@join_dependency = join_dependency(@object)
|
|
28
28
|
@join_type = options[:join_type] || Polyamorous::OuterJoin
|
|
29
29
|
@search_key = options[:search_key] || Ransack.options[:search_key]
|
|
30
|
+
@associations_pot = {}
|
|
31
|
+
@tables_pot = {}
|
|
32
|
+
@lock_associations = []
|
|
30
33
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
@engine = @base.base_klass.arel_engine
|
|
34
|
-
else
|
|
35
|
-
@base = @join_dependency.join_base
|
|
36
|
-
@engine = @base.arel_engine
|
|
37
|
-
end
|
|
34
|
+
@base = @join_dependency.instance_variable_get(:@join_root)
|
|
35
|
+
end
|
|
38
36
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
@bind_pairs
|
|
37
|
+
def bind_pair_for(key)
|
|
38
|
+
@bind_pairs ||= {}
|
|
39
|
+
|
|
40
|
+
@bind_pairs[key] ||= begin
|
|
43
41
|
parent, attr_name = get_parent_and_attribute_name(key.to_s)
|
|
44
|
-
if parent && attr_name
|
|
45
|
-
hash[key] = [parent, attr_name]
|
|
46
|
-
end
|
|
42
|
+
[parent, attr_name] if parent && attr_name
|
|
47
43
|
end
|
|
48
44
|
end
|
|
49
45
|
|
|
@@ -52,10 +48,6 @@ module Ransack
|
|
|
52
48
|
obj
|
|
53
49
|
elsif obj.respond_to? :klass
|
|
54
50
|
obj.klass
|
|
55
|
-
elsif obj.respond_to? :active_record # Rails 3
|
|
56
|
-
obj.active_record
|
|
57
|
-
elsif obj.respond_to? :base_klass # Rails 4
|
|
58
|
-
obj.base_klass
|
|
59
51
|
else
|
|
60
52
|
raise ArgumentError, "Don't know how to klassify #{obj.inspect}"
|
|
61
53
|
end
|