elasticsearch-persistence 5.0.2 → 6.1.1

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 (81) hide show
  1. checksums.yaml +5 -5
  2. data/.rspec +2 -0
  3. data/Gemfile +9 -0
  4. data/README.md +206 -338
  5. data/Rakefile +15 -12
  6. data/elasticsearch-persistence.gemspec +6 -7
  7. data/examples/notes/application.rb +3 -4
  8. data/lib/elasticsearch/persistence.rb +2 -110
  9. data/lib/elasticsearch/persistence/repository.rb +212 -53
  10. data/lib/elasticsearch/persistence/repository/dsl.rb +94 -0
  11. data/lib/elasticsearch/persistence/repository/find.rb +27 -10
  12. data/lib/elasticsearch/persistence/repository/response/results.rb +21 -8
  13. data/lib/elasticsearch/persistence/repository/search.rb +30 -18
  14. data/lib/elasticsearch/persistence/repository/serialize.rb +65 -7
  15. data/lib/elasticsearch/persistence/repository/store.rb +38 -44
  16. data/lib/elasticsearch/persistence/version.rb +1 -1
  17. data/spec/repository/find_spec.rb +179 -0
  18. data/spec/repository/response/results_spec.rb +128 -0
  19. data/spec/repository/search_spec.rb +181 -0
  20. data/spec/repository/serialize_spec.rb +53 -0
  21. data/spec/repository/store_spec.rb +327 -0
  22. data/spec/repository_spec.rb +723 -0
  23. data/spec/spec_helper.rb +32 -0
  24. metadata +26 -104
  25. data/examples/music/album.rb +0 -54
  26. data/examples/music/artist.rb +0 -70
  27. data/examples/music/artists/_form.html.erb +0 -8
  28. data/examples/music/artists/artists_controller.rb +0 -67
  29. data/examples/music/artists/artists_controller_test.rb +0 -53
  30. data/examples/music/artists/index.html.erb +0 -60
  31. data/examples/music/artists/show.html.erb +0 -54
  32. data/examples/music/assets/application.css +0 -257
  33. data/examples/music/assets/autocomplete.css +0 -48
  34. data/examples/music/assets/blank_artist.png +0 -0
  35. data/examples/music/assets/blank_cover.png +0 -0
  36. data/examples/music/assets/form.css +0 -113
  37. data/examples/music/index_manager.rb +0 -73
  38. data/examples/music/search/index.html.erb +0 -95
  39. data/examples/music/search/search_controller.rb +0 -41
  40. data/examples/music/search/search_controller_test.rb +0 -12
  41. data/examples/music/search/search_helper.rb +0 -15
  42. data/examples/music/suggester.rb +0 -69
  43. data/examples/music/template.rb +0 -430
  44. data/examples/music/vendor/assets/jquery-ui-1.10.4.custom.min.css +0 -7
  45. data/examples/music/vendor/assets/jquery-ui-1.10.4.custom.min.js +0 -6
  46. data/examples/music/vendor/assets/stylesheets/ui-lightness/images/ui-bg_highlight-soft_100_eeeeee_1x100.png +0 -0
  47. data/lib/elasticsearch/persistence/client.rb +0 -51
  48. data/lib/elasticsearch/persistence/model.rb +0 -135
  49. data/lib/elasticsearch/persistence/model/base.rb +0 -87
  50. data/lib/elasticsearch/persistence/model/errors.rb +0 -8
  51. data/lib/elasticsearch/persistence/model/find.rb +0 -180
  52. data/lib/elasticsearch/persistence/model/rails.rb +0 -47
  53. data/lib/elasticsearch/persistence/model/store.rb +0 -254
  54. data/lib/elasticsearch/persistence/model/utils.rb +0 -0
  55. data/lib/elasticsearch/persistence/repository/class.rb +0 -71
  56. data/lib/elasticsearch/persistence/repository/naming.rb +0 -115
  57. data/lib/rails/generators/elasticsearch/model/model_generator.rb +0 -21
  58. data/lib/rails/generators/elasticsearch/model/templates/model.rb.tt +0 -9
  59. data/lib/rails/generators/elasticsearch_generator.rb +0 -2
  60. data/test/integration/model/model_basic_test.rb +0 -233
  61. data/test/integration/repository/custom_class_test.rb +0 -85
  62. data/test/integration/repository/customized_class_test.rb +0 -82
  63. data/test/integration/repository/default_class_test.rb +0 -116
  64. data/test/integration/repository/virtus_model_test.rb +0 -118
  65. data/test/test_helper.rb +0 -55
  66. data/test/unit/model_base_test.rb +0 -72
  67. data/test/unit/model_find_test.rb +0 -153
  68. data/test/unit/model_gateway_test.rb +0 -101
  69. data/test/unit/model_rails_test.rb +0 -112
  70. data/test/unit/model_store_test.rb +0 -576
  71. data/test/unit/persistence_test.rb +0 -32
  72. data/test/unit/repository_class_test.rb +0 -51
  73. data/test/unit/repository_client_test.rb +0 -32
  74. data/test/unit/repository_find_test.rb +0 -388
  75. data/test/unit/repository_indexing_test.rb +0 -37
  76. data/test/unit/repository_module_test.rb +0 -146
  77. data/test/unit/repository_naming_test.rb +0 -146
  78. data/test/unit/repository_response_results_test.rb +0 -98
  79. data/test/unit/repository_search_test.rb +0 -117
  80. data/test/unit/repository_serialize_test.rb +0 -57
  81. data/test/unit/repository_store_test.rb +0 -303
data/Rakefile CHANGED
@@ -7,24 +7,27 @@ task :test => 'test:unit'
7
7
  # ----- Test tasks ------------------------------------------------------------
8
8
 
9
9
  require 'rake/testtask'
10
+ require 'rspec/core/rake_task'
11
+
10
12
  namespace :test do
11
- Rake::TestTask.new(:unit) do |test|
12
- test.libs << 'lib' << 'test'
13
- test.test_files = FileList["test/unit/**/*_test.rb"]
14
- test.verbose = false
15
- test.warning = false
16
- end
17
13
 
18
- Rake::TestTask.new(:integration) do |test|
19
- test.libs << 'lib' << 'test'
20
- test.test_files = FileList["test/integration/**/*_test.rb"]
14
+ RSpec::Core::RakeTask.new(:spec)
15
+
16
+ Rake::TestTask.new(:all) do |test|
21
17
  test.verbose = false
22
18
  test.warning = false
19
+ test.deps = [ :spec ]
23
20
  end
21
+ end
24
22
 
25
- Rake::TestTask.new(:all) do |test|
26
- test.libs << 'lib' << 'test'
27
- test.test_files = FileList["test/unit/**/*_test.rb", "test/integration/**/*_test.rb"]
23
+ namespace :bundle do
24
+ desc 'Install gem dependencies'
25
+ task :install do
26
+ puts '-'*80
27
+ Bundler.with_clean_env do
28
+ sh 'bundle install'
29
+ end
30
+ puts '-'*80
28
31
  end
29
32
  end
30
33
 
@@ -21,19 +21,18 @@ Gem::Specification.new do |s|
21
21
  s.extra_rdoc_files = [ "README.md", "LICENSE.txt" ]
22
22
  s.rdoc_options = [ "--charset=UTF-8" ]
23
23
 
24
- s.required_ruby_version = ">= 1.9.3"
24
+ s.required_ruby_version = ">= 2.2"
25
25
 
26
- s.add_dependency "elasticsearch", '~> 5'
27
- s.add_dependency "elasticsearch-model", '~> 5'
26
+ s.add_dependency "elasticsearch", '~> 6'
27
+ s.add_dependency "elasticsearch-model", '>= 5'
28
28
  s.add_dependency "activesupport", '> 4'
29
29
  s.add_dependency "activemodel", '> 4'
30
30
  s.add_dependency "hashie"
31
- s.add_dependency "virtus"
32
31
 
33
- s.add_development_dependency "bundler", "~> 1.5"
32
+ s.add_development_dependency "bundler"
34
33
  s.add_development_dependency "rake", "~> 11.1"
35
34
 
36
- s.add_development_dependency "oj"
35
+ s.add_development_dependency "oj" unless defined?(JRUBY_VERSION)
37
36
 
38
37
  s.add_development_dependency "rails", '> 4'
39
38
 
@@ -45,7 +44,7 @@ Gem::Specification.new do |s|
45
44
  s.add_development_dependency "mocha"
46
45
  s.add_development_dependency "turn"
47
46
  s.add_development_dependency "yard"
48
- s.add_development_dependency "ruby-prof"
47
+ s.add_development_dependency "ruby-prof" unless defined?(JRUBY_VERSION)
49
48
  s.add_development_dependency "pry"
50
49
 
51
50
  s.add_development_dependency "simplecov"
@@ -54,11 +54,12 @@ end
54
54
 
55
55
  class NoteRepository
56
56
  include Elasticsearch::Persistence::Repository
57
+ include Elasticsearch::Persistence::Repository::DSL
57
58
 
58
59
  client Elasticsearch::Client.new url: ENV['ELASTICSEARCH_URL'], log: true
59
60
 
60
- index :notes
61
- type :note
61
+ index_name :notes
62
+ document_type :note
62
63
 
63
64
  mapping do
64
65
  indexes :text, analyzer: 'snowball'
@@ -66,8 +67,6 @@ class NoteRepository
66
67
  indexes :created_at, type: 'date'
67
68
  end
68
69
 
69
- create_index!
70
-
71
70
  def deserialize(document)
72
71
  Note.new document['_source'].merge('id' => document['_id'])
73
72
  end
@@ -1,116 +1,8 @@
1
1
  require 'hashie/mash'
2
2
 
3
3
  require 'elasticsearch'
4
-
5
- require 'elasticsearch/model/hash_wrapper'
6
- require 'elasticsearch/model/indexing'
7
- require 'elasticsearch/model/searching'
8
-
9
- require 'active_support/inflector'
4
+ require 'elasticsearch/model'
10
5
 
11
6
  require 'elasticsearch/persistence/version'
12
-
13
- require 'elasticsearch/persistence/client'
14
- require 'elasticsearch/persistence/repository/response/results'
15
- require 'elasticsearch/persistence/repository/naming'
16
- require 'elasticsearch/persistence/repository/serialize'
17
- require 'elasticsearch/persistence/repository/store'
18
- require 'elasticsearch/persistence/repository/find'
19
- require 'elasticsearch/persistence/repository/search'
20
- require 'elasticsearch/persistence/repository/class'
21
7
  require 'elasticsearch/persistence/repository'
22
-
23
- module Elasticsearch
24
-
25
- # Persistence for Ruby domain objects and models in Elasticsearch
26
- # ===============================================================
27
- #
28
- # `Elasticsearch::Persistence` contains modules for storing and retrieving Ruby domain objects and models
29
- # in Elasticsearch.
30
- #
31
- # == Repository
32
- #
33
- # The repository patterns allows to store and retrieve Ruby objects in Elasticsearch.
34
- #
35
- # require 'elasticsearch/persistence'
36
- #
37
- # class Note
38
- # def to_hash; {foo: 'bar'}; end
39
- # end
40
- #
41
- # repository = Elasticsearch::Persistence::Repository.new
42
- #
43
- # repository.save Note.new
44
- # # => {"_index"=>"repository", "_type"=>"note", "_id"=>"mY108X9mSHajxIy2rzH2CA", ...}
45
- #
46
- # Customize your repository by including the main module in a Ruby class
47
- # class MyRepository
48
- # include Elasticsearch::Persistence::Repository
49
- #
50
- # index 'my_notes'
51
- # klass Note
52
- #
53
- # client Elasticsearch::Client.new log: true
54
- # end
55
- #
56
- # repository = MyRepository.new
57
- #
58
- # repository.save Note.new
59
- # # 2014-04-04 22:15:25 +0200: POST http://localhost:9200/my_notes/note [status:201, request:0.009s, query:n/a]
60
- # # 2014-04-04 22:15:25 +0200: > {"foo":"bar"}
61
- # # 2014-04-04 22:15:25 +0200: < {"_index":"my_notes","_type":"note","_id":"-d28yXLFSlusnTxb13WIZQ", ...}
62
- #
63
- # == Model
64
- #
65
- # The active record pattern allows to use the interface familiar from ActiveRecord models:
66
- #
67
- # require 'elasticsearch/persistence'
68
- #
69
- # class Article
70
- # attribute :title, String, mapping: { analyzer: 'snowball' }
71
- # end
72
- #
73
- # article = Article.new id: 1, title: 'Test'
74
- # article.save
75
- #
76
- # Article.find(1)
77
- #
78
- # article.update_attributes title: 'Update'
79
- #
80
- # article.destroy
81
- #
82
- module Persistence
83
-
84
- # :nodoc:
85
- module ClassMethods
86
-
87
- # Get or set the default client for all repositories and models
88
- #
89
- # @example Set and configure the default client
90
- #
91
- # Elasticsearch::Persistence.client Elasticsearch::Client.new host: 'http://localhost:9200', tracer: true
92
- #
93
- # @example Perform an API request through the client
94
- #
95
- # Elasticsearch::Persistence.client.cluster.health
96
- # # => { "cluster_name" => "elasticsearch" ... }
97
- #
98
- def client client=nil
99
- @client = client || @client || Elasticsearch::Client.new
100
- end
101
-
102
- # Set the default client for all repositories and models
103
- #
104
- # @example Set and configure the default client
105
- #
106
- # Elasticsearch::Persistence.client = Elasticsearch::Client.new host: 'http://localhost:9200', tracer: true
107
- # => #<Elasticsearch::Transport::Client:0x007f96a6dd0d80 @transport=... >
108
- #
109
- def client=(client)
110
- @client = client
111
- end
112
- end
113
-
114
- extend ClassMethods
115
- end
116
- end
8
+ require 'elasticsearch/persistence/repository/response/results'
@@ -1,77 +1,236 @@
1
+ require 'elasticsearch/persistence/repository/dsl'
2
+ require 'elasticsearch/persistence/repository/find'
3
+ require 'elasticsearch/persistence/repository/store'
4
+ require 'elasticsearch/persistence/repository/serialize'
5
+ require 'elasticsearch/persistence/repository/search'
6
+
1
7
  module Elasticsearch
2
8
  module Persistence
3
9
 
4
- # Delegate methods to the repository (acting as a gateway)
10
+ # The base Repository mixin. This module should be included in classes that
11
+ # represent an Elasticsearch repository.
5
12
  #
6
- module GatewayDelegation
7
- def method_missing(method_name, *arguments, &block)
8
- gateway.respond_to?(method_name) ? gateway.__send__(method_name, *arguments, &block) : super
13
+ # @since 6.0.0
14
+ module Repository
15
+ include Store
16
+ include Serialize
17
+ include Find
18
+ include Search
19
+ include Elasticsearch::Model::Indexing::ClassMethods
20
+
21
+ def self.included(base)
22
+ base.send(:extend, ClassMethods)
9
23
  end
10
24
 
11
- def respond_to?(method_name, include_private=false)
12
- gateway.respond_to?(method_name) || super
25
+ module ClassMethods
26
+
27
+ # Initialize a repository instance. Optionally provide a block to define index mappings or
28
+ # settings on the repository instance.
29
+ #
30
+ # @example Create a new repository.
31
+ # MyRepository.create(index_name: 'notes', klass: Note)
32
+ #
33
+ # @example Create a new repository and evaluate a block on it.
34
+ # MyRepository.create(index_name: 'notes', klass: Note) do
35
+ # mapping dynamic: 'strict' do
36
+ # indexes :title
37
+ # end
38
+ # end
39
+ #
40
+ # @param [ Hash ] options The options to use.
41
+ # @param [ Proc ] block A block to evaluate on the new repository instance.
42
+ #
43
+ # @option options [ Symbol, String ] :index_name The name of the index.
44
+ # @option options [ Symbol, String ] :document_type The type of documents persisted in this repository.
45
+ # @option options [ Symbol, String ] :client The client used to handle requests to and from Elasticsearch.
46
+ # @option options [ Symbol, String ] :klass The class used to instantiate an object when documents are
47
+ # deserialized. The default is nil, in which case the raw document will be returned as a Hash.
48
+ # @option options [ Elasticsearch::Model::Indexing::Mappings, Hash ] :mapping The mapping for this index.
49
+ # @option options [ Elasticsearch::Model::Indexing::Settings, Hash ] :settings The settings for this index.
50
+ #
51
+ # @since 6.0.0
52
+ def create(options = {}, &block)
53
+ new(options).tap do |obj|
54
+ obj.instance_eval(&block) if block_given?
55
+ end
56
+ end
13
57
  end
14
58
 
15
- def respond_to_missing?(method_name, *)
16
- gateway.respond_to?(method_name) || super
59
+ # The default index name.
60
+ #
61
+ # @return [ String ] The default index name.
62
+ #
63
+ # @since 6.0.0
64
+ DEFAULT_INDEX_NAME = 'repository'.freeze
65
+
66
+ # The default document type.
67
+ #
68
+ # @return [ String ] The default document type.
69
+ #
70
+ # @note the document type will no longer be configurable in future versions
71
+ # of Elasticsearch.
72
+ #
73
+ # @since 6.0.0
74
+ DEFAULT_DOC_TYPE = '_doc'.freeze
75
+
76
+ # The repository options.
77
+ #
78
+ # @return [ Hash ]
79
+ #
80
+ # @since 6.0.0
81
+ attr_reader :options
82
+
83
+ # Initialize a repository instance.
84
+ #
85
+ # @example Initialize the repository.
86
+ # MyRepository.new(index_name: 'notes', klass: Note)
87
+ #
88
+ # @param [ Hash ] options The options to use.
89
+ #
90
+ # @option options [ Symbol, String ] :index_name The name of the index.
91
+ # @option options [ Symbol, String ] :document_type The type of documents persisted in this repository.
92
+ # @option options [ Symbol, String ] :client The client used to handle requests to and from Elasticsearch.
93
+ # @option options [ Symbol, String ] :klass The class used to instantiate an object when documents are
94
+ # deserialized. The default is nil, in which case the raw document will be returned as a Hash.
95
+ # @option options [ Elasticsearch::Model::Indexing::Mappings, Hash ] :mapping The mapping for this index.
96
+ # @option options [ Elasticsearch::Model::Indexing::Settings, Hash ] :settings The settings for this index.
97
+ #
98
+ # @since 6.0.0
99
+ def initialize(options = {})
100
+ @options = options
17
101
  end
18
- end
19
102
 
20
- # When included, creates an instance of the {Repository::Class Repository} class as a "gateway"
21
- #
22
- # @example Include the repository in a custom class
23
- #
24
- # require 'elasticsearch/persistence'
25
- #
26
- # class MyRepository
27
- # include Elasticsearch::Persistence::Repository
28
- # end
29
- #
30
- module Repository
31
- def self.included(base)
32
- gateway = Elasticsearch::Persistence::Repository::Class.new host: base
103
+ # Get the client used by the repository.
104
+ #
105
+ # @example
106
+ # repository.client
107
+ #
108
+ # @return [ Elasticsearch::Client ] The repository's client.
109
+ #
110
+ # @since 6.0.0
111
+ def client
112
+ @client ||= @options[:client] ||
113
+ __get_class_value(:client) ||
114
+ Elasticsearch::Client.new
115
+ end
33
116
 
34
- # Define the instance level gateway
35
- #
36
- base.class_eval do
37
- define_method :gateway do
38
- @gateway ||= gateway
39
- end
117
+ # Get the document type used by the repository object.
118
+ #
119
+ # @example
120
+ # repository.document_type
121
+ #
122
+ # @return [ String, Symbol ] The repository's document type.
123
+ #
124
+ # @since 6.0.0
125
+ def document_type
126
+ @document_type ||= @options[:document_type] ||
127
+ __get_class_value(:document_type) ||
128
+ DEFAULT_DOC_TYPE
129
+ end
40
130
 
41
- include GatewayDelegation
42
- end
131
+ # Get the index name used by the repository.
132
+ #
133
+ # @example
134
+ # repository.index_name
135
+ #
136
+ # @return [ String, Symbol ] The repository's index name.
137
+ #
138
+ # @since 6.0.0
139
+ def index_name
140
+ @index_name ||= @options[:index_name] ||
141
+ __get_class_value(:index_name) ||
142
+ DEFAULT_INDEX_NAME
143
+ end
43
144
 
44
- # Define the class level gateway
45
- #
46
- (class << base; self; end).class_eval do
47
- define_method :gateway do |&block|
48
- @gateway ||= gateway
49
- @gateway.instance_eval(&block) if block
50
- @gateway
145
+ # Get the class used by the repository when deserializing.
146
+ #
147
+ # @example
148
+ # repository.klass
149
+ #
150
+ # @return [ Class ] The repository's klass for deserializing.
151
+ #
152
+ # @since 6.0.0
153
+ def klass
154
+ @klass ||= @options[:klass] || __get_class_value(:klass)
155
+ end
156
+
157
+ # Get the index mapping. Optionally pass a block to define the mappings.
158
+ #
159
+ # @example
160
+ # repository.mapping
161
+ #
162
+ # @example Set the mappings with a block.
163
+ # repository.mapping dynamic: 'strict' do
164
+ # indexes :foo
165
+ # end
166
+ # end
167
+ #
168
+ # @note If mappings were set when the repository was created, a block passed to this
169
+ # method will not be evaluated.
170
+ #
171
+ # @return [ Elasticsearch::Model::Indexing::Mappings ] The index mappings.
172
+ #
173
+ # @since 6.0.0
174
+ def mapping(*args)
175
+ @memoized_mapping ||= @options[:mapping] || (begin
176
+ if _mapping = __get_class_value(:mapping)
177
+ _mapping.instance_variable_set(:@type, document_type)
178
+ _mapping
51
179
  end
180
+ end) || (super && @mapping)
181
+ end
182
+ alias :mappings :mapping
52
183
 
53
- include GatewayDelegation
54
- end
184
+ # Get the index settings.
185
+ #
186
+ # @example
187
+ # repository.settings
188
+ #
189
+ # @example Set the settings with a block.
190
+ # repository.settings number_of_shards: 1, number_of_replicas: 0 do
191
+ # mapping dynamic: 'strict' do
192
+ # indexes :foo do
193
+ # indexes :bar
194
+ # end
195
+ # end
196
+ # end
197
+ #
198
+ # @return [ Elasticsearch::Model::Indexing::Settings ] The index settings.
199
+ #
200
+ # @since 6.0.0
201
+ def settings(*args)
202
+ @memoized_settings ||= @options[:settings] || __get_class_value(:settings) || (super && @settings)
203
+ end
55
204
 
56
- # Catch repository methods (such as `serialize` and others) defined in the receiving class,
57
- # and overload the default definition in the gateway
58
- #
59
- def base.method_added(name)
60
- if :gateway != name && respond_to?(:gateway) && (gateway.public_methods - Object.public_methods).include?(name)
61
- gateway.define_singleton_method(name, self.new.method(name).to_proc)
62
- end
63
- end
205
+ # Determine whether the index with this repository's index name exists.
206
+ #
207
+ # @example
208
+ # repository.index_exists?
209
+ #
210
+ # @return [ true, false ] Whether the index exists.
211
+ #
212
+ # @since 6.0.0
213
+ def index_exists?(*args)
214
+ super
64
215
  end
65
216
 
66
- # Shortcut method to allow concise repository initialization
217
+ # Get the nicer formatted string for use in inspection.
67
218
  #
68
- # @example Create a new default repository
219
+ # @example Inspect the repository.
220
+ # repository.inspect
69
221
  #
70
- # repository = Elasticsearch::Persistence::Repository.new
222
+ # @return [ String ] The repository inspection.
71
223
  #
72
- def new(options={}, &block)
73
- Elasticsearch::Persistence::Repository::Class.new( {index: 'repository'}.merge(options), &block )
74
- end; module_function :new
224
+ # @since 6.0.0
225
+ def inspect
226
+ "#<#{self.class}:0x#{object_id} index_name=#{index_name} document_type=#{document_type} klass=#{klass}>"
227
+ end
228
+
229
+ private
230
+
231
+ def __get_class_value(_method_)
232
+ self.class.send(_method_) if self.class.respond_to?(_method_)
233
+ end
75
234
  end
76
235
  end
77
236
  end