mini-fast-sys 0.0.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 +7 -0
- data/mini-fast-sys.gemspec +12 -0
- data/ransack-4.4.1/LICENSE +20 -0
- data/ransack-4.4.1/README.md +101 -0
- data/ransack-4.4.1/lib/polyamorous/activerecord/join_association.rb +70 -0
- data/ransack-4.4.1/lib/polyamorous/activerecord/join_association_7_2.rb +55 -0
- data/ransack-4.4.1/lib/polyamorous/activerecord/join_dependency.rb +102 -0
- data/ransack-4.4.1/lib/polyamorous/activerecord/reflection.rb +11 -0
- data/ransack-4.4.1/lib/polyamorous/join.rb +70 -0
- data/ransack-4.4.1/lib/polyamorous/polyamorous.rb +27 -0
- data/ransack-4.4.1/lib/polyamorous/swapping_reflection_class.rb +11 -0
- data/ransack-4.4.1/lib/polyamorous/tree_node.rb +7 -0
- data/ransack-4.4.1/lib/ransack/active_record.rb +14 -0
- data/ransack-4.4.1/lib/ransack/adapters/active_record/base.rb +152 -0
- data/ransack-4.4.1/lib/ransack/adapters/active_record/context.rb +373 -0
- data/ransack-4.4.1/lib/ransack/configuration.rb +209 -0
- data/ransack-4.4.1/lib/ransack/constants.rb +176 -0
- data/ransack-4.4.1/lib/ransack/context.rb +190 -0
- data/ransack-4.4.1/lib/ransack/helpers/form_builder.rb +277 -0
- data/ransack-4.4.1/lib/ransack/helpers/form_helper.rb +311 -0
- data/ransack-4.4.1/lib/ransack/helpers.rb +2 -0
- data/ransack-4.4.1/lib/ransack/invalid_search_error.rb +3 -0
- data/ransack-4.4.1/lib/ransack/locale/ar.yml +70 -0
- data/ransack-4.4.1/lib/ransack/locale/az.yml +70 -0
- data/ransack-4.4.1/lib/ransack/locale/bg.yml +70 -0
- data/ransack-4.4.1/lib/ransack/locale/ca.yml +70 -0
- data/ransack-4.4.1/lib/ransack/locale/cs.yml +70 -0
- data/ransack-4.4.1/lib/ransack/locale/da.yml +70 -0
- data/ransack-4.4.1/lib/ransack/locale/de.yml +70 -0
- data/ransack-4.4.1/lib/ransack/locale/el.yml +70 -0
- data/ransack-4.4.1/lib/ransack/locale/en.yml +70 -0
- data/ransack-4.4.1/lib/ransack/locale/es.yml +70 -0
- data/ransack-4.4.1/lib/ransack/locale/fa.yml +70 -0
- data/ransack-4.4.1/lib/ransack/locale/fi.yml +71 -0
- data/ransack-4.4.1/lib/ransack/locale/fr.yml +70 -0
- data/ransack-4.4.1/lib/ransack/locale/hu.yml +70 -0
- data/ransack-4.4.1/lib/ransack/locale/id.yml +70 -0
- data/ransack-4.4.1/lib/ransack/locale/it.yml +70 -0
- data/ransack-4.4.1/lib/ransack/locale/ja.yml +70 -0
- data/ransack-4.4.1/lib/ransack/locale/ko.yml +70 -0
- data/ransack-4.4.1/lib/ransack/locale/nl.yml +70 -0
- data/ransack-4.4.1/lib/ransack/locale/pt-BR.yml +70 -0
- data/ransack-4.4.1/lib/ransack/locale/ro.yml +70 -0
- data/ransack-4.4.1/lib/ransack/locale/ru.yml +70 -0
- data/ransack-4.4.1/lib/ransack/locale/sk.yml +70 -0
- data/ransack-4.4.1/lib/ransack/locale/sv.yml +70 -0
- data/ransack-4.4.1/lib/ransack/locale/tr.yml +70 -0
- data/ransack-4.4.1/lib/ransack/locale/uk.yml +72 -0
- data/ransack-4.4.1/lib/ransack/locale/zh-CN.yml +70 -0
- data/ransack-4.4.1/lib/ransack/locale/zh-TW.yml +70 -0
- data/ransack-4.4.1/lib/ransack/naming.rb +58 -0
- data/ransack-4.4.1/lib/ransack/nodes/attribute.rb +59 -0
- data/ransack-4.4.1/lib/ransack/nodes/bindable.rb +52 -0
- data/ransack-4.4.1/lib/ransack/nodes/condition.rb +399 -0
- data/ransack-4.4.1/lib/ransack/nodes/grouping.rb +206 -0
- data/ransack-4.4.1/lib/ransack/nodes/node.rb +34 -0
- data/ransack-4.4.1/lib/ransack/nodes/sort.rb +54 -0
- data/ransack-4.4.1/lib/ransack/nodes/value.rb +115 -0
- data/ransack-4.4.1/lib/ransack/predicate.rb +75 -0
- data/ransack-4.4.1/lib/ransack/ransacker.rb +24 -0
- data/ransack-4.4.1/lib/ransack/search.rb +195 -0
- data/ransack-4.4.1/lib/ransack/translate.rb +156 -0
- data/ransack-4.4.1/lib/ransack/version.rb +3 -0
- data/ransack-4.4.1/lib/ransack/visitor.rb +87 -0
- data/ransack-4.4.1/lib/ransack.rb +33 -0
- data/ransack-4.4.1/spec/console.rb +13 -0
- data/ransack-4.4.1/spec/factories/articles.rb +7 -0
- data/ransack-4.4.1/spec/factories/comments.rb +7 -0
- data/ransack-4.4.1/spec/factories/notes.rb +13 -0
- data/ransack-4.4.1/spec/factories/people.rb +10 -0
- data/ransack-4.4.1/spec/factories/tags.rb +5 -0
- data/ransack-4.4.1/spec/helpers/polyamorous_helper.rb +13 -0
- data/ransack-4.4.1/spec/helpers/ransack_helper.rb +9 -0
- data/ransack-4.4.1/spec/polyamorous/activerecord_compatibility_spec.rb +15 -0
- data/ransack-4.4.1/spec/polyamorous/join_association_spec.rb +29 -0
- data/ransack-4.4.1/spec/polyamorous/join_dependency_spec.rb +80 -0
- data/ransack-4.4.1/spec/polyamorous/join_spec.rb +19 -0
- data/ransack-4.4.1/spec/ransack/adapters/active_record/base_spec.rb +943 -0
- data/ransack-4.4.1/spec/ransack/adapters/active_record/context_spec.rb +219 -0
- data/ransack-4.4.1/spec/ransack/configuration_spec.rb +201 -0
- data/ransack-4.4.1/spec/ransack/helpers/form_builder_spec.rb +169 -0
- data/ransack-4.4.1/spec/ransack/helpers/form_helper_spec.rb +1075 -0
- data/ransack-4.4.1/spec/ransack/invalid_search_error_spec.rb +27 -0
- data/ransack-4.4.1/spec/ransack/nodes/condition_spec.rb +333 -0
- data/ransack-4.4.1/spec/ransack/nodes/grouping_spec.rb +111 -0
- data/ransack-4.4.1/spec/ransack/nodes/value_spec.rb +126 -0
- data/ransack-4.4.1/spec/ransack/predicate_spec.rb +504 -0
- data/ransack-4.4.1/spec/ransack/ransacker_spec.rb +69 -0
- data/ransack-4.4.1/spec/ransack/search_spec.rb +821 -0
- data/ransack-4.4.1/spec/ransack/translate_spec.rb +16 -0
- data/ransack-4.4.1/spec/spec_helper.rb +52 -0
- data/ransack-4.4.1/spec/support/en.yml +18 -0
- data/ransack-4.4.1/spec/support/schema.rb +419 -0
- metadata +133 -0
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
require 'ransack/context'
|
|
2
|
+
require 'polyamorous/polyamorous'
|
|
3
|
+
|
|
4
|
+
module Ransack
|
|
5
|
+
module Adapters
|
|
6
|
+
module ActiveRecord
|
|
7
|
+
class Context < ::Ransack::Context
|
|
8
|
+
|
|
9
|
+
def relation_for(object)
|
|
10
|
+
object.all
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def type_for(attr)
|
|
14
|
+
return nil unless attr && attr.valid?
|
|
15
|
+
relation = attr.arel_attribute.relation
|
|
16
|
+
name = attr.arel_attribute.name.to_s
|
|
17
|
+
table = relation.respond_to?(:table_name) ? relation.table_name : relation.name
|
|
18
|
+
schema_cache = self.klass.connection.schema_cache
|
|
19
|
+
unless schema_cache.send(:data_source_exists?, table)
|
|
20
|
+
raise "No table named #{table} exists."
|
|
21
|
+
end
|
|
22
|
+
column = attr.klass.columns.find { |column| column.name == name }
|
|
23
|
+
column&.type
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def evaluate(search, opts = {})
|
|
27
|
+
viz = Visitor.new
|
|
28
|
+
relation = @object.where(viz.accept(search.base))
|
|
29
|
+
|
|
30
|
+
if search.sorts.any?
|
|
31
|
+
relation = relation.except(:order)
|
|
32
|
+
# Rather than applying all of the search's sorts in one fell swoop,
|
|
33
|
+
# as the original implementation does, we apply one at a time.
|
|
34
|
+
#
|
|
35
|
+
# If the sort (returned by the Visitor above) is a symbol, we know
|
|
36
|
+
# that it represents a scope on the model and we can apply that
|
|
37
|
+
# scope.
|
|
38
|
+
#
|
|
39
|
+
# Otherwise, we fall back to the applying the sort with the "order"
|
|
40
|
+
# method as the original implementation did. Actually the original
|
|
41
|
+
# implementation used "reorder," which was overkill since we already
|
|
42
|
+
# have a clean slate after "relation.except(:order)" above.
|
|
43
|
+
viz.accept(search.sorts).each do |scope_or_sort|
|
|
44
|
+
if scope_or_sort.is_a?(Symbol)
|
|
45
|
+
relation = relation.send(scope_or_sort)
|
|
46
|
+
else
|
|
47
|
+
case Ransack.options[:postgres_fields_sort_option]
|
|
48
|
+
when :nulls_first
|
|
49
|
+
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")
|
|
50
|
+
when :nulls_last
|
|
51
|
+
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")
|
|
52
|
+
when :nulls_always_first
|
|
53
|
+
scope_or_sort = Arel.sql("#{scope_or_sort.to_sql} NULLS FIRST")
|
|
54
|
+
when :nulls_always_last
|
|
55
|
+
scope_or_sort = Arel.sql("#{scope_or_sort.to_sql} NULLS LAST")
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
relation = relation.order(scope_or_sort)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
opts[:distinct] ? relation.distinct : relation
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def attribute_method?(str, klass = @klass)
|
|
67
|
+
exists = false
|
|
68
|
+
if ransackable_attribute?(str, klass)
|
|
69
|
+
exists = true
|
|
70
|
+
elsif (segments = str.split(Constants::UNDERSCORE)).size > 1
|
|
71
|
+
remainder = []
|
|
72
|
+
found_assoc = nil
|
|
73
|
+
while !found_assoc && remainder.unshift(segments.pop) &&
|
|
74
|
+
segments.size > 0 do
|
|
75
|
+
assoc, poly_class = unpolymorphize_association(
|
|
76
|
+
segments.join(Constants::UNDERSCORE)
|
|
77
|
+
)
|
|
78
|
+
if found_assoc = get_association(assoc, klass)
|
|
79
|
+
exists = attribute_method?(
|
|
80
|
+
remainder.join(Constants::UNDERSCORE),
|
|
81
|
+
poly_class || found_assoc.klass
|
|
82
|
+
)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
exists
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def table_for(parent)
|
|
90
|
+
parent.table
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def klassify(obj)
|
|
94
|
+
if Class === obj && ::ActiveRecord::Base > obj
|
|
95
|
+
obj
|
|
96
|
+
elsif obj.respond_to? :klass
|
|
97
|
+
obj.klass
|
|
98
|
+
elsif obj.respond_to? :base_klass
|
|
99
|
+
obj.base_klass
|
|
100
|
+
else
|
|
101
|
+
raise ArgumentError, "Don't know how to klassify #{obj}"
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# All dependent Arel::Join nodes used in the search query.
|
|
106
|
+
#
|
|
107
|
+
# This could otherwise be done as `@object.arel.join_sources`, except
|
|
108
|
+
# that ActiveRecord's build_joins sets up its own JoinDependency.
|
|
109
|
+
# This extracts what we need to access the joins using our existing
|
|
110
|
+
# JoinDependency to track table aliases.
|
|
111
|
+
#
|
|
112
|
+
def join_sources
|
|
113
|
+
base, joins = begin
|
|
114
|
+
alias_tracker = @object.alias_tracker
|
|
115
|
+
constraints = @join_dependency.join_constraints(@object.joins_values, alias_tracker, @object.references_values)
|
|
116
|
+
|
|
117
|
+
[
|
|
118
|
+
Arel::SelectManager.new(@object.table),
|
|
119
|
+
constraints
|
|
120
|
+
]
|
|
121
|
+
end
|
|
122
|
+
joins.each do |aliased_join|
|
|
123
|
+
base.from(aliased_join)
|
|
124
|
+
end
|
|
125
|
+
base.join_sources
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def alias_tracker
|
|
129
|
+
@join_dependency.send(:alias_tracker)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def lock_association(association)
|
|
133
|
+
@lock_associations << association
|
|
134
|
+
end
|
|
135
|
+
|
|
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_variables.include?(:@join_root) &&
|
|
143
|
+
jd.instance_variable_get(:@join_root).children.map(&:object_id) == [association.object_id]
|
|
144
|
+
}
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Build an Arel subquery that selects keys for the top query,
|
|
148
|
+
# drawn from the first join association's foreign_key.
|
|
149
|
+
#
|
|
150
|
+
# Example: for an Article that has_and_belongs_to_many Tags
|
|
151
|
+
#
|
|
152
|
+
# context = Article.search.context
|
|
153
|
+
# attribute = Attribute.new(context, "tags_name").tap do |a|
|
|
154
|
+
# context.bind(a, a.name)
|
|
155
|
+
# end
|
|
156
|
+
# context.build_correlated_subquery(attribute.parent).to_sql
|
|
157
|
+
#
|
|
158
|
+
# # SELECT "articles_tags"."article_id" FROM "articles_tags"
|
|
159
|
+
# # INNER JOIN "tags" ON "tags"."id" = "articles_tags"."tag_id"
|
|
160
|
+
# # WHERE "articles_tags"."article_id" = "articles"."id"
|
|
161
|
+
#
|
|
162
|
+
# The WHERE condition on this query makes it invalid by itself,
|
|
163
|
+
# because it is correlated to the primary key on the outer query.
|
|
164
|
+
#
|
|
165
|
+
def build_correlated_subquery(association)
|
|
166
|
+
join_constraints = extract_joins(association)
|
|
167
|
+
join_root = join_constraints.shift
|
|
168
|
+
correlated_key = extract_correlated_key(join_root)
|
|
169
|
+
subquery = Arel::SelectManager.new(association.base_klass)
|
|
170
|
+
subquery.from(join_root.left)
|
|
171
|
+
subquery.project(correlated_key)
|
|
172
|
+
join_constraints.each do |j|
|
|
173
|
+
subquery.join_sources << Arel::Nodes::InnerJoin.new(j.left, j.right)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Handle polymorphic associations where correlated_key is an array
|
|
177
|
+
if correlated_key.is_a?(Array)
|
|
178
|
+
# For polymorphic associations, we need to add conditions for both the foreign key and type
|
|
179
|
+
correlated_key.each_with_index do |key, index|
|
|
180
|
+
if index == 0
|
|
181
|
+
# This is the foreign key
|
|
182
|
+
subquery = subquery.where(key.eq(primary_key))
|
|
183
|
+
else
|
|
184
|
+
# This is the type key, which should be equal to the model name
|
|
185
|
+
subquery = subquery.where(key.eq(@klass.name))
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
else
|
|
189
|
+
# Original behavior for non-polymorphic associations
|
|
190
|
+
subquery = subquery.where(correlated_key.eq(primary_key))
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
subquery
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def primary_key
|
|
197
|
+
@object.table[@object.primary_key]
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
private
|
|
201
|
+
|
|
202
|
+
def extract_correlated_key(join_root)
|
|
203
|
+
case join_root
|
|
204
|
+
when Arel::Nodes::OuterJoin
|
|
205
|
+
# one of join_root.right/join_root.left is expected to be Arel::Nodes::On
|
|
206
|
+
if join_root.right.is_a?(Arel::Nodes::On)
|
|
207
|
+
extract_correlated_key(join_root.right.expr)
|
|
208
|
+
elsif join_root.left.is_a?(Arel::Nodes::On)
|
|
209
|
+
extract_correlated_key(join_root.left.expr)
|
|
210
|
+
else
|
|
211
|
+
raise 'Ransack encountered an unexpected arel structure'
|
|
212
|
+
end
|
|
213
|
+
when Arel::Nodes::Equality
|
|
214
|
+
pk = primary_key
|
|
215
|
+
if join_root.left == pk
|
|
216
|
+
join_root.right
|
|
217
|
+
elsif join_root.right == pk
|
|
218
|
+
join_root.left
|
|
219
|
+
else
|
|
220
|
+
nil
|
|
221
|
+
end
|
|
222
|
+
when Arel::Nodes::And
|
|
223
|
+
# And may have multiple children, so we need to check all, not via left/right
|
|
224
|
+
if join_root.children.any?
|
|
225
|
+
join_root.children.each do |child|
|
|
226
|
+
key = extract_correlated_key(child)
|
|
227
|
+
return key if key
|
|
228
|
+
end
|
|
229
|
+
else
|
|
230
|
+
extract_correlated_key(join_root.left) || extract_correlated_key(join_root.right)
|
|
231
|
+
end
|
|
232
|
+
else
|
|
233
|
+
# eg parent was Arel::Nodes::And and the evaluated side was one of
|
|
234
|
+
# Arel::Nodes::Grouping or MultiTenant::TenantEnforcementClause
|
|
235
|
+
nil
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def get_parent_and_attribute_name(str, parent = @base)
|
|
240
|
+
attr_name = nil
|
|
241
|
+
|
|
242
|
+
if ransackable_attribute?(str, klassify(parent))
|
|
243
|
+
attr_name = str
|
|
244
|
+
elsif (segments = str.split(Constants::UNDERSCORE)).size > 1
|
|
245
|
+
remainder = []
|
|
246
|
+
found_assoc = nil
|
|
247
|
+
while remainder.unshift(segments.pop) && segments.size > 0 &&
|
|
248
|
+
!found_assoc do
|
|
249
|
+
assoc, klass = unpolymorphize_association(
|
|
250
|
+
segments.join(Constants::UNDERSCORE)
|
|
251
|
+
)
|
|
252
|
+
if found_assoc = get_association(assoc, parent)
|
|
253
|
+
join = build_or_find_association(
|
|
254
|
+
found_assoc.name, parent, klass
|
|
255
|
+
)
|
|
256
|
+
parent, attr_name = get_parent_and_attribute_name(
|
|
257
|
+
remainder.join(Constants::UNDERSCORE), join
|
|
258
|
+
)
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
[parent, attr_name]
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def get_association(str, parent = @base)
|
|
267
|
+
klass = klassify parent
|
|
268
|
+
ransackable_association?(str, klass) &&
|
|
269
|
+
klass.reflect_on_all_associations.detect { |a| a.name.to_s == str }
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
def join_dependency(relation)
|
|
273
|
+
if relation.respond_to?(:join_dependency) # Polyamorous enables this
|
|
274
|
+
relation.join_dependency
|
|
275
|
+
else
|
|
276
|
+
build_joins(relation)
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
# Checkout active_record/relation/query_methods.rb +build_joins+ for
|
|
281
|
+
# reference. Lots of duplicated code maybe we can avoid it
|
|
282
|
+
def build_joins(relation)
|
|
283
|
+
buckets = relation.joins_values + relation.left_outer_joins_values
|
|
284
|
+
|
|
285
|
+
buckets = buckets.group_by do |join|
|
|
286
|
+
case join
|
|
287
|
+
when String
|
|
288
|
+
:string_join
|
|
289
|
+
when Hash, Symbol, Array
|
|
290
|
+
:association_join
|
|
291
|
+
when Polyamorous::JoinDependency, Polyamorous::JoinAssociation
|
|
292
|
+
:stashed_join
|
|
293
|
+
when Arel::Nodes::Join
|
|
294
|
+
:join_node
|
|
295
|
+
else
|
|
296
|
+
raise 'unknown class: %s' % join.class.name
|
|
297
|
+
end
|
|
298
|
+
end
|
|
299
|
+
buckets.default = []
|
|
300
|
+
association_joins = buckets[:association_join]
|
|
301
|
+
stashed_association_joins = buckets[:stashed_join]
|
|
302
|
+
join_nodes = buckets[:join_node].uniq
|
|
303
|
+
string_joins = buckets[:string_join].map(&:strip)
|
|
304
|
+
string_joins.uniq!
|
|
305
|
+
|
|
306
|
+
join_list = join_nodes + convert_join_strings_to_ast(relation.table, string_joins)
|
|
307
|
+
|
|
308
|
+
alias_tracker = relation.alias_tracker(join_list)
|
|
309
|
+
join_dependency = Polyamorous::JoinDependency.new(relation.klass, relation.table, association_joins, Arel::Nodes::OuterJoin)
|
|
310
|
+
join_dependency.instance_variable_set(:@alias_tracker, alias_tracker)
|
|
311
|
+
join_nodes.each do |join|
|
|
312
|
+
join_dependency.send(:alias_tracker).aliases[join.left.name.downcase] = 1
|
|
313
|
+
end
|
|
314
|
+
join_dependency
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
def convert_join_strings_to_ast(table, joins)
|
|
318
|
+
joins.map! { |join| table.create_string_join(Arel.sql(join)) unless join.blank? }
|
|
319
|
+
joins.compact!
|
|
320
|
+
joins
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
def build_or_find_association(name, parent = @base, klass = nil)
|
|
324
|
+
find_association(name, parent, klass) or build_association(name, parent, klass)
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
def find_association(name, parent = @base, klass = nil)
|
|
328
|
+
@join_dependency.instance_variable_get(:@join_root).children.detect do |assoc|
|
|
329
|
+
assoc.reflection.name == name && assoc.table &&
|
|
330
|
+
(@associations_pot.empty? || @associations_pot[assoc] == parent || !@associations_pot.key?(assoc)) &&
|
|
331
|
+
(!klass || assoc.reflection.klass == klass)
|
|
332
|
+
end
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
def build_association(name, parent = @base, klass = nil)
|
|
336
|
+
jd = Polyamorous::JoinDependency.new(
|
|
337
|
+
parent.base_klass,
|
|
338
|
+
parent.table,
|
|
339
|
+
Polyamorous::Join.new(name, @join_type, klass),
|
|
340
|
+
@join_type
|
|
341
|
+
)
|
|
342
|
+
found_association = jd.instance_variable_get(:@join_root).children.last
|
|
343
|
+
|
|
344
|
+
@associations_pot[found_association] = parent
|
|
345
|
+
|
|
346
|
+
# TODO maybe we dont need to push associations here, we could loop
|
|
347
|
+
# through the @associations_pot instead
|
|
348
|
+
@join_dependency.instance_variable_get(:@join_root).children.push found_association
|
|
349
|
+
|
|
350
|
+
# Builds the arel nodes properly for this association
|
|
351
|
+
@tables_pot[found_association] = @join_dependency.construct_tables_for_association!(jd.instance_variable_get(:@join_root), found_association)
|
|
352
|
+
|
|
353
|
+
# Leverage the stashed association functionality in AR
|
|
354
|
+
@object = @object.joins(jd)
|
|
355
|
+
found_association
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
def extract_joins(association)
|
|
359
|
+
parent = @join_dependency.instance_variable_get(:@join_root)
|
|
360
|
+
reflection = association.reflection
|
|
361
|
+
join_constraints = association.join_constraints_with_tables(
|
|
362
|
+
parent.table,
|
|
363
|
+
parent.base_klass,
|
|
364
|
+
Arel::Nodes::OuterJoin,
|
|
365
|
+
@join_dependency.instance_variable_get(:@alias_tracker),
|
|
366
|
+
@tables_pot[association]
|
|
367
|
+
)
|
|
368
|
+
join_constraints.to_a.flatten
|
|
369
|
+
end
|
|
370
|
+
end
|
|
371
|
+
end
|
|
372
|
+
end
|
|
373
|
+
end
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
require 'ransack/constants'
|
|
2
|
+
require 'ransack/predicate'
|
|
3
|
+
|
|
4
|
+
module Ransack
|
|
5
|
+
module Configuration
|
|
6
|
+
|
|
7
|
+
mattr_accessor :predicates, :options
|
|
8
|
+
|
|
9
|
+
class PredicateCollection
|
|
10
|
+
attr_reader :sorted_names_with_underscores
|
|
11
|
+
|
|
12
|
+
def initialize
|
|
13
|
+
@collection = {}
|
|
14
|
+
@sorted_names_with_underscores = []
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
delegate :[], :keys, :has_key?, to: :@collection
|
|
18
|
+
|
|
19
|
+
def []=(key, value)
|
|
20
|
+
@sorted_names_with_underscores << [key, '_' + key]
|
|
21
|
+
@sorted_names_with_underscores.sort! { |(a, _), (b, _)| b.length <=> a.length }
|
|
22
|
+
|
|
23
|
+
@collection[key] = value
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
self.predicates = PredicateCollection.new
|
|
28
|
+
|
|
29
|
+
self.options = {
|
|
30
|
+
search_key: :q,
|
|
31
|
+
ignore_unknown_conditions: true,
|
|
32
|
+
hide_sort_order_indicators: false,
|
|
33
|
+
up_arrow: '▼'.freeze,
|
|
34
|
+
down_arrow: '▲'.freeze,
|
|
35
|
+
default_arrow: nil,
|
|
36
|
+
sanitize_scope_args: true,
|
|
37
|
+
postgres_fields_sort_option: nil,
|
|
38
|
+
strip_whitespace: true
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
def configure
|
|
42
|
+
yield self
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def add_predicate(name, opts = {})
|
|
46
|
+
name = name.to_s
|
|
47
|
+
opts[:name] = name
|
|
48
|
+
compounds = opts.delete(:compounds)
|
|
49
|
+
compounds = true if compounds.nil?
|
|
50
|
+
compounds = false if opts[:wants_array]
|
|
51
|
+
|
|
52
|
+
self.predicates[name] = Predicate.new(opts)
|
|
53
|
+
|
|
54
|
+
Constants::SUFFIXES.each do |suffix|
|
|
55
|
+
compound_name = name + suffix
|
|
56
|
+
self.predicates[compound_name] = Predicate.new(
|
|
57
|
+
opts.merge(
|
|
58
|
+
name: compound_name,
|
|
59
|
+
arel_predicate: arel_predicate_with_suffix(
|
|
60
|
+
opts[:arel_predicate], suffix
|
|
61
|
+
),
|
|
62
|
+
compound: true
|
|
63
|
+
)
|
|
64
|
+
)
|
|
65
|
+
end if compounds
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# The default `search_key` name is `:q`. The default key may be overridden
|
|
69
|
+
# in an initializer file like `config/initializers/ransack.rb` as follows:
|
|
70
|
+
#
|
|
71
|
+
# Ransack.configure do |config|
|
|
72
|
+
# # Name the search_key `:query` instead of the default `:q`
|
|
73
|
+
# config.search_key = :query
|
|
74
|
+
# end
|
|
75
|
+
#
|
|
76
|
+
# Sometimes there are situations when the default search parameter name
|
|
77
|
+
# cannot be used, for instance if there were two searches on one page.
|
|
78
|
+
# Another name can be set using the `search_key` option with Ransack
|
|
79
|
+
# `ransack`, `search` and `@search_form_for` methods in controllers & views.
|
|
80
|
+
#
|
|
81
|
+
# In the controller:
|
|
82
|
+
# @search = Log.ransack(params[:log_search], search_key: :log_search)
|
|
83
|
+
#
|
|
84
|
+
# In the view:
|
|
85
|
+
# <%= f.search_form_for @search, as: :log_search %>
|
|
86
|
+
#
|
|
87
|
+
def search_key=(name)
|
|
88
|
+
self.options[:search_key] = name
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# By default Ransack ignores errors if an unknown predicate, condition or
|
|
92
|
+
# attribute is passed into a search. The default may be overridden in an
|
|
93
|
+
# initializer file like `config/initializers/ransack.rb` as follows:
|
|
94
|
+
#
|
|
95
|
+
# Ransack.configure do |config|
|
|
96
|
+
# # Raise if an unknown predicate, condition or attribute is passed
|
|
97
|
+
# config.ignore_unknown_conditions = false
|
|
98
|
+
# end
|
|
99
|
+
#
|
|
100
|
+
def ignore_unknown_conditions=(boolean)
|
|
101
|
+
self.options[:ignore_unknown_conditions] = boolean
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# By default Ransack ignores empty predicates. Ransack can also fallback to
|
|
105
|
+
# a default predicate by setting it in an initializer file
|
|
106
|
+
# like `config/initializers/ransack.rb` as follows:
|
|
107
|
+
#
|
|
108
|
+
# Ransack.configure do |config|
|
|
109
|
+
# # Use the 'eq' predicate if an unknown predicate is passed
|
|
110
|
+
# config.default_predicate = 'eq'
|
|
111
|
+
# end
|
|
112
|
+
#
|
|
113
|
+
def default_predicate=(name)
|
|
114
|
+
self.options[:default_predicate] = name
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# By default, Ransack displays sort order indicator arrows with HTML codes:
|
|
118
|
+
#
|
|
119
|
+
# up_arrow: '▼'
|
|
120
|
+
# down_arrow: '▲'
|
|
121
|
+
#
|
|
122
|
+
# There is also a default arrow which is displayed if a column is not sorted.
|
|
123
|
+
# By default this is nil so nothing will be displayed.
|
|
124
|
+
#
|
|
125
|
+
# Any of the defaults may be globally overridden in an initializer file
|
|
126
|
+
# like `config/initializers/ransack.rb` as follows:
|
|
127
|
+
#
|
|
128
|
+
# Ransack.configure do |config|
|
|
129
|
+
# # Globally set the up arrow to an icon, and the down and default arrows to unicode.
|
|
130
|
+
# config.custom_arrows = {
|
|
131
|
+
# up_arrow: '<i class="fa fa-long-arrow-up"></i>',
|
|
132
|
+
# down_arrow: 'U+02193',
|
|
133
|
+
# default_arrow: 'U+11047'
|
|
134
|
+
# }
|
|
135
|
+
# end
|
|
136
|
+
#
|
|
137
|
+
def custom_arrows=(opts = {})
|
|
138
|
+
self.options[:up_arrow] = opts[:up_arrow].freeze if opts[:up_arrow]
|
|
139
|
+
self.options[:down_arrow] = opts[:down_arrow].freeze if opts[:down_arrow]
|
|
140
|
+
self.options[:default_arrow] = opts[:default_arrow].freeze if opts[:default_arrow]
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Ransack sanitizes many values in your custom scopes into booleans.
|
|
144
|
+
# [1, '1', 't', 'T', 'true', 'TRUE'] all evaluate to true.
|
|
145
|
+
# [0, '0', 'f', 'F', 'false', 'FALSE'] all evaluate to false.
|
|
146
|
+
#
|
|
147
|
+
# This default may be globally overridden in an initializer file like
|
|
148
|
+
# `config/initializers/ransack.rb` as follows:
|
|
149
|
+
#
|
|
150
|
+
# Ransack.configure do |config|
|
|
151
|
+
# # Accept my custom scope values as what they are.
|
|
152
|
+
# config.sanitize_custom_scope_booleans = false
|
|
153
|
+
# end
|
|
154
|
+
#
|
|
155
|
+
def sanitize_custom_scope_booleans=(boolean)
|
|
156
|
+
self.options[:sanitize_scope_args] = boolean
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# The `NULLS FIRST` and `NULLS LAST` options can be used to determine
|
|
160
|
+
# whether nulls appear before or after non-null values in the sort ordering.
|
|
161
|
+
#
|
|
162
|
+
# User may want to configure it like this:
|
|
163
|
+
#
|
|
164
|
+
# Ransack.configure do |c|
|
|
165
|
+
# c.postgres_fields_sort_option = :nulls_first # or e.g. :nulls_always_last
|
|
166
|
+
# end
|
|
167
|
+
#
|
|
168
|
+
# See this feature: https://www.postgresql.org/docs/13/queries-order.html
|
|
169
|
+
#
|
|
170
|
+
def postgres_fields_sort_option=(setting)
|
|
171
|
+
self.options[:postgres_fields_sort_option] = setting
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# By default, Ransack displays sort order indicator arrows in sort links.
|
|
175
|
+
# The default may be globally overridden in an initializer file like
|
|
176
|
+
# `config/initializers/ransack.rb` as follows:
|
|
177
|
+
#
|
|
178
|
+
# Ransack.configure do |config|
|
|
179
|
+
# # Hide sort link order indicators globally across the application
|
|
180
|
+
# config.hide_sort_order_indicators = true
|
|
181
|
+
# end
|
|
182
|
+
#
|
|
183
|
+
def hide_sort_order_indicators=(boolean)
|
|
184
|
+
self.options[:hide_sort_order_indicators] = boolean
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# By default, Ransack displays strips all whitespace when searching for a string.
|
|
188
|
+
# The default may be globally changed in an initializer file like
|
|
189
|
+
# `config/initializers/ransack.rb` as follows:
|
|
190
|
+
#
|
|
191
|
+
# Ransack.configure do |config|
|
|
192
|
+
# # Enable whitespace stripping for string searches
|
|
193
|
+
# config.strip_whitespace = true
|
|
194
|
+
# end
|
|
195
|
+
#
|
|
196
|
+
def strip_whitespace=(boolean)
|
|
197
|
+
self.options[:strip_whitespace] = boolean
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def arel_predicate_with_suffix(arel_predicate, suffix)
|
|
201
|
+
if arel_predicate === Proc
|
|
202
|
+
proc { |v| "#{arel_predicate.call(v)}#{suffix}" }
|
|
203
|
+
else
|
|
204
|
+
"#{arel_predicate}#{suffix}"
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
end
|
|
209
|
+
end
|