rom-elasticsearch 0.1.0
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/.travis.yml +24 -0
- data/.yardopts +7 -0
- data/CHANGELOG.md +3 -0
- data/CONTRIBUTING.md +29 -0
- data/Gemfile +20 -0
- data/LICENSE.txt +22 -0
- data/README.md +40 -0
- data/Rakefile +19 -0
- data/lib/rom-elasticsearch.rb +1 -0
- data/lib/rom/elasticsearch.rb +8 -0
- data/lib/rom/elasticsearch/attribute.rb +30 -0
- data/lib/rom/elasticsearch/commands.rb +49 -0
- data/lib/rom/elasticsearch/dataset.rb +219 -0
- data/lib/rom/elasticsearch/errors.rb +23 -0
- data/lib/rom/elasticsearch/gateway.rb +81 -0
- data/lib/rom/elasticsearch/plugins/relation/query_dsl.rb +57 -0
- data/lib/rom/elasticsearch/query_methods.rb +64 -0
- data/lib/rom/elasticsearch/relation.rb +241 -0
- data/lib/rom/elasticsearch/schema.rb +26 -0
- data/lib/rom/elasticsearch/types.rb +33 -0
- data/lib/rom/elasticsearch/version.rb +5 -0
- data/rom-elasticsearch.gemspec +27 -0
- data/spec/integration/rom/elasticsearch/relation/command_spec.rb +47 -0
- data/spec/shared/setup.rb +16 -0
- data/spec/shared/unit/user_fixtures.rb +15 -0
- data/spec/shared/unit/users.rb +18 -0
- data/spec/spec_helper.rb +43 -0
- data/spec/unit/rom/elasticsearch/dataset/body_spec.rb +13 -0
- data/spec/unit/rom/elasticsearch/dataset/delete_spec.rb +17 -0
- data/spec/unit/rom/elasticsearch/dataset/params_spec.rb +13 -0
- data/spec/unit/rom/elasticsearch/dataset/put_spec.rb +14 -0
- data/spec/unit/rom/elasticsearch/dataset/query_string_spec.rb +12 -0
- data/spec/unit/rom/elasticsearch/dataset/search_spec.rb +20 -0
- data/spec/unit/rom/elasticsearch/gateway_spec.rb +10 -0
- data/spec/unit/rom/elasticsearch/plugins/relation/query_dsl_spec.rb +34 -0
- data/spec/unit/rom/elasticsearch/relation/create_index_spec.rb +75 -0
- data/spec/unit/rom/elasticsearch/relation/dataset_spec.rb +26 -0
- data/spec/unit/rom/elasticsearch/relation/delete_spec.rb +32 -0
- data/spec/unit/rom/elasticsearch/relation/get_spec.rb +22 -0
- data/spec/unit/rom/elasticsearch/relation/map_spec.rb +18 -0
- data/spec/unit/rom/elasticsearch/relation/pluck_spec.rb +18 -0
- data/spec/unit/rom/elasticsearch/relation/query_spec.rb +18 -0
- data/spec/unit/rom/elasticsearch/relation/query_string_spec.rb +18 -0
- data/spec/unit/rom/elasticsearch/relation/search_spec.rb +18 -0
- data/spec/unit/rom/elasticsearch/relation/to_a_spec.rb +28 -0
- metadata +186 -0
@@ -0,0 +1,23 @@
|
|
1
|
+
module ROM
|
2
|
+
module Elasticsearch
|
3
|
+
# @api private
|
4
|
+
class Error < StandardError
|
5
|
+
def initialize(wrapped_error)
|
6
|
+
super(wrapped_error.message)
|
7
|
+
@wrapped_error = wrapped_error
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_reader :wrapped_error
|
11
|
+
end
|
12
|
+
|
13
|
+
# @api private
|
14
|
+
class SearchError < Error
|
15
|
+
attr_reader :query
|
16
|
+
|
17
|
+
def initialize(wrapped_error, query)
|
18
|
+
super(wrapped_error)
|
19
|
+
@query = query
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'elasticsearch'
|
2
|
+
require 'uri'
|
3
|
+
|
4
|
+
require 'rom/gateway'
|
5
|
+
require 'rom/support/inflector'
|
6
|
+
require 'rom/elasticsearch/dataset'
|
7
|
+
|
8
|
+
module ROM
|
9
|
+
module Elasticsearch
|
10
|
+
# Elasticsearch gateway
|
11
|
+
#
|
12
|
+
# @example basic configuration
|
13
|
+
# conf = ROM::Configuration.new(:elasticsearch, 'http://localhost:9200')
|
14
|
+
#
|
15
|
+
# class Posts < ROM::Relation[:elasticsearch]
|
16
|
+
# schema(:posts) do
|
17
|
+
# attribute :id, Types::Int
|
18
|
+
# attribute :title, Types::String
|
19
|
+
#
|
20
|
+
# primary_key :id
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# def like(title)
|
24
|
+
# query(prefix: { title: title })
|
25
|
+
# end
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# conf.register_relation(Posts)
|
29
|
+
#
|
30
|
+
# rom = ROM.container(conf)
|
31
|
+
#
|
32
|
+
# posts = rom.relations[:posts]
|
33
|
+
#
|
34
|
+
# posts.command(:create).call(id: 1, title: 'Hello World')
|
35
|
+
#
|
36
|
+
# posts.like('Hello').first
|
37
|
+
#
|
38
|
+
# @api public
|
39
|
+
class Gateway < ROM::Gateway
|
40
|
+
adapter :elasticsearch
|
41
|
+
|
42
|
+
# @!attribute [r] url
|
43
|
+
# @return [URI] Connection URL
|
44
|
+
attr_reader :url
|
45
|
+
|
46
|
+
# @!attribute [r] client
|
47
|
+
# @return [::Elasticsearch::Client] configured ES client
|
48
|
+
attr_reader :client
|
49
|
+
|
50
|
+
# @api private
|
51
|
+
def initialize(uri, log: false)
|
52
|
+
@url = URI.parse(uri)
|
53
|
+
@client = ::Elasticsearch::Client.new(host: url.select(:host, :port).join(":"), log: log)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Return true if a dataset with the given :index exists
|
57
|
+
#
|
58
|
+
# @param [Symbol] index The name of the index
|
59
|
+
#
|
60
|
+
# @return [Boolean]
|
61
|
+
#
|
62
|
+
# @api public
|
63
|
+
def dataset?(index)
|
64
|
+
client.indices.exists?(index: index)
|
65
|
+
end
|
66
|
+
alias_method :index?, :dataset?
|
67
|
+
|
68
|
+
# Get a dataset by its :index name
|
69
|
+
#
|
70
|
+
# @param [Symbol] index The name of the index
|
71
|
+
#
|
72
|
+
# @return [Dataset]
|
73
|
+
#
|
74
|
+
# @api public
|
75
|
+
def dataset(index)
|
76
|
+
Dataset.new(client, params: { index: index, type: Inflector.singularize(index).to_sym })
|
77
|
+
end
|
78
|
+
alias_method :[], :dataset
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'rom/global'
|
2
|
+
require 'elasticsearch/dsl'
|
3
|
+
|
4
|
+
module ROM
|
5
|
+
module Elasticsearch
|
6
|
+
module Plugins
|
7
|
+
module Relation
|
8
|
+
# Relation plugin which adds query DSL from elasticsearch-dsl gem
|
9
|
+
#
|
10
|
+
# @api public
|
11
|
+
module QueryDSL
|
12
|
+
# @api private
|
13
|
+
def self.included(klass)
|
14
|
+
super
|
15
|
+
klass.include(InstanceMethods)
|
16
|
+
klass.option :query_builder, default: -> { Builder.new(self) }
|
17
|
+
end
|
18
|
+
|
19
|
+
# @api public
|
20
|
+
module InstanceMethods
|
21
|
+
# Restrict a relation via query DSL
|
22
|
+
#
|
23
|
+
# @see Relation#search
|
24
|
+
# @see https://github.com/elastic/elasticsearch-ruby/tree/master/elasticsearch-dsl
|
25
|
+
#
|
26
|
+
# @return [Relation]
|
27
|
+
#
|
28
|
+
# @api public
|
29
|
+
def search(options = EMPTY_HASH, &block)
|
30
|
+
if block
|
31
|
+
super(query_builder.search(&block).to_hash)
|
32
|
+
else
|
33
|
+
super
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class Builder
|
39
|
+
include ::Elasticsearch::DSL
|
40
|
+
|
41
|
+
attr_reader :relation
|
42
|
+
|
43
|
+
def initialize(relation)
|
44
|
+
@relation = relation
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
plugins do
|
53
|
+
adapter :elasticsearch do
|
54
|
+
register :query_dsl, ROM::Elasticsearch::Plugins::Relation::QueryDSL, type: :relation
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'rom/elasticsearch/errors'
|
2
|
+
|
3
|
+
module ROM
|
4
|
+
module Elasticsearch
|
5
|
+
# Dataset's query methods
|
6
|
+
#
|
7
|
+
# @see Dataset
|
8
|
+
#
|
9
|
+
# @api public
|
10
|
+
module QueryMethods
|
11
|
+
# Return a new dataset configured to search by :id
|
12
|
+
#
|
13
|
+
# @param [Integer] id
|
14
|
+
#
|
15
|
+
# @return [Dataset]
|
16
|
+
#
|
17
|
+
# @see Relation#get
|
18
|
+
#
|
19
|
+
# @api public
|
20
|
+
def get(id)
|
21
|
+
params(id: id)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Return a new dataset configured to search via new body options
|
25
|
+
#
|
26
|
+
# @param [Hash] options Body options
|
27
|
+
#
|
28
|
+
# @return [Dataset]
|
29
|
+
#
|
30
|
+
# @see Relation#search
|
31
|
+
#
|
32
|
+
# @api public
|
33
|
+
def search(options)
|
34
|
+
body(options)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Return a new dataset configured to search via :query_string body option
|
38
|
+
#
|
39
|
+
# @param [String] expression A string query
|
40
|
+
#
|
41
|
+
# @return [Dataset]
|
42
|
+
#
|
43
|
+
# @see Relation#query_string
|
44
|
+
#
|
45
|
+
# @api public
|
46
|
+
def query_string(expression)
|
47
|
+
query(query_string: { query: expression })
|
48
|
+
end
|
49
|
+
|
50
|
+
# Return a new dataset configured to search via :query body option
|
51
|
+
#
|
52
|
+
# @param [Hash] query A query hash
|
53
|
+
#
|
54
|
+
# @return [Dataset]
|
55
|
+
#
|
56
|
+
# @see Relation#query
|
57
|
+
#
|
58
|
+
# @api public
|
59
|
+
def query(query)
|
60
|
+
body(query: query)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,241 @@
|
|
1
|
+
require 'rom/relation'
|
2
|
+
require 'rom/elasticsearch/types'
|
3
|
+
require 'rom/elasticsearch/schema'
|
4
|
+
require 'rom/elasticsearch/attribute'
|
5
|
+
|
6
|
+
module ROM
|
7
|
+
module Elasticsearch
|
8
|
+
# Elasticsearch relation API
|
9
|
+
#
|
10
|
+
# Provides access to indexed data, and methods for managing indices.
|
11
|
+
# Works like a standard Relation, which means it's lazy and composable,
|
12
|
+
# and has access to commands via `Relation#command`.
|
13
|
+
#
|
14
|
+
# Indices are configured based on two settings:
|
15
|
+
# - `Relation#name.dataset` - which is configured in a standard way via `schema` block
|
16
|
+
# - `Relation.index_settings` - which is a class-level setting
|
17
|
+
#
|
18
|
+
# Optionally, query DSL can be enabled via `:query_dsl` plugin.
|
19
|
+
#
|
20
|
+
# @example setting up a relation
|
21
|
+
# class Pages < ROM::Relation[:elasticsearch]
|
22
|
+
# schema do
|
23
|
+
# attribute :id, Types::ID
|
24
|
+
# attribute :title, Types.Keyword
|
25
|
+
# attribute :body, Types.Text(analyzer: "snowball")
|
26
|
+
# end
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# @example using query DSL
|
30
|
+
# class Users < ROM::Relation[:elasticsearch]
|
31
|
+
# use :query_dsl
|
32
|
+
#
|
33
|
+
# schema do
|
34
|
+
# # ...
|
35
|
+
# end
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# users.search do
|
39
|
+
# query do
|
40
|
+
# match name: "Jane"
|
41
|
+
# end
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# @api public
|
45
|
+
class Relation < ROM::Relation
|
46
|
+
include ROM::Elasticsearch
|
47
|
+
|
48
|
+
adapter :elasticsearch
|
49
|
+
|
50
|
+
# @!method self.index_settings
|
51
|
+
# Manage the index_settings
|
52
|
+
#
|
53
|
+
# This is set by default to:
|
54
|
+
#
|
55
|
+
# ``` ruby
|
56
|
+
# { number_of_shards: 1,
|
57
|
+
# index: {
|
58
|
+
# analysis: {
|
59
|
+
# analyzer: {
|
60
|
+
# standard_stopwords: {
|
61
|
+
# type: "standard",
|
62
|
+
# stopwords: "_english_"
|
63
|
+
# }
|
64
|
+
# }
|
65
|
+
# }
|
66
|
+
# } }.freeze
|
67
|
+
# ```
|
68
|
+
#
|
69
|
+
# @overload index_settings
|
70
|
+
# Return the index_settings that the relation will use
|
71
|
+
# @return [Hash]
|
72
|
+
#
|
73
|
+
# @overload index_settings(settings)
|
74
|
+
# Set index settings
|
75
|
+
#
|
76
|
+
# @see https://www.elastic.co/guide/en/elasticsearch/guide/current/index-management.html
|
77
|
+
#
|
78
|
+
# @example
|
79
|
+
# class Users < ROM::Relation[:elasticsearch]
|
80
|
+
# index_settings(
|
81
|
+
# # your custom settings
|
82
|
+
# )
|
83
|
+
#
|
84
|
+
# schema do
|
85
|
+
# # ...
|
86
|
+
# end
|
87
|
+
# end
|
88
|
+
#
|
89
|
+
# @param [Hash] index_settings_hash
|
90
|
+
defines :index_settings
|
91
|
+
|
92
|
+
schema_class Elasticsearch::Schema
|
93
|
+
schema_attr_class Elasticsearch::Attribute
|
94
|
+
|
95
|
+
# Overridden output_schema, as we *always* want to use it,
|
96
|
+
# whereas in core, it is only used when there's at least one read-type
|
97
|
+
option :output_schema, default: -> { schema.to_output_hash }
|
98
|
+
|
99
|
+
# Default index settings that can be overridden
|
100
|
+
index_settings(
|
101
|
+
{ number_of_shards: 1,
|
102
|
+
index: {
|
103
|
+
analysis: {
|
104
|
+
analyzer: {
|
105
|
+
standard_stopwords: {
|
106
|
+
type: "standard",
|
107
|
+
stopwords: "_english_"
|
108
|
+
}
|
109
|
+
}
|
110
|
+
}
|
111
|
+
} }.freeze
|
112
|
+
)
|
113
|
+
|
114
|
+
# Map indexed data
|
115
|
+
#
|
116
|
+
# @yieldparam [Hash,ROM::Struct]
|
117
|
+
#
|
118
|
+
# @return [Array<Hash,ROM::Struct>]
|
119
|
+
#
|
120
|
+
# @api public
|
121
|
+
def map(&block)
|
122
|
+
to_a.map(&block)
|
123
|
+
end
|
124
|
+
|
125
|
+
# Pluck specific attribute values
|
126
|
+
#
|
127
|
+
# @param [Symbol] name The name of the attribute
|
128
|
+
#
|
129
|
+
# @return [Array]
|
130
|
+
#
|
131
|
+
# @api public
|
132
|
+
def pluck(name)
|
133
|
+
map { |t| t[name] }
|
134
|
+
end
|
135
|
+
|
136
|
+
# Restrict indexed data by id
|
137
|
+
#
|
138
|
+
# @return [Relation]
|
139
|
+
#
|
140
|
+
# @api public
|
141
|
+
def get(id)
|
142
|
+
new(dataset.get(id))
|
143
|
+
end
|
144
|
+
|
145
|
+
# Restrict relation data by a search query
|
146
|
+
#
|
147
|
+
# @example
|
148
|
+
# users.search(query: { match: { name: "Jane" } })
|
149
|
+
#
|
150
|
+
# @param [Hash] options Search options compatible with Elasticsearch::Client API
|
151
|
+
#
|
152
|
+
# @return [Relation]
|
153
|
+
#
|
154
|
+
# @api public
|
155
|
+
def search(options)
|
156
|
+
new(dataset.search(options))
|
157
|
+
end
|
158
|
+
|
159
|
+
# Restrict relation data by a query search
|
160
|
+
#
|
161
|
+
# @example
|
162
|
+
# users.query(match: { name: "Jane" })
|
163
|
+
#
|
164
|
+
# @param [Hash] query Query options compatible with Elasticsearch::Client API
|
165
|
+
#
|
166
|
+
# @return [Relation]
|
167
|
+
#
|
168
|
+
# @api public
|
169
|
+
def query(query)
|
170
|
+
new(dataset.query(query))
|
171
|
+
end
|
172
|
+
|
173
|
+
# Restrict relation data by a string-based query
|
174
|
+
#
|
175
|
+
# @example
|
176
|
+
# users.query_string("name:'Jane'")
|
177
|
+
#
|
178
|
+
# @param [Hash] query Query string compatible with Elasticsearch::Client API
|
179
|
+
#
|
180
|
+
# @return [Relation]
|
181
|
+
#
|
182
|
+
# @api public
|
183
|
+
def query_string(expr)
|
184
|
+
new(dataset.query_string(expr))
|
185
|
+
end
|
186
|
+
|
187
|
+
# Create relation's index in ES
|
188
|
+
#
|
189
|
+
# @return [Hash] raw response from the client
|
190
|
+
#
|
191
|
+
# @api public
|
192
|
+
def create_index
|
193
|
+
dataset.create_index(index_params)
|
194
|
+
end
|
195
|
+
|
196
|
+
# Delete relation's index in ES
|
197
|
+
#
|
198
|
+
# @return [Hash] raw response from the client
|
199
|
+
#
|
200
|
+
# @api public
|
201
|
+
def delete_index
|
202
|
+
dataset.delete_index
|
203
|
+
end
|
204
|
+
|
205
|
+
# Delete all indexed data from the current relation
|
206
|
+
#
|
207
|
+
# @return [Hash] raw response from the client
|
208
|
+
#
|
209
|
+
# @api public
|
210
|
+
def delete
|
211
|
+
dataset.delete
|
212
|
+
end
|
213
|
+
|
214
|
+
# Refresh indexed data
|
215
|
+
#
|
216
|
+
# @example
|
217
|
+
# users.command(:create).call(id: 1, name: "Jane").refresh.to_a
|
218
|
+
#
|
219
|
+
# @return [Relation]
|
220
|
+
#
|
221
|
+
# @api public
|
222
|
+
def refresh
|
223
|
+
new(dataset.refresh)
|
224
|
+
end
|
225
|
+
|
226
|
+
private
|
227
|
+
|
228
|
+
# Dataset index params based on relation configuration
|
229
|
+
#
|
230
|
+
# @return [Hash]
|
231
|
+
#
|
232
|
+
# @api private
|
233
|
+
def index_params
|
234
|
+
{ index: name.dataset,
|
235
|
+
body: {
|
236
|
+
settings: self.class.index_settings,
|
237
|
+
mappings: { dataset.type => { properties: schema.to_properties } } } }
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|