elasticsearch-persistence-queryable 0.1.8

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