rom-elasticsearch 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +22 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +24 -0
  5. data/.yardopts +7 -0
  6. data/CHANGELOG.md +3 -0
  7. data/CONTRIBUTING.md +29 -0
  8. data/Gemfile +20 -0
  9. data/LICENSE.txt +22 -0
  10. data/README.md +40 -0
  11. data/Rakefile +19 -0
  12. data/lib/rom-elasticsearch.rb +1 -0
  13. data/lib/rom/elasticsearch.rb +8 -0
  14. data/lib/rom/elasticsearch/attribute.rb +30 -0
  15. data/lib/rom/elasticsearch/commands.rb +49 -0
  16. data/lib/rom/elasticsearch/dataset.rb +219 -0
  17. data/lib/rom/elasticsearch/errors.rb +23 -0
  18. data/lib/rom/elasticsearch/gateway.rb +81 -0
  19. data/lib/rom/elasticsearch/plugins/relation/query_dsl.rb +57 -0
  20. data/lib/rom/elasticsearch/query_methods.rb +64 -0
  21. data/lib/rom/elasticsearch/relation.rb +241 -0
  22. data/lib/rom/elasticsearch/schema.rb +26 -0
  23. data/lib/rom/elasticsearch/types.rb +33 -0
  24. data/lib/rom/elasticsearch/version.rb +5 -0
  25. data/rom-elasticsearch.gemspec +27 -0
  26. data/spec/integration/rom/elasticsearch/relation/command_spec.rb +47 -0
  27. data/spec/shared/setup.rb +16 -0
  28. data/spec/shared/unit/user_fixtures.rb +15 -0
  29. data/spec/shared/unit/users.rb +18 -0
  30. data/spec/spec_helper.rb +43 -0
  31. data/spec/unit/rom/elasticsearch/dataset/body_spec.rb +13 -0
  32. data/spec/unit/rom/elasticsearch/dataset/delete_spec.rb +17 -0
  33. data/spec/unit/rom/elasticsearch/dataset/params_spec.rb +13 -0
  34. data/spec/unit/rom/elasticsearch/dataset/put_spec.rb +14 -0
  35. data/spec/unit/rom/elasticsearch/dataset/query_string_spec.rb +12 -0
  36. data/spec/unit/rom/elasticsearch/dataset/search_spec.rb +20 -0
  37. data/spec/unit/rom/elasticsearch/gateway_spec.rb +10 -0
  38. data/spec/unit/rom/elasticsearch/plugins/relation/query_dsl_spec.rb +34 -0
  39. data/spec/unit/rom/elasticsearch/relation/create_index_spec.rb +75 -0
  40. data/spec/unit/rom/elasticsearch/relation/dataset_spec.rb +26 -0
  41. data/spec/unit/rom/elasticsearch/relation/delete_spec.rb +32 -0
  42. data/spec/unit/rom/elasticsearch/relation/get_spec.rb +22 -0
  43. data/spec/unit/rom/elasticsearch/relation/map_spec.rb +18 -0
  44. data/spec/unit/rom/elasticsearch/relation/pluck_spec.rb +18 -0
  45. data/spec/unit/rom/elasticsearch/relation/query_spec.rb +18 -0
  46. data/spec/unit/rom/elasticsearch/relation/query_string_spec.rb +18 -0
  47. data/spec/unit/rom/elasticsearch/relation/search_spec.rb +18 -0
  48. data/spec/unit/rom/elasticsearch/relation/to_a_spec.rb +28 -0
  49. 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