Skip to content

Commit dbba6b5

Browse files
committed
[DSL] Allow calling methods in the scope of the surrounding search definition
1 parent 784281f commit dbba6b5

File tree

9 files changed

+141
-7
lines changed

9 files changed

+141
-7
lines changed

lib/elasticsearch/dsl/search.rb

+4-1
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@ class Search
5858

5959
def initialize(*args, &block)
6060
@options = Options.new *args
61-
block.arity < 1 ? self.instance_eval(&block) : block.call(self) if block
61+
@block = block
62+
@block.arity < 1 ? self.instance_eval(&@block) : @block.call(self) if @block
6263
end
6364

6465
# DSL method for building or accessing the `query` part of a search definition
@@ -263,6 +264,8 @@ def method_missing(name, *args, &block)
263264
if @options.respond_to? name
264265
@options.__send__ name, *args, &block
265266
self
267+
elsif @block
268+
@block.binding.eval('self').send(name, *args, &block)
266269
else
267270
super
268271
end

lib/elasticsearch/dsl/search/aggregation.rb

+3-1
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,10 @@ def method_missing(name, *args, &block)
4646
klass = Utils.__camelize(name)
4747
if Aggregations.const_defined? klass
4848
@value = Aggregations.const_get(klass).new *args, &block
49+
elsif @block && _self = @block.binding.eval('self') && _self.respond_to?(name)
50+
_self.send(name, *args, &block)
4951
else
50-
raise NoMethodError, "undefined method '#{name}' for #{self}"
52+
super
5153
end
5254
end
5355

lib/elasticsearch/dsl/search/base_aggregation_component.rb

+3-1
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,10 @@ def method_missing(name, *args, &block)
4040
klass = Utils.__camelize(name)
4141
if Aggregations.const_defined? klass
4242
@value = Aggregations.const_get(klass).new *args, &block
43+
elsif @block
44+
@block.binding.eval('self').send(name, *args, &block)
4345
else
44-
raise NoMethodError, "undefined method '#{name}' for #{self}"
46+
super
4547
end
4648
end
4749

lib/elasticsearch/dsl/search/base_component.rb

+10
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,16 @@ def to_hash(options={})
179179
@hash
180180
end
181181
end
182+
183+
private
184+
185+
def method_missing(name, *args, &block)
186+
if @block && _self = @block.binding.eval('self') && _self.respond_to?(name)
187+
_self.send(name, *args, &block)
188+
else
189+
super
190+
end
191+
end
182192
end
183193
end
184194
end

lib/elasticsearch/dsl/search/base_compound_filter_component.rb

+3-1
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,10 @@ def method_missing(name, *args, &block)
103103
klass = Utils.__camelize(name)
104104
if Filters.const_defined? klass
105105
@value << Filters.const_get(klass).new(*args, &block)
106+
elsif @block
107+
@block.binding.eval('self').send(name, *args, &block)
106108
else
107-
raise NoMethodError, "undefined method '#{name}' for #{self}"
109+
super
108110
end
109111
end
110112
end

lib/elasticsearch/dsl/search/filter.rb

+3-1
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,10 @@ def method_missing(name, *args, &block)
4040
klass = Utils.__camelize(name)
4141
if Filters.const_defined? klass
4242
@value = Filters.const_get(klass).new *args, &block
43+
elsif @block && _self = @block.binding.eval('self') && _self.respond_to?(name)
44+
_self.send(name, *args, &block)
4345
else
44-
raise NoMethodError, "undefined method '#{name}' for #{self}"
46+
super
4547
end
4648
end
4749

lib/elasticsearch/dsl/search/filters/not.rb

+3-1
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,10 @@ def method_missing(name, *args, &block)
6262
klass = Utils.__camelize(name)
6363
if Filters.const_defined? klass
6464
@value = Filters.const_get(klass).new(*args, &block)
65+
elsif @block && _self = @block.binding.eval('self') && _self.respond_to?(name)
66+
_self.send(name, *args, &block)
6567
else
66-
raise NoMethodError, "undefined method '#{name}' for #{self}"
68+
super
6769
end
6870
end
6971

lib/elasticsearch/dsl/search/query.rb

+3-1
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,10 @@ def method_missing(name, *args, &block)
4040
klass = Utils.__camelize(name)
4141
if Queries.const_defined? klass
4242
@value = Queries.const_get(klass).new *args, &block
43+
elsif @block
44+
@value = @block.binding.eval('self').send(name, *args, &block)
4345
else
44-
raise NoMethodError, "undefined method '#{name}' for #{self}"
46+
super
4547
end
4648
end
4749

spec/elasticsearch/dsl/search_spec.rb

+109
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,115 @@
4040
end
4141
end
4242

43+
context 'when a block is provided' do
44+
45+
context 'when the containing scope is accessed in the block' do
46+
47+
let(:s) do
48+
def query_value
49+
{ title: 'test' }
50+
end
51+
52+
search do
53+
query do
54+
match query_value
55+
end
56+
end
57+
end
58+
59+
let(:expected_hash) do
60+
{ query: { match: { title: 'test' } } }
61+
end
62+
63+
it 'allows access to the containing scope' do
64+
expect(s.to_hash).to eq(expected_hash)
65+
end
66+
end
67+
68+
context 'when other methods are used to construct the query' do
69+
70+
def bool_query(obj)
71+
obj.instance_eval do
72+
bool do
73+
must do
74+
match foo: 'bar'
75+
end
76+
filter do
77+
term foo: 'bar'
78+
end
79+
end
80+
end
81+
end
82+
83+
let(:my_search) do
84+
search do
85+
query do
86+
bool_query(self)
87+
end
88+
end
89+
end
90+
91+
it 'finds the correct bindings' do
92+
expect(my_search.to_hash).to eq(query: { bool: { filter: [{ term: { foo: 'bar' } }],
93+
must: [{ match: { foo: 'bar' } }] } })
94+
end
95+
end
96+
end
97+
98+
describe '#collapse' do
99+
100+
let(:s) do
101+
search do
102+
query do
103+
match title: 'test'
104+
end
105+
collapse :user do
106+
max_concurrent_group_searches 4
107+
inner_hits 'last_tweet' do
108+
size 10
109+
from 5
110+
sort do
111+
by :date, order: 'desc'
112+
by :likes, order: 'asc'
113+
end
114+
end
115+
end
116+
end
117+
end
118+
119+
let(:inner_hits_hash) do
120+
{ name: 'last_tweet',
121+
size: 10,
122+
from: 5,
123+
sort: [ { date: { order: 'desc' } },
124+
{ likes: { order: 'asc' } }]
125+
}
126+
end
127+
128+
let(:expected_hash) do
129+
{ query: { match: { title: 'test' } },
130+
collapse: { field: :user,
131+
max_concurrent_group_searches: 4,
132+
inner_hits: inner_hits_hash } }
133+
end
134+
135+
it 'sets the field name' do
136+
expect(s.to_hash[:collapse][:field]).to eq(:user)
137+
end
138+
139+
it 'sets the max_concurrent_group_searches option' do
140+
expect(s.to_hash[:collapse][:max_concurrent_group_searches]).to eq(4)
141+
end
142+
143+
it 'sets the inner_hits' do
144+
expect(s.to_hash[:collapse][:inner_hits]).to eq(inner_hits_hash)
145+
end
146+
147+
it 'constructs the correct hash' do
148+
expect(s.to_hash).to eq(expected_hash)
149+
end
150+
end
151+
43152
describe '#collapse' do
44153

45154
let(:s) do

0 commit comments

Comments
 (0)