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
|
@@ -6,6 +6,16 @@ module Ransack
|
|
|
6
6
|
# and exposes the table aliasing Ransack needs to add one association at a
|
|
7
7
|
# time to a relation that may already carry joins of its own.
|
|
8
8
|
module JoinDependencyExtensions
|
|
9
|
+
# The table aliases a search counted on for one association it added,
|
|
10
|
+
# by position in its reflection chain (nil where the name could not be
|
|
11
|
+
# kept), and the join that ended up with them; see #pin_search_tables.
|
|
12
|
+
SearchPin = Struct.new(:name, :tables, :top_level, :joined_by, :joined_as)
|
|
13
|
+
|
|
14
|
+
# Set on a dependency Ransack stashes on a relation for an association
|
|
15
|
+
# a search added, and whether the search counted the relation's eager
|
|
16
|
+
# load first.
|
|
17
|
+
attr_accessor :added_by_search, :after_eager_load
|
|
18
|
+
|
|
9
19
|
# Replaces ActiveRecord::Associations::JoinDependency#build
|
|
10
20
|
def build(associations, base_klass)
|
|
11
21
|
associations.map do |name, right|
|
|
@@ -42,10 +52,14 @@ module Ransack
|
|
|
42
52
|
@references[table_name.to_sym] = table_name if table_name.is_a?(String)
|
|
43
53
|
end
|
|
44
54
|
|
|
55
|
+
pin_search_tables(joins_to_add)
|
|
56
|
+
|
|
45
57
|
joins = make_join_constraints(join_root, join_type)
|
|
46
58
|
|
|
47
|
-
joins.concat joins_to_add.flat_map { |oj|
|
|
48
|
-
if
|
|
59
|
+
joins.concat eager_load_first(joins_to_add.to_a).flat_map { |oj|
|
|
60
|
+
if oj.added_by_search
|
|
61
|
+
search_join_constraints(oj)
|
|
62
|
+
elsif top_level?(oj)
|
|
49
63
|
walk join_root, oj.join_root, oj.join_type
|
|
50
64
|
else
|
|
51
65
|
make_join_constraints(oj.join_root, oj.join_type)
|
|
@@ -53,6 +67,41 @@ module Ransack
|
|
|
53
67
|
}
|
|
54
68
|
end
|
|
55
69
|
|
|
70
|
+
# A search binds its conditions to the table aliases its joins are
|
|
71
|
+
# going to have, and roots a nested join at its parent's alias, all
|
|
72
|
+
# worked out from the relation it searched. A join added to the result
|
|
73
|
+
# afterwards (`result.joins(:target_person)`) renders first, and would
|
|
74
|
+
# take a name one of the search's joins was counting on, moving its
|
|
75
|
+
# conditions and nested joins onto the new join (#1776).
|
|
76
|
+
#
|
|
77
|
+
# So each association a search added keeps its aliases, those of a
|
|
78
|
+
# through association's intermediate tables included: the names are
|
|
79
|
+
# reserved before anything is joined, and a join of the same
|
|
80
|
+
# association added afterwards takes them over and stands in for the
|
|
81
|
+
# search's join; any other join is aliased around them. A name that a
|
|
82
|
+
# string or Arel join already claims cannot be kept; the joins nested
|
|
83
|
+
# under that association then follow the alias it does get.
|
|
84
|
+
def pin_search_tables(joins)
|
|
85
|
+
@search_pins = {}.compare_by_identity
|
|
86
|
+
aliases = alias_tracker.aliases
|
|
87
|
+
joins.each do |jd|
|
|
88
|
+
next unless jd.added_by_search
|
|
89
|
+
|
|
90
|
+
top_level = top_level?(jd)
|
|
91
|
+
jd.join_root.children.each do |node|
|
|
92
|
+
next if node.own_tables.blank?
|
|
93
|
+
|
|
94
|
+
tables = node.own_tables.map do |table|
|
|
95
|
+
next unless aliases[table.name].zero?
|
|
96
|
+
|
|
97
|
+
aliases[table.name] = 1
|
|
98
|
+
table
|
|
99
|
+
end
|
|
100
|
+
@search_pins[node] = SearchPin.new(node.own_tables.first.name, tables, top_level)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
56
105
|
def construct_tables_for_association!(join_root, association, join_type = self.join_type)
|
|
57
106
|
tables = table_aliases_for(join_root, association, join_type)
|
|
58
107
|
association.table = tables.first
|
|
@@ -61,6 +110,85 @@ module Ransack
|
|
|
61
110
|
|
|
62
111
|
private
|
|
63
112
|
|
|
113
|
+
# Active Record renders the eager load last, after every
|
|
114
|
+
# `left_outer_joins` dependency. A search of a relation that eager
|
|
115
|
+
# loads counted the eager load's aliases before those of the joins it
|
|
116
|
+
# added, and may have rooted one of them at an eager-loaded join
|
|
117
|
+
# (#1767), so the eager load goes back in front of the first of them.
|
|
118
|
+
def eager_load_first(joins)
|
|
119
|
+
index = joins.index(&:after_eager_load)
|
|
120
|
+
joins.insert(index, joins.pop) if index && !joins.last.added_by_search
|
|
121
|
+
joins
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def top_level?(jd)
|
|
125
|
+
join_root.match?(jd.join_root) && join_root.table.name == jd.join_root.table.name
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# The pin for a node about to be joined: the search's own node, or a
|
|
129
|
+
# join of the same association under the root, which stands in for it.
|
|
130
|
+
# Each pin goes to whichever of them is joined first.
|
|
131
|
+
def take_search_pin(parent, node)
|
|
132
|
+
return unless @search_pins
|
|
133
|
+
|
|
134
|
+
pin = @search_pins[node]
|
|
135
|
+
if pin.nil? && parent.equal?(join_root)
|
|
136
|
+
pin = @search_pins.find { |search_node, p| p.top_level && !p.joined_by && search_node.match?(node) }&.last
|
|
137
|
+
end
|
|
138
|
+
return if pin.nil? || pin.joined_by
|
|
139
|
+
|
|
140
|
+
pin.joined_by = node
|
|
141
|
+
pin
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# A search's join: left out when a join added afterwards stands in for
|
|
145
|
+
# it, and a nested one hung off the alias its parent actually got.
|
|
146
|
+
def search_join_constraints(jd)
|
|
147
|
+
root = jd.join_root
|
|
148
|
+
if top_level?(jd)
|
|
149
|
+
root.children.flat_map do |node|
|
|
150
|
+
joined_by = @search_pins[node]&.joined_by
|
|
151
|
+
if (joined_by && !joined_by.equal?(node)) || join_root.children.any? { |child| node.match?(child) }
|
|
152
|
+
[]
|
|
153
|
+
else
|
|
154
|
+
make_constraints(join_root, node, jd.join_type)
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
else
|
|
158
|
+
parent = @search_pins.each_value.find { |pin| pin.name == root.table.name && pin.joined_as }
|
|
159
|
+
if parent && parent.joined_as.name != root.table.name
|
|
160
|
+
root = JoinDependency::JoinBase.new(root.base_klass, parent.joined_as, root.children)
|
|
161
|
+
end
|
|
162
|
+
make_join_constraints(root, jd.join_type)
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Replaces ActiveRecord::Associations::JoinDependency#make_constraints,
|
|
167
|
+
# to join an association a search added with the alias it pinned.
|
|
168
|
+
def make_constraints(parent, child, join_type)
|
|
169
|
+
foreign_table = parent.table
|
|
170
|
+
foreign_klass = parent.base_klass
|
|
171
|
+
pin = nil
|
|
172
|
+
child.join_constraints(foreign_table, foreign_klass, join_type, alias_tracker) do |reflection, remaining_reflection_chain|
|
|
173
|
+
table, terminated = @joined_tables[remaining_reflection_chain]
|
|
174
|
+
root = reflection == child.reflection
|
|
175
|
+
index = child.reflection.chain.size - remaining_reflection_chain.size
|
|
176
|
+
pin = take_search_pin(parent, child) if root
|
|
177
|
+
|
|
178
|
+
if table && (!root || !terminated) && reusable?(table, pin, index)
|
|
179
|
+
@joined_tables[remaining_reflection_chain] = [table, root] if root
|
|
180
|
+
pin.joined_as = table if pin && root
|
|
181
|
+
next table, true
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
table = aliased_table_for(parent, reflection, index, pin)
|
|
185
|
+
pin.joined_as = table if pin && root
|
|
186
|
+
|
|
187
|
+
@joined_tables[remaining_reflection_chain] ||= [table, root] if join_type == Arel::Nodes::OuterJoin
|
|
188
|
+
table
|
|
189
|
+
end.concat child.children.flat_map { |c| make_constraints(child, c, join_type) }
|
|
190
|
+
end
|
|
191
|
+
|
|
64
192
|
# The tables for a node's reflection chain, assigned the way Active
|
|
65
193
|
# Record's JoinDependency#make_constraints assigns them, so that the
|
|
66
194
|
# aliases Ransack binds conditions to are the ones the query will have.
|
|
@@ -71,28 +199,54 @@ module Ransack
|
|
|
71
199
|
@joined_tables ||= {}
|
|
72
200
|
chain = node.reflection.chain
|
|
73
201
|
terminated_at = nil
|
|
202
|
+
pin = nil
|
|
74
203
|
|
|
75
204
|
chain.each_with_index.map { |reflection, index|
|
|
76
205
|
remaining = chain[index..]
|
|
77
206
|
table, terminated = @joined_tables[remaining]
|
|
78
207
|
root = reflection == node.reflection
|
|
208
|
+
pin = take_search_pin(parent, node) if root && !terminated_at
|
|
79
209
|
|
|
80
210
|
if terminated_at
|
|
81
211
|
table || reflection.klass.arel_table
|
|
82
|
-
elsif table && (!root || !terminated)
|
|
212
|
+
elsif table && (!root || !terminated) && reusable?(table, pin, index)
|
|
83
213
|
@joined_tables[remaining] = [table, root] if root
|
|
214
|
+
pin.joined_as = table if pin && root
|
|
84
215
|
terminated_at = index
|
|
85
216
|
table
|
|
86
217
|
else
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
name = reflection.alias_candidate(parent.table_name)
|
|
90
|
-
root ? name : "#{name}_join"
|
|
91
|
-
end
|
|
218
|
+
table = aliased_table_for(parent, reflection, index, pin)
|
|
219
|
+
pin.joined_as = table if pin && root
|
|
92
220
|
@joined_tables[remaining] ||= [table, root] if join_type == Arel::Nodes::OuterJoin
|
|
93
221
|
table
|
|
94
222
|
end
|
|
95
|
-
}
|
|
223
|
+
}.tap do |tables|
|
|
224
|
+
# Kept from the search that built the node. A later search of the
|
|
225
|
+
# same result shares the node and may give it other aliases, but
|
|
226
|
+
# the first result's conditions still name these.
|
|
227
|
+
node.own_tables ||= tables.first(terminated_at || tables.size)
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
# Whether a table an earlier join already provides can stand in for
|
|
232
|
+
# one position of a node's reflection chain. Not when a search pinned
|
|
233
|
+
# another alias there: the search's conditions name that one, so the
|
|
234
|
+
# node is joined on its own (#1776).
|
|
235
|
+
def reusable?(table, pin, index)
|
|
236
|
+
pinned = pin&.tables&.at(index)
|
|
237
|
+
pinned.nil? || pinned.name == table.name
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# A new table for one position of a node's reflection chain: the one a
|
|
241
|
+
# search pinned for it, or the next alias Active Record would give.
|
|
242
|
+
def aliased_table_for(parent, reflection, index, pin)
|
|
243
|
+
return pin.tables[index] if pin&.tables&.at(index)
|
|
244
|
+
|
|
245
|
+
table_name = @references && @references[reflection.name.to_sym]&.to_s
|
|
246
|
+
alias_tracker.aliased_table_for(reflection.klass.arel_table, table_name) do
|
|
247
|
+
name = reflection.alias_candidate(parent.table_name)
|
|
248
|
+
index.zero? ? name : "#{name}_join"
|
|
249
|
+
end
|
|
96
250
|
end
|
|
97
251
|
|
|
98
252
|
module ClassMethods
|
|
@@ -7,11 +7,12 @@ module Ransack
|
|
|
7
7
|
mattr_accessor :predicates, :options
|
|
8
8
|
|
|
9
9
|
class PredicateCollection
|
|
10
|
-
attr_reader :sorted_names_with_underscores
|
|
10
|
+
attr_reader :sorted_names_with_underscores, :max_name_length
|
|
11
11
|
|
|
12
12
|
def initialize
|
|
13
13
|
@collection = {}
|
|
14
14
|
@sorted_names_with_underscores = []
|
|
15
|
+
@max_name_length = 0
|
|
15
16
|
end
|
|
16
17
|
|
|
17
18
|
delegate :[], :keys, :has_key?, to: :@collection
|
|
@@ -20,6 +21,7 @@ module Ransack
|
|
|
20
21
|
@sorted_names_with_underscores << [key, '_' + key]
|
|
21
22
|
@sorted_names_with_underscores.sort! { |(a, _), (b, _)| b.length <=> a.length }
|
|
22
23
|
|
|
24
|
+
@max_name_length = key.length if key.length > @max_name_length
|
|
23
25
|
@collection[key] = value
|
|
24
26
|
end
|
|
25
27
|
end
|
|
@@ -48,9 +50,26 @@ module Ransack
|
|
|
48
50
|
def add_predicate(name, opts = {})
|
|
49
51
|
name = name.to_s
|
|
50
52
|
opts[:name] = name
|
|
53
|
+
|
|
54
|
+
if opts[:arel_predicate] && opts[:arel_node]
|
|
55
|
+
raise ArgumentError,
|
|
56
|
+
"add_predicate '#{name}' takes :arel_predicate or :arel_node, " \
|
|
57
|
+
"not both"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
if opts[:arel_node] && !opts[:arel_node].respond_to?(:call)
|
|
61
|
+
raise ArgumentError,
|
|
62
|
+
"add_predicate '#{name}' needs :arel_node to be callable, " \
|
|
63
|
+
"got #{opts[:arel_node].class}"
|
|
64
|
+
end
|
|
65
|
+
|
|
51
66
|
compounds = opts.delete(:compounds)
|
|
52
67
|
compounds = true if compounds.nil?
|
|
53
68
|
compounds = false if opts[:wants_array]
|
|
69
|
+
# _any/_all are built by appending a suffix to an Arel method name
|
|
70
|
+
# (see #arel_predicate_with_suffix below), which an arel_node builder
|
|
71
|
+
# has none of.
|
|
72
|
+
compounds = false if opts[:arel_node]
|
|
54
73
|
|
|
55
74
|
self.predicates[name] = Predicate.new(opts)
|
|
56
75
|
|
|
@@ -279,7 +298,11 @@ module Ransack
|
|
|
279
298
|
end
|
|
280
299
|
|
|
281
300
|
def arel_predicate_with_suffix(arel_predicate, suffix)
|
|
282
|
-
|
|
301
|
+
# `arel_predicate === Proc` reads as a class check, but `Proc#===` is
|
|
302
|
+
# an alias for `#call`: it invokes the proc, passing the `Proc`
|
|
303
|
+
# class itself as the argument, and branches on whatever comes
|
|
304
|
+
# back. `#respond_to?(:call)` is a real check.
|
|
305
|
+
if arel_predicate.respond_to?(:call)
|
|
283
306
|
proc { |v| "#{arel_predicate.call(v)}#{suffix}" }
|
|
284
307
|
else
|
|
285
308
|
"#{arel_predicate}#{suffix}"
|
data/lib/ransack/context.rb
CHANGED
|
@@ -3,13 +3,25 @@ require 'ransack/visitor'
|
|
|
3
3
|
module Ransack
|
|
4
4
|
class Context
|
|
5
5
|
attr_reader :search, :object, :klass, :base, :engine, :arel_visitor
|
|
6
|
-
attr_accessor :
|
|
6
|
+
attr_accessor :search_key, :ignore_unknown_conditions
|
|
7
|
+
attr_reader :auth_object, :permitted
|
|
7
8
|
attr_writer :null_sentinel
|
|
8
9
|
|
|
10
|
+
# The allowlists are read once per model and kept for the life of the
|
|
11
|
+
# context (see `allowlist_set`), so changing what they depend on drops
|
|
12
|
+
# the kept copies.
|
|
13
|
+
def auth_object=(value)
|
|
14
|
+
reset_name_caches
|
|
15
|
+
@auth_object = value
|
|
16
|
+
end
|
|
17
|
+
|
|
9
18
|
# True when the search was built from `ActionController::Parameters` the
|
|
10
19
|
# controller permitted. A model with no allowlist of its own then lets
|
|
11
20
|
# that permit list be the boundary (see `allowlist` below).
|
|
12
|
-
|
|
21
|
+
def permitted=(value)
|
|
22
|
+
reset_name_caches
|
|
23
|
+
@permitted = value
|
|
24
|
+
end
|
|
13
25
|
|
|
14
26
|
class << self
|
|
15
27
|
|
|
@@ -122,7 +134,9 @@ module Ransack
|
|
|
122
134
|
# For scopes with arity 1, pass the array as a single argument instead of splatting
|
|
123
135
|
@object.public_send(scope, args)
|
|
124
136
|
else
|
|
125
|
-
|
|
137
|
+
# Only an Array is spread into several arguments. A bare
|
|
138
|
+
# splat would call to_a, turning a Time into ten (#1769).
|
|
139
|
+
@object.public_send(scope, *Array.wrap(args))
|
|
126
140
|
end
|
|
127
141
|
end
|
|
128
142
|
|
|
@@ -202,7 +216,7 @@ module Ransack
|
|
|
202
216
|
end
|
|
203
217
|
|
|
204
218
|
def unpolymorphize_association(str)
|
|
205
|
-
if (match = str.match(/_of_([^_]+?)_type
|
|
219
|
+
if (match = str.match(/_of_([^_]+?)_type\z/)) &&
|
|
206
220
|
(klass = polymorphic_class(match.captures.first))
|
|
207
221
|
[match.pre_match, klass]
|
|
208
222
|
else
|
|
@@ -240,9 +254,19 @@ module Ransack
|
|
|
240
254
|
# `term_or_daddy` with `term => name_or_email` becomes
|
|
241
255
|
# `name_or_email_or_parent_name`. Returns the name unchanged when it
|
|
242
256
|
# holds no alias.
|
|
257
|
+
#
|
|
258
|
+
# A key is resolved several times while a search is built (to check it,
|
|
259
|
+
# to split it into attributes, to bind them), so the result is kept for
|
|
260
|
+
# the life of the context, frozen, and dropped with the allowlists it
|
|
261
|
+
# depends on.
|
|
243
262
|
def resolve_aliases(str)
|
|
244
263
|
return str.to_s unless key_within_depth_limit?(str)
|
|
245
264
|
|
|
265
|
+
key = str.to_s
|
|
266
|
+
(@resolved_aliases ||= {})[key] ||= -resolve_aliases_uncached(key)
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
def resolve_aliases_uncached(str)
|
|
246
270
|
whole = resolve_alias_segment(str)
|
|
247
271
|
return whole if whole != str
|
|
248
272
|
# A name that is an attribute in its own right is never split, even if
|
|
@@ -256,17 +280,18 @@ module Ransack
|
|
|
256
280
|
combinator = str[/_(or|and)_/, 1] || Constants::OR
|
|
257
281
|
resolved.join("_#{combinator}_")
|
|
258
282
|
end
|
|
283
|
+
private :resolve_aliases_uncached
|
|
259
284
|
|
|
260
285
|
def ransackable_attribute?(str, klass)
|
|
261
|
-
|
|
286
|
+
allowlist_set(klass, :attributes_for).include?(str.to_s)
|
|
262
287
|
end
|
|
263
288
|
|
|
264
289
|
def ransortable_attribute?(str, klass)
|
|
265
|
-
|
|
290
|
+
allowlist_set(klass, :sortable_attributes_for).include?(str.to_s)
|
|
266
291
|
end
|
|
267
292
|
|
|
268
293
|
def ransackable_association?(str, klass)
|
|
269
|
-
|
|
294
|
+
allowlist_set(klass, :associations_for).include?(str.to_s)
|
|
270
295
|
end
|
|
271
296
|
|
|
272
297
|
def ransackable_scope?(str, klass)
|
|
@@ -289,8 +314,31 @@ module Ransack
|
|
|
289
314
|
associations_for(traverse(str))
|
|
290
315
|
end
|
|
291
316
|
|
|
317
|
+
# Hooks a condition calls to record the joins it reads. The Active
|
|
318
|
+
# Record context overrides them; a context registered for another ORM
|
|
319
|
+
# need not, and a search through it still builds.
|
|
320
|
+
def lock_association(_association); end
|
|
321
|
+
|
|
322
|
+
def use_association(_association); end
|
|
323
|
+
|
|
324
|
+
def use_ancestors_of(_association); end
|
|
325
|
+
|
|
292
326
|
private
|
|
293
327
|
|
|
328
|
+
# Every name in a search is checked against the allowlists several times
|
|
329
|
+
# over, and each check used to call the model's `ransackable_*` method
|
|
330
|
+
# and scan the array it returned. They are asked once per model per
|
|
331
|
+
# context instead, and what is cached here is dropped whenever the auth
|
|
332
|
+
# object or the permitted flag changes.
|
|
333
|
+
def reset_name_caches
|
|
334
|
+
@allowlist_sets = @resolved_aliases = @attribute_methods = nil
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
def allowlist_set(klass, kind)
|
|
338
|
+
((@allowlist_sets ||= {})[kind] ||= {})[klass] ||=
|
|
339
|
+
send(kind, klass).to_set(&:to_s).freeze
|
|
340
|
+
end
|
|
341
|
+
|
|
294
342
|
def attributes_for(klass)
|
|
295
343
|
allowlist(klass, :ransackable_attributes) { klass.authorizable_ransackable_attributes }
|
|
296
344
|
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module Ransack
|
|
2
|
+
# Raised for a search that cannot be built from its params: an unknown
|
|
3
|
+
# attribute, predicate, combinator or sort under a strict search, or params
|
|
4
|
+
# of the wrong shape (a string where a hash of conditions belongs) under any
|
|
5
|
+
# search. Search params usually come from the request, so this is the error
|
|
6
|
+
# to rescue to turn a bad search into a 400 rather than a 500. It is an
|
|
7
|
+
# ArgumentError, so code that rescues ArgumentError still catches it.
|
|
8
|
+
class InvalidSearchError < ArgumentError; end
|
|
9
|
+
|
|
10
|
+
# Raised internally when a name does not resolve to an association. The
|
|
11
|
+
# form builder rescues it; a search never lets it escape.
|
|
12
|
+
class UntraversableAssociationError < StandardError; end
|
|
13
|
+
end
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
# Kept so that `require 'ransack/invalid_search_error'` still works; the error
|
|
2
|
+
# classes live in ransack/errors.
|
|
3
|
+
require 'ransack/errors'
|
data/lib/ransack/locale/ar.yml
CHANGED
|
@@ -50,21 +50,56 @@ ar:
|
|
|
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': "عدم"
|
|
70
90
|
not_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: "الطول أكبر أو مساو للجميع"
|
data/lib/ransack/locale/az.yml
CHANGED
|
@@ -32,7 +32,7 @@ az:
|
|
|
32
32
|
lteq: "daha kiçik və ya bərabər"
|
|
33
33
|
lteq_any: "daha kiçik və ya hər hansı birinə bərabər"
|
|
34
34
|
lteq_all: "daha kiçik və ya hamısına bərabər"
|
|
35
|
-
gt: "daha böyük
|
|
35
|
+
gt: "daha böyük"
|
|
36
36
|
gt_any: "hər hansı birindən daha böyük"
|
|
37
37
|
gt_all: "hamısından daha böyük"
|
|
38
38
|
gteq: "daha böyük və ya bərabər"
|
|
@@ -50,21 +50,56 @@ az:
|
|
|
50
50
|
not_cont: "ehtiva etməyən"
|
|
51
51
|
not_cont_any: "hər hansı birini ehtiva etməyən"
|
|
52
52
|
not_cont_all: "heç birini birini ehtiva etməyən"
|
|
53
|
+
i_cont: "ehtiva edən, hərf halına baxmayaraq"
|
|
54
|
+
i_cont_any: "hər hansı birini ehtiva edən, hərf halına baxmayaraq"
|
|
55
|
+
i_cont_all: "hamısını ehtiva edən, hərf halına baxmayaraq"
|
|
56
|
+
not_i_cont: "ehtiva etməyən, hərf halına baxmayaraq"
|
|
57
|
+
not_i_cont_any: "hər hansı birini ehtiva etməyən, hərf halına baxmayaraq"
|
|
58
|
+
not_i_cont_all: "heç birini birini ehtiva etməyən, hərf halına baxmayaraq"
|
|
53
59
|
start: "ilə başlayan"
|
|
54
60
|
start_any: "hər hansı biri ilə başlayan"
|
|
55
61
|
start_all: "hamısı ilə başlayan"
|
|
56
62
|
not_start: "ilə başlamayan"
|
|
57
63
|
not_start_any: "hər hansı biri ilə başlamayan"
|
|
58
64
|
not_start_all: "hiçbiri ilə başlamayan"
|
|
65
|
+
i_start: "ilə başlayan, hərf halına baxmayaraq"
|
|
66
|
+
i_start_any: "hər hansı biri ilə başlayan, hərf halına baxmayaraq"
|
|
67
|
+
i_start_all: "hamısı ilə başlayan, hərf halına baxmayaraq"
|
|
68
|
+
not_i_start: "ilə başlamayan, hərf halına baxmayaraq"
|
|
69
|
+
not_i_start_any: "hər hansı biri ilə başlamayan, hərf halına baxmayaraq"
|
|
70
|
+
not_i_start_all: "hiçbiri ilə başlamayan, hərf halına baxmayaraq"
|
|
59
71
|
end: "ilə bitən"
|
|
60
72
|
end_any: "hər hansı biri ilə bitən"
|
|
61
73
|
end_all: "hamısı ilə bitən"
|
|
62
74
|
not_end: "ilə bitməyən"
|
|
63
75
|
not_end_any: "hər hansı biri ilə bitməyən"
|
|
64
76
|
not_end_all: "heç biri ilə bitməyən"
|
|
77
|
+
i_end: "ilə bitən, hərf halına baxmayaraq"
|
|
78
|
+
i_end_any: "hər hansı biri ilə bitən, hərf halına baxmayaraq"
|
|
79
|
+
i_end_all: "hamısı ilə bitən, hərf halına baxmayaraq"
|
|
80
|
+
not_i_end: "ilə bitməyən, hərf halına baxmayaraq"
|
|
81
|
+
not_i_end_any: "hər hansı biri ilə bitməyən, hərf halına baxmayaraq"
|
|
82
|
+
not_i_end_all: "heç biri ilə bitməyən, hərf halına baxmayaraq"
|
|
65
83
|
'true': "doğru"
|
|
84
|
+
not_true: "doğru deyil"
|
|
66
85
|
'false': "yanlış"
|
|
86
|
+
not_false: "yanlış deyil"
|
|
67
87
|
present: "mövcüd"
|
|
68
88
|
blank: "boş"
|
|
69
89
|
'null': "keçərsiz"
|
|
70
90
|
not_null: "keçərli"
|
|
91
|
+
length_eq: "uzunluğu bərabər"
|
|
92
|
+
length_eq_any: "uzunluğu hər hansı birinə bərabər"
|
|
93
|
+
length_eq_all: "uzunluğu hamısına bərabər"
|
|
94
|
+
length_lt: "uzunluğu daha kiçik"
|
|
95
|
+
length_lt_any: "uzunluğu hər hansı birindən kiçik"
|
|
96
|
+
length_lt_all: "uzunluğu hamısından kiçik"
|
|
97
|
+
length_lteq: "uzunluğu daha kiçik və ya bərabər"
|
|
98
|
+
length_lteq_any: "uzunluğu daha kiçik və ya hər hansı birinə bərabər"
|
|
99
|
+
length_lteq_all: "uzunluğu daha kiçik və ya hamısına bərabər"
|
|
100
|
+
length_gt: "uzunluğu daha böyük"
|
|
101
|
+
length_gt_any: "uzunluğu hər hansı birindən daha böyük"
|
|
102
|
+
length_gt_all: "uzunluğu hamısından daha böyük"
|
|
103
|
+
length_gteq: "uzunluğu daha böyük və ya bərabər"
|
|
104
|
+
length_gteq_any: "uzunluğu daha böyük və ya hər hansı birine bərabər"
|
|
105
|
+
length_gteq_all: "uzunluğu daha böyük və ya hamısıne bərabər"
|
data/lib/ransack/locale/bg.yml
CHANGED
|
@@ -50,21 +50,56 @@ bg:
|
|
|
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': "е нула"
|
|
70
90
|
not_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: "дължината по-голямо или равно на всички"
|