elastic_record 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.
- 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: []
|