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,26 @@
1
+ require 'rom/types'
2
+ require 'rom/schema'
3
+
4
+ module ROM
5
+ module Elasticsearch
6
+ # Elasticsearch relation schema
7
+ #
8
+ # @api public
9
+ class Schema < ROM::Schema
10
+ # Return a hash with mapping properties
11
+ #
12
+ # @api private
13
+ def to_properties
14
+ select(&:properties?).map { |attr| [attr.name, attr.properties] }.to_h
15
+ end
16
+
17
+ # Customized output hash constructor which symbolizes keys
18
+ # and optionally applies custom read-type coercions
19
+ #
20
+ # @api private
21
+ def to_output_hash
22
+ Types::Hash.symbolized(map { |attr| [attr.key, attr.to_read_type] }.to_h)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,33 @@
1
+ require 'rom/types'
2
+
3
+ module ROM
4
+ module Elasticsearch
5
+ # Elasticsearch types use by schema attributes
6
+ #
7
+ # @api public
8
+ module Types
9
+ include ROM::Types
10
+
11
+ # Default integer primary key
12
+ ID = Int.meta(primary_key: true)
13
+
14
+ # Define a keywoard attribute type
15
+ #
16
+ # @return [Dry::Types::Type]
17
+ #
18
+ # @api public
19
+ def self.Keyword(meta = {})
20
+ String.meta(type: "keyword").meta(meta)
21
+ end
22
+
23
+ # Define a keywoard attribute type
24
+ #
25
+ # @return [Dry::Types::Type]
26
+ #
27
+ # @api public
28
+ def self.Text(meta = {})
29
+ String.meta(type: "text").meta(meta)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,5 @@
1
+ module ROM
2
+ module Elasticsearch
3
+ VERSION = '0.1.0'.freeze
4
+ end
5
+ end
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'rom/elasticsearch/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'rom-elasticsearch'
8
+ spec.version = ROM::Elasticsearch::VERSION
9
+ spec.authors = ['Hannes Nevalainen', 'Piotr Solnica']
10
+ spec.email = ['hannes.nevalainen@me.com', 'piotr.solnica+oss@gmail.com']
11
+ spec.summary = %q{ROM adapter for Elasticsearch}
12
+ spec.description = %q{}
13
+ spec.homepage = 'http://rom-rb.org'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_runtime_dependency 'rom-core', '~> 4.1'
22
+ spec.add_runtime_dependency 'elasticsearch', '~> 6.0'
23
+
24
+ spec.add_development_dependency 'bundler', '~> 1.6'
25
+ spec.add_development_dependency 'rake'
26
+ spec.add_development_dependency 'rspec'
27
+ end
@@ -0,0 +1,47 @@
1
+ RSpec.describe ROM::Elasticsearch::Relation, '#command' do
2
+ subject(:relation) { relations[:users] }
3
+
4
+ include_context 'setup'
5
+
6
+ before do
7
+ conf.relation(:users) do
8
+ schema(:users) do
9
+ attribute :id, ROM::Elasticsearch::Types::ID
10
+ attribute :name, ROM::Types::String
11
+ end
12
+ end
13
+ end
14
+
15
+ describe ':create' do
16
+ it 'returns a create command' do
17
+ command = relation.command(:create, result: :one)
18
+
19
+ expect(command.call(id: 1, name: 'Jane')).to eql(id: 1, name: 'Jane')
20
+ end
21
+
22
+ it 'applies input function' do
23
+ command = relation.command(:create, result: :one)
24
+
25
+ input = double(:user, to_hash: { id: 1, name: 'Jane' })
26
+
27
+ expect(command.call(input)).to eql(id: 1, name: 'Jane')
28
+ end
29
+ end
30
+
31
+ describe ':delete' do
32
+ before do
33
+ relation.command(:create).call(id: 1, name: 'Jane')
34
+ relation.command(:create).call(id: 2, name: 'John')
35
+
36
+ relation.refresh
37
+ end
38
+
39
+ it 'deletes matching data' do
40
+ relation.get(2).command(:delete).call
41
+
42
+ relation.refresh
43
+
44
+ expect(relation.to_a).to eql([{ id: 1, name: 'Jane' }])
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,16 @@
1
+ RSpec.shared_context 'setup' do
2
+ let(:uri) { "http://127.0.0.1:9200" }
3
+
4
+ let(:conf) { ROM::Configuration.new(:elasticsearch, uri) }
5
+ let(:container) { ROM.container(conf) }
6
+
7
+ let(:gateway) { conf.gateways[:default] }
8
+ let(:client) { gateway.client }
9
+
10
+ let(:relations) { container[:relations] }
11
+ let(:commands) { container[:commands] }
12
+
13
+ after do
14
+ client.indices.delete(index: :users) if gateway.index?(:users)
15
+ end
16
+ end
@@ -0,0 +1,15 @@
1
+ RSpec.shared_context 'user fixtures' do
2
+ include_context 'setup'
3
+
4
+ before do
5
+ dataset.put(username: 'eve')
6
+ dataset.put(username: 'bob')
7
+ dataset.put(username: 'alice')
8
+
9
+ dataset.refresh
10
+ end
11
+
12
+ after do
13
+ gateway[:users].refresh.delete
14
+ end
15
+ end
@@ -0,0 +1,18 @@
1
+ RSpec.shared_context 'users' do
2
+ include_context 'setup'
3
+
4
+ before do
5
+ conf.relation(:users) do
6
+ schema(:users) do
7
+ attribute :id, ROM::Types::Int
8
+ attribute :name, ROM::Types::String
9
+
10
+ primary_key :id
11
+ end
12
+ end
13
+ end
14
+
15
+ after do
16
+ gateway[:users].refresh.delete
17
+ end
18
+ end
@@ -0,0 +1,43 @@
1
+ require 'bundler'
2
+ Bundler.setup
3
+
4
+ if RUBY_ENGINE == 'ruby' && ENV['COVERAGE'] == 'true'
5
+ require 'yaml'
6
+ rubies = YAML.load(File.read(File.join(__dir__, '..', '.travis.yml')))['rvm']
7
+ latest_mri = rubies.select { |v| v =~ /\A\d+\.\d+.\d+\z/ }.max
8
+
9
+ if RUBY_VERSION == latest_mri
10
+ require 'simplecov'
11
+ SimpleCov.start do
12
+ add_filter '/spec/'
13
+ end
14
+ end
15
+ end
16
+
17
+ begin
18
+ require 'pry-byebug'
19
+ rescue LoadError
20
+ require 'pry'
21
+ end
22
+
23
+ require 'rom-elasticsearch'
24
+
25
+ SPEC_ROOT = Pathname(__FILE__).dirname
26
+
27
+ Dir[SPEC_ROOT.join('shared/**/*.rb')].each { |f| require f }
28
+
29
+ RSpec.configure do |config|
30
+ config.disable_monkey_patching!
31
+
32
+ # elasticsearch-dsl warnings are killing me - solnic
33
+ config.warnings = false
34
+
35
+ config.before do
36
+ module Test
37
+ end
38
+ end
39
+
40
+ config.after do
41
+ Object.send(:remove_const, :Test)
42
+ end
43
+ end
@@ -0,0 +1,13 @@
1
+ RSpec.describe ROM::Elasticsearch::Dataset, '#body' do
2
+ subject(:dataset) do
3
+ ROM::Elasticsearch::Dataset.new(client, params: { index: :users, type: :user })
4
+ end
5
+
6
+ include_context 'user fixtures'
7
+
8
+ it 'returns a new dataset with updated body' do
9
+ new_ds = dataset.body(id: 1)
10
+
11
+ expect(new_ds.body).to eql(id: 1)
12
+ end
13
+ end
@@ -0,0 +1,17 @@
1
+ RSpec.describe ROM::Elasticsearch::Dataset, '#delete' do
2
+ subject(:dataset) do
3
+ ROM::Elasticsearch::Dataset.new(client, params: { index: :users, type: :user })
4
+ end
5
+
6
+ include_context 'user fixtures'
7
+
8
+ it 'deletes data' do
9
+ expect(dataset.to_a.size).to eql(3)
10
+
11
+ dataset.refresh.delete
12
+
13
+ dataset.refresh
14
+
15
+ expect(dataset.to_a.size).to eql(0)
16
+ end
17
+ end
@@ -0,0 +1,13 @@
1
+ RSpec.describe ROM::Elasticsearch::Dataset, '#params' do
2
+ subject(:dataset) do
3
+ ROM::Elasticsearch::Dataset.new(client, params: { index: :users, type: :user })
4
+ end
5
+
6
+ include_context 'user fixtures'
7
+
8
+ it 'returns a new dataset with updated params' do
9
+ new_ds = dataset.params(size: 100)
10
+
11
+ expect(new_ds.params).to eql(size: 100, index: :users, type: :user)
12
+ end
13
+ end
@@ -0,0 +1,14 @@
1
+ RSpec.describe ROM::Elasticsearch::Dataset, '#put' do
2
+ subject(:dataset) do
3
+ ROM::Elasticsearch::Dataset.new(client, params: { index: :users, type: :user })
4
+ end
5
+
6
+ include_context 'setup'
7
+
8
+ it 'puts new data' do
9
+ result = dataset.put(username: 'eve')
10
+
11
+ expect(result['_id']).to_not be(nil)
12
+ expect(result['result']).to eql('created')
13
+ end
14
+ end
@@ -0,0 +1,12 @@
1
+ RSpec.describe ROM::Elasticsearch::Dataset, '#query_string' do
2
+ subject(:dataset) do
3
+ ROM::Elasticsearch::Dataset.new(client, params: { index: :users, type: :user })
4
+ end
5
+
6
+ include_context 'user fixtures'
7
+
8
+ it 'returns data matching query string' do
9
+ expect(dataset.query_string('username:alice').to_a).to eql([{'username' => 'alice'}])
10
+ expect(dataset.query_string('username:nisse').to_a).to eql([])
11
+ end
12
+ end
@@ -0,0 +1,20 @@
1
+ RSpec.describe ROM::Elasticsearch::Dataset, '#search' do
2
+ subject(:dataset) do
3
+ ROM::Elasticsearch::Dataset.new(client, params: { index: :users, type: :user })
4
+ end
5
+
6
+ include_context 'setup'
7
+
8
+ before do
9
+ dataset.put(username: 'eve')
10
+ dataset.put(username: 'bob')
11
+ dataset.put(username: 'alice')
12
+
13
+ dataset.refresh
14
+ end
15
+
16
+ it 'returns data matching query options' do
17
+ expect(dataset.search(query: {query_string: {query: 'username:eve'}}).to_a).
18
+ to eql([{'username' => 'eve'}])
19
+ end
20
+ end
@@ -0,0 +1,10 @@
1
+ require 'rom/lint/spec'
2
+
3
+ RSpec.describe ROM::Elasticsearch::Gateway do
4
+ let(:uri) { 'http://localhost:9200/rom-test' }
5
+
6
+ it_behaves_like 'a rom gateway' do
7
+ let(:identifier) { :elasticsearch }
8
+ let(:gateway) { ROM::Elasticsearch::Gateway }
9
+ end
10
+ end
@@ -0,0 +1,34 @@
1
+ require 'rom/elasticsearch/plugins/relation/query_dsl'
2
+ require 'rom/elasticsearch/relation'
3
+
4
+ RSpec.describe ROM::Elasticsearch::Relation, '#search' do
5
+ subject(:relation) { relations[:users] }
6
+
7
+ include_context 'setup'
8
+
9
+ before do
10
+ conf.relation(:users) do
11
+ schema do
12
+ attribute :id, ROM::Types::Int.meta(type: "integer")
13
+ attribute :name, ROM::Types::Int.meta(type: "text")
14
+ end
15
+
16
+ use :query_dsl
17
+ end
18
+
19
+ relation.command(:create).(id: 1, name: 'Jane')
20
+ relation.command(:create).(id: 2, name: 'John')
21
+
22
+ relation.refresh
23
+ end
24
+
25
+ it 'builds a query using a block-based DSL' do
26
+ result = relation.search do
27
+ query do
28
+ match name: 'Jane'
29
+ end
30
+ end
31
+
32
+ expect(result.to_a).to eql([{ id: 1, name: 'Jane' }])
33
+ end
34
+ end
@@ -0,0 +1,75 @@
1
+ require 'rom/elasticsearch/relation'
2
+
3
+ RSpec.describe ROM::Elasticsearch::Relation, '#create_index' do
4
+ subject(:relation) { relations[:users] }
5
+
6
+ include_context 'setup'
7
+
8
+ context 'when custom :index is configured' do
9
+ after do
10
+ relation.delete_index
11
+ end
12
+
13
+ context 'with default settings' do
14
+ before do
15
+ conf.relation(:users) do
16
+ schema do
17
+ attribute :id, ROM::Elasticsearch::Types::ID
18
+ attribute :name, ROM::Types::String
19
+ end
20
+ end
21
+ end
22
+
23
+ it 'creates an index' do
24
+ relation.create_index
25
+
26
+ expect(gateway.index?(:users)).to be(true)
27
+ end
28
+ end
29
+
30
+ context 'with customized settings' do
31
+ before do
32
+ conf.relation(:users) do
33
+ schema do
34
+ attribute :id, ROM::Types::Int
35
+ attribute :name, ROM::Types::String
36
+ end
37
+
38
+ index_settings number_of_shards: 2
39
+ end
40
+ end
41
+
42
+ it 'creates an index' do
43
+ relation.create_index
44
+
45
+ expect(gateway.index?(:users)).to be(true)
46
+ expect(relation.dataset.settings['number_of_shards']).to eql("2")
47
+ end
48
+ end
49
+
50
+ context 'with customized attribute mappings' do
51
+ before do
52
+ conf.relation(:users) do
53
+ schema do
54
+ attribute :id, ROM::Elasticsearch::Types::ID
55
+ attribute :name, ROM::Elasticsearch::Types.Keyword
56
+ attribute :desc, ROM::Elasticsearch::Types.Text(analyzer: "snowball")
57
+ end
58
+
59
+ index_settings number_of_shards: 2
60
+ end
61
+ end
62
+
63
+ it 'creates an index' do
64
+ relation.create_index
65
+
66
+ expect(gateway.index?(:users)).to be(true)
67
+
68
+ expect(relation.dataset.mappings).
69
+ to eql("properties" => {
70
+ "name" => { "type" => "keyword" },
71
+ "desc" => { "type" => "text", "analyzer" => "snowball" }})
72
+ end
73
+ end
74
+ end
75
+ end