elastic_record 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +23 -0
- data/lib/elastic_record.rb +15 -0
- data/lib/elastic_record/config.rb +21 -0
- data/lib/elastic_record/connection.rb +15 -0
- data/lib/elastic_record/model.rb +21 -0
- data/lib/elastic_record/orm/active_record.rb +4 -0
- data/lib/elastic_record/relation.rb +61 -0
- data/lib/elastic_record/relation/batches.rb +14 -0
- data/lib/elastic_record/relation/delegation.rb +18 -0
- data/lib/elastic_record/relation/finder_methods.rb +25 -0
- data/lib/elastic_record/relation/merging.rb +34 -0
- data/lib/elastic_record/relation/search_methods.rb +185 -0
- data/lib/elastic_record/searching.rb +30 -0
- data/test/elastic_record/config_test.rb +4 -0
- data/test/elastic_record/connection_test.rb +10 -0
- data/test/elastic_record/model_test.rb +10 -0
- data/test/elastic_record/relation/batches_test.rb +33 -0
- data/test/elastic_record/relation/delegation_test.rb +32 -0
- data/test/elastic_record/relation/finder_methods_test.rb +42 -0
- data/test/elastic_record/relation/merging_test.rb +21 -0
- data/test/elastic_record/relation/search_methods_test.rb +157 -0
- data/test/elastic_record/relation_test.rb +52 -0
- data/test/elastic_record/searching_test.rb +22 -0
- data/test/helper.rb +16 -0
- data/test/support/connect.rb +1 -0
- data/test/support/widget.rb +39 -0
- metadata +120 -0
data/README.rdoc
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
Expects that your model implements find(*ids)
|
2
|
+
|
3
|
+
class Widgets
|
4
|
+
include ElasticRecord::Model
|
5
|
+
end
|
6
|
+
|
7
|
+
scope = Widgets.filter(color: 'red').order(:price)
|
8
|
+
|
9
|
+
scope.count
|
10
|
+
scope.first
|
11
|
+
scope.last
|
12
|
+
scope.all
|
13
|
+
scope.each
|
14
|
+
|
15
|
+
Class methods are executed within scopes:
|
16
|
+
|
17
|
+
class Widgets
|
18
|
+
def self.ouput_to_screen
|
19
|
+
all.each do { |widget| puts widget }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
scope.output_to_screen
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module ElasticRecord
|
2
|
+
autoload :Config, 'elastic_record/config'
|
3
|
+
autoload :Connection, 'elastic_record/connection'
|
4
|
+
|
5
|
+
autoload :Relation, 'elastic_record/relation'
|
6
|
+
autoload :Batches, 'elastic_record/relation/batches'
|
7
|
+
autoload :Delegation, 'elastic_record/relation/delegation'
|
8
|
+
autoload :FinderMethods, 'elastic_record/relation/finder_methods'
|
9
|
+
autoload :Merging, 'elastic_record/relation/merging'
|
10
|
+
autoload :SearchMethods, 'elastic_record/relation/search_methods'
|
11
|
+
|
12
|
+
autoload :Searching, 'elastic_record/searching'
|
13
|
+
|
14
|
+
autoload :Model, 'elastic_record/model'
|
15
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module ElasticRecord
|
2
|
+
class Config
|
3
|
+
class << self
|
4
|
+
def servers=(value)
|
5
|
+
@servers = value
|
6
|
+
end
|
7
|
+
|
8
|
+
def servers
|
9
|
+
@servers
|
10
|
+
end
|
11
|
+
|
12
|
+
def connection_options
|
13
|
+
@connection_options ||= {}
|
14
|
+
end
|
15
|
+
|
16
|
+
def connection_options=(options)
|
17
|
+
@connection_options = options
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module ElasticRecord
|
2
|
+
module Connection
|
3
|
+
def elastic_connection
|
4
|
+
ElasticRecord::Config.connection_options
|
5
|
+
@elastic_connection ||= ElasticSearch.new(
|
6
|
+
ElasticRecord::Config.servers,
|
7
|
+
ElasticRecord::Config.connection_options.merge(index: model_name.collection, type: model_name.element)
|
8
|
+
)
|
9
|
+
end
|
10
|
+
|
11
|
+
def elastic_connection=(connection)
|
12
|
+
@elastic_connection = connection
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module ElasticRecord
|
2
|
+
module Model
|
3
|
+
def self.included(base)
|
4
|
+
base.class_eval do
|
5
|
+
extend Connection
|
6
|
+
extend Searching
|
7
|
+
extend ClassMethods
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
def relation
|
13
|
+
ElasticRecord::Relation.new(self, arelastic)
|
14
|
+
end
|
15
|
+
|
16
|
+
def arelastic
|
17
|
+
@arelastic ||= Arelastic::Builders::Search.new
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# require 'elastic_record/relation/delegation'
|
2
|
+
# require 'elastic_record/relation/finder_methods'
|
3
|
+
# require 'elastic_record/relation/merging'
|
4
|
+
# require 'elastic_record/relation/search_methods'
|
5
|
+
|
6
|
+
module ElasticRecord
|
7
|
+
class Relation
|
8
|
+
MULTI_VALUE_METHODS = [:extending, :filter, :facet, :order]
|
9
|
+
SINGLE_VALUE_METHODS = [:query, :limit, :offset]
|
10
|
+
|
11
|
+
include Batches, Delegation, FinderMethods, Merging, SearchMethods
|
12
|
+
|
13
|
+
attr_reader :klass, :arelastic, :values
|
14
|
+
|
15
|
+
def initialize(klass, arelastic)
|
16
|
+
@klass = klass
|
17
|
+
@arelastic = arelastic
|
18
|
+
@values = {}
|
19
|
+
end
|
20
|
+
|
21
|
+
def count
|
22
|
+
to_hits.total_entries
|
23
|
+
end
|
24
|
+
|
25
|
+
def facets
|
26
|
+
to_hits.facets
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_a
|
30
|
+
@records ||= klass.find(to_ids)
|
31
|
+
end
|
32
|
+
|
33
|
+
def to_ids
|
34
|
+
to_hits.to_a.map(&:id)
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_hits
|
38
|
+
@hits ||= klass.elastic_connection.search(as_elastic)#, ids_only: true)
|
39
|
+
end
|
40
|
+
|
41
|
+
def ==(other)
|
42
|
+
case other
|
43
|
+
when Relation
|
44
|
+
other.as_elastic == as_elastic
|
45
|
+
when Array
|
46
|
+
to_a == other
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def inspect
|
51
|
+
to_a.inspect
|
52
|
+
end
|
53
|
+
|
54
|
+
def scoping
|
55
|
+
previous, klass.current_elastic_search = klass.current_elastic_search, self
|
56
|
+
yield
|
57
|
+
ensure
|
58
|
+
klass.current_elastic_search = previous
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module ElasticRecord
|
2
|
+
module Batches
|
3
|
+
def find_each
|
4
|
+
options = {scroll: '20m', size: 100, search_type: 'scan'}
|
5
|
+
|
6
|
+
hits = klass.elastic_connection.search(as_elastic, options)
|
7
|
+
|
8
|
+
klass.elastic_connection.scroll(hits.scroll_id, scroll: options[:scroll], ids_only: true) do |hits|
|
9
|
+
records = klass.find(hits.to_a)
|
10
|
+
records.each { |record| yield record }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module ElasticRecord
|
2
|
+
module Delegation
|
3
|
+
def to_ary
|
4
|
+
to_a.to_ary
|
5
|
+
end
|
6
|
+
|
7
|
+
private
|
8
|
+
def method_missing(method, *args, &block)
|
9
|
+
if klass.respond_to?(method)
|
10
|
+
scoping { klass.send(method, *args, &block) }
|
11
|
+
elsif Array.method_defined?(method)
|
12
|
+
to_a.send(method, *args, &block)
|
13
|
+
else
|
14
|
+
super
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module ElasticRecord
|
2
|
+
module FinderMethods
|
3
|
+
def find(id)
|
4
|
+
filter(arelastic.filter.ids(id)).to_a.first
|
5
|
+
end
|
6
|
+
|
7
|
+
def first
|
8
|
+
find_one order('_uid')
|
9
|
+
end
|
10
|
+
|
11
|
+
def last
|
12
|
+
find_one order('color' => 'reverse')
|
13
|
+
end
|
14
|
+
|
15
|
+
def all
|
16
|
+
to_a
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def find_one(relation)
|
22
|
+
relation.limit(1).to_a.first
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module ElasticRecord
|
2
|
+
module Merging
|
3
|
+
def merge!(other)
|
4
|
+
Merger.new(self, other).merge
|
5
|
+
end
|
6
|
+
|
7
|
+
def merge(other)
|
8
|
+
clone.merge!(other)
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
class Merger
|
13
|
+
attr_accessor :relation, :values
|
14
|
+
|
15
|
+
def initialize(relation, other)
|
16
|
+
@relation = relation
|
17
|
+
@values = other.values
|
18
|
+
end
|
19
|
+
|
20
|
+
def normal_values
|
21
|
+
Relation::MULTI_VALUE_METHODS + Relation::SINGLE_VALUE_METHODS
|
22
|
+
end
|
23
|
+
|
24
|
+
def merge
|
25
|
+
normal_values.each do |name|
|
26
|
+
value = values[name]
|
27
|
+
relation.send("#{name}!", value) unless value.blank?
|
28
|
+
end
|
29
|
+
|
30
|
+
relation
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,185 @@
|
|
1
|
+
module ElasticRecord
|
2
|
+
module SearchMethods
|
3
|
+
Relation::MULTI_VALUE_METHODS.each do |name|
|
4
|
+
class_eval <<-CODE, __FILE__, __LINE__ + 1
|
5
|
+
def #{name}_values # def filter_values
|
6
|
+
@values[:#{name}] || [] # @values[:filter] || []
|
7
|
+
end # end
|
8
|
+
#
|
9
|
+
def #{name}_values=(values) # def filter_values=(values)
|
10
|
+
@values[:#{name}] = values # @values[:filter] = values
|
11
|
+
end # end
|
12
|
+
CODE
|
13
|
+
end
|
14
|
+
|
15
|
+
Relation::SINGLE_VALUE_METHODS.each do |name|
|
16
|
+
class_eval <<-CODE, __FILE__, __LINE__ + 1
|
17
|
+
def #{name}_value # def offset_value
|
18
|
+
@values[:#{name}] # @values[:offset]
|
19
|
+
end # end
|
20
|
+
|
21
|
+
def #{name}_value=(value) # def offset_value=(value)
|
22
|
+
@values[:#{name}] = value # @values[:offset] = value
|
23
|
+
end # end
|
24
|
+
CODE
|
25
|
+
end
|
26
|
+
|
27
|
+
def query!(value)
|
28
|
+
self.query_value = value
|
29
|
+
self
|
30
|
+
end
|
31
|
+
|
32
|
+
def query(value)
|
33
|
+
clone.query! value
|
34
|
+
end
|
35
|
+
|
36
|
+
def filter!(*args)
|
37
|
+
self.filter_values += args.flatten
|
38
|
+
self
|
39
|
+
end
|
40
|
+
|
41
|
+
def filter(*args)
|
42
|
+
clone.filter!(*args)
|
43
|
+
end
|
44
|
+
|
45
|
+
def limit!(value)
|
46
|
+
self.limit_value = value
|
47
|
+
self
|
48
|
+
end
|
49
|
+
|
50
|
+
def limit(value)
|
51
|
+
clone.limit!(value)
|
52
|
+
end
|
53
|
+
|
54
|
+
def offset!(value)
|
55
|
+
self.offset_value = value
|
56
|
+
self
|
57
|
+
end
|
58
|
+
|
59
|
+
def offset(value)
|
60
|
+
clone.offset! value
|
61
|
+
end
|
62
|
+
|
63
|
+
def facet!(name_or_facet, options = {})
|
64
|
+
if name_or_facet.is_a?(String)
|
65
|
+
self.facet_values += [arelastic.facet[name_or_facet].terms(name_or_facet, options)]
|
66
|
+
else
|
67
|
+
self.facet_values += [name_or_facet]
|
68
|
+
end
|
69
|
+
|
70
|
+
self
|
71
|
+
end
|
72
|
+
|
73
|
+
def facet(facet_or_name, options = {})
|
74
|
+
clone.facet! facet_or_name, options = {}
|
75
|
+
end
|
76
|
+
|
77
|
+
def order!(*args)
|
78
|
+
self.order_values += args.flatten
|
79
|
+
self
|
80
|
+
end
|
81
|
+
|
82
|
+
def order(*args)
|
83
|
+
clone.order! *args
|
84
|
+
end
|
85
|
+
|
86
|
+
def extending!(*modules, &block)
|
87
|
+
modules << Module.new(&block) if block_given?
|
88
|
+
|
89
|
+
self.extending_values += modules.flatten
|
90
|
+
extend(*extending_values)
|
91
|
+
|
92
|
+
self
|
93
|
+
end
|
94
|
+
|
95
|
+
def extending(*modules, &block)
|
96
|
+
clone.extending!(*modules, &block)
|
97
|
+
end
|
98
|
+
|
99
|
+
def as_elastic
|
100
|
+
build_search.as_elastic
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
def build_search
|
105
|
+
searches = [
|
106
|
+
build_query_and_filter(query_value, filter_values),
|
107
|
+
build_limit(limit_value),
|
108
|
+
build_offset(offset_value),
|
109
|
+
build_facets(facet_values),
|
110
|
+
build_orders(order_values)
|
111
|
+
].compact
|
112
|
+
|
113
|
+
Arelastic::Nodes::HashGroup.new searches
|
114
|
+
end
|
115
|
+
|
116
|
+
def build_query_and_filter(query, filters)
|
117
|
+
query = build_query(query)
|
118
|
+
filter = build_filter(filters)
|
119
|
+
if query && filter
|
120
|
+
arelastic.query.filtered(query, filter)
|
121
|
+
elsif query
|
122
|
+
query
|
123
|
+
elsif filter
|
124
|
+
arelastic.query.constant_score(filter)
|
125
|
+
else
|
126
|
+
arelastic.query.match_all
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def build_query(query)
|
131
|
+
if query.is_a?(String)
|
132
|
+
query = Arelastic::Queries::QueryString.new query
|
133
|
+
end
|
134
|
+
|
135
|
+
if query
|
136
|
+
Arelastic::Searches::Query.new query
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def build_filter(filters)
|
141
|
+
nodes = []
|
142
|
+
|
143
|
+
filters.map do |filter|
|
144
|
+
if filter.is_a?(Arelastic::Filters::Filter)
|
145
|
+
nodes << filter
|
146
|
+
else
|
147
|
+
filter.each do |field, terms|
|
148
|
+
case terms
|
149
|
+
when Array, Range
|
150
|
+
nodes << arelastic[field].in(terms)
|
151
|
+
else
|
152
|
+
nodes << arelastic[field].eq(terms)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
if nodes.size == 1
|
159
|
+
Arelastic::Searches::Filter.new nodes.first
|
160
|
+
elsif nodes.size > 1
|
161
|
+
Arelastic::Searches::Filter.new Arelastic::Filters::And.new(nodes)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def build_limit(limit)
|
166
|
+
if limit
|
167
|
+
Arelastic::Searches::Size.new(limit)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def build_offset(offset)
|
172
|
+
if offset
|
173
|
+
Arelastic::Searches::From.new(offset)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def build_facets(facets)
|
178
|
+
Arelastic::Searches::Facets.new(facets) unless facets.empty?
|
179
|
+
end
|
180
|
+
|
181
|
+
def build_orders(orders)
|
182
|
+
Arelastic::Searches::Sort.new(orders) unless orders.empty?
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module ElasticRecord
|
2
|
+
module Searching
|
3
|
+
def elastic_search
|
4
|
+
if current_elastic_search
|
5
|
+
current_elastic_search.clone
|
6
|
+
else
|
7
|
+
relation
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def elastic_scope(name, body, &block)
|
12
|
+
extension = Module.new(&block) if block
|
13
|
+
|
14
|
+
singleton_class.send(:define_method, name) do |*args|
|
15
|
+
relation = body.call(*args)
|
16
|
+
relation = elastic_search.merge(relation)
|
17
|
+
|
18
|
+
extension ? relation.extending(extension) : relation
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def current_elastic_search #:nodoc:
|
23
|
+
Thread.current["#{self}_current_elastic_search"]
|
24
|
+
end
|
25
|
+
|
26
|
+
def current_elastic_search=(relation) #:nodoc:
|
27
|
+
Thread.current["#{self}_current_elastic_search"] = relation
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class ElasticRecord::Relation::BatchesTest < MiniTest::Spec
|
4
|
+
def setup
|
5
|
+
Widget.reset_index!
|
6
|
+
create_widgets
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_find_each
|
10
|
+
results = []
|
11
|
+
Widget.relation.find_each do |widget|
|
12
|
+
results << widget.id
|
13
|
+
end
|
14
|
+
assert_equal ['5', '10', '15'].to_set, results.to_set
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_find_each_with_scope
|
18
|
+
results = []
|
19
|
+
Widget.relation.filter(color: %w(red blue)).find_each do |widget|
|
20
|
+
results << widget.id
|
21
|
+
end
|
22
|
+
assert_equal ['5', '10'].to_set, results.to_set
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
def create_widgets
|
27
|
+
Widget.elastic_connection.index({'widget' => {'color' => 'red'}}, {index: 'widgets', type: 'widget', id: 5})
|
28
|
+
Widget.elastic_connection.index({'widget' => {'color' => 'blue'}}, {index: 'widgets', type: 'widget', id: 10})
|
29
|
+
Widget.elastic_connection.index({'widget' => {'color' => 'green'}}, {index: 'widgets', type: 'widget', id: 15})
|
30
|
+
|
31
|
+
Widget.elastic_connection.refresh
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class ElasticRecord::Relation::DelegationTest < MiniTest::Spec
|
4
|
+
def setup
|
5
|
+
Widget.reset_index!
|
6
|
+
end
|
7
|
+
|
8
|
+
def test_delegate_to_array
|
9
|
+
Widget.elastic_connection.index({'widget' => {'color' => 'red'}}, {index: 'widgets', type: 'widget', id: 5})
|
10
|
+
Widget.elastic_connection.refresh
|
11
|
+
|
12
|
+
records = []
|
13
|
+
Widget.relation.each do |record|
|
14
|
+
records << record
|
15
|
+
end
|
16
|
+
|
17
|
+
assert_equal 1, records.size
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_delegate_to_klass
|
21
|
+
model = Class.new(Widget) do
|
22
|
+
def self.do_it
|
23
|
+
elastic_search.as_elastic
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
result = model.relation.filter('foo' => 'bar').do_it
|
28
|
+
|
29
|
+
expected = {"query"=>{"constant_score"=>{"filter"=>{"term"=>{"foo"=>"bar"}}}}}
|
30
|
+
assert_equal expected, result
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class ElasticRecord::Relation::FinderMethodsTest < MiniTest::Spec
|
4
|
+
def setup
|
5
|
+
Widget.reset_index!
|
6
|
+
create_widgets
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_find
|
10
|
+
refute_nil Widget.relation.find('05')
|
11
|
+
refute_nil Widget.relation.filter('color' => 'red').find('05')
|
12
|
+
assert_nil Widget.relation.filter('color' => 'blue').find('05')
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_first
|
16
|
+
assert_equal '05', Widget.relation.first.id
|
17
|
+
assert_equal '05', Widget.relation.filter('color' => 'red').first.id
|
18
|
+
assert_equal '10', Widget.relation.filter('color' => 'blue').first.id
|
19
|
+
assert_nil Widget.relation.filter('color' => 'green').first
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_last
|
23
|
+
assert_equal '10', Widget.relation.last.id
|
24
|
+
assert_equal '05', Widget.relation.filter('color' => 'red').last.id
|
25
|
+
assert_equal '10', Widget.relation.filter('color' => 'blue').last.id
|
26
|
+
assert_nil Widget.relation.filter('color' => 'green').last
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_all
|
30
|
+
assert_equal 2, Widget.relation.all.size
|
31
|
+
assert_equal 1, Widget.relation.filter('color' => 'red').all.size
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def create_widgets
|
37
|
+
Widget.elastic_connection.index({'widget' => {'color' => 'red'}}, {index: 'widgets', type: 'widget', id: '05'})
|
38
|
+
Widget.elastic_connection.index({'widget' => {'color' => 'blue'}}, {index: 'widgets', type: 'widget', id: '10'})
|
39
|
+
|
40
|
+
Widget.elastic_connection.refresh
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class ElasticRecord::Relation::MergingTest < MiniTest::Spec
|
4
|
+
def test_merge_single_values
|
5
|
+
relation = Widget.relation.limit(5)
|
6
|
+
other = Widget.relation.limit(10)
|
7
|
+
|
8
|
+
relation.merge! other
|
9
|
+
|
10
|
+
assert_equal 10, relation.limit_value
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_merge_multi_values
|
14
|
+
relation = Widget.relation.filter(color: 'green')
|
15
|
+
other = Widget.relation.filter(weight: 1.0)
|
16
|
+
|
17
|
+
relation.merge! other
|
18
|
+
|
19
|
+
assert_equal [{color: 'green'}, {weight: 1.0}], relation.filter_values
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,157 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class ElasticRecord::Relation::SearchMethodsTest < MiniTest::Spec
|
4
|
+
def test_query_with_no_queries
|
5
|
+
expected = {"match_all" => {}}
|
6
|
+
|
7
|
+
assert_equal expected, relation.as_elastic['query']
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_query_with_multiple_filters
|
11
|
+
relation.filter!('foo' => 'bar')
|
12
|
+
relation.filter!(Widget.arelastic['faz'].in ['baz', 'fum'])
|
13
|
+
|
14
|
+
expected = {
|
15
|
+
"constant_score" => {
|
16
|
+
"filter" => {
|
17
|
+
"and" => [
|
18
|
+
{"term" => {"foo" => "bar"}},
|
19
|
+
{"terms" => {"faz" => ["baz", "fum"]}}
|
20
|
+
]
|
21
|
+
}
|
22
|
+
}
|
23
|
+
}
|
24
|
+
|
25
|
+
assert_equal expected, relation.as_elastic['query']
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_query_with_range_filter
|
29
|
+
relation.filter!(Widget.arelastic['faz'].in 3..5)
|
30
|
+
|
31
|
+
expected = {
|
32
|
+
"constant_score" => {
|
33
|
+
"filter" => {
|
34
|
+
"range" => {
|
35
|
+
"faz" => {"gte"=>3, "lte"=>5}
|
36
|
+
}
|
37
|
+
}
|
38
|
+
}
|
39
|
+
}
|
40
|
+
|
41
|
+
assert_equal expected, relation.as_elastic['query']
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_query_with_only_query
|
45
|
+
relation.query!('foo')
|
46
|
+
|
47
|
+
expected = {"query_string" => {"query" => "foo"}}
|
48
|
+
|
49
|
+
assert_equal expected, relation.as_elastic['query']
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_query_with_both_filter_and_query
|
53
|
+
relation.query!('field' => {'name' => 'joe'})
|
54
|
+
relation.filter!(Widget.arelastic['name'].prefix "mat")
|
55
|
+
|
56
|
+
expected = {
|
57
|
+
"filtered" => {
|
58
|
+
"query" => {
|
59
|
+
"field" => {
|
60
|
+
"name"=>"joe"
|
61
|
+
},
|
62
|
+
},
|
63
|
+
"filter" => {
|
64
|
+
"prefix" => {
|
65
|
+
"name" => "mat"
|
66
|
+
}
|
67
|
+
}
|
68
|
+
}
|
69
|
+
}
|
70
|
+
|
71
|
+
assert_equal expected, relation.as_elastic['query']
|
72
|
+
end
|
73
|
+
|
74
|
+
def test_facet_with_arelastic
|
75
|
+
relation.facet!(Widget.arelastic.facet['popular_tags'].histogram('field' => 'field_name', 'interval' => 100))
|
76
|
+
|
77
|
+
expected = {
|
78
|
+
"popular_tags" => {
|
79
|
+
"histogram" =>
|
80
|
+
{
|
81
|
+
"field" => "field_name",
|
82
|
+
"interval" => 100
|
83
|
+
}
|
84
|
+
}
|
85
|
+
}
|
86
|
+
|
87
|
+
assert_equal expected, relation.as_elastic['facets']
|
88
|
+
end
|
89
|
+
|
90
|
+
def test_facet_with_string
|
91
|
+
relation.facet!('tags', 'size' => 10)
|
92
|
+
|
93
|
+
expected = {
|
94
|
+
"tags" => {
|
95
|
+
"terms" => {
|
96
|
+
"field" => "tags",
|
97
|
+
"size" => 10
|
98
|
+
}
|
99
|
+
}
|
100
|
+
}
|
101
|
+
|
102
|
+
assert_equal expected, relation.as_elastic['facets']
|
103
|
+
end
|
104
|
+
|
105
|
+
def test_limit
|
106
|
+
relation.limit!(5)
|
107
|
+
|
108
|
+
expected = 5
|
109
|
+
assert_equal expected, relation.as_elastic['size']
|
110
|
+
end
|
111
|
+
|
112
|
+
def test_offset
|
113
|
+
relation.offset!(42)
|
114
|
+
|
115
|
+
expected = 42
|
116
|
+
assert_equal expected, relation.as_elastic['from']
|
117
|
+
end
|
118
|
+
|
119
|
+
def test_order
|
120
|
+
relation.order! 'foo'
|
121
|
+
relation.order! 'bar' => 'desc'
|
122
|
+
|
123
|
+
expected = [
|
124
|
+
'foo',
|
125
|
+
'bar' => 'desc'
|
126
|
+
]
|
127
|
+
|
128
|
+
assert_equal expected, relation.as_elastic['sort']
|
129
|
+
end
|
130
|
+
|
131
|
+
def test_extending_with_block
|
132
|
+
relation.extending! do
|
133
|
+
def foo
|
134
|
+
'foo'
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
assert_equal 'foo', relation.foo
|
139
|
+
end
|
140
|
+
|
141
|
+
def test_extending_with_module
|
142
|
+
mod = Module.new do
|
143
|
+
def bar
|
144
|
+
'bar'
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
relation.extending! mod
|
149
|
+
|
150
|
+
assert_equal 'bar', relation.bar
|
151
|
+
end
|
152
|
+
|
153
|
+
private
|
154
|
+
def relation
|
155
|
+
@relation ||= Widget.relation
|
156
|
+
end
|
157
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class ElasticRecord::RelationTest < MiniTest::Spec
|
4
|
+
def setup
|
5
|
+
Widget.reset_index!
|
6
|
+
create_widgets
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_to_hits
|
10
|
+
assert Widget.relation.to_hits.is_a?(ElasticSearch::Api::Hits)
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_to_ids
|
14
|
+
assert_equal ['5', '10'], Widget.relation.to_ids
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_to_a
|
18
|
+
array = Widget.relation.to_a
|
19
|
+
|
20
|
+
assert_equal 2, array.size
|
21
|
+
assert array.first.is_a?(Widget)
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_count
|
25
|
+
assert_equal 2, Widget.relation.count
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_facets
|
29
|
+
facets = Widget.relation.facet(Widget.arelastic.facet['popular_colors'].terms('color')).facets
|
30
|
+
|
31
|
+
assert_equal 2, facets['popular_colors']['total']
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_equal
|
35
|
+
assert(Widget.relation.filter(color: 'green') == Widget.relation.filter(color: 'green'))
|
36
|
+
assert(Widget.relation.filter(color: 'green') != Widget.relation.filter(color: 'blue'))
|
37
|
+
|
38
|
+
assert(Widget.relation.filter(color: 'magenta') == [])
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_inspect
|
42
|
+
assert_equal [].inspect, Widget.relation.filter(color: 'magenta').inspect
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
def create_widgets
|
47
|
+
Widget.elastic_connection.index({'widget' => {'color' => 'red'}}, {index: 'widgets', type: 'widget', id: 5})
|
48
|
+
Widget.elastic_connection.index({'widget' => {'color' => 'blue'}}, {index: 'widgets', type: 'widget', id: 10})
|
49
|
+
|
50
|
+
Widget.elastic_connection.refresh
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class ElasticRecord::SearchingTest < MiniTest::Spec
|
4
|
+
def test_elastic_search
|
5
|
+
|
6
|
+
end
|
7
|
+
|
8
|
+
def test_elastic_scope
|
9
|
+
model = Class.new(Widget) do
|
10
|
+
elastic_scope :by_color, ->(color) { elastic_search.filter(color: color) } do
|
11
|
+
def negative_offset
|
12
|
+
-offset_value
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
relation = model.by_color('blue')
|
18
|
+
|
19
|
+
assert_equal model.relation.filter(color: 'blue'), relation
|
20
|
+
assert_equal -5, relation.offset(5).negative_offset
|
21
|
+
end
|
22
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
Bundler.require
|
3
|
+
|
4
|
+
require 'minitest/autorun'
|
5
|
+
|
6
|
+
require 'rubberband'
|
7
|
+
require 'active_support/core_ext/object/blank'
|
8
|
+
require 'active_model'
|
9
|
+
|
10
|
+
require 'support/widget'
|
11
|
+
require 'support/connect'
|
12
|
+
|
13
|
+
module MiniTest
|
14
|
+
class Spec
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
ElasticRecord::Config.servers = '127.0.0.1:9200'
|
@@ -0,0 +1,39 @@
|
|
1
|
+
class Widget
|
2
|
+
extend ActiveModel::Naming
|
3
|
+
include ElasticRecord::Model
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def find(ids)
|
7
|
+
ids.map { |id| new(id: id, color: 'red') }
|
8
|
+
end
|
9
|
+
|
10
|
+
|
11
|
+
def reset_index!
|
12
|
+
elastic_connection.delete_index('widgets')
|
13
|
+
elastic_connection.create_index('widgets')
|
14
|
+
elastic_connection.update_mapping(
|
15
|
+
{
|
16
|
+
properties: {
|
17
|
+
color: {
|
18
|
+
type: 'string',
|
19
|
+
index: 'not_analyzed'
|
20
|
+
}
|
21
|
+
},
|
22
|
+
_source: {
|
23
|
+
enabled: false
|
24
|
+
}
|
25
|
+
},
|
26
|
+
{
|
27
|
+
index: 'widgets'
|
28
|
+
}
|
29
|
+
)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
attr_accessor :id, :color
|
34
|
+
def initialize(attributes = {})
|
35
|
+
attributes.each do |key, val|
|
36
|
+
send("#{key}=", val)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
metadata
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: elastic_record
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Matthew Higgins
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-07-23 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: arelastic
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rubberband
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - '='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 0.1.1
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - '='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 0.1.1
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: activemodel
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
description: Find your records with elastic search
|
63
|
+
email: developer@matthewhiggins.com
|
64
|
+
executables: []
|
65
|
+
extensions: []
|
66
|
+
extra_rdoc_files:
|
67
|
+
- README.rdoc
|
68
|
+
files:
|
69
|
+
- lib/elastic_record/config.rb
|
70
|
+
- lib/elastic_record/connection.rb
|
71
|
+
- lib/elastic_record/model.rb
|
72
|
+
- lib/elastic_record/orm/active_record.rb
|
73
|
+
- lib/elastic_record/relation/batches.rb
|
74
|
+
- lib/elastic_record/relation/delegation.rb
|
75
|
+
- lib/elastic_record/relation/finder_methods.rb
|
76
|
+
- lib/elastic_record/relation/merging.rb
|
77
|
+
- lib/elastic_record/relation/search_methods.rb
|
78
|
+
- lib/elastic_record/relation.rb
|
79
|
+
- lib/elastic_record/searching.rb
|
80
|
+
- lib/elastic_record.rb
|
81
|
+
- test/elastic_record/config_test.rb
|
82
|
+
- test/elastic_record/connection_test.rb
|
83
|
+
- test/elastic_record/model_test.rb
|
84
|
+
- test/elastic_record/relation/batches_test.rb
|
85
|
+
- test/elastic_record/relation/delegation_test.rb
|
86
|
+
- test/elastic_record/relation/finder_methods_test.rb
|
87
|
+
- test/elastic_record/relation/merging_test.rb
|
88
|
+
- test/elastic_record/relation/search_methods_test.rb
|
89
|
+
- test/elastic_record/relation_test.rb
|
90
|
+
- test/elastic_record/searching_test.rb
|
91
|
+
- test/helper.rb
|
92
|
+
- test/support/connect.rb
|
93
|
+
- test/support/widget.rb
|
94
|
+
- README.rdoc
|
95
|
+
homepage: http://github.com/matthuhiggins/elastic_record
|
96
|
+
licenses:
|
97
|
+
- MIT
|
98
|
+
post_install_message:
|
99
|
+
rdoc_options: []
|
100
|
+
require_paths:
|
101
|
+
- lib
|
102
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
103
|
+
none: false
|
104
|
+
requirements:
|
105
|
+
- - ! '>='
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
version: 1.9.3
|
108
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
109
|
+
none: false
|
110
|
+
requirements:
|
111
|
+
- - ! '>='
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: 1.8.11
|
114
|
+
requirements: []
|
115
|
+
rubyforge_project:
|
116
|
+
rubygems_version: 1.8.24
|
117
|
+
signing_key:
|
118
|
+
specification_version: 3
|
119
|
+
summary: Use Elastic Search with your objects
|
120
|
+
test_files: []
|