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.
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