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.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/CHANGELOG.md +16 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +13 -0
- data/README.md +678 -0
- data/Rakefile +57 -0
- data/elasticsearch-persistence.gemspec +57 -0
- data/examples/music/album.rb +34 -0
- data/examples/music/artist.rb +50 -0
- data/examples/music/artists/_form.html.erb +8 -0
- data/examples/music/artists/artists_controller.rb +67 -0
- data/examples/music/artists/artists_controller_test.rb +53 -0
- data/examples/music/artists/index.html.erb +57 -0
- data/examples/music/artists/show.html.erb +51 -0
- data/examples/music/assets/application.css +226 -0
- data/examples/music/assets/autocomplete.css +48 -0
- data/examples/music/assets/blank_cover.png +0 -0
- data/examples/music/assets/form.css +113 -0
- data/examples/music/index_manager.rb +60 -0
- data/examples/music/search/index.html.erb +93 -0
- data/examples/music/search/search_controller.rb +41 -0
- data/examples/music/search/search_controller_test.rb +9 -0
- data/examples/music/search/search_helper.rb +15 -0
- data/examples/music/suggester.rb +45 -0
- data/examples/music/template.rb +392 -0
- data/examples/music/vendor/assets/jquery-ui-1.10.4.custom.min.css +7 -0
- data/examples/music/vendor/assets/jquery-ui-1.10.4.custom.min.js +6 -0
- data/examples/notes/.gitignore +7 -0
- data/examples/notes/Gemfile +28 -0
- data/examples/notes/README.markdown +36 -0
- data/examples/notes/application.rb +238 -0
- data/examples/notes/config.ru +7 -0
- data/examples/notes/test.rb +118 -0
- data/lib/elasticsearch/per_thread_registry.rb +53 -0
- data/lib/elasticsearch/persistence/client.rb +51 -0
- data/lib/elasticsearch/persistence/inheritence.rb +9 -0
- data/lib/elasticsearch/persistence/model/base.rb +95 -0
- data/lib/elasticsearch/persistence/model/callbacks.rb +37 -0
- data/lib/elasticsearch/persistence/model/errors.rb +9 -0
- data/lib/elasticsearch/persistence/model/find.rb +155 -0
- data/lib/elasticsearch/persistence/model/gateway_delegation.rb +23 -0
- data/lib/elasticsearch/persistence/model/hash_wrapper.rb +17 -0
- data/lib/elasticsearch/persistence/model/rails.rb +39 -0
- data/lib/elasticsearch/persistence/model/store.rb +271 -0
- data/lib/elasticsearch/persistence/model.rb +148 -0
- data/lib/elasticsearch/persistence/null_relation.rb +56 -0
- data/lib/elasticsearch/persistence/query_cache.rb +68 -0
- data/lib/elasticsearch/persistence/querying.rb +21 -0
- data/lib/elasticsearch/persistence/relation/delegation.rb +130 -0
- data/lib/elasticsearch/persistence/relation/finder_methods.rb +39 -0
- data/lib/elasticsearch/persistence/relation/merger.rb +179 -0
- data/lib/elasticsearch/persistence/relation/query_builder.rb +279 -0
- data/lib/elasticsearch/persistence/relation/query_methods.rb +362 -0
- data/lib/elasticsearch/persistence/relation/search_option_methods.rb +44 -0
- data/lib/elasticsearch/persistence/relation/spawn_methods.rb +61 -0
- data/lib/elasticsearch/persistence/relation.rb +110 -0
- data/lib/elasticsearch/persistence/repository/class.rb +71 -0
- data/lib/elasticsearch/persistence/repository/find.rb +73 -0
- data/lib/elasticsearch/persistence/repository/naming.rb +115 -0
- data/lib/elasticsearch/persistence/repository/response/results.rb +105 -0
- data/lib/elasticsearch/persistence/repository/search.rb +156 -0
- data/lib/elasticsearch/persistence/repository/serialize.rb +31 -0
- data/lib/elasticsearch/persistence/repository/store.rb +94 -0
- data/lib/elasticsearch/persistence/repository.rb +77 -0
- data/lib/elasticsearch/persistence/scoping/default.rb +137 -0
- data/lib/elasticsearch/persistence/scoping/named.rb +70 -0
- data/lib/elasticsearch/persistence/scoping.rb +52 -0
- data/lib/elasticsearch/persistence/version.rb +5 -0
- data/lib/elasticsearch/persistence.rb +157 -0
- data/lib/elasticsearch/rails_compatibility.rb +17 -0
- data/lib/rails/generators/elasticsearch/model/model_generator.rb +21 -0
- data/lib/rails/generators/elasticsearch/model/templates/model.rb.tt +9 -0
- data/lib/rails/generators/elasticsearch_generator.rb +2 -0
- data/lib/rails/instrumentation/railtie.rb +31 -0
- data/lib/rails/instrumentation.rb +10 -0
- data/test/integration/model/model_basic_test.rb +157 -0
- data/test/integration/repository/custom_class_test.rb +85 -0
- data/test/integration/repository/customized_class_test.rb +82 -0
- data/test/integration/repository/default_class_test.rb +114 -0
- data/test/integration/repository/virtus_model_test.rb +114 -0
- data/test/test_helper.rb +53 -0
- data/test/unit/model_base_test.rb +48 -0
- data/test/unit/model_find_test.rb +148 -0
- data/test/unit/model_gateway_test.rb +99 -0
- data/test/unit/model_rails_test.rb +88 -0
- data/test/unit/model_store_test.rb +514 -0
- data/test/unit/persistence_test.rb +32 -0
- data/test/unit/repository_class_test.rb +51 -0
- data/test/unit/repository_client_test.rb +32 -0
- data/test/unit/repository_find_test.rb +388 -0
- data/test/unit/repository_indexing_test.rb +37 -0
- data/test/unit/repository_module_test.rb +146 -0
- data/test/unit/repository_naming_test.rb +146 -0
- data/test/unit/repository_response_results_test.rb +98 -0
- data/test/unit/repository_search_test.rb +117 -0
- data/test/unit/repository_serialize_test.rb +57 -0
- data/test/unit/repository_store_test.rb +303 -0
- 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 %>">→ Load next</a></p>
|
237
|
+
<% end %>
|
238
|
+
</section>
|
@@ -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,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
|