elasticsearch-persistence-queryable 0.1.8

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 (99) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/CHANGELOG.md +16 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE.txt +13 -0
  6. data/README.md +678 -0
  7. data/Rakefile +57 -0
  8. data/elasticsearch-persistence.gemspec +57 -0
  9. data/examples/music/album.rb +34 -0
  10. data/examples/music/artist.rb +50 -0
  11. data/examples/music/artists/_form.html.erb +8 -0
  12. data/examples/music/artists/artists_controller.rb +67 -0
  13. data/examples/music/artists/artists_controller_test.rb +53 -0
  14. data/examples/music/artists/index.html.erb +57 -0
  15. data/examples/music/artists/show.html.erb +51 -0
  16. data/examples/music/assets/application.css +226 -0
  17. data/examples/music/assets/autocomplete.css +48 -0
  18. data/examples/music/assets/blank_cover.png +0 -0
  19. data/examples/music/assets/form.css +113 -0
  20. data/examples/music/index_manager.rb +60 -0
  21. data/examples/music/search/index.html.erb +93 -0
  22. data/examples/music/search/search_controller.rb +41 -0
  23. data/examples/music/search/search_controller_test.rb +9 -0
  24. data/examples/music/search/search_helper.rb +15 -0
  25. data/examples/music/suggester.rb +45 -0
  26. data/examples/music/template.rb +392 -0
  27. data/examples/music/vendor/assets/jquery-ui-1.10.4.custom.min.css +7 -0
  28. data/examples/music/vendor/assets/jquery-ui-1.10.4.custom.min.js +6 -0
  29. data/examples/notes/.gitignore +7 -0
  30. data/examples/notes/Gemfile +28 -0
  31. data/examples/notes/README.markdown +36 -0
  32. data/examples/notes/application.rb +238 -0
  33. data/examples/notes/config.ru +7 -0
  34. data/examples/notes/test.rb +118 -0
  35. data/lib/elasticsearch/per_thread_registry.rb +53 -0
  36. data/lib/elasticsearch/persistence/client.rb +51 -0
  37. data/lib/elasticsearch/persistence/inheritence.rb +9 -0
  38. data/lib/elasticsearch/persistence/model/base.rb +95 -0
  39. data/lib/elasticsearch/persistence/model/callbacks.rb +37 -0
  40. data/lib/elasticsearch/persistence/model/errors.rb +9 -0
  41. data/lib/elasticsearch/persistence/model/find.rb +155 -0
  42. data/lib/elasticsearch/persistence/model/gateway_delegation.rb +23 -0
  43. data/lib/elasticsearch/persistence/model/hash_wrapper.rb +17 -0
  44. data/lib/elasticsearch/persistence/model/rails.rb +39 -0
  45. data/lib/elasticsearch/persistence/model/store.rb +271 -0
  46. data/lib/elasticsearch/persistence/model.rb +148 -0
  47. data/lib/elasticsearch/persistence/null_relation.rb +56 -0
  48. data/lib/elasticsearch/persistence/query_cache.rb +68 -0
  49. data/lib/elasticsearch/persistence/querying.rb +21 -0
  50. data/lib/elasticsearch/persistence/relation/delegation.rb +130 -0
  51. data/lib/elasticsearch/persistence/relation/finder_methods.rb +39 -0
  52. data/lib/elasticsearch/persistence/relation/merger.rb +179 -0
  53. data/lib/elasticsearch/persistence/relation/query_builder.rb +279 -0
  54. data/lib/elasticsearch/persistence/relation/query_methods.rb +362 -0
  55. data/lib/elasticsearch/persistence/relation/search_option_methods.rb +44 -0
  56. data/lib/elasticsearch/persistence/relation/spawn_methods.rb +61 -0
  57. data/lib/elasticsearch/persistence/relation.rb +110 -0
  58. data/lib/elasticsearch/persistence/repository/class.rb +71 -0
  59. data/lib/elasticsearch/persistence/repository/find.rb +73 -0
  60. data/lib/elasticsearch/persistence/repository/naming.rb +115 -0
  61. data/lib/elasticsearch/persistence/repository/response/results.rb +105 -0
  62. data/lib/elasticsearch/persistence/repository/search.rb +156 -0
  63. data/lib/elasticsearch/persistence/repository/serialize.rb +31 -0
  64. data/lib/elasticsearch/persistence/repository/store.rb +94 -0
  65. data/lib/elasticsearch/persistence/repository.rb +77 -0
  66. data/lib/elasticsearch/persistence/scoping/default.rb +137 -0
  67. data/lib/elasticsearch/persistence/scoping/named.rb +70 -0
  68. data/lib/elasticsearch/persistence/scoping.rb +52 -0
  69. data/lib/elasticsearch/persistence/version.rb +5 -0
  70. data/lib/elasticsearch/persistence.rb +157 -0
  71. data/lib/elasticsearch/rails_compatibility.rb +17 -0
  72. data/lib/rails/generators/elasticsearch/model/model_generator.rb +21 -0
  73. data/lib/rails/generators/elasticsearch/model/templates/model.rb.tt +9 -0
  74. data/lib/rails/generators/elasticsearch_generator.rb +2 -0
  75. data/lib/rails/instrumentation/railtie.rb +31 -0
  76. data/lib/rails/instrumentation.rb +10 -0
  77. data/test/integration/model/model_basic_test.rb +157 -0
  78. data/test/integration/repository/custom_class_test.rb +85 -0
  79. data/test/integration/repository/customized_class_test.rb +82 -0
  80. data/test/integration/repository/default_class_test.rb +114 -0
  81. data/test/integration/repository/virtus_model_test.rb +114 -0
  82. data/test/test_helper.rb +53 -0
  83. data/test/unit/model_base_test.rb +48 -0
  84. data/test/unit/model_find_test.rb +148 -0
  85. data/test/unit/model_gateway_test.rb +99 -0
  86. data/test/unit/model_rails_test.rb +88 -0
  87. data/test/unit/model_store_test.rb +514 -0
  88. data/test/unit/persistence_test.rb +32 -0
  89. data/test/unit/repository_class_test.rb +51 -0
  90. data/test/unit/repository_client_test.rb +32 -0
  91. data/test/unit/repository_find_test.rb +388 -0
  92. data/test/unit/repository_indexing_test.rb +37 -0
  93. data/test/unit/repository_module_test.rb +146 -0
  94. data/test/unit/repository_naming_test.rb +146 -0
  95. data/test/unit/repository_response_results_test.rb +98 -0
  96. data/test/unit/repository_search_test.rb +117 -0
  97. data/test/unit/repository_serialize_test.rb +57 -0
  98. data/test/unit/repository_store_test.rb +303 -0
  99. metadata +487 -0
@@ -0,0 +1,238 @@
1
+ $LOAD_PATH.unshift File.expand_path('../../../lib/', __FILE__)
2
+
3
+ require 'sinatra/base'
4
+
5
+ require 'multi_json'
6
+ require 'oj'
7
+ require 'hashie/mash'
8
+
9
+ require 'elasticsearch'
10
+ require 'elasticsearch/model'
11
+ require 'elasticsearch/persistence'
12
+
13
+ class Note
14
+ attr_reader :attributes
15
+
16
+ def initialize(attributes={})
17
+ @attributes = Hashie::Mash.new(attributes)
18
+ __add_date
19
+ __extract_tags
20
+ __truncate_text
21
+ self
22
+ end
23
+
24
+ def method_missing(method_name, *arguments, &block)
25
+ attributes.respond_to?(method_name) ? attributes.__send__(method_name, *arguments, &block) : super
26
+ end
27
+
28
+ def respond_to?(method_name, include_private=false)
29
+ attributes.respond_to?(method_name) || super
30
+ end
31
+
32
+ def tags; attributes.tags || []; end
33
+
34
+ def to_hash
35
+ @attributes.to_hash
36
+ end
37
+
38
+ def __extract_tags
39
+ tags = attributes['text'].scan(/(\[\w+\])/).flatten if attributes['text']
40
+ unless tags.nil? || tags.empty?
41
+ attributes.update 'tags' => tags.map { |t| t.tr('[]', '') }
42
+ attributes['text'].gsub!(/(\[\w+\])/, '').strip!
43
+ end
44
+ end
45
+
46
+ def __add_date
47
+ attributes['created_at'] ||= Time.now.utc.iso8601
48
+ end
49
+
50
+ def __truncate_text
51
+ attributes['text'] = attributes['text'][0...80] + ' (...)' if attributes['text'] && attributes['text'].size > 80
52
+ end
53
+ end
54
+
55
+ class NoteRepository
56
+ include Elasticsearch::Persistence::Repository
57
+
58
+ client Elasticsearch::Client.new url: ENV['ELASTICSEARCH_URL'], log: true
59
+
60
+ index :notes
61
+ type :note
62
+
63
+ mapping do
64
+ indexes :text, analyzer: 'snowball'
65
+ indexes :tags, analyzer: 'keyword'
66
+ indexes :created_at, type: 'date'
67
+ end
68
+
69
+ create_index!
70
+
71
+ def deserialize(document)
72
+ Note.new document['_source'].merge('id' => document['_id'])
73
+ end
74
+ end unless defined?(NoteRepository)
75
+
76
+ class Application < Sinatra::Base
77
+ enable :logging
78
+ enable :inline_templates
79
+ enable :method_override
80
+
81
+ configure :development do
82
+ enable :dump_errors
83
+ disable :show_exceptions
84
+
85
+ require 'sinatra/reloader'
86
+ register Sinatra::Reloader
87
+ end
88
+
89
+ set :repository, NoteRepository.new
90
+ set :per_page, 25
91
+
92
+ get '/' do
93
+ @page = [ params[:p].to_i, 1 ].max
94
+
95
+ @notes = settings.repository.search \
96
+ query: ->(q, t) do
97
+ query = if q && !q.empty?
98
+ { match: { text: q } }
99
+ else
100
+ { match_all: {} }
101
+ end
102
+
103
+ filter = if t && !t.empty?
104
+ { term: { tags: t } }
105
+ end
106
+
107
+ if filter
108
+ { filtered: { query: query, filter: filter } }
109
+ else
110
+ query
111
+ end
112
+ end.(params[:q], params[:t]),
113
+
114
+ sort: [{created_at: {order: 'desc'}}],
115
+
116
+ size: settings.per_page,
117
+ from: settings.per_page * (@page-1),
118
+
119
+ aggregations: { tags: { terms: { field: 'tags' } } },
120
+
121
+ highlight: { fields: { text: { fragment_size: 0, pre_tags: ['<em class="hl">'],post_tags: ['</em>'] } } }
122
+
123
+ erb :index
124
+ end
125
+
126
+ post '/' do
127
+ unless params[:text].empty?
128
+ @note = Note.new params
129
+ settings.repository.save(@note, refresh: true)
130
+ end
131
+
132
+ redirect back
133
+ end
134
+
135
+ delete '/:id' do |id|
136
+ settings.repository.delete(id, refresh: true)
137
+ redirect back
138
+ end
139
+ end
140
+
141
+ Application.run! if $0 == __FILE__
142
+
143
+ __END__
144
+
145
+ @@ layout
146
+ <!DOCTYPE html>
147
+ <html>
148
+ <head>
149
+ <title>Notes</title>
150
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
151
+ <style>
152
+ body { color: #222; background: #fff; font: normal 80%/120% 'Helvetica Neue', sans-serif; margin: 4em; position: relative; }
153
+ header { color: #666; border-bottom: 2px solid #666; }
154
+ header:after { display: table; content: ""; line-height: 0; clear: both; }
155
+ #left { width: 20em; float: left }
156
+ #main { margin-left: 20em; }
157
+ header h1 { font-weight: normal; float: left; padding: 0.4em 0 0 0; margin: 0; }
158
+ header form { margin-left: 19.5em; }
159
+ header form input { font-size: 120%; width: 40em; border: none; padding: 0.5em; position: relative; bottom: -0.2em; background: transparent; }
160
+ header form input:focus { outline-width: 0; }
161
+
162
+ #left h2 { color: #999; font-size: 160%; font-weight: normal; text-transform: uppercase; letter-spacing: -0.05em; }
163
+ #left h2 { border-top: 2px solid #999; width: 9.4em; padding: 0.5em 0 0.5em 0; margin: 0; }
164
+ #left textarea { font: normal 140%/140% monospace; border: 1px solid #999; padding: 0.5em; width: 12em; }
165
+ #left form p { margin: 0; }
166
+ #left a { color: #000; }
167
+ #left small.c { color: #333; background: #ccc; text-align: center; min-width: 1.75em; min-height: 1.5em; border-radius: 1em; display: inline-block; padding-top: 0.25em; float: right; margin-right: 6em; }
168
+ #left small.i { color: #ccc; background: #333; }
169
+
170
+ #facets { list-style-type: none; padding: 0; margin: 0 0 1em 0; }
171
+ #facets li { padding: 0 0 0.5em 0; }
172
+
173
+ .note { border-bottom: 1px solid #999; position: relative; padding: 0.5em 0; }
174
+ .note p { font-size: 140%; }
175
+ .note small { font-size: 70%; color: #999; }
176
+ .note small.d { border-left: 1px solid #999; padding-left: 0.5em; margin-left: 0.5em; }
177
+ .note em.hl { background: #fcfcad; border-radius: 0.5em; padding: 0.2em 0.4em 0.2em 0.4em; }
178
+ .note strong.t { color: #fff; background: #999; font-size: 70%; font-weight: bold; border-radius: 0.6em; padding: 0.2em 0.6em 0.3em 0.7em; }
179
+ .note form { position: absolute; bottom: 1.5em; right: 1em; }
180
+
181
+ .pagination { color: #000; font-weight: bold; text-align: right; }
182
+ .pagination:visited { color: #000; }
183
+ .pagination a { text-decoration: none; }
184
+ .pagination:hover a { text-decoration: underline; }
185
+ }
186
+
187
+ </style>
188
+ </head>
189
+ <body>
190
+ <%= yield %>
191
+ </body>
192
+ </html>
193
+
194
+ @@ index
195
+
196
+ <header>
197
+ <h1>Notes</h1>
198
+ <form action="/" method='get'>
199
+ <input type="text" name="q" value="<%= params[:q] %>" id="q" autofocus="autofocus" placeholder="type a search query and press enter..." />
200
+ </form>
201
+ </header>
202
+
203
+ <section id="left">
204
+ <p><a href="/">All notes</a> <small class="c i"><%= @notes.size %></small></p>
205
+ <ul id="facets">
206
+ <% @notes.response.aggregations.tags.buckets.each do |term| %>
207
+ <li><a href="/?t=<%= term['key'] %>"><%= term['key'] %></a> <small class="c"><%= term['doc_count'] %></small></li>
208
+ <% end %>
209
+ </ul>
210
+ <h2>Add a note</h2>
211
+ <form action="/" method='post'>
212
+ <p><textarea name="text" rows="5"></textarea></p>
213
+ <p><input type="submit" accesskey="s" value="Save" /></p>
214
+ </form>
215
+ </section>
216
+
217
+ <section id="main">
218
+ <% if @notes.empty? %>
219
+ <p>No notes found.</p>
220
+ <% end %>
221
+
222
+ <% @notes.each_with_hit do |note, hit| %>
223
+ <div class="note">
224
+ <p>
225
+ <%= hit.highlight && hit.highlight.size > 0 ? hit.highlight.text.first : note.text %>
226
+
227
+ <% note.tags.each do |tag| %> <strong class="t"><%= tag %></strong><% end %>
228
+ <small class="d"><%= Time.parse(note.created_at).strftime('%d/%m/%Y %H:%M') %></small>
229
+
230
+ <form action="/<%= note.id %>" method="post"><input type="hidden" name="_method" value="delete" /><button>Delete</button></form>
231
+ </p>
232
+ </div>
233
+ <% end %>
234
+
235
+ <% if @notes.size > 0 && @page.next <= @notes.total / settings.per_page %>
236
+ <p class="pagination"><a href="?p=<%= @page.next %>">&rarr; Load next</a></p>
237
+ <% end %>
238
+ </section>
@@ -0,0 +1,7 @@
1
+ #\ --port 3000 --server thin
2
+
3
+ require File.expand_path('../application', __FILE__)
4
+
5
+ map '/' do
6
+ run Application
7
+ end
@@ -0,0 +1,118 @@
1
+ ENV['RACK_ENV'] = 'test'
2
+
3
+ at_exit { Elasticsearch::Test::IntegrationTestCase.__run_at_exit_hooks } if ENV['SERVER']
4
+
5
+ require 'test/unit'
6
+ require 'shoulda-context'
7
+ require 'mocha/setup'
8
+ require 'rack/test'
9
+ require 'turn'
10
+
11
+ require 'elasticsearch/extensions/test/cluster'
12
+ require 'elasticsearch/extensions/test/startup_shutdown'
13
+
14
+ require_relative 'application'
15
+
16
+ NoteRepository.index_name = 'notes_test'
17
+
18
+ class Elasticsearch::Persistence::ExampleApplicationTest < Test::Unit::TestCase
19
+ include Rack::Test::Methods
20
+ alias :response :last_response
21
+
22
+ def app
23
+ Application.new
24
+ end
25
+
26
+ context "Note" do
27
+ should "be initialized with a Hash" do
28
+ note = Note.new 'foo' => 'bar'
29
+ assert_equal 'bar', note.attributes['foo']
30
+ end
31
+
32
+ should "add created_at when it's not passed" do
33
+ note = Note.new
34
+ assert_not_nil note.created_at
35
+ assert_match /#{Time.now.year}/, note.created_at
36
+ end
37
+
38
+ should "not add created_at when it's passed" do
39
+ note = Note.new 'created_at' => 'FOO'
40
+ assert_equal 'FOO', note.created_at
41
+ end
42
+
43
+ should "trim long text" do
44
+ assert_equal 'Hello World', Note.new('text' => 'Hello World').text
45
+ assert_equal 'FOOFOOFOOFOOFOOFOOFOOFOOFOOFOOFOOFOOFOOFOOFOOFOOFOOFOOFOOFOOFOOFOOFOOFOOFOOFOOFO (...)',
46
+ Note.new('text' => 'FOO'*200).text
47
+ end
48
+
49
+ should "delegate methods to attributes" do
50
+ note = Note.new 'foo' => 'bar'
51
+ assert_equal 'bar', note.foo
52
+ end
53
+
54
+ should "have tags" do
55
+ assert_not_nil Note.new.tags
56
+ end
57
+
58
+ should "provide a `to_hash` method" do
59
+ note = Note.new 'foo' => 'bar'
60
+ assert_instance_of Hash, note.to_hash
61
+ assert_equal ['created_at', 'foo'], note.to_hash.keys.sort
62
+ end
63
+
64
+ should "extract tags from the text" do
65
+ note = Note.new 'text' => 'Hello [foo] [bar]'
66
+ assert_equal 'Hello', note.text
67
+ assert_equal ['foo', 'bar'], note.tags
68
+ end
69
+ end
70
+
71
+ context "Application" do
72
+ setup do
73
+ app.settings.repository.client = Elasticsearch::Client.new \
74
+ hosts: [{ host: 'localhost', port: ENV.fetch('TEST_CLUSTER_PORT', 9250)}],
75
+ log: true
76
+ app.settings.repository.client.transport.logger.formatter = proc { |s, d, p, m| "\e[2m#{m}\n\e[0m" }
77
+ app.settings.repository.create_index! force: true
78
+ app.settings.repository.client.cluster.health wait_for_status: 'yellow'
79
+ end
80
+
81
+ should "have the correct index name" do
82
+ assert_equal 'notes_test', app.settings.repository.index
83
+ end
84
+
85
+ should "display empty page when there are no notes" do
86
+ get '/'
87
+ assert response.ok?, response.status.to_s
88
+ assert_match /No notes found/, response.body.to_s
89
+ end
90
+
91
+ should "display the notes" do
92
+ app.settings.repository.save Note.new('text' => 'Hello')
93
+ app.settings.repository.refresh_index!
94
+
95
+ get '/'
96
+ assert response.ok?, response.status.to_s
97
+ assert_match /<p>\s*Hello/, response.body.to_s
98
+ end
99
+
100
+ should "create a note" do
101
+ post '/', { 'text' => 'Hello World' }
102
+ follow_redirect!
103
+
104
+ assert response.ok?, response.status.to_s
105
+ assert_match /Hello World/, response.body.to_s
106
+ end
107
+
108
+ should "delete a note" do
109
+ app.settings.repository.save Note.new('id' => 'foobar', 'text' => 'Perish...')
110
+ delete "/foobar"
111
+ follow_redirect!
112
+
113
+ assert response.ok?, response.status.to_s
114
+ assert_no_match /Perish/, response.body.to_s
115
+ end
116
+ end
117
+
118
+ end
@@ -0,0 +1,53 @@
1
+ module ActiveSupport
2
+ # This module is used to encapsulate access to thread local variables.
3
+ #
4
+ # Instead of polluting the thread locals namespace:
5
+ #
6
+ # Thread.current[:connection_handler]
7
+ #
8
+ # you define a class that extends this module:
9
+ #
10
+ # module ActiveRecord
11
+ # class RuntimeRegistry
12
+ # extend ActiveSupport::PerThreadRegistry
13
+ #
14
+ # attr_accessor :connection_handler
15
+ # end
16
+ # end
17
+ #
18
+ # and invoke the declared instance accessors as class methods. So
19
+ #
20
+ # ActiveRecord::RuntimeRegistry.connection_handler = connection_handler
21
+ #
22
+ # sets a connection handler local to the current thread, and
23
+ #
24
+ # ActiveRecord::RuntimeRegistry.connection_handler
25
+ #
26
+ # returns a connection handler local to the current thread.
27
+ #
28
+ # This feature is accomplished by instantiating the class and storing the
29
+ # instance as a thread local keyed by the class name. In the example above
30
+ # a key "ActiveRecord::RuntimeRegistry" is stored in <tt>Thread.current</tt>.
31
+ # The class methods proxy to said thread local instance.
32
+ #
33
+ # If the class has an initializer, it must accept no arguments.
34
+ module PerThreadRegistry
35
+ def self.extended(object)
36
+ object.instance_variable_set '@per_thread_registry_key', object.name.freeze
37
+ end
38
+
39
+ def instance
40
+ Thread.current[@per_thread_registry_key] ||= new
41
+ end
42
+
43
+ protected
44
+ def method_missing(name, *args, &block) # :nodoc:
45
+ # Caches the method definition as a singleton method of the receiver.
46
+ define_singleton_method(name) do |*a, &b|
47
+ instance.public_send(name, *a, &b)
48
+ end
49
+
50
+ send(name, *args, &block)
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,51 @@
1
+ module Elasticsearch
2
+ module Persistence
3
+
4
+ # Wraps the Elasticsearch Ruby
5
+ # [client](https://github.com/elasticsearch/elasticsearch-ruby/tree/master/elasticsearch#usage)
6
+ #
7
+ module Client
8
+ extend ActiveSupport::Concern
9
+
10
+ module ClassMethods
11
+ # Get or set the default client for this repository
12
+ #
13
+ # @example Set and configure the client for the repository class
14
+ #
15
+ # class MyRepository
16
+ # include Elasticsearch::Persistence::Repository
17
+ # client Elasticsearch::Client.new host: 'http://localhost:9200', log: true
18
+ # end
19
+ #
20
+ # @example Set and configure the client for this repository instance
21
+ #
22
+ # repository.client Elasticsearch::Client.new host: 'http://localhost:9200', tracer: true
23
+ #
24
+ # @example Perform an API request through the client
25
+ #
26
+ # MyRepository.client.cluster.health
27
+ # repository.client.cluster.health
28
+ # # => { "cluster_name" => "elasticsearch" ... }
29
+ #
30
+ def client client=nil
31
+ @client = client || @client || Elasticsearch::Persistence.client
32
+ end
33
+
34
+ # Set the default client for this repository
35
+ #
36
+ # @example Set and configure the client for the repository class
37
+ #
38
+ # MyRepository.client = Elasticsearch::Client.new host: 'http://localhost:9200', log: true
39
+ #
40
+ # @example Set and configure the client for this repository instance
41
+ #
42
+ # repository.client = Elasticsearch::Client.new host: 'http://localhost:9200', tracer: true
43
+ #
44
+ def client=(client)
45
+ @client = client
46
+ end
47
+ end
48
+ end
49
+
50
+ end
51
+ end
@@ -0,0 +1,9 @@
1
+ module Elasticsearch
2
+ module Persistence
3
+ module Inheritence
4
+ def base_class
5
+ self
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,95 @@
1
+ module Elasticsearch
2
+ module Persistence
3
+ module Model
4
+ # This module contains the base interface for models
5
+ #
6
+ module Base
7
+ module InstanceMethods
8
+
9
+ # Model initializer sets the `@id` variable if passed
10
+ #
11
+ def initialize(attributes = {})
12
+ @_id = attributes[:id] || attributes["id"]
13
+ super
14
+ end
15
+
16
+ # Return model attributes as a Hash, merging in the `id`
17
+ #
18
+ def attributes
19
+ super.merge id: id
20
+ end
21
+
22
+ # Return the document `_id`
23
+ #
24
+ def id
25
+ @_id
26
+ end
27
+
28
+ alias :_id :id
29
+
30
+ # Set the document `_id`
31
+ #
32
+ def id=(value)
33
+ @_id = value
34
+ end
35
+
36
+ alias :_id= :id=
37
+
38
+ # Return the document `_index`
39
+ #
40
+ def _index
41
+ @_index
42
+ end
43
+
44
+ # Return the document `_type`
45
+ #
46
+ def _type
47
+ @_type
48
+ end
49
+
50
+ # Return the document `_version`
51
+ #
52
+ def _version
53
+ @_version
54
+ end
55
+
56
+ def to_ary # :nodoc:
57
+ nil
58
+ end
59
+
60
+ def to_s
61
+ "#<#{self.class} #{attributes.to_hash.inspect.gsub(/:(\w+)=>/, '\1: ')}>"
62
+ end
63
+
64
+ alias :inspect :to_s
65
+ end
66
+ end
67
+
68
+ # Utility methods for {Elasticsearch::Persistence::Model}
69
+ #
70
+ module Utils
71
+
72
+ # Return Elasticsearch type based on passed Ruby class (used in the `attribute` method)
73
+ #
74
+ def lookup_type(type)
75
+ case
76
+ when type == :keyword
77
+ "keyword"
78
+ when type == String
79
+ "text"
80
+ when type == Integer
81
+ "integer"
82
+ when type == Float
83
+ "float"
84
+ when type == Date || type == Time || type == DateTime
85
+ "date"
86
+ when type == Virtus::Attribute::Boolean
87
+ "boolean"
88
+ end
89
+ end
90
+
91
+ module_function :lookup_type
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,37 @@
1
+ module Elasticsearch
2
+ module Persistence
3
+ module Model
4
+ module Callbacks
5
+
6
+ extend ActiveSupport::Concern
7
+
8
+
9
+ included do
10
+
11
+ instance_variable_set("@_circuit_breaker_callbacks", [])
12
+
13
+ end
14
+
15
+ class_methods do
16
+
17
+
18
+ def circuit_breaker_callbacks
19
+ instance_variable_get("@_circuit_breaker_callbacks") || []
20
+ end
21
+
22
+ def query_must_have(*args, &block)
23
+ options = args.extract_options!
24
+
25
+ cb = block_given? ? block : options[:validate_with]
26
+
27
+ options[:message] = "does not exist in #{options[:in]}." unless options.has_key? :message
28
+
29
+ instance_variable_get("@_circuit_breaker_callbacks") << {name: args.first, options: options, callback: cb}
30
+
31
+ end
32
+ end
33
+
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,9 @@
1
+ module Elasticsearch
2
+ module Persistence
3
+ module Model
4
+ class DocumentNotSaved < StandardError; end
5
+ class DocumentNotPersisted < StandardError; end
6
+ class QueryOptionMissing < StandardError; end
7
+ end
8
+ end
9
+ end