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,399 @@
|
|
|
1
|
+
require 'ransack/invalid_search_error'
|
|
2
|
+
|
|
3
|
+
module Ransack
|
|
4
|
+
module Nodes
|
|
5
|
+
class Condition < Node
|
|
6
|
+
i18n_word :attribute, :predicate, :combinator, :value
|
|
7
|
+
i18n_alias a: :attribute, p: :predicate,
|
|
8
|
+
m: :combinator, v: :value
|
|
9
|
+
|
|
10
|
+
attr_accessor :predicate
|
|
11
|
+
|
|
12
|
+
class << self
|
|
13
|
+
def extract(context, key, values)
|
|
14
|
+
attributes, predicate, combinator =
|
|
15
|
+
extract_values_for_condition(key, context)
|
|
16
|
+
|
|
17
|
+
if attributes.size > 0 && predicate
|
|
18
|
+
condition = self.new(context)
|
|
19
|
+
condition.build(
|
|
20
|
+
a: attributes,
|
|
21
|
+
p: predicate.name,
|
|
22
|
+
m: combinator,
|
|
23
|
+
v: predicate.wants_array ? Array(values) : [values]
|
|
24
|
+
)
|
|
25
|
+
# TODO: Figure out what to do with multiple types of attributes,
|
|
26
|
+
# if anything. Tempted to go with "garbage in, garbage out" here.
|
|
27
|
+
if predicate.validate(condition.values, condition.default_type)
|
|
28
|
+
condition
|
|
29
|
+
else
|
|
30
|
+
nil
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def extract_values_for_condition(key, context = nil)
|
|
38
|
+
str = key.dup
|
|
39
|
+
name = Predicate.detect_and_strip_from_string!(str)
|
|
40
|
+
predicate = Predicate.named(name)
|
|
41
|
+
|
|
42
|
+
unless predicate || Ransack.options[:ignore_unknown_conditions]
|
|
43
|
+
raise InvalidSearchError, "No valid predicate for #{key}"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
if context.present?
|
|
47
|
+
str = context.ransackable_alias(str)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
combinator =
|
|
51
|
+
if str.match(/_(or|and)_/)
|
|
52
|
+
$1
|
|
53
|
+
else
|
|
54
|
+
nil
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
if context.present? && context.attribute_method?(str)
|
|
58
|
+
attributes = [str]
|
|
59
|
+
else
|
|
60
|
+
attributes = str.split(/_and_|_or_/)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
[attributes, predicate, combinator]
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def valid?
|
|
68
|
+
attributes.detect(&:valid?) && predicate && valid_arity? &&
|
|
69
|
+
predicate.validate(values, default_type) && valid_combinator?
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def valid_arity?
|
|
73
|
+
values.size <= 1 || predicate.wants_array
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def attributes
|
|
77
|
+
@attributes ||= []
|
|
78
|
+
end
|
|
79
|
+
alias :a :attributes
|
|
80
|
+
|
|
81
|
+
def attributes=(args)
|
|
82
|
+
case args
|
|
83
|
+
when Array
|
|
84
|
+
args.each do |name|
|
|
85
|
+
build_attribute(name)
|
|
86
|
+
end
|
|
87
|
+
when Hash
|
|
88
|
+
args.each do |index, attrs|
|
|
89
|
+
build_attribute(attrs[:name], attrs[:ransacker_args])
|
|
90
|
+
end
|
|
91
|
+
else
|
|
92
|
+
raise ArgumentError,
|
|
93
|
+
"Invalid argument (#{args.class}) supplied to attributes="
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
alias :a= :attributes=
|
|
97
|
+
|
|
98
|
+
def values
|
|
99
|
+
@values ||= []
|
|
100
|
+
end
|
|
101
|
+
alias :v :values
|
|
102
|
+
|
|
103
|
+
def values=(args)
|
|
104
|
+
case args
|
|
105
|
+
when Array
|
|
106
|
+
args.each do |val|
|
|
107
|
+
val = Value.new(@context, val)
|
|
108
|
+
self.values << val
|
|
109
|
+
end
|
|
110
|
+
when Hash
|
|
111
|
+
args.each do |index, attrs|
|
|
112
|
+
val = Value.new(@context, attrs[:value])
|
|
113
|
+
self.values << val
|
|
114
|
+
end
|
|
115
|
+
else
|
|
116
|
+
raise ArgumentError,
|
|
117
|
+
"Invalid argument (#{args.class}) supplied to values="
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
alias :v= :values=
|
|
121
|
+
|
|
122
|
+
def combinator
|
|
123
|
+
@attributes.size > 1 ? @combinator : nil
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def combinator=(val)
|
|
127
|
+
@combinator = Constants::AND_OR.detect { |v| v == val.to_s } || nil
|
|
128
|
+
end
|
|
129
|
+
alias :m= :combinator=
|
|
130
|
+
alias :m :combinator
|
|
131
|
+
|
|
132
|
+
# == build_attribute
|
|
133
|
+
#
|
|
134
|
+
# This method was originally called from Nodes::Grouping#new_condition
|
|
135
|
+
# only, without arguments, without #valid? checking, to build a new
|
|
136
|
+
# grouping condition.
|
|
137
|
+
#
|
|
138
|
+
# After refactoring in 235eae3, it is now called from 2 places:
|
|
139
|
+
#
|
|
140
|
+
# 1. Nodes::Condition#attributes=, with +name+ argument passed or +name+
|
|
141
|
+
# and +ransacker_args+. Attributes are included only if #valid?.
|
|
142
|
+
#
|
|
143
|
+
# 2. Nodes::Grouping#new_condition without arguments. In this case, the
|
|
144
|
+
# #valid? conditional needs to be bypassed, otherwise nothing is
|
|
145
|
+
# built. The `name.nil?` conditional below currently does this.
|
|
146
|
+
#
|
|
147
|
+
# TODO: Add test coverage for this behavior and ensure that `name.nil?`
|
|
148
|
+
# isn't fixing issue #701 by introducing untested regressions.
|
|
149
|
+
#
|
|
150
|
+
def build_attribute(name = nil, ransacker_args = [])
|
|
151
|
+
Attribute.new(@context, name, ransacker_args).tap do |attribute|
|
|
152
|
+
@context.bind(attribute, attribute.name)
|
|
153
|
+
self.attributes << attribute if name.nil? || attribute.valid?
|
|
154
|
+
if predicate && !negative?
|
|
155
|
+
@context.lock_association(attribute.parent)
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def build_value(val = nil)
|
|
161
|
+
Value.new(@context, val).tap do |value|
|
|
162
|
+
self.values << value
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def value
|
|
167
|
+
if predicate.wants_array
|
|
168
|
+
values.map { |v| v.cast(default_type) }
|
|
169
|
+
else
|
|
170
|
+
values.first.cast(default_type)
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def build(params)
|
|
175
|
+
params.with_indifferent_access.each do |key, value|
|
|
176
|
+
if key.match(/^(a|v|p|m)$/)
|
|
177
|
+
self.send("#{key}=", value)
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
self
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def persisted?
|
|
185
|
+
false
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def key
|
|
189
|
+
@key ||= attributes.map(&:name).join("_#{combinator}_") +
|
|
190
|
+
"_#{predicate.name}"
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def eql?(other)
|
|
194
|
+
self.class == other.class &&
|
|
195
|
+
self.attributes == other.attributes &&
|
|
196
|
+
self.predicate == other.predicate &&
|
|
197
|
+
self.values == other.values &&
|
|
198
|
+
self.combinator == other.combinator
|
|
199
|
+
end
|
|
200
|
+
alias :== :eql?
|
|
201
|
+
|
|
202
|
+
def hash
|
|
203
|
+
[attributes, predicate, values, combinator].hash
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def predicate_name=(name)
|
|
207
|
+
self.predicate = Predicate.named(name)
|
|
208
|
+
unless negative?
|
|
209
|
+
attributes.each { |a| context.lock_association(a.parent) }
|
|
210
|
+
end
|
|
211
|
+
@predicate
|
|
212
|
+
end
|
|
213
|
+
alias :p= :predicate_name=
|
|
214
|
+
|
|
215
|
+
def predicate_name
|
|
216
|
+
predicate.name if predicate
|
|
217
|
+
end
|
|
218
|
+
alias :p :predicate_name
|
|
219
|
+
|
|
220
|
+
def arel_predicate
|
|
221
|
+
raise "not implemented"
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def validated_values
|
|
225
|
+
values.select { |v| predicate.validator.call(v.value) }
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def casted_values_for_attribute(attr)
|
|
229
|
+
validated_values.map { |v| v.cast(predicate.type || attr.type) }
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def formatted_values_for_attribute(attr)
|
|
233
|
+
formatted = casted_values_for_attribute(attr).map do |val|
|
|
234
|
+
if attr.ransacker && attr.ransacker.formatter
|
|
235
|
+
val = attr.ransacker.formatter.call(val)
|
|
236
|
+
end
|
|
237
|
+
val = predicate.format(val)
|
|
238
|
+
if val.is_a?(String) && val.include?('%')
|
|
239
|
+
val = Arel::Nodes::Quoted.new(val)
|
|
240
|
+
end
|
|
241
|
+
val
|
|
242
|
+
end
|
|
243
|
+
if predicate.wants_array
|
|
244
|
+
formatted
|
|
245
|
+
else
|
|
246
|
+
formatted.first
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def arel_predicate_for_attribute(attr)
|
|
251
|
+
if predicate.arel_predicate === Proc
|
|
252
|
+
values = casted_values_for_attribute(attr)
|
|
253
|
+
unless predicate.wants_array
|
|
254
|
+
values = values.first
|
|
255
|
+
end
|
|
256
|
+
predicate.arel_predicate.call(values)
|
|
257
|
+
else
|
|
258
|
+
predicate.arel_predicate
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def attr_value_for_attribute(attr)
|
|
263
|
+
return attr.attr if ActiveRecord::Base.connection.adapter_name == "PostgreSQL"
|
|
264
|
+
|
|
265
|
+
predicate.case_insensitive ? attr.attr.lower : attr.attr
|
|
266
|
+
rescue
|
|
267
|
+
attr.attr
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
def default_type
|
|
271
|
+
predicate.type || (attributes.first && attributes.first.type)
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
def inspect
|
|
275
|
+
data = [
|
|
276
|
+
['attributes'.freeze, a.try(:map, &:name)],
|
|
277
|
+
['predicate'.freeze, p],
|
|
278
|
+
[Constants::COMBINATOR, m],
|
|
279
|
+
['values'.freeze, v.try(:map, &:value)]
|
|
280
|
+
]
|
|
281
|
+
.reject { |e| e[1].blank? }
|
|
282
|
+
.map { |v| "#{v[0]}: #{v[1]}" }
|
|
283
|
+
.join(', '.freeze)
|
|
284
|
+
"Condition <#{data}>"
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
def negative?
|
|
288
|
+
predicate.negative?
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
def arel_predicate
|
|
292
|
+
predicate = attributes.map { |attribute|
|
|
293
|
+
association = attribute.parent
|
|
294
|
+
parent_table = association.table
|
|
295
|
+
|
|
296
|
+
if negative? && attribute.associated_collection? && not_nested_condition(attribute, parent_table)
|
|
297
|
+
query = context.build_correlated_subquery(association)
|
|
298
|
+
context.remove_association(association)
|
|
299
|
+
|
|
300
|
+
case self.predicate_name
|
|
301
|
+
when 'not_null'
|
|
302
|
+
if self.value
|
|
303
|
+
query.where(format_predicate(attribute))
|
|
304
|
+
Arel::Nodes::In.new(context.primary_key, Arel.sql(query.to_sql))
|
|
305
|
+
else
|
|
306
|
+
query.where(format_predicate(attribute).not)
|
|
307
|
+
Arel::Nodes::NotIn.new(context.primary_key, Arel.sql(query.to_sql))
|
|
308
|
+
end
|
|
309
|
+
when 'not_cont'
|
|
310
|
+
query.where(attribute.attr.matches(formatted_values_for_attribute(attribute)))
|
|
311
|
+
Arel::Nodes::NotIn.new(context.primary_key, Arel.sql(query.to_sql))
|
|
312
|
+
else
|
|
313
|
+
query.where(format_predicate(attribute).not)
|
|
314
|
+
Arel::Nodes::NotIn.new(context.primary_key, Arel.sql(query.to_sql))
|
|
315
|
+
end
|
|
316
|
+
else
|
|
317
|
+
format_predicate(attribute)
|
|
318
|
+
end
|
|
319
|
+
}.reduce(combinator_method)
|
|
320
|
+
|
|
321
|
+
if replace_right_node?(predicate)
|
|
322
|
+
# Replace right node object to plain integer value in order to avoid
|
|
323
|
+
# ActiveModel::RangeError from Arel::Node::Casted.
|
|
324
|
+
# The error can be ignored here because RDBMSs accept large numbers
|
|
325
|
+
# in condition clauses.
|
|
326
|
+
plain_value = predicate.right.value
|
|
327
|
+
predicate.right = plain_value
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
predicate
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
def not_nested_condition(attribute, parent_table)
|
|
334
|
+
parent_table.class != Arel::Nodes::TableAlias && attribute.name.starts_with?(parent_table.name)
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
private
|
|
338
|
+
|
|
339
|
+
def combinator_method
|
|
340
|
+
combinator === Constants::OR ? :or : :and
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
def format_predicate(attribute)
|
|
344
|
+
arel_pred = arel_predicate_for_attribute(attribute)
|
|
345
|
+
arel_values = formatted_values_for_attribute(attribute)
|
|
346
|
+
|
|
347
|
+
# For LIKE predicates, wrap the value in Arel::Nodes.build_quoted to prevent
|
|
348
|
+
# ActiveRecord normalization from affecting wildcard patterns
|
|
349
|
+
if like_predicate?(arel_pred)
|
|
350
|
+
arel_values = Arel::Nodes.build_quoted(arel_values)
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
predicate = attr_value_for_attribute(attribute).public_send(arel_pred, arel_values)
|
|
354
|
+
|
|
355
|
+
if in_predicate?(predicate)
|
|
356
|
+
predicate.right = predicate.right.map do |pr|
|
|
357
|
+
casted_array?(pr) ? format_values_for(pr) : pr
|
|
358
|
+
end
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
predicate
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
def in_predicate?(predicate)
|
|
365
|
+
return unless defined?(Arel::Nodes::Casted)
|
|
366
|
+
predicate.class == Arel::Nodes::In || predicate.class == Arel::Nodes::NotIn
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
def like_predicate?(arel_predicate)
|
|
370
|
+
arel_predicate == 'matches' || arel_predicate == 'does_not_match'
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
def casted_array?(predicate)
|
|
374
|
+
predicate.is_a?(Arel::Nodes::Casted) && predicate.value.is_a?(Array)
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
def format_values_for(predicate)
|
|
378
|
+
predicate.value.map do |val|
|
|
379
|
+
val.is_a?(String) ? Arel::Nodes.build_quoted(val) : val
|
|
380
|
+
end
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
def replace_right_node?(predicate)
|
|
384
|
+
return false unless predicate.is_a?(Arel::Nodes::Binary)
|
|
385
|
+
|
|
386
|
+
arel_node = predicate.right
|
|
387
|
+
return false unless arel_node.is_a?(Arel::Nodes::Casted)
|
|
388
|
+
|
|
389
|
+
relation, name = arel_node.attribute.values
|
|
390
|
+
attribute_type = relation.type_for_attribute(name).type
|
|
391
|
+
attribute_type == :integer && arel_node.value.is_a?(Integer)
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
def valid_combinator?
|
|
395
|
+
attributes.size < 2 || Constants::AND_OR.include?(combinator)
|
|
396
|
+
end
|
|
397
|
+
end
|
|
398
|
+
end
|
|
399
|
+
end
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
module Ransack
|
|
2
|
+
module Nodes
|
|
3
|
+
class Grouping < Node
|
|
4
|
+
attr_reader :conditions
|
|
5
|
+
attr_accessor :combinator
|
|
6
|
+
alias :m :combinator
|
|
7
|
+
alias :m= :combinator=
|
|
8
|
+
|
|
9
|
+
i18n_word :condition, :and, :or
|
|
10
|
+
i18n_alias c: :condition, n: :and, o: :or
|
|
11
|
+
|
|
12
|
+
delegate :each, to: :values
|
|
13
|
+
|
|
14
|
+
def initialize(context, combinator = nil)
|
|
15
|
+
super(context)
|
|
16
|
+
self.combinator = combinator.to_s if combinator
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def persisted?
|
|
20
|
+
false
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def translate(key, options = {})
|
|
24
|
+
super or Translate.attribute(
|
|
25
|
+
key.to_s, options.merge(context: context)
|
|
26
|
+
)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def conditions
|
|
30
|
+
@conditions ||= []
|
|
31
|
+
end
|
|
32
|
+
alias :c :conditions
|
|
33
|
+
|
|
34
|
+
def conditions=(conditions)
|
|
35
|
+
case conditions
|
|
36
|
+
when Array
|
|
37
|
+
conditions.each do |attrs|
|
|
38
|
+
condition = Condition.new(@context).build(attrs)
|
|
39
|
+
self.conditions << condition if condition.valid?
|
|
40
|
+
end
|
|
41
|
+
when Hash
|
|
42
|
+
conditions.each do |index, attrs|
|
|
43
|
+
condition = Condition.new(@context).build(attrs)
|
|
44
|
+
self.conditions << condition if condition.valid?
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
remove_duplicate_conditions!
|
|
48
|
+
end
|
|
49
|
+
alias :c= :conditions=
|
|
50
|
+
|
|
51
|
+
def [](key)
|
|
52
|
+
conditions.detect { |c| c.key == key.to_s }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def []=(key, value)
|
|
56
|
+
conditions.reject! { |c| c.key == key.to_s && c&.value == value&.value }
|
|
57
|
+
self.conditions << value
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def values
|
|
61
|
+
conditions + groupings
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def respond_to?(method_id)
|
|
65
|
+
super or begin
|
|
66
|
+
method_name = method_id.to_s
|
|
67
|
+
attribute_method?(method_name) ? true : false
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def build_condition(opts = {})
|
|
72
|
+
new_condition(opts).tap do |condition|
|
|
73
|
+
self.conditions << condition
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def new_condition(opts = {})
|
|
78
|
+
attrs = opts[:attributes] || 1
|
|
79
|
+
vals = opts[:values] || 1
|
|
80
|
+
condition = Condition.new(@context)
|
|
81
|
+
attrs.times { condition.build_attribute }
|
|
82
|
+
vals.times { condition.build_value }
|
|
83
|
+
condition
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def groupings
|
|
87
|
+
@groupings ||= []
|
|
88
|
+
end
|
|
89
|
+
alias :g :groupings
|
|
90
|
+
|
|
91
|
+
def groupings=(groupings)
|
|
92
|
+
case groupings
|
|
93
|
+
when Array
|
|
94
|
+
groupings.each do |attrs|
|
|
95
|
+
grouping_object = new_grouping(attrs)
|
|
96
|
+
self.groupings << grouping_object if grouping_object.values.any?
|
|
97
|
+
end
|
|
98
|
+
when Hash
|
|
99
|
+
groupings.each do |index, attrs|
|
|
100
|
+
grouping_object = new_grouping(attrs)
|
|
101
|
+
self.groupings << grouping_object if grouping_object.values.any?
|
|
102
|
+
end
|
|
103
|
+
else
|
|
104
|
+
raise ArgumentError,
|
|
105
|
+
"Invalid argument (#{groupings.class}) supplied to groupings="
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
alias :g= :groupings=
|
|
109
|
+
|
|
110
|
+
def method_missing(method_id, *args)
|
|
111
|
+
method_name = method_id.to_s.dup
|
|
112
|
+
writer = method_name.sub!(/\=$/, ''.freeze)
|
|
113
|
+
if attribute_method?(method_name)
|
|
114
|
+
if writer
|
|
115
|
+
write_attribute(method_name, *args)
|
|
116
|
+
else
|
|
117
|
+
read_attribute(method_name)
|
|
118
|
+
end
|
|
119
|
+
else
|
|
120
|
+
super
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def attribute_method?(name)
|
|
125
|
+
stripped_name = strip_predicate_and_index(name)
|
|
126
|
+
return true if @context.attribute_method?(stripped_name) ||
|
|
127
|
+
@context.attribute_method?(name)
|
|
128
|
+
case stripped_name
|
|
129
|
+
when /^(g|c|m|groupings|conditions|combinator)=?$/
|
|
130
|
+
true
|
|
131
|
+
else
|
|
132
|
+
stripped_name
|
|
133
|
+
.split(/_and_|_or_/)
|
|
134
|
+
.none? { |n| !@context.attribute_method?(n) }
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def build_grouping(params = {})
|
|
139
|
+
params ||= {}
|
|
140
|
+
new_grouping(params).tap do |new_grouping|
|
|
141
|
+
self.groupings << new_grouping
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def new_grouping(params = {})
|
|
146
|
+
Grouping.new(@context).build(params)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def build(params)
|
|
150
|
+
params.with_indifferent_access.each do |key, value|
|
|
151
|
+
case key
|
|
152
|
+
when /^(g|c|m)$/
|
|
153
|
+
self.send("#{key}=", value)
|
|
154
|
+
else
|
|
155
|
+
write_attribute(key.to_s, value)
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
self
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def inspect
|
|
162
|
+
data = [
|
|
163
|
+
['conditions'.freeze, conditions], [Constants::COMBINATOR, combinator]
|
|
164
|
+
]
|
|
165
|
+
.reject { |e| e[1].blank? }
|
|
166
|
+
.map { |v| "#{v[0]}: #{v[1]}" }
|
|
167
|
+
.join(', '.freeze)
|
|
168
|
+
"Grouping <#{data}>"
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
private
|
|
172
|
+
|
|
173
|
+
def write_attribute(name, val)
|
|
174
|
+
# TODO: Methods
|
|
175
|
+
if condition = Condition.extract(@context, name, val)
|
|
176
|
+
self[name] = condition
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def read_attribute(name)
|
|
181
|
+
if self[name].respond_to?(:value)
|
|
182
|
+
self[name].value
|
|
183
|
+
else
|
|
184
|
+
self[name]
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def strip_predicate_and_index(str)
|
|
189
|
+
string = str[/(.+?)\(/, 1] || str.dup
|
|
190
|
+
Predicate.detect_and_strip_from_string!(string)
|
|
191
|
+
string
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def remove_duplicate_conditions!
|
|
195
|
+
# If self.conditions.uniq! is called without passing a block, then
|
|
196
|
+
# conditions differing only by ransacker_args within attributes are
|
|
197
|
+
# wrongly considered equal and are removed.
|
|
198
|
+
self.conditions.uniq! do |c|
|
|
199
|
+
c.attributes.map { |a| [a.name, a.ransacker_args] }.flatten +
|
|
200
|
+
[c.predicate.name] +
|
|
201
|
+
c.values.map { |v| v.value }
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module Ransack
|
|
2
|
+
module Nodes
|
|
3
|
+
class Node
|
|
4
|
+
attr_reader :context
|
|
5
|
+
delegate :contextualize, to: :context
|
|
6
|
+
class_attribute :i18n_words
|
|
7
|
+
class_attribute :i18n_aliases
|
|
8
|
+
self.i18n_words = []
|
|
9
|
+
self.i18n_aliases = {}
|
|
10
|
+
|
|
11
|
+
class << self
|
|
12
|
+
def i18n_word(*args)
|
|
13
|
+
self.i18n_words += args.map(&:to_s)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def i18n_alias(opts = {})
|
|
17
|
+
self.i18n_aliases.merge! Hash[opts.map { |k, v| [k.to_s, v.to_s] }]
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def initialize(context)
|
|
22
|
+
@context = context
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def translate(key, options = {})
|
|
26
|
+
key = i18n_aliases[key.to_s] if i18n_aliases.has_key?(key.to_s)
|
|
27
|
+
if i18n_words.include?(key.to_s)
|
|
28
|
+
Translate.word(key)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
module Ransack
|
|
2
|
+
module Nodes
|
|
3
|
+
class Sort < Node
|
|
4
|
+
include Bindable
|
|
5
|
+
|
|
6
|
+
attr_reader :name, :dir, :ransacker_args
|
|
7
|
+
i18n_word :asc, :desc
|
|
8
|
+
|
|
9
|
+
class << self
|
|
10
|
+
def extract(context, str)
|
|
11
|
+
return if str.blank?
|
|
12
|
+
attr, direction = str.split(/\s+/, 2)
|
|
13
|
+
self.new(context).build(name: attr, dir: direction)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def build(params)
|
|
18
|
+
params.with_indifferent_access.each do |key, value|
|
|
19
|
+
if key.match(/^(name|dir|ransacker_args)$/)
|
|
20
|
+
self.send("#{key}=", value)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
self
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def valid?
|
|
28
|
+
bound? && attr &&
|
|
29
|
+
context.klassify(parent).ransortable_attributes(context.auth_object)
|
|
30
|
+
.include?(attr_name)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def name=(name)
|
|
34
|
+
@name = context.ransackable_alias(name) || name
|
|
35
|
+
context.bind(self, @name)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def dir=(dir)
|
|
39
|
+
dir = dir.downcase if dir
|
|
40
|
+
@dir =
|
|
41
|
+
if dir == 'asc'.freeze || dir == 'desc'.freeze
|
|
42
|
+
dir
|
|
43
|
+
else
|
|
44
|
+
'asc'.freeze
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def ransacker_args=(ransack_args)
|
|
49
|
+
@ransacker_args = ransack_args
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|