elasticsearch-persistence 5.0.2 → 6.1.1

Sign up to get free protection for your applications and to get access to all the features.
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