Class: Praxis::Extensions::AttributeFiltering::FilteringParams
- Inherits:
-
Object
- Object
- Praxis::Extensions::AttributeFiltering::FilteringParams
show all
- Includes:
- Attributor::Dumpable, Attributor::Type
- Defined in:
- lib/praxis/extensions/attribute_filtering/filters_parser.rb,
lib/praxis/extensions/attribute_filtering/filtering_params.rb
Defined Under Namespace
Classes: Condition, ConditionGroup, DSLCompiler, Parser
Constant Summary
collapse
- VALUE_OPERATORS =
Set.new(['!=', '>=', '<=', '=', '<', '>']).freeze
- NOVALUE_OPERATORS =
Set.new(['!', '!!']).freeze
- AVAILABLE_OPERATORS =
Set.new(VALUE_OPERATORS + NOVALUE_OPERATORS).freeze
Class Attribute Summary collapse
Instance Attribute Summary collapse
Class Method Summary
collapse
-
.add_any(name, operators:, fuzzy:) ⇒ Object
-
.add_filter(name, operators:, fuzzy:) ⇒ Object
-
.construct(definition, **options) ⇒ Object
-
.constructable? ⇒ Boolean
-
.describe(_root = false, example: nil) ⇒ Object
-
.display_name ⇒ Object
-
.dump(value, **_opts) ⇒ Object
-
.example(_context = Attributor::DEFAULT_ROOT_CONTEXT, **_options) ⇒ Object
-
.family ⇒ Object
-
.find_filter_attribute(name_components, type) ⇒ Object
-
.for(media_type, **_opts) ⇒ Object
-
.json_schema_type ⇒ Object
-
.load(filters, _context = Attributor::DEFAULT_ROOT_CONTEXT, **_options) ⇒ Object
-
.name ⇒ Object
-
.native_type ⇒ Object
-
.validate(value, context = Attributor::DEFAULT_ROOT_CONTEXT, _attribute = nil) ⇒ Object
Instance Method Summary
collapse
Constructor Details
Returns a new instance of FilteringParams.
220
221
222
|
# File 'lib/praxis/extensions/attribute_filtering/filtering_params.rb', line 220
def initialize(parsed = [])
@parsed_array = parsed
end
|
Class Attribute Details
.allowed_filters ⇒ Object
Returns the value of attribute allowed_filters.
59
60
61
|
# File 'lib/praxis/extensions/attribute_filtering/filtering_params.rb', line 59
def allowed_filters
@allowed_filters
end
|
.allowed_leaves ⇒ Object
Returns the value of attribute allowed_leaves.
59
60
61
|
# File 'lib/praxis/extensions/attribute_filtering/filtering_params.rb', line 59
def allowed_leaves
@allowed_leaves
end
|
Returns the value of attribute media_type.
59
60
61
|
# File 'lib/praxis/extensions/attribute_filtering/filtering_params.rb', line 59
def media_type
@media_type
end
|
Instance Attribute Details
#parsed_array ⇒ Object
Returns the value of attribute parsed_array.
33
34
35
|
# File 'lib/praxis/extensions/attribute_filtering/filtering_params.rb', line 33
def parsed_array
@parsed_array
end
|
Class Method Details
.add_any(name, operators:, fuzzy:) ⇒ Object
90
91
92
93
94
95
96
97
|
# File 'lib/praxis/extensions/attribute_filtering/filtering_params.rb', line 90
def add_any(name, operators:, fuzzy:)
raise 'Invalid set of operators passed' unless AVAILABLE_OPERATORS.superset?(operators)
@allowed_leaves[name] = {
operators: operators,
fuzzy_match: fuzzy
}
end
|
.add_filter(name, operators:, fuzzy:) ⇒ Object
78
79
80
81
82
83
84
85
86
87
88
|
# File 'lib/praxis/extensions/attribute_filtering/filtering_params.rb', line 78
def add_filter(name, operators:, fuzzy:)
components = name.to_s.split('.').map(&:to_sym)
attribute, _enclosing_type = find_filter_attribute(components, media_type)
raise 'Invalid set of operators passed' unless AVAILABLE_OPERATORS.superset?(operators)
@allowed_filters[name] = {
value_type: attribute.type,
operators: operators,
fuzzy_match: fuzzy
}
end
|
.construct(definition, **options) ⇒ Object
120
121
122
123
124
125
|
# File 'lib/praxis/extensions/attribute_filtering/filtering_params.rb', line 120
def self.construct(definition, **options)
return self if definition.nil?
DSLCompiler.new(self, **options).parse(*definition)
self
end
|
.constructable? ⇒ Boolean
116
117
118
|
# File 'lib/praxis/extensions/attribute_filtering/filtering_params.rb', line 116
def self.constructable?
true
end
|
.describe(_root = false, example: nil) ⇒ Object
208
209
210
211
212
213
214
215
216
217
218
|
# File 'lib/praxis/extensions/attribute_filtering/filtering_params.rb', line 208
def self.describe(_root = false, example: nil)
hash = super
if allowed_filters
hash[:filters] = allowed_filters.each_with_object({}) do |(name, spec), accum|
accum[name] = { operators: spec[:operators].to_a }
accum[name][:fuzzy] = true if spec[:fuzzy_match]
end
end
hash
end
|
.display_name ⇒ Object
108
109
110
|
# File 'lib/praxis/extensions/attribute_filtering/filtering_params.rb', line 108
def self.display_name
'Filtering'
end
|
.dump(value, **_opts) ⇒ Object
204
205
206
|
# File 'lib/praxis/extensions/attribute_filtering/filtering_params.rb', line 204
def self.dump(value, **_opts)
load(value).dump
end
|
.example(_context = Attributor::DEFAULT_ROOT_CONTEXT, **_options) ⇒ Object
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
|
# File 'lib/praxis/extensions/attribute_filtering/filtering_params.rb', line 138
def self.example(_context = Attributor::DEFAULT_ROOT_CONTEXT, **_options)
fields = if media_type
mt_example = media_type.example
pickable_fields = mt_example.object.keys & allowed_filters.keys
pickable_fields.sample(2).each_with_object([]) do |filter_name, arr|
op = allowed_filters[filter_name][:operators].to_a.sample(1).first
filter_components = filter_name.to_s.split('.').map(&:to_sym)
mapped_attribute, _enclosing_type = find_filter_attribute(filter_components, media_type)
unless mapped_attribute
raise "filter with name #{filter_name} does not correspond to an existing field inside " \
" MediaType #{media_type.name}"
end
if NOVALUE_OPERATORS.include?(op)
arr << "#{filter_name}#{op}" else
attr_example = filter_components.inject(mt_example) do |last, name|
last.send(name)
end
arr << "#{filter_name}#{op}#{attr_example}"
end
end.join('&')
else
'name=Joe&date>2017-01-01'
end
load(fields)
end
|
.family ⇒ Object
112
113
114
|
# File 'lib/praxis/extensions/attribute_filtering/filtering_params.rb', line 112
def self.family
'string'
end
|
.find_filter_attribute(name_components, type) ⇒ Object
127
128
129
130
131
132
133
134
135
136
|
# File 'lib/praxis/extensions/attribute_filtering/filtering_params.rb', line 127
def self.find_filter_attribute(name_components, type)
type = type.member_type if type < Attributor::Collection
first, *rest = name_components
first_attr = type.attributes[first]
raise "Error, you've requested to filter by field '#{first}' which does not exist in the #{type.name} mediatype!\n" unless first_attr
return find_filter_attribute(rest, first_attr.type) if rest.present?
[first_attr, type] end
|
.for(media_type, **_opts) ⇒ Object
61
62
63
64
65
66
67
68
69
70
71
72
|
# File 'lib/praxis/extensions/attribute_filtering/filtering_params.rb', line 61
def for(media_type, **_opts)
unless media_type < Praxis::MediaType
raise ArgumentError, "Invalid type: #{media_type.name} for Filters. " \
'Using the .for method for defining a filter, requires passing a subclass of a MediaType'
end
::Class.new(self) do
@media_type = media_type
@allowed_filters = {}
@allowed_leaves = {}
end
end
|
.json_schema_type ⇒ Object
74
75
76
|
# File 'lib/praxis/extensions/attribute_filtering/filtering_params.rb', line 74
def json_schema_type
:string
end
|
.load(filters, _context = Attributor::DEFAULT_ROOT_CONTEXT, **_options) ⇒ Object
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
|
# File 'lib/praxis/extensions/attribute_filtering/filtering_params.rb', line 173
def self.load(filters, _context = Attributor::DEFAULT_ROOT_CONTEXT, **_options)
return filters if filters.is_a?(native_type)
return new if filters.nil? || filters.blank?
parsed = Parser.new.parse(filters)
tree = ConditionGroup.load(parsed)
rr = tree.flattened_conditions
accum = []
rr.each do |spec|
attr_name = spec[:name]
coerced = \
if media_type
filter_components = attr_name.to_s.split('.').map(&:to_sym)
attr, _enclosing_type = find_filter_attribute(filter_components, media_type)
if spec[:values].is_a?(Array)
attr_coll = Attributor::Collection.of(attr.type)
attr_coll.load(spec[:values])
else
attr.load(spec[:values])
end
else
spec[:values]
end
accum.push(name: attr_name, op: spec[:op], value: coerced, fuzzy: spec[:fuzzies], node_object: spec[:node_object])
end
new(accum)
end
|
.name ⇒ Object
104
105
106
|
# File 'lib/praxis/extensions/attribute_filtering/filtering_params.rb', line 104
def self.name
'Praxis::Types::FilteringParams'
end
|
.native_type ⇒ Object
100
101
102
|
# File 'lib/praxis/extensions/attribute_filtering/filtering_params.rb', line 100
def self.native_type
self
end
|
.validate(value, context = Attributor::DEFAULT_ROOT_CONTEXT, _attribute = nil) ⇒ Object
168
169
170
171
|
# File 'lib/praxis/extensions/attribute_filtering/filtering_params.rb', line 168
def self.validate(value, context = Attributor::DEFAULT_ROOT_CONTEXT, _attribute = nil)
instance = load(value, context)
instance.validate(context)
end
|
Instance Method Details
#allowed_filters ⇒ Object
297
298
299
300
|
# File 'lib/praxis/extensions/attribute_filtering/filtering_params.rb', line 297
def allowed_filters
self.class.allowed_filters
end
|
#allowed_leaves ⇒ Object
302
303
304
305
|
# File 'lib/praxis/extensions/attribute_filtering/filtering_params.rb', line 302
def allowed_leaves
self.class.allowed_leaves
end
|
#dump ⇒ Object
Dump back string parseable form
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
|
# File 'lib/praxis/extensions/attribute_filtering/filtering_params.rb', line 264
def dump
parsed_array.each_with_object([]) do |item, arr|
field = item[:name]
value = \
if item[:value].is_a?(Array)
item[:value].map.with_index do |i, idx|
case item[:fuzzy][idx]
when nil
i
when :start
"*#{i}"
when :end
"#{i}*"
end
end.join(',')
else
case item[:fuzzy]
when nil
item[:value]
when :start
"*#{item[:value]}"
when :end
"#{item[:value]}*"
end
end
arr << "#{field}#{item[:op]}#{value}"
end.join('&')
end
|
#each(&block) ⇒ Object
293
294
295
|
# File 'lib/praxis/extensions/attribute_filtering/filtering_params.rb', line 293
def each(&block)
parsed_array&.each(&block)
end
|
#matching_leaf_filter(filter_string) ⇒ Object
224
225
226
227
228
229
|
# File 'lib/praxis/extensions/attribute_filtering/filtering_params.rb', line 224
def matching_leaf_filter(filter_string)
return nil unless allowed_leaves.keys.present?
last_component = filter_string.to_s.split('.').last.to_sym
allowed_leaves[last_component]
end
|
#validate(_context = Attributor::DEFAULT_ROOT_CONTEXT) ⇒ Object
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
|
# File 'lib/praxis/extensions/attribute_filtering/filtering_params.rb', line 231
def validate(_context = Attributor::DEFAULT_ROOT_CONTEXT)
return [] if allowed_filters.blank? && allowed_leaves.blank?
parsed_array.each_with_object([]) do |item, errors|
attr_name = item[:name]
attr_filters = allowed_filters[attr_name]
unless attr_filters
attr_filters = matching_leaf_filter(attr_name)
unless attr_filters
msg = "Filtering by #{attr_name} is not allowed. You can filter by #{allowed_filters.keys.map(&:to_s).join(', ')}"
msg += " or leaf attributes matching #{allowed_leaves.keys.map(&:to_s).join(', ')}" if allowed_leaves.keys.presence
errors << msg
next
end
end
allowed_operators = attr_filters[:operators]
errors << "Operator #{item[:op]} not allowed for filter #{attr_name}" unless allowed_operators.include?(item[:op])
value_type = attr_filters[:value_type]
next unless value_type == Attributor::String
next unless item[:value].presence
fuzzy_match = attr_filters[:fuzzy_match]
errors << "Fuzzy matching for #{attr_name} is not allowed (yet '*' was found in the value)" if item[:fuzzy] && !fuzzy_match && !(item[:fuzzy].is_a?(Array) && item[:fuzzy].compact.empty?)
end
end
|