elasticsearch-persistence 5.1.0 → 6.0.0.pre
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 +4 -4
- data/.rspec +2 -0
- data/Gemfile +9 -0
- data/README.md +164 -323
- data/Rakefile +8 -8
- data/elasticsearch-persistence.gemspec +4 -5
- data/lib/elasticsearch/persistence.rb +2 -110
- data/lib/elasticsearch/persistence/repository.rb +212 -53
- data/lib/elasticsearch/persistence/repository/dsl.rb +94 -0
- data/lib/elasticsearch/persistence/repository/find.rb +27 -10
- data/lib/elasticsearch/persistence/repository/response/results.rb +17 -5
- data/lib/elasticsearch/persistence/repository/search.rb +15 -4
- data/lib/elasticsearch/persistence/repository/serialize.rb +65 -7
- data/lib/elasticsearch/persistence/repository/store.rb +38 -44
- data/lib/elasticsearch/persistence/version.rb +1 -1
- data/spec/repository/find_spec.rb +179 -0
- data/spec/repository/response/results_spec.rb +105 -0
- data/spec/repository/search_spec.rb +181 -0
- data/spec/repository/serialize_spec.rb +53 -0
- data/spec/repository/store_spec.rb +327 -0
- data/spec/repository_spec.rb +716 -0
- data/spec/spec_helper.rb +28 -0
- metadata +25 -80
- data/lib/elasticsearch/persistence/client.rb +0 -51
- data/lib/elasticsearch/persistence/model.rb +0 -153
- data/lib/elasticsearch/persistence/model/base.rb +0 -87
- data/lib/elasticsearch/persistence/model/errors.rb +0 -8
- data/lib/elasticsearch/persistence/model/find.rb +0 -180
- data/lib/elasticsearch/persistence/model/rails.rb +0 -47
- data/lib/elasticsearch/persistence/model/store.rb +0 -254
- data/lib/elasticsearch/persistence/model/utils.rb +0 -0
- data/lib/elasticsearch/persistence/repository/class.rb +0 -71
- data/lib/elasticsearch/persistence/repository/naming.rb +0 -115
- data/lib/rails/generators/elasticsearch/model/model_generator.rb +0 -21
- data/lib/rails/generators/elasticsearch/model/templates/model.rb.tt +0 -9
- data/lib/rails/generators/elasticsearch_generator.rb +0 -2
- data/test/integration/model/model_basic_test.rb +0 -238
- data/test/integration/repository/custom_class_test.rb +0 -85
- data/test/integration/repository/customized_class_test.rb +0 -82
- data/test/integration/repository/default_class_test.rb +0 -116
- data/test/integration/repository/virtus_model_test.rb +0 -118
- data/test/test_helper.rb +0 -55
- data/test/unit/model_base_test.rb +0 -72
- data/test/unit/model_find_test.rb +0 -153
- data/test/unit/model_gateway_test.rb +0 -101
- data/test/unit/model_rails_test.rb +0 -112
- data/test/unit/model_store_test.rb +0 -576
- data/test/unit/persistence_test.rb +0 -32
- data/test/unit/repository_class_test.rb +0 -51
- data/test/unit/repository_client_test.rb +0 -32
- data/test/unit/repository_find_test.rb +0 -388
- data/test/unit/repository_indexing_test.rb +0 -37
- data/test/unit/repository_module_test.rb +0 -146
- data/test/unit/repository_naming_test.rb +0 -146
- data/test/unit/repository_response_results_test.rb +0 -98
- data/test/unit/repository_search_test.rb +0 -117
- data/test/unit/repository_serialize_test.rb +0 -57
- data/test/unit/repository_store_test.rb +0 -303
@@ -0,0 +1,94 @@
|
|
1
|
+
module Elasticsearch
|
2
|
+
module Persistence
|
3
|
+
module Repository
|
4
|
+
|
5
|
+
# Include this module to get class-level methods for repository configuration.
|
6
|
+
#
|
7
|
+
# @since 6.0.0
|
8
|
+
module DSL
|
9
|
+
|
10
|
+
def self.included(base)
|
11
|
+
base.send(:extend, Elasticsearch::Model::Indexing::ClassMethods)
|
12
|
+
base.send(:extend, ClassMethods)
|
13
|
+
end
|
14
|
+
|
15
|
+
# These methods are necessary to define at the class-level so that the methods available
|
16
|
+
# via Elasticsearch::Model::Indexing::ClassMethods have the references they depend on.
|
17
|
+
#
|
18
|
+
# @since 6.0.0
|
19
|
+
module ClassMethods
|
20
|
+
|
21
|
+
# Get or set the class-level document type setting.
|
22
|
+
#
|
23
|
+
# @example
|
24
|
+
# MyRepository.document_type
|
25
|
+
#
|
26
|
+
# @return [ String, Symbol ] _type The repository's document type.
|
27
|
+
#
|
28
|
+
# @since 6.0.0
|
29
|
+
def document_type(_type = nil)
|
30
|
+
@document_type ||= (_type || DEFAULT_DOC_TYPE)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Get or set the class-level index name setting.
|
34
|
+
#
|
35
|
+
# @example
|
36
|
+
# MyRepository.index_name
|
37
|
+
#
|
38
|
+
# @return [ String, Symbol ] _name The repository's index name.
|
39
|
+
#
|
40
|
+
# @since 6.0.0
|
41
|
+
def index_name(_name = nil)
|
42
|
+
@index_name ||= (_name || DEFAULT_INDEX_NAME)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Get or set the class-level setting for the class used by the repository when deserializing.
|
46
|
+
#
|
47
|
+
# @example
|
48
|
+
# MyRepository.klass
|
49
|
+
#
|
50
|
+
# @return [ Class ] _class The repository's klass for deserializing.
|
51
|
+
#
|
52
|
+
# @since 6.0.0
|
53
|
+
def klass(_class = nil)
|
54
|
+
instance_variables.include?(:@klass) ? @klass : @klass = _class
|
55
|
+
end
|
56
|
+
|
57
|
+
# Get or set the class-level setting for the client used by the repository.
|
58
|
+
#
|
59
|
+
# @example
|
60
|
+
# MyRepository.client
|
61
|
+
#
|
62
|
+
# @return [ Class ] _client The repository's client.
|
63
|
+
#
|
64
|
+
# @since 6.0.0
|
65
|
+
def client(_client = nil)
|
66
|
+
@client ||= (_client || Elasticsearch::Client.new)
|
67
|
+
end
|
68
|
+
|
69
|
+
def create_index!(*args)
|
70
|
+
__raise_not_implemented_error(__method__)
|
71
|
+
end
|
72
|
+
|
73
|
+
def delete_index!(*args)
|
74
|
+
__raise_not_implemented_error(__method__)
|
75
|
+
end
|
76
|
+
|
77
|
+
def refresh_index!(*args)
|
78
|
+
__raise_not_implemented_error(__method__)
|
79
|
+
end
|
80
|
+
|
81
|
+
def index_exists?(*args)
|
82
|
+
__raise_not_implemented_error(__method__)
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
def __raise_not_implemented_error(_method_)
|
88
|
+
raise NotImplementedError, "The '#{_method_}' method is not implemented on the Repository class."
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -40,19 +40,35 @@ module Elasticsearch
|
|
40
40
|
# repository.exists?(1)
|
41
41
|
# => true
|
42
42
|
#
|
43
|
+
# @param [ String, Integer ] id The id to search.
|
44
|
+
# @param [ Hash ] options The options.
|
45
|
+
#
|
43
46
|
# @return [true, false]
|
44
47
|
#
|
45
48
|
def exists?(id, options={})
|
46
|
-
|
47
|
-
|
49
|
+
request = { index: index_name, id: id }
|
50
|
+
request[:type] = document_type if document_type
|
51
|
+
client.exists(request.merge(options))
|
48
52
|
end
|
49
53
|
|
54
|
+
private
|
55
|
+
|
56
|
+
# The key for accessing the document found and returned from an
|
57
|
+
# Elasticsearch _mget query.
|
58
|
+
#
|
59
|
+
DOCS = 'docs'.freeze
|
60
|
+
|
61
|
+
# The key for the boolean value indicating whether a particular id
|
62
|
+
# has been successfully found in an Elasticsearch _mget query.
|
63
|
+
#
|
64
|
+
FOUND = 'found'.freeze
|
65
|
+
|
50
66
|
# @api private
|
51
67
|
#
|
52
68
|
def __find_one(id, options={})
|
53
|
-
|
54
|
-
|
55
|
-
|
69
|
+
request = { index: index_name, id: id }
|
70
|
+
request[:type] = document_type if document_type
|
71
|
+
document = client.get(request.merge(options))
|
56
72
|
deserialize(document)
|
57
73
|
rescue Elasticsearch::Transport::Transport::Errors::NotFound => e
|
58
74
|
raise DocumentNotFound, e.message, caller
|
@@ -61,13 +77,14 @@ module Elasticsearch
|
|
61
77
|
# @api private
|
62
78
|
#
|
63
79
|
def __find_many(ids, options={})
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
documents[
|
80
|
+
request = { index: index_name, body: { ids: ids } }
|
81
|
+
request[:type] = document_type if document_type
|
82
|
+
documents = client.mget(request.merge(options))
|
83
|
+
documents[DOCS].map do |document|
|
84
|
+
deserialize(document) if document[FOUND]
|
85
|
+
end
|
68
86
|
end
|
69
87
|
end
|
70
|
-
|
71
88
|
end
|
72
89
|
end
|
73
90
|
end
|
@@ -12,6 +12,18 @@ module Elasticsearch
|
|
12
12
|
|
13
13
|
attr_reader :repository
|
14
14
|
|
15
|
+
# The key for accessing the results in an Elasticsearch query response.
|
16
|
+
#
|
17
|
+
HITS = 'hits'.freeze
|
18
|
+
|
19
|
+
# The key for accessing the total number of hits in an Elasticsearch query response.
|
20
|
+
#
|
21
|
+
TOTAL = 'total'.freeze
|
22
|
+
|
23
|
+
# The key for accessing the maximum score in an Elasticsearch query response.
|
24
|
+
#
|
25
|
+
MAX_SCORE = 'max_score'.freeze
|
26
|
+
|
15
27
|
# @param repository [Elasticsearch::Persistence::Repository::Class] The repository instance
|
16
28
|
# @param response [Hash] The full response returned from the Elasticsearch client
|
17
29
|
# @param options [Hash] Optional parameters
|
@@ -33,25 +45,25 @@ module Elasticsearch
|
|
33
45
|
# The number of total hits for a query
|
34
46
|
#
|
35
47
|
def total
|
36
|
-
response[
|
48
|
+
response[HITS][TOTAL]
|
37
49
|
end
|
38
50
|
|
39
51
|
# The maximum score for a query
|
40
52
|
#
|
41
53
|
def max_score
|
42
|
-
response[
|
54
|
+
response[HITS][MAX_SCORE]
|
43
55
|
end
|
44
56
|
|
45
57
|
# Yields [object, hit] pairs to the block
|
46
58
|
#
|
47
59
|
def each_with_hit(&block)
|
48
|
-
results.zip(response[
|
60
|
+
results.zip(response[HITS][HITS]).each(&block)
|
49
61
|
end
|
50
62
|
|
51
63
|
# Yields [object, hit] pairs and returns the result
|
52
64
|
#
|
53
65
|
def map_with_hit(&block)
|
54
|
-
results.zip(response[
|
66
|
+
results.zip(response[HITS][HITS]).map(&block)
|
55
67
|
end
|
56
68
|
|
57
69
|
# Return the collection of domain objects
|
@@ -64,7 +76,7 @@ module Elasticsearch
|
|
64
76
|
# @return [Array]
|
65
77
|
#
|
66
78
|
def results
|
67
|
-
@results ||= response[
|
79
|
+
@results ||= response[HITS][HITS].map do |document|
|
68
80
|
repository.deserialize(document.to_hash)
|
69
81
|
end
|
70
82
|
end
|
@@ -37,10 +37,13 @@ module Elasticsearch
|
|
37
37
|
# # GET http://localhost:9200/notes/note/_search
|
38
38
|
# # > {"query":{"match":{"title":"fox dog"}},"size":25}
|
39
39
|
#
|
40
|
+
# @param [ Hash, String ] query_or_definition The query or search definition.
|
41
|
+
# @param [ Hash ] options The search options.
|
42
|
+
#
|
40
43
|
# @return [Elasticsearch::Persistence::Repository::Response::Results]
|
41
44
|
#
|
42
45
|
def search(query_or_definition, options={})
|
43
|
-
type = document_type
|
46
|
+
type = document_type
|
44
47
|
|
45
48
|
case
|
46
49
|
when query_or_definition.respond_to?(:to_hash)
|
@@ -71,11 +74,14 @@ module Elasticsearch
|
|
71
74
|
# repository.search(query: { match: { title: 'fox dog' } })
|
72
75
|
# # => 1
|
73
76
|
#
|
77
|
+
# @param [ Hash, String ] query_or_definition The query or search definition.
|
78
|
+
# @param [ Hash ] options The search options.
|
79
|
+
#
|
74
80
|
# @return [Integer]
|
75
81
|
#
|
76
82
|
def count(query_or_definition=nil, options={})
|
77
83
|
query_or_definition ||= { query: { match_all: {} } }
|
78
|
-
type = document_type
|
84
|
+
type = document_type
|
79
85
|
|
80
86
|
case
|
81
87
|
when query_or_definition.respond_to?(:to_hash)
|
@@ -86,10 +92,15 @@ module Elasticsearch
|
|
86
92
|
raise ArgumentError, "[!] Pass the search definition as a Hash-like object or pass the query as a String, not as [#{query_or_definition.class}]"
|
87
93
|
end
|
88
94
|
|
89
|
-
response[
|
95
|
+
response[COUNT]
|
90
96
|
end
|
91
|
-
end
|
92
97
|
|
98
|
+
private
|
99
|
+
|
100
|
+
# The key for accessing the count in a Elasticsearch query response.
|
101
|
+
#
|
102
|
+
COUNT = 'count'.freeze
|
103
|
+
end
|
93
104
|
end
|
94
105
|
end
|
95
106
|
end
|
@@ -2,30 +2,88 @@ module Elasticsearch
|
|
2
2
|
module Persistence
|
3
3
|
module Repository
|
4
4
|
|
5
|
-
# Provide serialization and deserialization between Ruby objects and Elasticsearch documents
|
5
|
+
# Provide serialization and deserialization between Ruby objects and Elasticsearch documents.
|
6
6
|
#
|
7
7
|
# Override these methods in your repository class to customize the logic.
|
8
8
|
#
|
9
9
|
module Serialize
|
10
10
|
|
11
|
-
# Serialize the object for storing it in Elasticsearch
|
11
|
+
# Serialize the object for storing it in Elasticsearch.
|
12
12
|
#
|
13
13
|
# In the default implementation, call the `to_hash` method on the passed object.
|
14
14
|
#
|
15
|
+
# @param [ Object ] document The Ruby object to serialize.
|
16
|
+
#
|
17
|
+
# @return [ Hash ] The serialized document.
|
18
|
+
#
|
15
19
|
def serialize(document)
|
16
20
|
document.to_hash
|
17
21
|
end
|
18
22
|
|
19
|
-
# Deserialize the document retrieved from Elasticsearch into a Ruby object
|
23
|
+
# Deserialize the document retrieved from Elasticsearch into a Ruby object.
|
24
|
+
# If no klass is set for the Repository then the raw document '_source' field will be returned.
|
25
|
+
#
|
26
|
+
# def deserialize(document)
|
27
|
+
# Note.new document[SOURCE]
|
28
|
+
# end
|
20
29
|
#
|
21
|
-
#
|
30
|
+
# @param [ Hash ] document The raw document.
|
31
|
+
#
|
32
|
+
# @return [ Object ] The deserialized object.
|
22
33
|
#
|
23
34
|
def deserialize(document)
|
24
|
-
|
25
|
-
_klass.new document['_source']
|
35
|
+
klass ? klass.new(document[SOURCE]) : document[SOURCE]
|
26
36
|
end
|
27
|
-
end
|
28
37
|
|
38
|
+
private
|
39
|
+
|
40
|
+
# The key for document fields in an Elasticsearch query response.
|
41
|
+
#
|
42
|
+
SOURCE = '_source'.freeze
|
43
|
+
|
44
|
+
# The key for the document type in an Elasticsearch query response.
|
45
|
+
# Note that it will be removed eventually, as multiple types in a single
|
46
|
+
# index are deprecated as of Elasticsearch 6.0.
|
47
|
+
#
|
48
|
+
TYPE = '_type'.freeze
|
49
|
+
|
50
|
+
IDS = [:id, 'id', :_id, '_id'].freeze
|
51
|
+
|
52
|
+
# Get a document ID from the document (assuming Hash or Hash-like object)
|
53
|
+
#
|
54
|
+
# @example
|
55
|
+
# repository.__get_id_from_document title: 'Test', id: 'abc123'
|
56
|
+
# => "abc123"
|
57
|
+
#
|
58
|
+
# @api private
|
59
|
+
#
|
60
|
+
def __get_id_from_document(document)
|
61
|
+
document[IDS.find { |id| document[id] }]
|
62
|
+
end
|
63
|
+
|
64
|
+
# Extract a document ID from the document (assuming Hash or Hash-like object)
|
65
|
+
#
|
66
|
+
# @note Calling this method will *remove* the `id` or `_id` key from the passed object.
|
67
|
+
#
|
68
|
+
# @example
|
69
|
+
# options = { title: 'Test', id: 'abc123' }
|
70
|
+
# repository.__extract_id_from_document options
|
71
|
+
# # => "abc123"
|
72
|
+
# options
|
73
|
+
# # => { title: 'Test' }
|
74
|
+
#
|
75
|
+
# @api private
|
76
|
+
#
|
77
|
+
def __extract_id_from_document(document)
|
78
|
+
IDS.inject(nil) do |deleted, id|
|
79
|
+
if document[id]
|
80
|
+
document.delete(id)
|
81
|
+
else
|
82
|
+
deleted
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
29
87
|
end
|
30
88
|
end
|
31
89
|
end
|
@@ -12,13 +12,19 @@ module Elasticsearch
|
|
12
12
|
# repository.save(myobject)
|
13
13
|
# => {"_index"=>"...", "_type"=>"...", "_id"=>"...", "_version"=>1, "created"=>true}
|
14
14
|
#
|
15
|
-
# @
|
15
|
+
# @param [ Object ] document The document to save into Elasticsearch.
|
16
|
+
# @param [ Hash ] options The save request options.
|
17
|
+
#
|
18
|
+
# @return [ Hash ] The response from Elasticsearch
|
16
19
|
#
|
17
20
|
def save(document, options={})
|
18
21
|
serialized = serialize(document)
|
19
|
-
id
|
20
|
-
|
21
|
-
|
22
|
+
id = __get_id_from_document(serialized)
|
23
|
+
request = { index: index_name,
|
24
|
+
id: id,
|
25
|
+
body: serialized }
|
26
|
+
request[:type] = document_type if document_type
|
27
|
+
client.index(request.merge(options))
|
22
28
|
end
|
23
29
|
|
24
30
|
# Update the serialized object in Elasticsearch with partial data or script
|
@@ -33,39 +39,27 @@ module Elasticsearch
|
|
33
39
|
# repository.update 1, script: 'ctx._source.views += 1'
|
34
40
|
# # => {"_index"=>"...", "_type"=>"...", "_id"=>"1", "_version"=>3}
|
35
41
|
#
|
36
|
-
# @
|
42
|
+
# @param [ Object ] document_or_id The document to update or the id of the document to update.
|
43
|
+
# @param [ Hash ] options The update request options.
|
37
44
|
#
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
else
|
46
|
-
raise ArgumentError, "Expected a document ID or a Hash-like object, #{document.class} given"
|
47
|
-
end
|
48
|
-
|
49
|
-
type = options.delete(:type) || \
|
50
|
-
(defined?(serialized) && serialized && serialized.delete(:type)) || \
|
51
|
-
document_type || \
|
52
|
-
__get_type_from_class(klass)
|
53
|
-
|
54
|
-
if defined?(serialized) && serialized
|
55
|
-
body = if serialized[:script]
|
56
|
-
serialized.select { |k, v| [:script, :params, :upsert].include? k }
|
57
|
-
else
|
58
|
-
{ doc: serialized }
|
59
|
-
end
|
45
|
+
# @return [ Hash ] The response from Elasticsearch
|
46
|
+
#
|
47
|
+
def update(document_or_id, options = {})
|
48
|
+
if document_or_id.is_a?(String) || document_or_id.is_a?(Integer)
|
49
|
+
id = document_or_id
|
50
|
+
body = options
|
51
|
+
type = document_type
|
60
52
|
else
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
53
|
+
document = serialize(document_or_id)
|
54
|
+
id = __extract_id_from_document(document)
|
55
|
+
if options[:script]
|
56
|
+
body = options
|
57
|
+
else
|
58
|
+
body = { doc: document }.merge(options)
|
59
|
+
end
|
60
|
+
type = document.delete(:type) || document_type
|
66
61
|
end
|
67
|
-
|
68
|
-
client.update( { index: index_name, type: type, id: id, body: body }.merge(options) )
|
62
|
+
client.update(index: index_name, id: id, type: type, body: body)
|
69
63
|
end
|
70
64
|
|
71
65
|
# Remove the serialized object or document with specified ID from Elasticsearch
|
@@ -75,21 +69,21 @@ module Elasticsearch
|
|
75
69
|
# repository.delete(1)
|
76
70
|
# # => {"_index"=>"...", "_type"=>"...", "_id"=>"1", "_version"=>4}
|
77
71
|
#
|
78
|
-
# @
|
72
|
+
# @param [ Object ] document_or_id The document to delete or the id of the document to delete.
|
73
|
+
# @param [ Hash ] options The delete request options.
|
79
74
|
#
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
75
|
+
# @return [ Hash ] The response from Elasticsearch
|
76
|
+
#
|
77
|
+
def delete(document_or_id, options = {})
|
78
|
+
if document_or_id.is_a?(String) || document_or_id.is_a?(Integer)
|
79
|
+
id = document_or_id
|
84
80
|
else
|
85
|
-
serialized = serialize(
|
86
|
-
id
|
87
|
-
type = document_type || __get_type_from_class(klass || document.class)
|
81
|
+
serialized = serialize(document_or_id)
|
82
|
+
id = __get_id_from_document(serialized)
|
88
83
|
end
|
89
|
-
client.delete(
|
84
|
+
client.delete({ index: index_name, type: document_type, id: id }.merge(options))
|
90
85
|
end
|
91
86
|
end
|
92
|
-
|
93
87
|
end
|
94
88
|
end
|
95
89
|
end
|