ransack 6.0.1 → 6.1.0
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 +4 -4
- data/README.md +43 -68
- data/lib/ransack/active_record/context.rb +207 -75
- data/lib/ransack/active_record/join_association.rb +8 -17
- data/lib/ransack/active_record/join_dependency.rb +163 -9
- data/lib/ransack/configuration.rb +25 -2
- data/lib/ransack/context.rb +55 -7
- data/lib/ransack/errors.rb +13 -0
- data/lib/ransack/invalid_search_error.rb +3 -3
- data/lib/ransack/locale/ar.yml +35 -0
- data/lib/ransack/locale/az.yml +36 -1
- data/lib/ransack/locale/bg.yml +35 -0
- data/lib/ransack/locale/ca.yml +36 -1
- data/lib/ransack/locale/cs.yml +35 -0
- data/lib/ransack/locale/da.yml +35 -0
- data/lib/ransack/locale/de.yml +36 -1
- data/lib/ransack/locale/el.yml +36 -1
- data/lib/ransack/locale/en.yml +35 -0
- data/lib/ransack/locale/es.yml +36 -1
- data/lib/ransack/locale/fa.yml +35 -0
- data/lib/ransack/locale/fi.yml +35 -0
- data/lib/ransack/locale/fr.yml +35 -0
- data/lib/ransack/locale/hu.yml +35 -0
- data/lib/ransack/locale/id.yml +35 -0
- data/lib/ransack/locale/it.yml +35 -0
- data/lib/ransack/locale/ja.yml +35 -0
- data/lib/ransack/locale/ko.yml +35 -0
- data/lib/ransack/locale/nl.yml +35 -0
- data/lib/ransack/locale/pt-BR.yml +35 -0
- data/lib/ransack/locale/ro.yml +35 -0
- data/lib/ransack/locale/ru.yml +35 -0
- data/lib/ransack/locale/sk.yml +35 -0
- data/lib/ransack/locale/sv.yml +35 -0
- data/lib/ransack/locale/tr.yml +36 -1
- data/lib/ransack/locale/uk.yml +35 -0
- data/lib/ransack/locale/zh-CN.yml +35 -0
- data/lib/ransack/locale/zh-TW.yml +35 -0
- data/lib/ransack/nodes/condition.rb +168 -42
- data/lib/ransack/nodes/grouping.rb +8 -17
- data/lib/ransack/nodes/node.rb +23 -1
- data/lib/ransack/nodes/sort.rb +7 -4
- data/lib/ransack/nodes/value.rb +6 -0
- data/lib/ransack/predicate.rb +19 -6
- data/lib/ransack/search.rb +21 -10
- data/lib/ransack/translate.rb +43 -35
- data/lib/ransack/version.rb +1 -1
- data/lib/ransack.rb +2 -1
- metadata +7 -6
data/lib/ransack/locale/tr.yml
CHANGED
|
@@ -32,7 +32,7 @@ tr:
|
|
|
32
32
|
lteq: "daha küçük veya eşit"
|
|
33
33
|
lteq_any: "daha küçük veya herhangi birine eşit"
|
|
34
34
|
lteq_all: "daha küçük veya hepsine eşit"
|
|
35
|
-
gt: "daha büyük
|
|
35
|
+
gt: "daha büyük"
|
|
36
36
|
gt_any: "herhangi birinden daha büyük"
|
|
37
37
|
gt_all: "hepsinden daha büyük"
|
|
38
38
|
gteq: "daha büyük veya eşit"
|
|
@@ -50,21 +50,56 @@ tr:
|
|
|
50
50
|
not_cont: "içermeyen"
|
|
51
51
|
not_cont_any: "herhangi birini içermeyen"
|
|
52
52
|
not_cont_all: "hiçbirini birini içermeyen"
|
|
53
|
+
i_cont: "içeren, büyük-küçük harf ayrımı olmadan"
|
|
54
|
+
i_cont_any: "herhangi birini içeren, büyük-küçük harf ayrımı olmadan"
|
|
55
|
+
i_cont_all: "hepsini içeren, büyük-küçük harf ayrımı olmadan"
|
|
56
|
+
not_i_cont: "içermeyen, büyük-küçük harf ayrımı olmadan"
|
|
57
|
+
not_i_cont_any: "herhangi birini içermeyen, büyük-küçük harf ayrımı olmadan"
|
|
58
|
+
not_i_cont_all: "hiçbirini birini içermeyen, büyük-küçük harf ayrımı olmadan"
|
|
53
59
|
start: "ile başlayan"
|
|
54
60
|
start_any: "herhangi biriyle başlayan"
|
|
55
61
|
start_all: "hepsiyle başlayan"
|
|
56
62
|
not_start: "ile başlamayan"
|
|
57
63
|
not_start_any: "herhangi biriyle başlamayan"
|
|
58
64
|
not_start_all: "hiçbiriyle başlamayan"
|
|
65
|
+
i_start: "ile başlayan, büyük-küçük harf ayrımı olmadan"
|
|
66
|
+
i_start_any: "herhangi biriyle başlayan, büyük-küçük harf ayrımı olmadan"
|
|
67
|
+
i_start_all: "hepsiyle başlayan, büyük-küçük harf ayrımı olmadan"
|
|
68
|
+
not_i_start: "ile başlamayan, büyük-küçük harf ayrımı olmadan"
|
|
69
|
+
not_i_start_any: "herhangi biriyle başlamayan, büyük-küçük harf ayrımı olmadan"
|
|
70
|
+
not_i_start_all: "hiçbiriyle başlamayan, büyük-küçük harf ayrımı olmadan"
|
|
59
71
|
end: "ile biten"
|
|
60
72
|
end_any: "herhangi biriyle biten"
|
|
61
73
|
end_all: "hepsi ile biten"
|
|
62
74
|
not_end: "ile bitmeyen"
|
|
63
75
|
not_end_any: "herhangi biriyle bitmeyen"
|
|
64
76
|
not_end_all: "hiçbiriyle bitmeyen"
|
|
77
|
+
i_end: "ile biten, büyük-küçük harf ayrımı olmadan"
|
|
78
|
+
i_end_any: "herhangi biriyle biten, büyük-küçük harf ayrımı olmadan"
|
|
79
|
+
i_end_all: "hepsi ile biten, büyük-küçük harf ayrımı olmadan"
|
|
80
|
+
not_i_end: "ile bitmeyen, büyük-küçük harf ayrımı olmadan"
|
|
81
|
+
not_i_end_any: "herhangi biriyle bitmeyen, büyük-küçük harf ayrımı olmadan"
|
|
82
|
+
not_i_end_all: "hiçbiriyle bitmeyen, büyük-küçük harf ayrımı olmadan"
|
|
65
83
|
'true': "doğru"
|
|
84
|
+
not_true: "doğru değil"
|
|
66
85
|
'false': "yanlış"
|
|
86
|
+
not_false: "yanlış değil"
|
|
67
87
|
present: "mevcut"
|
|
68
88
|
blank: "boş"
|
|
69
89
|
'null': "geçersiz"
|
|
70
90
|
not_null: "geçerli"
|
|
91
|
+
length_eq: "uzunluğu eşit"
|
|
92
|
+
length_eq_any: "uzunluğu herhangi birine eşit"
|
|
93
|
+
length_eq_all: "uzunluğu hepsine eşit"
|
|
94
|
+
length_lt: "uzunluğu daha küçük"
|
|
95
|
+
length_lt_any: "uzunluğu herhangi birinden küçük"
|
|
96
|
+
length_lt_all: "uzunluğu hepsinden küçük"
|
|
97
|
+
length_lteq: "uzunluğu daha küçük veya eşit"
|
|
98
|
+
length_lteq_any: "uzunluğu daha küçük veya herhangi birine eşit"
|
|
99
|
+
length_lteq_all: "uzunluğu daha küçük veya hepsine eşit"
|
|
100
|
+
length_gt: "uzunluğu daha büyük"
|
|
101
|
+
length_gt_any: "uzunluğu herhangi birinden daha büyük"
|
|
102
|
+
length_gt_all: "uzunluğu hepsinden daha büyük"
|
|
103
|
+
length_gteq: "uzunluğu daha büyük veya eşit"
|
|
104
|
+
length_gteq_any: "uzunluğu daha büyük veya herhangi birine eşit"
|
|
105
|
+
length_gteq_all: "uzunluğu daha büyük veya hepsine eşit"
|
data/lib/ransack/locale/uk.yml
CHANGED
|
@@ -52,21 +52,56 @@ uk:
|
|
|
52
52
|
not_cont: не містить
|
|
53
53
|
not_cont_any: не містить жодного
|
|
54
54
|
not_cont_all: не містить усіх
|
|
55
|
+
i_cont: містить, без урахування регістру
|
|
56
|
+
i_cont_any: містить будь-який, без урахування регістру
|
|
57
|
+
i_cont_all: містить усі, без урахування регістру
|
|
58
|
+
not_i_cont: не містить, без урахування регістру
|
|
59
|
+
not_i_cont_any: не містить жодного, без урахування регістру
|
|
60
|
+
not_i_cont_all: не містить усіх, без урахування регістру
|
|
55
61
|
start: починається з
|
|
56
62
|
start_any: починається з будь-якого
|
|
57
63
|
start_all: починається з усіх
|
|
58
64
|
not_start: не починається з
|
|
59
65
|
not_start_any: не починається з будь-якого
|
|
60
66
|
not_start_all: не починається з усіх
|
|
67
|
+
i_start: починається з, без урахування регістру
|
|
68
|
+
i_start_any: починається з будь-якого, без урахування регістру
|
|
69
|
+
i_start_all: починається з усіх, без урахування регістру
|
|
70
|
+
not_i_start: не починається з, без урахування регістру
|
|
71
|
+
not_i_start_any: не починається з будь-якого, без урахування регістру
|
|
72
|
+
not_i_start_all: не починається з усіх, без урахування регістру
|
|
61
73
|
end: закінчується на
|
|
62
74
|
end_any: закінчується на будь-який
|
|
63
75
|
end_all: закінчується на всі
|
|
64
76
|
not_end: не закінчується на
|
|
65
77
|
not_end_any: не закінчується на будь-який
|
|
66
78
|
not_end_all: не закінчується на всі
|
|
79
|
+
i_end: закінчується на, без урахування регістру
|
|
80
|
+
i_end_any: закінчується на будь-який, без урахування регістру
|
|
81
|
+
i_end_all: закінчується на всі, без урахування регістру
|
|
82
|
+
not_i_end: не закінчується на, без урахування регістру
|
|
83
|
+
not_i_end_any: не закінчується на будь-який, без урахування регістру
|
|
84
|
+
not_i_end_all: не закінчується на всі, без урахування регістру
|
|
67
85
|
'true': так
|
|
86
|
+
not_true: не істина
|
|
68
87
|
'false': ні
|
|
88
|
+
not_false: не хиба
|
|
69
89
|
present: присутній
|
|
70
90
|
blank: порожній
|
|
71
91
|
'null': нульовий
|
|
72
92
|
not_null: не нульовий
|
|
93
|
+
length_eq: довжина рівна
|
|
94
|
+
length_eq_any: довжина рівна будь-якому
|
|
95
|
+
length_eq_all: довжина рівна усім
|
|
96
|
+
length_lt: довжина менше ніж
|
|
97
|
+
length_lt_any: довжина менше за будь-який
|
|
98
|
+
length_lt_all: довжина менше за всі
|
|
99
|
+
length_lteq: довжина менше або рівна
|
|
100
|
+
length_lteq_any: довжина менше або рівна будь-якому
|
|
101
|
+
length_lteq_all: довжина менше або рівна всім
|
|
102
|
+
length_gt: довжина більше ніж
|
|
103
|
+
length_gt_any: довжина більше ніж будь-який
|
|
104
|
+
length_gt_all: довжина більше ніж усі
|
|
105
|
+
length_gteq: довжина більше або рівна
|
|
106
|
+
length_gteq_any: довжина більше або рівна будь-якому
|
|
107
|
+
length_gteq_all: довжина більше або рівна всім
|
|
@@ -50,21 +50,56 @@ zh-CN:
|
|
|
50
50
|
not_cont: "不包含"
|
|
51
51
|
not_cont_any: "不包含任意一个值"
|
|
52
52
|
not_cont_all: "不包含所有值"
|
|
53
|
+
i_cont: "包含(忽略大小写)"
|
|
54
|
+
i_cont_any: "包含任意一个值(忽略大小写)"
|
|
55
|
+
i_cont_all: "包含所有值(忽略大小写)"
|
|
56
|
+
not_i_cont: "不包含(忽略大小写)"
|
|
57
|
+
not_i_cont_any: "不包含任意一个值(忽略大小写)"
|
|
58
|
+
not_i_cont_all: "不包含所有值(忽略大小写)"
|
|
53
59
|
start: "始于"
|
|
54
60
|
start_any: "始于任一值"
|
|
55
61
|
start_all: "始于任意值"
|
|
56
62
|
not_start: "非始于"
|
|
57
63
|
not_start_any: "非始于任一值"
|
|
58
64
|
not_start_all: "非始于任意值"
|
|
65
|
+
i_start: "始于(忽略大小写)"
|
|
66
|
+
i_start_any: "始于任一值(忽略大小写)"
|
|
67
|
+
i_start_all: "始于任意值(忽略大小写)"
|
|
68
|
+
not_i_start: "非始于(忽略大小写)"
|
|
69
|
+
not_i_start_any: "非始于任一值(忽略大小写)"
|
|
70
|
+
not_i_start_all: "非始于任意值(忽略大小写)"
|
|
59
71
|
end: "止于"
|
|
60
72
|
end_any: "止于任一值"
|
|
61
73
|
end_all: "止于任意值"
|
|
62
74
|
not_end: "非止于"
|
|
63
75
|
not_end_any: "非止于任一值"
|
|
64
76
|
not_end_all: "非止于任意值"
|
|
77
|
+
i_end: "止于(忽略大小写)"
|
|
78
|
+
i_end_any: "止于任一值(忽略大小写)"
|
|
79
|
+
i_end_all: "止于任意值(忽略大小写)"
|
|
80
|
+
not_i_end: "非止于(忽略大小写)"
|
|
81
|
+
not_i_end_any: "非止于任一值(忽略大小写)"
|
|
82
|
+
not_i_end_all: "非止于任意值(忽略大小写)"
|
|
65
83
|
'true': "等于true"
|
|
84
|
+
not_true: "不等于true"
|
|
66
85
|
'false': "等于false"
|
|
86
|
+
not_false: "不等于false"
|
|
67
87
|
present: "有值"
|
|
68
88
|
blank: "为空"
|
|
69
89
|
'null': "是null"
|
|
70
90
|
not_null: "不是null"
|
|
91
|
+
length_eq: "长度等于"
|
|
92
|
+
length_eq_any: "长度等于任意值"
|
|
93
|
+
length_eq_all: "长度等于所有值"
|
|
94
|
+
length_lt: "长度小于"
|
|
95
|
+
length_lt_any: "长度小于任意一个值"
|
|
96
|
+
length_lt_all: "长度小于所有值"
|
|
97
|
+
length_lteq: "长度小于等于"
|
|
98
|
+
length_lteq_any: "长度小于等于任意一个值"
|
|
99
|
+
length_lteq_all: "长度小于等于所有值"
|
|
100
|
+
length_gt: "长度大于"
|
|
101
|
+
length_gt_any: "长度大于任意一个值"
|
|
102
|
+
length_gt_all: "长度大于所有值"
|
|
103
|
+
length_gteq: "长度大于等于"
|
|
104
|
+
length_gteq_any: "长度大于等于任意一个值"
|
|
105
|
+
length_gteq_all: "长度大于等于所有值"
|
|
@@ -50,21 +50,56 @@ zh-TW:
|
|
|
50
50
|
not_cont: "不包含"
|
|
51
51
|
not_cont_any: "不包含任何一個值"
|
|
52
52
|
not_cont_all: "不包含所有值"
|
|
53
|
+
i_cont: "包含(忽略大小寫)"
|
|
54
|
+
i_cont_any: "包含任何一個值(忽略大小寫)"
|
|
55
|
+
i_cont_all: "包含所有值(忽略大小寫)"
|
|
56
|
+
not_i_cont: "不包含(忽略大小寫)"
|
|
57
|
+
not_i_cont_any: "不包含任何一個值(忽略大小寫)"
|
|
58
|
+
not_i_cont_all: "不包含所有值(忽略大小寫)"
|
|
53
59
|
start: "以某個值開始"
|
|
54
60
|
start_any: "以任何一個值開始"
|
|
55
61
|
start_all: "以所有值開始"
|
|
56
62
|
not_start: "不以某個值開始"
|
|
57
63
|
not_start_any: "不以任何一值開始"
|
|
58
64
|
not_start_all: "不以所有值開始"
|
|
65
|
+
i_start: "以某個值開始(忽略大小寫)"
|
|
66
|
+
i_start_any: "以任何一個值開始(忽略大小寫)"
|
|
67
|
+
i_start_all: "以所有值開始(忽略大小寫)"
|
|
68
|
+
not_i_start: "不以某個值開始(忽略大小寫)"
|
|
69
|
+
not_i_start_any: "不以任何一值開始(忽略大小寫)"
|
|
70
|
+
not_i_start_all: "不以所有值開始(忽略大小寫)"
|
|
59
71
|
end: "以某個值結尾"
|
|
60
72
|
end_any: "以任何一個值結尾"
|
|
61
73
|
end_all: "以所有值結尾"
|
|
62
74
|
not_end: "不以某個值結尾"
|
|
63
75
|
not_end_any: "不以任何一個值結尾"
|
|
64
76
|
not_end_all: "不以所有值結尾"
|
|
77
|
+
i_end: "以某個值結尾(忽略大小寫)"
|
|
78
|
+
i_end_any: "以任何一個值結尾(忽略大小寫)"
|
|
79
|
+
i_end_all: "以所有值結尾(忽略大小寫)"
|
|
80
|
+
not_i_end: "不以某個值結尾(忽略大小寫)"
|
|
81
|
+
not_i_end_any: "不以任何一個值結尾(忽略大小寫)"
|
|
82
|
+
not_i_end_all: "不以所有值結尾(忽略大小寫)"
|
|
65
83
|
'true': "為真"
|
|
84
|
+
not_true: "不為真"
|
|
66
85
|
'false': "為假"
|
|
86
|
+
not_false: "不為假"
|
|
67
87
|
present: "有值"
|
|
68
88
|
blank: "為空"
|
|
69
89
|
'null': "為 null"
|
|
70
90
|
not_null: "不為 null"
|
|
91
|
+
length_eq: "長度等於"
|
|
92
|
+
length_eq_any: "長度等於任何一個值"
|
|
93
|
+
length_eq_all: "長度等於所有值"
|
|
94
|
+
length_lt: "長度小於"
|
|
95
|
+
length_lt_any: "長度小於任何一個值"
|
|
96
|
+
length_lt_all: "長度小於所有值"
|
|
97
|
+
length_lteq: "長度小於或等於"
|
|
98
|
+
length_lteq_any: "長度小於或等於任何一個值"
|
|
99
|
+
length_lteq_all: "長度小於或等於所有值"
|
|
100
|
+
length_gt: "長度大於"
|
|
101
|
+
length_gt_any: "長度大於任何一個值"
|
|
102
|
+
length_gt_all: "長度大於所有值"
|
|
103
|
+
length_gteq: "長度大於或等於"
|
|
104
|
+
length_gteq_any: "長度大於或等於任何一個值"
|
|
105
|
+
length_gteq_all: "長度大於或等於所有值"
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
require 'ransack/
|
|
1
|
+
require 'ransack/errors'
|
|
2
2
|
|
|
3
3
|
module Ransack
|
|
4
4
|
module Nodes
|
|
@@ -34,9 +34,15 @@ module Ransack
|
|
|
34
34
|
m: combinator,
|
|
35
35
|
v: predicate.wants_array ? Array(values) : [values]
|
|
36
36
|
)
|
|
37
|
+
# An Array given to a single-value predicate is spread into
|
|
38
|
+
# several values by #build, so it fails the arity check here the
|
|
39
|
+
# same way the c: form does (#1772).
|
|
40
|
+
if condition.arity_failure?
|
|
41
|
+
raise condition.arity_error if context&.strict_conditions?
|
|
42
|
+
nil
|
|
37
43
|
# TODO: Figure out what to do with multiple types of attributes,
|
|
38
44
|
# if anything. Tempted to go with "garbage in, garbage out" here.
|
|
39
|
-
|
|
45
|
+
elsif predicate.validate(condition.values, condition.default_type, condition.null_sentinel)
|
|
40
46
|
condition
|
|
41
47
|
else
|
|
42
48
|
nil
|
|
@@ -97,6 +103,21 @@ module Ransack
|
|
|
97
103
|
values.size <= 1 || predicate.wants_array
|
|
98
104
|
end
|
|
99
105
|
|
|
106
|
+
# A single-value predicate given several values is worth naming, so a
|
|
107
|
+
# strict search can say why the condition was dropped (#1724). It only
|
|
108
|
+
# counts once the predicate and an attribute have resolved; a condition
|
|
109
|
+
# that is invalid for another reason is not an arity failure.
|
|
110
|
+
def arity_failure?
|
|
111
|
+
predicate && attributes.any?(&:valid?) && !valid_arity?
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def arity_error
|
|
115
|
+
InvalidSearchError.new(
|
|
116
|
+
"Predicate #{predicate_name} takes a single value, " \
|
|
117
|
+
"#{values.size} given for #{attributes.map(&:name).join(', ')}"
|
|
118
|
+
)
|
|
119
|
+
end
|
|
120
|
+
|
|
100
121
|
def attributes
|
|
101
122
|
@attributes ||= []
|
|
102
123
|
end
|
|
@@ -115,10 +136,11 @@ module Ransack
|
|
|
115
136
|
end
|
|
116
137
|
when Hash
|
|
117
138
|
args.each do |index, attrs|
|
|
139
|
+
attrs = indifferent_params(attrs, 'attribute')
|
|
118
140
|
build_attributes(attrs[:name], attrs[:ransacker_args])
|
|
119
141
|
end
|
|
120
142
|
else
|
|
121
|
-
raise
|
|
143
|
+
raise InvalidSearchError,
|
|
122
144
|
"Invalid argument (#{args.class}) supplied to attributes="
|
|
123
145
|
end
|
|
124
146
|
end
|
|
@@ -140,11 +162,11 @@ module Ransack
|
|
|
140
162
|
end
|
|
141
163
|
when Hash
|
|
142
164
|
args.each do |index, attrs|
|
|
143
|
-
val = Value.new(@context, attrs[:value])
|
|
165
|
+
val = Value.new(@context, indifferent_params(attrs, 'value')[:value])
|
|
144
166
|
self.values << val
|
|
145
167
|
end
|
|
146
168
|
else
|
|
147
|
-
raise
|
|
169
|
+
raise InvalidSearchError,
|
|
148
170
|
"Invalid argument (#{args.class}) supplied to values="
|
|
149
171
|
end
|
|
150
172
|
end
|
|
@@ -196,6 +218,7 @@ module Ransack
|
|
|
196
218
|
# compound's combinator (#1728). Left alone, the alias passed `valid?`
|
|
197
219
|
# by being allowlisted and bound a column that does not exist.
|
|
198
220
|
def build_attributes(name, ransacker_args = [])
|
|
221
|
+
scalar_param(name, 'an attribute name')
|
|
199
222
|
return build_attribute(name, ransacker_args) if name.blank? || @context.nil?
|
|
200
223
|
|
|
201
224
|
resolved = @context.resolve_aliases(name.to_s)
|
|
@@ -211,9 +234,6 @@ module Ransack
|
|
|
211
234
|
Attribute.new(@context, name, ransacker_args).tap do |attribute|
|
|
212
235
|
@context.bind(attribute, attribute.name)
|
|
213
236
|
self.attributes << attribute if name.nil? || attribute.valid?
|
|
214
|
-
if predicate && !negative?
|
|
215
|
-
@context.lock_association(attribute.parent)
|
|
216
|
-
end
|
|
217
237
|
end
|
|
218
238
|
end
|
|
219
239
|
|
|
@@ -254,15 +274,45 @@ module Ransack
|
|
|
254
274
|
}.freeze
|
|
255
275
|
|
|
256
276
|
def build(params)
|
|
257
|
-
params.
|
|
258
|
-
if key.match(
|
|
277
|
+
indifferent_params(params, 'condition').each do |key, value|
|
|
278
|
+
if key.match(/\A(a|v|p|m|attributes|values|predicate|predicate_name|combinator)\z/)
|
|
259
279
|
self.send("#{LONG_KEYS.fetch(key, key)}=", value)
|
|
260
280
|
end
|
|
261
281
|
end
|
|
282
|
+
spread_array_values!
|
|
262
283
|
|
|
263
284
|
self
|
|
264
285
|
end
|
|
265
286
|
|
|
287
|
+
# An Array held by one value stands for several values: `[5, 6]` in one
|
|
288
|
+
# envelope of the c: form (#1771), or `name_eq: ['a', 'b']` in the short
|
|
289
|
+
# form, as a group of check boxes posts it (#1772). Cast as one value it
|
|
290
|
+
# became nil on a typed column, dropping the condition, or its string
|
|
291
|
+
# form elsewhere. Each element becomes a value of its own instead, so a
|
|
292
|
+
# predicate that wants an array gets them all, and a single-value
|
|
293
|
+
# predicate is held to its arity like any condition given several
|
|
294
|
+
# values; a one-element Array is just its element. Blank elements, such
|
|
295
|
+
# as the empty string a check box group posts alongside its choices, are
|
|
296
|
+
# dropped when blank values are ignored. Date and time types keep the
|
|
297
|
+
# Array, which their cast builds one value from.
|
|
298
|
+
def spread_array_values!
|
|
299
|
+
return unless predicate && values.any? { |v| v.value.is_a?(Array) }
|
|
300
|
+
# An incomplete or unsearchable attribute has no type to look up;
|
|
301
|
+
# valid? discards the condition instead.
|
|
302
|
+
return unless predicate.type || attributes.first&.valid?
|
|
303
|
+
return if Value::ARRAY_CAST_TYPES.include?(default_type&.to_sym)
|
|
304
|
+
|
|
305
|
+
@values = values.flat_map do |v|
|
|
306
|
+
next [v] unless v.value.is_a?(Array)
|
|
307
|
+
|
|
308
|
+
elements = v.value
|
|
309
|
+
if Ransack.options[:ignore_blank_values]
|
|
310
|
+
elements = elements.reject { |e| e.blank? && e != false && !e.nil? }
|
|
311
|
+
end
|
|
312
|
+
elements.map { |e| Value.new(@context, e) }
|
|
313
|
+
end
|
|
314
|
+
end
|
|
315
|
+
|
|
266
316
|
def persisted?
|
|
267
317
|
false
|
|
268
318
|
end
|
|
@@ -286,10 +336,7 @@ module Ransack
|
|
|
286
336
|
end
|
|
287
337
|
|
|
288
338
|
def predicate_name=(name)
|
|
289
|
-
self.predicate = Predicate.named(name)
|
|
290
|
-
if predicate && !negative?
|
|
291
|
-
attributes.each { |a| context.lock_association(a.parent) }
|
|
292
|
-
end
|
|
339
|
+
self.predicate = Predicate.named(scalar_param(name, 'a predicate'))
|
|
293
340
|
@predicate
|
|
294
341
|
end
|
|
295
342
|
alias :p= :predicate_name=
|
|
@@ -314,7 +361,7 @@ module Ransack
|
|
|
314
361
|
end
|
|
315
362
|
|
|
316
363
|
def casted_values_for_attribute(attr)
|
|
317
|
-
validated_values.map { |v| v.cast(
|
|
364
|
+
validated_values.map { |v| v.cast(value_type_for_attribute(attr)) }
|
|
318
365
|
end
|
|
319
366
|
|
|
320
367
|
def formatted_values_for_attribute(attr)
|
|
@@ -336,7 +383,13 @@ module Ransack
|
|
|
336
383
|
end
|
|
337
384
|
|
|
338
385
|
def arel_predicate_for_attribute(attr)
|
|
339
|
-
|
|
386
|
+
# `.arel_predicate === Proc` reads as a class check, but `Proc#===`
|
|
387
|
+
# is an alias for `#call`. It actually invokes the proc, passing
|
|
388
|
+
# the `Proc` class itself as the argument, and branches on whatever
|
|
389
|
+
# comes back. `#respond_to?(:call)` is a real check, and it also
|
|
390
|
+
# catches an object that defines `#call` but is neither a Proc nor
|
|
391
|
+
# a Method, which `=== Proc` treats as a method name.
|
|
392
|
+
if predicate.arel_predicate.respond_to?(:call)
|
|
340
393
|
values = casted_values_for_attribute(attr)
|
|
341
394
|
unless predicate.wants_array
|
|
342
395
|
values = values.first
|
|
@@ -362,7 +415,8 @@ module Ransack
|
|
|
362
415
|
end
|
|
363
416
|
|
|
364
417
|
def default_type
|
|
365
|
-
|
|
418
|
+
attribute = attributes.first
|
|
419
|
+
predicate.type || (attribute && value_type_for_attribute(attribute))
|
|
366
420
|
end
|
|
367
421
|
|
|
368
422
|
def inspect
|
|
@@ -385,29 +439,29 @@ module Ransack
|
|
|
385
439
|
def arel_predicate
|
|
386
440
|
attributes.map { |attribute|
|
|
387
441
|
association = attribute.parent
|
|
388
|
-
parent_table = association.table
|
|
389
442
|
|
|
390
|
-
predicate = if negative? && attribute.associated_collection? && not_nested_condition(attribute
|
|
391
|
-
query = context.
|
|
392
|
-
context.
|
|
443
|
+
predicate = if negative? && attribute.associated_collection? && not_nested_condition(attribute)
|
|
444
|
+
outer_key, query = context.correlated_subquery(association)
|
|
445
|
+
context.use_ancestors_of(association)
|
|
393
446
|
|
|
394
447
|
case self.predicate_name
|
|
395
448
|
when 'not_null'
|
|
396
449
|
if self.value
|
|
397
450
|
query.where(format_predicate(attribute))
|
|
398
|
-
Arel::Nodes::In.new(
|
|
451
|
+
Arel::Nodes::In.new(outer_key, Arel.sql(context.subquery_sql(query)))
|
|
399
452
|
else
|
|
400
453
|
query.where(format_predicate(attribute).not)
|
|
401
|
-
Arel::Nodes::NotIn.new(
|
|
454
|
+
Arel::Nodes::NotIn.new(outer_key, Arel.sql(context.subquery_sql(query)))
|
|
402
455
|
end
|
|
403
456
|
when 'not_cont'
|
|
404
457
|
query.where(like_predicate(attribute, 'matches'.freeze))
|
|
405
|
-
Arel::Nodes::NotIn.new(
|
|
458
|
+
Arel::Nodes::NotIn.new(outer_key, Arel.sql(context.subquery_sql(query)))
|
|
406
459
|
else
|
|
407
460
|
query.where(format_predicate(attribute).not)
|
|
408
|
-
Arel::Nodes::NotIn.new(
|
|
461
|
+
Arel::Nodes::NotIn.new(outer_key, Arel.sql(context.subquery_sql(query)))
|
|
409
462
|
end
|
|
410
463
|
else
|
|
464
|
+
context.use_association(attribute.parent)
|
|
411
465
|
format_predicate(attribute)
|
|
412
466
|
end
|
|
413
467
|
|
|
@@ -415,8 +469,15 @@ module Ransack
|
|
|
415
469
|
}.reduce(combinator_method)
|
|
416
470
|
end
|
|
417
471
|
|
|
418
|
-
|
|
419
|
-
|
|
472
|
+
# Whether the attribute is on a collection the searched model has
|
|
473
|
+
# directly, rather than one nested under another association. A
|
|
474
|
+
# negative condition on a nested collection keeps its join (#1411).
|
|
475
|
+
# Told apart by the association's depth, not by its name: the name of
|
|
476
|
+
# an STI, scoped, aliased or through collection need not start with
|
|
477
|
+
# its table's (#1773). The table argument is no longer used, and is
|
|
478
|
+
# kept for callers of the 4.4.0 API.
|
|
479
|
+
def not_nested_condition(attribute, _parent_table = nil)
|
|
480
|
+
context.root_association?(attribute.parent)
|
|
420
481
|
end
|
|
421
482
|
|
|
422
483
|
private
|
|
@@ -425,18 +486,28 @@ module Ransack
|
|
|
425
486
|
combinator === Constants::OR ? :or : :and
|
|
426
487
|
end
|
|
427
488
|
|
|
489
|
+
# The type a value is cast to for this attribute. A LIKE compares text,
|
|
490
|
+
# so its value stays text whatever the column's type: cast to an integer,
|
|
491
|
+
# 'Alpha' becomes 0 and the pattern '%0%' matches unrelated rows, and cast
|
|
492
|
+
# to a date it becomes nil, which either drops the condition or gives the
|
|
493
|
+
# pattern '%%' (#1793). A ransacker's formatter is still given the type
|
|
494
|
+
# the ransacker declares, as it may depend on it, and an Array still goes
|
|
495
|
+
# to the attribute's cast: a multiparameter date has to be assembled, and
|
|
496
|
+
# #spread_array_values! decides from this type whether to keep the Array.
|
|
497
|
+
def value_type_for_attribute(attr)
|
|
498
|
+
return predicate.type if predicate.type
|
|
499
|
+
return attr.type unless like_predicate?(predicate.arel_predicate)
|
|
500
|
+
return attr.type if attr.ransacker&.formatter
|
|
501
|
+
return attr.type if values.any? { |v| v.value.is_a?(Array) }
|
|
502
|
+
|
|
503
|
+
:string
|
|
504
|
+
end
|
|
505
|
+
|
|
428
506
|
def format_predicate(attribute)
|
|
429
|
-
|
|
430
|
-
arel_values = formatted_values_for_attribute(attribute)
|
|
507
|
+
return format_predicate_from_node(attribute) if predicate.arel_node
|
|
431
508
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
# ActiveRecord casts `''` to NULL for those types, producing the
|
|
435
|
-
# always-UNKNOWN `column != NULL` (or `column = NULL`) clause. Strip the
|
|
436
|
-
# empty string when the attribute is not a string-like column.
|
|
437
|
-
if arel_values.is_a?(Array) && arel_values.include?(''.freeze) && !string_like_attribute?(attribute)
|
|
438
|
-
arel_values = arel_values.reject { |v| v == ''.freeze }
|
|
439
|
-
end
|
|
509
|
+
arel_pred = arel_predicate_for_attribute(attribute)
|
|
510
|
+
arel_values = formatted_values_without_blank_for_non_string(attribute)
|
|
440
511
|
|
|
441
512
|
# A length predicate compares LENGTH(column) rather than the column.
|
|
442
513
|
attr_value = length_predicate? ? length_function_for_attribute(attribute) : attr_value_for_attribute(attribute)
|
|
@@ -491,6 +562,57 @@ module Ransack
|
|
|
491
562
|
predicate.class == Arel::Nodes::In || predicate.class == Arel::Nodes::NotIn
|
|
492
563
|
end
|
|
493
564
|
|
|
565
|
+
# The `present` / `blank` predicate formatters produce `[nil, '']` regardless
|
|
566
|
+
# of column type. The empty-string half has no meaning on non-string columns:
|
|
567
|
+
# ActiveRecord casts `''` to NULL for those types, producing the
|
|
568
|
+
# always-UNKNOWN `column != NULL` (or `column = NULL`) clause. Strip the
|
|
569
|
+
# empty string when the attribute is not a string-like column. A LIKE
|
|
570
|
+
# keeps it: its value stays text, so `''` is a pattern like any other.
|
|
571
|
+
def formatted_values_without_blank_for_non_string(attribute)
|
|
572
|
+
arel_values = formatted_values_for_attribute(attribute)
|
|
573
|
+
|
|
574
|
+
if arel_values.is_a?(Array) && arel_values.include?(''.freeze) &&
|
|
575
|
+
!string_like_attribute?(attribute) && !like_predicate?(predicate.arel_predicate)
|
|
576
|
+
arel_values = arel_values.reject { |v| v == ''.freeze }
|
|
577
|
+
end
|
|
578
|
+
|
|
579
|
+
arel_values
|
|
580
|
+
end
|
|
581
|
+
|
|
582
|
+
# `arel_node` receives the attribute node and the already cast and
|
|
583
|
+
# formatted values, and returns a finished Arel node directly,
|
|
584
|
+
# instead of naming a method `#format_predicate` sends to
|
|
585
|
+
# `attr_value` with one argument.
|
|
586
|
+
def format_predicate_from_node(attribute)
|
|
587
|
+
attr_value = attr_value_for_attribute(attribute)
|
|
588
|
+
# A sentinel-only search has no real values, so the builder gets no
|
|
589
|
+
# input and the condition is `IS NULL` alone.
|
|
590
|
+
return attr_value.eq(nil) if null_sentinel_requested? && validated_values.empty?
|
|
591
|
+
|
|
592
|
+
values = formatted_values_without_blank_for_non_string(attribute)
|
|
593
|
+
predicate = self.predicate.arel_node.call(attr_value, values)
|
|
594
|
+
|
|
595
|
+
# A nil result would drop the condition and a String would reach the
|
|
596
|
+
# SQL as raw text, both without an error.
|
|
597
|
+
unless predicate.is_a?(Arel::Nodes::Node)
|
|
598
|
+
raise ArgumentError,
|
|
599
|
+
"arel_node for '#{self.predicate.name}' must return an Arel node, " \
|
|
600
|
+
"got #{predicate.class}"
|
|
601
|
+
end
|
|
602
|
+
|
|
603
|
+
if in_predicate?(predicate) && predicate.right.is_a?(Array)
|
|
604
|
+
predicate.right = predicate.right.map do |pr|
|
|
605
|
+
casted_array?(pr) ? format_values_for(pr) : pr
|
|
606
|
+
end
|
|
607
|
+
end
|
|
608
|
+
|
|
609
|
+
predicate = null_sentinel_requested? ? apply_null_sentinel(predicate, attr_value) : predicate
|
|
610
|
+
|
|
611
|
+
unwrap_oversized_integers(predicate)
|
|
612
|
+
|
|
613
|
+
predicate
|
|
614
|
+
end
|
|
615
|
+
|
|
494
616
|
LIKE_PREDICATES = %w[
|
|
495
617
|
matches matches_any matches_all
|
|
496
618
|
does_not_match does_not_match_any does_not_match_all
|
|
@@ -557,18 +679,21 @@ module Ransack
|
|
|
557
679
|
end
|
|
558
680
|
end
|
|
559
681
|
|
|
560
|
-
# Walks a predicate node for the shapes `#arel_predicate`
|
|
561
|
-
# (a single Binary, an In/NotIn
|
|
562
|
-
#
|
|
563
|
-
#
|
|
682
|
+
# Walks a predicate node for the shapes `#arel_predicate` or an
|
|
683
|
+
# `arel_node` builder can produce (a single Binary, an In/NotIn
|
|
684
|
+
# holding an Array, a Between, or several of these combined with
|
|
685
|
+
# Grouping/Not/And/Or) and replaces each integer Casted value it
|
|
686
|
+
# finds with the plain integer, in place.
|
|
564
687
|
def unwrap_oversized_integers(node)
|
|
565
688
|
case node
|
|
566
|
-
when Arel::Nodes::Grouping
|
|
689
|
+
when Arel::Nodes::Grouping, Arel::Nodes::Not
|
|
567
690
|
unwrap_oversized_integers(node.expr)
|
|
568
691
|
when Arel::Nodes::And, Arel::Nodes::Or
|
|
569
692
|
# Both are Nary: `grouping_any` (behind every `_any` predicate)
|
|
570
693
|
# builds a single Or with one child per value, not a pairwise
|
|
571
694
|
# chain, so `left`/`right` alone would miss the third value on.
|
|
695
|
+
# A BETWEEN keeps its two bounds as Casted children of an And.
|
|
696
|
+
node.children.map! { |child| plain_integer(child) || child }
|
|
572
697
|
node.children.each { |child| unwrap_oversized_integers(child) }
|
|
573
698
|
when Arel::Nodes::In, Arel::Nodes::NotIn
|
|
574
699
|
if node.right.is_a?(Array)
|
|
@@ -580,6 +705,7 @@ module Ransack
|
|
|
580
705
|
end
|
|
581
706
|
when Arel::Nodes::Binary
|
|
582
707
|
unwrap_binary_right(node)
|
|
708
|
+
unwrap_oversized_integers(node.right) if node.right.is_a?(Arel::Nodes::And)
|
|
583
709
|
end
|
|
584
710
|
end
|
|
585
711
|
|