chewy_query 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/.gitignore +22 -0
- data/.rspec +3 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +22 -0
- data/README.md +37 -0
- data/Rakefile +2 -0
- data/chewy_query.gemspec +27 -0
- data/lib/chewy_query.rb +12 -0
- data/lib/chewy_query/builder.rb +865 -0
- data/lib/chewy_query/builder/compose.rb +64 -0
- data/lib/chewy_query/builder/criteria.rb +182 -0
- data/lib/chewy_query/builder/filters.rb +227 -0
- data/lib/chewy_query/builder/nodes/and.rb +26 -0
- data/lib/chewy_query/builder/nodes/base.rb +17 -0
- data/lib/chewy_query/builder/nodes/bool.rb +33 -0
- data/lib/chewy_query/builder/nodes/equal.rb +34 -0
- data/lib/chewy_query/builder/nodes/exists.rb +20 -0
- data/lib/chewy_query/builder/nodes/expr.rb +28 -0
- data/lib/chewy_query/builder/nodes/field.rb +106 -0
- data/lib/chewy_query/builder/nodes/has_child.rb +14 -0
- data/lib/chewy_query/builder/nodes/has_parent.rb +14 -0
- data/lib/chewy_query/builder/nodes/has_relation.rb +61 -0
- data/lib/chewy_query/builder/nodes/match_all.rb +11 -0
- data/lib/chewy_query/builder/nodes/missing.rb +20 -0
- data/lib/chewy_query/builder/nodes/not.rb +26 -0
- data/lib/chewy_query/builder/nodes/or.rb +26 -0
- data/lib/chewy_query/builder/nodes/prefix.rb +18 -0
- data/lib/chewy_query/builder/nodes/query.rb +20 -0
- data/lib/chewy_query/builder/nodes/range.rb +63 -0
- data/lib/chewy_query/builder/nodes/raw.rb +15 -0
- data/lib/chewy_query/builder/nodes/regexp.rb +33 -0
- data/lib/chewy_query/builder/nodes/script.rb +20 -0
- data/lib/chewy_query/version.rb +3 -0
- data/spec/chewy_query/builder/context_spec.rb +529 -0
- data/spec/chewy_query/builder/filters_spec.rb +181 -0
- data/spec/chewy_query/builder/nodes/and_spec.rb +16 -0
- data/spec/chewy_query/builder/nodes/bool_spec.rb +22 -0
- data/spec/chewy_query/builder/nodes/equal_spec.rb +58 -0
- data/spec/chewy_query/builder/nodes/exists_spec.rb +16 -0
- data/spec/chewy_query/builder/nodes/has_child_spec.rb +79 -0
- data/spec/chewy_query/builder/nodes/has_parent_spec.rb +84 -0
- data/spec/chewy_query/builder/nodes/match_all_spec.rb +11 -0
- data/spec/chewy_query/builder/nodes/missing_spec.rb +14 -0
- data/spec/chewy_query/builder/nodes/not_spec.rb +14 -0
- data/spec/chewy_query/builder/nodes/or_spec.rb +16 -0
- data/spec/chewy_query/builder/nodes/prefix_spec.rb +15 -0
- data/spec/chewy_query/builder/nodes/query_spec.rb +17 -0
- data/spec/chewy_query/builder/nodes/range_spec.rb +36 -0
- data/spec/chewy_query/builder/nodes/raw_spec.rb +11 -0
- data/spec/chewy_query/builder/nodes/regexp_spec.rb +45 -0
- data/spec/chewy_query/builder/nodes/script_spec.rb +16 -0
- data/spec/chewy_query/builder_spec.rb +196 -0
- data/spec/chewy_query_spec.rb +0 -0
- data/spec/spec_helper.rb +8 -0
- metadata +191 -0
@@ -0,0 +1,64 @@
|
|
1
|
+
module ChewyQuery
|
2
|
+
class Builder
|
3
|
+
module Compose
|
4
|
+
|
5
|
+
protected
|
6
|
+
|
7
|
+
def _filtered_query(query, filter, options = {})
|
8
|
+
query = { match_all: {} } if !query.present? && filter.present?
|
9
|
+
|
10
|
+
if filter.present?
|
11
|
+
filtered = if query.present?
|
12
|
+
{ query: { filtered: { query: query, filter: filter } } }
|
13
|
+
else
|
14
|
+
{ query: { filtered: { filter: filter } } }
|
15
|
+
end
|
16
|
+
filtered[:query][:filtered].merge!(strategy: options[:strategy].to_s) if options[:strategy].present?
|
17
|
+
filtered
|
18
|
+
elsif query.present?
|
19
|
+
{ query: query }
|
20
|
+
else
|
21
|
+
{ }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def _queries_join(queries, logic)
|
26
|
+
queries = queries.compact
|
27
|
+
|
28
|
+
if queries.many? || (queries.any? && logic == :must_not)
|
29
|
+
case logic
|
30
|
+
when :dis_max
|
31
|
+
{ dis_max: { queries: queries } }
|
32
|
+
when :must, :should, :must_not
|
33
|
+
{ bool: { logic => queries } }
|
34
|
+
else
|
35
|
+
if logic.is_a?(Float)
|
36
|
+
{ dis_max: { queries: queries, tie_breaker: logic } }
|
37
|
+
else
|
38
|
+
{ bool: { should: queries, minimum_should_match: logic } }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
else
|
42
|
+
queries.first
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def _filters_join(filters, logic)
|
47
|
+
filters = filters.compact
|
48
|
+
|
49
|
+
if filters.many? || (filters.any? && logic == :must_not)
|
50
|
+
case logic
|
51
|
+
when :and, :or
|
52
|
+
{ logic => filters }
|
53
|
+
when :must, :should, :must_not
|
54
|
+
{ bool: { logic => filters } }
|
55
|
+
else
|
56
|
+
{ bool: { should: filters, minimum_should_match: logic } }
|
57
|
+
end
|
58
|
+
else
|
59
|
+
filters.first
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,182 @@
|
|
1
|
+
require 'chewy_query/builder/compose'
|
2
|
+
|
3
|
+
module ChewyQuery
|
4
|
+
class Builder
|
5
|
+
class Criteria
|
6
|
+
include Compose
|
7
|
+
|
8
|
+
ARRAY_STORAGES = [:queries, :filters, :post_filters, :sort, :fields, :types, :scores]
|
9
|
+
HASH_STORAGES = [:options, :request_options, :facets, :aggregations, :suggest]
|
10
|
+
STORAGES = ARRAY_STORAGES + HASH_STORAGES
|
11
|
+
|
12
|
+
def initialize(options = {})
|
13
|
+
@options = { query_mode: :must, filter_mode: :and, post_filter_mode: nil }.merge(options)
|
14
|
+
@options[:post_filter_mode] = @options[:filter_mode] unless @options[:post_filter_mode]
|
15
|
+
end
|
16
|
+
|
17
|
+
def ==(other)
|
18
|
+
other.is_a?(self.class) && storages == other.storages
|
19
|
+
end
|
20
|
+
|
21
|
+
{ ARRAY_STORAGES => '[]', HASH_STORAGES => '{}' }.each do |storages, default|
|
22
|
+
storages.each do |storage|
|
23
|
+
class_eval <<-METHODS, __FILE__, __LINE__ + 1
|
24
|
+
def #{storage}
|
25
|
+
@#{storage} ||= #{default}
|
26
|
+
end
|
27
|
+
METHODS
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
STORAGES.each do |storage|
|
32
|
+
define_method "#{storage}?" do
|
33
|
+
send(storage).any?
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def none?
|
38
|
+
!!options[:none]
|
39
|
+
end
|
40
|
+
|
41
|
+
def update_options(modifer)
|
42
|
+
options.merge!(modifer)
|
43
|
+
end
|
44
|
+
|
45
|
+
def update_request_options(modifer)
|
46
|
+
request_options.merge!(modifer)
|
47
|
+
end
|
48
|
+
|
49
|
+
def update_facets(modifer)
|
50
|
+
facets.merge!(modifer)
|
51
|
+
end
|
52
|
+
|
53
|
+
def update_scores(modifer)
|
54
|
+
@scores = scores + Array.wrap(modifer).reject(&:blank?)
|
55
|
+
end
|
56
|
+
|
57
|
+
def update_aggregations(modifer)
|
58
|
+
aggregations.merge!(modifer)
|
59
|
+
end
|
60
|
+
|
61
|
+
def update_suggest(modifier)
|
62
|
+
suggest.merge!(modifier)
|
63
|
+
end
|
64
|
+
|
65
|
+
[:filters, :queries, :post_filters].each do |storage|
|
66
|
+
class_eval <<-RUBY
|
67
|
+
def update_#{storage}(modifer)
|
68
|
+
@#{storage} = #{storage} + Array.wrap(modifer).reject(&:blank?)
|
69
|
+
end
|
70
|
+
RUBY
|
71
|
+
end
|
72
|
+
|
73
|
+
def update_sort(modifer, options = {})
|
74
|
+
@sort = nil if options[:purge]
|
75
|
+
modifer = Array.wrap(modifer).flatten.map do |element|
|
76
|
+
element.is_a?(Hash) ? element.map{|k, v| { k => v } } : element
|
77
|
+
end.flatten
|
78
|
+
@sort = sort + modifer
|
79
|
+
end
|
80
|
+
|
81
|
+
%w(fields types).each do |storage|
|
82
|
+
define_method "update_#{storage}" do |modifer, options = {}|
|
83
|
+
variable = "@#{storage}"
|
84
|
+
instance_variable_set(variable, nil) if options[:purge]
|
85
|
+
modifer = send(storage) | Array.wrap(modifer).flatten.map(&:to_s).reject(&:blank?)
|
86
|
+
instance_variable_set(variable, modifer)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def merge!(other)
|
91
|
+
STORAGES.each do |storage|
|
92
|
+
send("update_#{storage}", other.send(storage))
|
93
|
+
end
|
94
|
+
self
|
95
|
+
end
|
96
|
+
|
97
|
+
def merge(other)
|
98
|
+
clone.merge!(other)
|
99
|
+
end
|
100
|
+
|
101
|
+
def request_body
|
102
|
+
body = {}
|
103
|
+
|
104
|
+
body.merge!(_filtered_query(_request_query, _request_filter, options.slice(:strategy)))
|
105
|
+
body.merge!(post_filter: _request_post_filter) if post_filters?
|
106
|
+
body.merge!(facets: facets) if facets?
|
107
|
+
body.merge!(aggregations: aggregations) if aggregations?
|
108
|
+
body.merge!(suggest: suggest) if suggest?
|
109
|
+
body.merge!(sort: sort) if sort?
|
110
|
+
body.merge!(_source: fields) if fields?
|
111
|
+
|
112
|
+
body = _boost_query(body)
|
113
|
+
|
114
|
+
{ body: body.merge!(request_options) }
|
115
|
+
end
|
116
|
+
|
117
|
+
def delete_all_request_body
|
118
|
+
filtered_query = _filtered_query(_request_query, _request_filter, options.slice(:strategy))
|
119
|
+
{ body: filtered_query.presence || { query: { match_all: {} } } }
|
120
|
+
end
|
121
|
+
|
122
|
+
protected
|
123
|
+
|
124
|
+
def storages
|
125
|
+
STORAGES.map{|storage| send(storage) }
|
126
|
+
end
|
127
|
+
|
128
|
+
def initialize_clone(other)
|
129
|
+
STORAGES.each do |storage|
|
130
|
+
value = other.send(storage)
|
131
|
+
instance_variable_set("@#{storage}", value.deep_dup)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def _boost_query(body)
|
136
|
+
scores? or return body
|
137
|
+
query = body.delete(:query)
|
138
|
+
filter = body.delete(:filter)
|
139
|
+
|
140
|
+
if query && filter
|
141
|
+
query = { filtered: { query: query, filter: filter } }
|
142
|
+
filter = nil
|
143
|
+
end
|
144
|
+
|
145
|
+
score = { }
|
146
|
+
score[:functions] = scores
|
147
|
+
score[:boost_mode] = options[:boost_mode] if options[:boost_mode]
|
148
|
+
score[:score_mode] = options[:score_mode] if options[:score_mode]
|
149
|
+
score[:query] = query if query
|
150
|
+
score[:filter] = filter if filter
|
151
|
+
body.tap{|b| b[:query] = { function_score: score } }
|
152
|
+
end
|
153
|
+
|
154
|
+
def _request_options
|
155
|
+
options.slice(:size, :from, :explain, :highlight, :rescore)
|
156
|
+
end
|
157
|
+
|
158
|
+
def _request_query
|
159
|
+
_queries_join(queries, options[:query_mode])
|
160
|
+
end
|
161
|
+
|
162
|
+
def _request_filter
|
163
|
+
filter_mode = options[:filter_mode]
|
164
|
+
request_filter = if filter_mode == :and
|
165
|
+
filters
|
166
|
+
else
|
167
|
+
[_filters_join(filters, filter_mode)]
|
168
|
+
end
|
169
|
+
|
170
|
+
_filters_join([_request_types, *request_filter], :and)
|
171
|
+
end
|
172
|
+
|
173
|
+
def _request_types
|
174
|
+
_filters_join(types.map{|type| { type: { value: type } } }, :or)
|
175
|
+
end
|
176
|
+
|
177
|
+
def _request_post_filter
|
178
|
+
_filters_join(post_filters, options[:post_filter_mode])
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
@@ -0,0 +1,227 @@
|
|
1
|
+
require 'chewy_query/builder/nodes/base'
|
2
|
+
require 'chewy_query/builder/nodes/expr'
|
3
|
+
require 'chewy_query/builder/nodes/field'
|
4
|
+
require 'chewy_query/builder/nodes/bool'
|
5
|
+
require 'chewy_query/builder/nodes/and'
|
6
|
+
require 'chewy_query/builder/nodes/or'
|
7
|
+
require 'chewy_query/builder/nodes/not'
|
8
|
+
require 'chewy_query/builder/nodes/raw'
|
9
|
+
require 'chewy_query/builder/nodes/exists'
|
10
|
+
require 'chewy_query/builder/nodes/missing'
|
11
|
+
require 'chewy_query/builder/nodes/range'
|
12
|
+
require 'chewy_query/builder/nodes/prefix'
|
13
|
+
require 'chewy_query/builder/nodes/regexp'
|
14
|
+
require 'chewy_query/builder/nodes/equal'
|
15
|
+
require 'chewy_query/builder/nodes/query'
|
16
|
+
require 'chewy_query/builder/nodes/script'
|
17
|
+
require 'chewy_query/builder/nodes/has_child'
|
18
|
+
require 'chewy_query/builder/nodes/has_parent'
|
19
|
+
require 'chewy_query/builder/nodes/match_all'
|
20
|
+
|
21
|
+
module ChewyQuery
|
22
|
+
class Builder
|
23
|
+
# Context provides simplified DSL functionality for filters declaring.
|
24
|
+
# You can use logic operations <tt>&</tt> and <tt>|</tt> to concat
|
25
|
+
# expressions.
|
26
|
+
#
|
27
|
+
# builder.filter{ (article.title =~ /Honey/) & (age < 42) & !rate }
|
28
|
+
#
|
29
|
+
#
|
30
|
+
class Filters
|
31
|
+
def initialize(outer = nil, &block)
|
32
|
+
@block = block
|
33
|
+
@outer = outer || eval('self', block.binding)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Outer scope call
|
37
|
+
# Block evaluates in the external context
|
38
|
+
#
|
39
|
+
# def name
|
40
|
+
# 'Friend'
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
# builder.filter{ name == o{ name } } # => {filter: {term: {name: 'Friend'}}}
|
44
|
+
#
|
45
|
+
def o(&block)
|
46
|
+
@outer.instance_exec(&block)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Returns field node
|
50
|
+
# Used if method_missing is not working by some reason.
|
51
|
+
# Additional expression options might be passed as second argument hash.
|
52
|
+
#
|
53
|
+
# builder.filter{ f(:name) == 'Name' } == builder.filter{ name == 'Name' } # => true
|
54
|
+
# builder.filter{ f(:name, execution: :bool) == ['Name1', 'Name2'] } ==
|
55
|
+
# builder.filter{ name(execution: :bool) == ['Name1', 'Name2'] } # => true
|
56
|
+
#
|
57
|
+
# Supports block for getting field name from the outer scope
|
58
|
+
#
|
59
|
+
# def field
|
60
|
+
# :name
|
61
|
+
# end
|
62
|
+
#
|
63
|
+
# builder.filter{ f{ field } == 'Name' } == builder.filter{ name == 'Name' } # => true
|
64
|
+
#
|
65
|
+
def f(name = nil, *args, &block)
|
66
|
+
name = block ? o(&block) : name
|
67
|
+
Nodes::Field.new(name, *args)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Returns script filter
|
71
|
+
# Just script filter. Supports additional params.
|
72
|
+
#
|
73
|
+
# builder.filter{ s('doc["num1"].value > 1') }
|
74
|
+
# builder.filter{ s('doc["num1"].value > param1', param1: 42) }
|
75
|
+
#
|
76
|
+
# Supports block for getting script from the outer scope
|
77
|
+
#
|
78
|
+
# def script
|
79
|
+
# 'doc["num1"].value > param1 || 1'
|
80
|
+
# end
|
81
|
+
#
|
82
|
+
# builder.filter{ s{ script } } == builder.filter{ s('doc["num1"].value > 1') } # => true
|
83
|
+
# builder.filter{ s(param1: 42) { script } } == builder.filter{ s('doc["num1"].value > 1', param1: 42) } # => true
|
84
|
+
#
|
85
|
+
def s(*args, &block)
|
86
|
+
params = args.extract_options!
|
87
|
+
script = block ? o(&block) : args.first
|
88
|
+
Nodes::Script.new(script, params)
|
89
|
+
end
|
90
|
+
|
91
|
+
# Returns query filter
|
92
|
+
#
|
93
|
+
# builder.filter{ q(query_string: {query: 'name: hello'}) }
|
94
|
+
#
|
95
|
+
# Supports block for getting query from the outer scope
|
96
|
+
#
|
97
|
+
# def query
|
98
|
+
# {query_string: {query: 'name: hello'}}
|
99
|
+
# end
|
100
|
+
#
|
101
|
+
# builder.filter{ q{ query } } == builder.filter{ q(query_string: {query: 'name: hello'}) } # => true
|
102
|
+
#
|
103
|
+
def q(query = nil, &block)
|
104
|
+
Nodes::Query.new(block ? o(&block) : query)
|
105
|
+
end
|
106
|
+
|
107
|
+
# Returns raw expression
|
108
|
+
# Same as filter with arguments instead of block, but can participate in expressions
|
109
|
+
#
|
110
|
+
# builder.filter{ r(term: {name: 'Name'}) }
|
111
|
+
# builder.filter{ r(term: {name: 'Name'}) & (age < 42) }
|
112
|
+
#
|
113
|
+
# Supports block for getting raw filter from the outer scope
|
114
|
+
#
|
115
|
+
# def filter
|
116
|
+
# {term: {name: 'Name'}}
|
117
|
+
# end
|
118
|
+
#
|
119
|
+
# builder.filter{ r{ filter } } == builder.filter{ r(term: {name: 'Name'}) } # => true
|
120
|
+
# builder.filter{ r{ filter } } == builder.filter(term: {name: 'Name'}) # => true
|
121
|
+
#
|
122
|
+
def r(raw = nil, &block)
|
123
|
+
Nodes::Raw.new(block ? o(&block) : raw)
|
124
|
+
end
|
125
|
+
|
126
|
+
# Bool filter chainable methods
|
127
|
+
# Used to create bool query. Nodes are passed as arguments.
|
128
|
+
#
|
129
|
+
# builder.filter{ must(age < 42, name == 'Name') }
|
130
|
+
# builder.filter{ should(age < 42, name == 'Name') }
|
131
|
+
# builder.filter{ must(age < 42).should(name == 'Name1', name == 'Name2') }
|
132
|
+
# builder.filter{ should_not(age >= 42).must(name == 'Name1') }
|
133
|
+
#
|
134
|
+
%w(must must_not should).each do |method|
|
135
|
+
define_method method do |*exprs|
|
136
|
+
Nodes::Bool.new.send(method, *exprs)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# Initializes has_child filter.
|
141
|
+
# Chainable interface acts the same as main query interface. You can pass plain
|
142
|
+
# filters or plain queries or filter with DSL block.
|
143
|
+
#
|
144
|
+
# builder.filter{ has_child('user').filter(term: {role: 'Admin'}) }
|
145
|
+
# builder.filter{ has_child('user').filter{ role == 'Admin' } }
|
146
|
+
# builder.filter{ has_child('user').query(match: {name: 'borogoves'}) }
|
147
|
+
#
|
148
|
+
# Filters and queries might be combined and filter_mode and query_mode are configurable:
|
149
|
+
#
|
150
|
+
# builder.filter do
|
151
|
+
# has_child('user')
|
152
|
+
# .filter{ name: 'Peter' }
|
153
|
+
# .query(match: {name: 'Peter'})
|
154
|
+
# .filter{ age > 42 }
|
155
|
+
# .filter_mode(:or)
|
156
|
+
# end
|
157
|
+
#
|
158
|
+
def has_child(type)
|
159
|
+
Nodes::HasChild.new(type, @outer)
|
160
|
+
end
|
161
|
+
|
162
|
+
# Initializes has_parent filter.
|
163
|
+
# Chainable interface acts the same as main query interface. You can pass plain
|
164
|
+
# filters or plain queries or filter with DSL block.
|
165
|
+
#
|
166
|
+
# builder.filter{ has_parent('user').filter(term: {role: 'Admin'}) }
|
167
|
+
# builder.filter{ has_parent('user').filter{ role == 'Admin' } }
|
168
|
+
# builder.filter{ has_parent('user').query(match: {name: 'borogoves'}) }
|
169
|
+
#
|
170
|
+
# Filters and queries might be combined and filter_mode and query_mode are configurable:
|
171
|
+
#
|
172
|
+
# builder.filter do
|
173
|
+
# has_parent('user')
|
174
|
+
# .filter{ name: 'Peter' }
|
175
|
+
# .query(match: {name: 'Peter'})
|
176
|
+
# .filter{ age > 42 }
|
177
|
+
# .filter_mode(:or)
|
178
|
+
# end
|
179
|
+
#
|
180
|
+
def has_parent(type)
|
181
|
+
Nodes::HasParent.new(type, @outer)
|
182
|
+
end
|
183
|
+
|
184
|
+
# Just simple match_all filter.
|
185
|
+
#
|
186
|
+
def match_all
|
187
|
+
Nodes::MatchAll.new
|
188
|
+
end
|
189
|
+
|
190
|
+
# Creates field or exists node
|
191
|
+
# Additional options for further expression might be passed as hash
|
192
|
+
#
|
193
|
+
# builder.filter{ name == 'Name' } == builder.filter(term: {name: 'Name'}) # => true
|
194
|
+
# builder.filter{ name? } == builder.filter(exists: {term: 'name'}) # => true
|
195
|
+
# builder.filter{ name(execution: :bool) == ['Name1', 'Name2'] } ==
|
196
|
+
# builder.filter(terms: {name: ['Name1', 'Name2'], execution: :bool}) # => true
|
197
|
+
#
|
198
|
+
# Also field names might be chained to use dot-notation for ES field names
|
199
|
+
#
|
200
|
+
# builder.filter{ article.title =~ 'Hello' }
|
201
|
+
# builder.filter{ article.tags? }
|
202
|
+
#
|
203
|
+
def method_missing(method, *args, &block)
|
204
|
+
method = method.to_s
|
205
|
+
if method =~ /\?\Z/
|
206
|
+
Nodes::Exists.new(method.gsub(/\?\Z/, ''))
|
207
|
+
else
|
208
|
+
f(method, *args)
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
# Evaluates context block, returns top node.
|
213
|
+
# For internal usage.
|
214
|
+
#
|
215
|
+
def __result__
|
216
|
+
instance_exec(&@block)
|
217
|
+
end
|
218
|
+
|
219
|
+
# Renders evaluated filters.
|
220
|
+
# For internal usage.
|
221
|
+
#
|
222
|
+
def __render__
|
223
|
+
__result__.__render__ # haha, wtf?
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|