elasticsearch-persistence-queryable 0.1.8
Sign up to get free protection for your applications and to get access to all the features.
- 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,31 @@
|
|
1
|
+
module Elasticsearch
|
2
|
+
module Persistence
|
3
|
+
module Repository
|
4
|
+
|
5
|
+
# Provide serialization and deserialization between Ruby objects and Elasticsearch documents
|
6
|
+
#
|
7
|
+
# Override these methods in your repository class to customize the logic.
|
8
|
+
#
|
9
|
+
module Serialize
|
10
|
+
|
11
|
+
# Serialize the object for storing it in Elasticsearch
|
12
|
+
#
|
13
|
+
# In the default implementation, call the `to_hash` method on the passed object.
|
14
|
+
#
|
15
|
+
def serialize(document)
|
16
|
+
document.to_hash
|
17
|
+
end
|
18
|
+
|
19
|
+
# Deserialize the document retrieved from Elasticsearch into a Ruby object
|
20
|
+
#
|
21
|
+
# Use the `klass` property, if defined, otherwise try to get the class from the document's `_type`.
|
22
|
+
#
|
23
|
+
def deserialize(document)
|
24
|
+
_klass = klass || __get_klass_from_type(document['_type'])
|
25
|
+
_klass.new document['_source'] || document['fields']
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
module Elasticsearch
|
2
|
+
module Persistence
|
3
|
+
module Repository
|
4
|
+
|
5
|
+
# Save and delete documents in Elasticsearch
|
6
|
+
#
|
7
|
+
module Store
|
8
|
+
|
9
|
+
# Store the serialized object in Elasticsearch
|
10
|
+
#
|
11
|
+
# @example
|
12
|
+
# repository.save(myobject)
|
13
|
+
# => {"_index"=>"...", "_type"=>"...", "_id"=>"...", "_version"=>1, "created"=>true}
|
14
|
+
#
|
15
|
+
# @return {Hash} The response from Elasticsearch
|
16
|
+
#
|
17
|
+
def save(document, options={})
|
18
|
+
serialized = serialize(document)
|
19
|
+
id = __get_id_from_document(serialized)
|
20
|
+
client.index( { index: index_name, id: id, body: serialized }.merge(options) )
|
21
|
+
end
|
22
|
+
|
23
|
+
# Update the serialized object in Elasticsearch with partial data or script
|
24
|
+
#
|
25
|
+
# @example Update the document with partial data
|
26
|
+
#
|
27
|
+
# repository.update id: 1, title: 'UPDATED', tags: []
|
28
|
+
# # => {"_index"=>"...", "_type"=>"...", "_id"=>"1", "_version"=>2}
|
29
|
+
#
|
30
|
+
# @example Update the document with a script
|
31
|
+
#
|
32
|
+
# repository.update 1, script: 'ctx._source.views += 1'
|
33
|
+
# # => {"_index"=>"...", "_type"=>"...", "_id"=>"1", "_version"=>3}
|
34
|
+
#
|
35
|
+
# @return {Hash} The response from Elasticsearch
|
36
|
+
#
|
37
|
+
def update(document, options={})
|
38
|
+
case
|
39
|
+
when document.is_a?(String) || document.is_a?(Integer)
|
40
|
+
id = document
|
41
|
+
when document.respond_to?(:to_hash)
|
42
|
+
serialized = document.to_hash
|
43
|
+
id = __extract_id_from_document(serialized)
|
44
|
+
else
|
45
|
+
raise ArgumentError, "Expected a document ID or a Hash-like object, #{document.class} given"
|
46
|
+
end
|
47
|
+
|
48
|
+
type = options.delete(:type) || \
|
49
|
+
(defined?(serialized) && serialized && serialized.delete(:type)) || \
|
50
|
+
document_type || \
|
51
|
+
__get_type_from_class(klass)
|
52
|
+
|
53
|
+
if defined?(serialized) && serialized
|
54
|
+
body = if serialized[:script]
|
55
|
+
serialized.select { |k, v| [:script, :params, :upsert].include? k }
|
56
|
+
else
|
57
|
+
{ doc: serialized }
|
58
|
+
end
|
59
|
+
else
|
60
|
+
body = {}
|
61
|
+
body.update( doc: options.delete(:doc)) if options[:doc]
|
62
|
+
body.update( script: options.delete(:script)) if options[:script]
|
63
|
+
body.update( params: options.delete(:params)) if options[:params]
|
64
|
+
body.update( upsert: options.delete(:upsert)) if options[:upsert]
|
65
|
+
end
|
66
|
+
|
67
|
+
client.update( { index: index_name, id: id, body: body }.merge(options) )
|
68
|
+
end
|
69
|
+
|
70
|
+
# Remove the serialized object or document with specified ID from Elasticsearch
|
71
|
+
#
|
72
|
+
# @example Remove the document with ID 1
|
73
|
+
#
|
74
|
+
# repository.delete(1)
|
75
|
+
# # => {"_index"=>"...", "_type"=>"...", "_id"=>"1", "_version"=>4}
|
76
|
+
#
|
77
|
+
# @return {Hash} The response from Elasticsearch
|
78
|
+
#
|
79
|
+
def delete(document, options={})
|
80
|
+
if document.is_a?(String) || document.is_a?(Integer)
|
81
|
+
id = document
|
82
|
+
type = document_type || __get_type_from_class(klass)
|
83
|
+
else
|
84
|
+
serialized = serialize(document)
|
85
|
+
id = __get_id_from_document(serialized)
|
86
|
+
type = document_type || __get_type_from_class(klass || document.class)
|
87
|
+
end
|
88
|
+
client.delete( { index: index_name, id: id }.merge(options) )
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module Elasticsearch
|
2
|
+
module Persistence
|
3
|
+
|
4
|
+
# Delegate methods to the repository (acting as a gateway)
|
5
|
+
#
|
6
|
+
module GatewayDelegation
|
7
|
+
def method_missing(method_name, *arguments, &block)
|
8
|
+
gateway.respond_to?(method_name) ? gateway.__send__(method_name, *arguments, &block) : super
|
9
|
+
end
|
10
|
+
|
11
|
+
def respond_to?(method_name, include_private=false)
|
12
|
+
gateway.respond_to?(method_name) || super
|
13
|
+
end
|
14
|
+
|
15
|
+
def respond_to_missing?(method_name, *)
|
16
|
+
gateway.respond_to?(method_name) || super
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
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
|
33
|
+
|
34
|
+
# Define the instance level gateway
|
35
|
+
#
|
36
|
+
base.class_eval do
|
37
|
+
define_method :gateway do
|
38
|
+
@gateway ||= gateway
|
39
|
+
end
|
40
|
+
|
41
|
+
include GatewayDelegation
|
42
|
+
end
|
43
|
+
|
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
|
51
|
+
end
|
52
|
+
|
53
|
+
include GatewayDelegation
|
54
|
+
end
|
55
|
+
|
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
|
64
|
+
end
|
65
|
+
|
66
|
+
# Shortcut method to allow concise repository initialization
|
67
|
+
#
|
68
|
+
# @example Create a new default repository
|
69
|
+
#
|
70
|
+
# repository = Elasticsearch::Persistence::Repository.new
|
71
|
+
#
|
72
|
+
def new(options={}, &block)
|
73
|
+
Elasticsearch::Persistence::Repository::Class.new( {index: 'repository'}.merge(options), &block )
|
74
|
+
end; module_function :new
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
module Elasticsearch
|
2
|
+
module Persistence
|
3
|
+
module Scoping
|
4
|
+
module Default
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
# Stores the default scope for the class.
|
9
|
+
class_attribute :default_scopes, instance_writer: false, instance_predicate: false
|
10
|
+
|
11
|
+
self.default_scopes = []
|
12
|
+
end
|
13
|
+
|
14
|
+
module ClassMethods
|
15
|
+
# Returns a scope for the model without the previously set scopes.
|
16
|
+
#
|
17
|
+
# class Post < ActiveRecord::Base
|
18
|
+
# def self.default_scope
|
19
|
+
# where published: true
|
20
|
+
# end
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# Post.all # Fires "SELECT * FROM posts WHERE published = true"
|
24
|
+
# Post.unscoped.all # Fires "SELECT * FROM posts"
|
25
|
+
# Post.where(published: false).unscoped.all # Fires "SELECT * FROM posts"
|
26
|
+
#
|
27
|
+
# This method also accepts a block. All queries inside the block will
|
28
|
+
# not use the previously set scopes.
|
29
|
+
#
|
30
|
+
# Post.unscoped {
|
31
|
+
# Post.limit(10) # Fires "SELECT * FROM posts LIMIT 10"
|
32
|
+
# }
|
33
|
+
def unscoped
|
34
|
+
block_given? ? relation.scoping { yield } : relation
|
35
|
+
end
|
36
|
+
|
37
|
+
def before_remove_const #:nodoc:
|
38
|
+
self.current_scope = nil
|
39
|
+
end
|
40
|
+
|
41
|
+
protected
|
42
|
+
|
43
|
+
# Use this macro in your model to set a default scope for all operations on
|
44
|
+
# the model.
|
45
|
+
#
|
46
|
+
# class Article < ActiveRecord::Base
|
47
|
+
# default_scope { where(published: true) }
|
48
|
+
# end
|
49
|
+
#
|
50
|
+
# Article.all # => SELECT * FROM articles WHERE published = true
|
51
|
+
#
|
52
|
+
# The +default_scope+ is also applied while creating/building a record.
|
53
|
+
# It is not applied while updating a record.
|
54
|
+
#
|
55
|
+
# Article.new.published # => true
|
56
|
+
# Article.create.published # => true
|
57
|
+
#
|
58
|
+
# (You can also pass any object which responds to +call+ to the
|
59
|
+
# +default_scope+ macro, and it will be called when building the
|
60
|
+
# default scope.)
|
61
|
+
#
|
62
|
+
# If you use multiple +default_scope+ declarations in your model then
|
63
|
+
# they will be merged together:
|
64
|
+
#
|
65
|
+
# class Article < ActiveRecord::Base
|
66
|
+
# default_scope { where(published: true) }
|
67
|
+
# default_scope { where(rating: 'G') }
|
68
|
+
# end
|
69
|
+
#
|
70
|
+
# Article.all # => SELECT * FROM articles WHERE published = true AND rating = 'G'
|
71
|
+
#
|
72
|
+
# This is also the case with inheritance and module includes where the
|
73
|
+
# parent or module defines a +default_scope+ and the child or including
|
74
|
+
# class defines a second one.
|
75
|
+
#
|
76
|
+
# If you need to do more complex things with a default scope, you can
|
77
|
+
# alternatively define it as a class method:
|
78
|
+
#
|
79
|
+
# class Article < ActiveRecord::Base
|
80
|
+
# def self.default_scope
|
81
|
+
# # Should return a scope, you can call 'super' here etc.
|
82
|
+
# end
|
83
|
+
# end
|
84
|
+
def default_scope(scope = nil)
|
85
|
+
scope = Proc.new if block_given?
|
86
|
+
|
87
|
+
if scope.is_a?(Relation) || !scope.respond_to?(:call)
|
88
|
+
raise ArgumentError,
|
89
|
+
"Support for calling #default_scope without a block is removed. For example instead " \
|
90
|
+
"of `default_scope where(color: 'red')`, please use " \
|
91
|
+
"`default_scope { where(color: 'red') }`. (Alternatively you can just redefine " \
|
92
|
+
"self.default_scope.)"
|
93
|
+
end
|
94
|
+
|
95
|
+
self.default_scopes += [scope]
|
96
|
+
end
|
97
|
+
|
98
|
+
def build_default_scope(base_rel = relation) # :nodoc:
|
99
|
+
if !self.is_a?(method(:default_scope).owner)
|
100
|
+
# The user has defined their own default scope method, so call that
|
101
|
+
evaluate_default_scope { default_scope }
|
102
|
+
elsif default_scopes.any?
|
103
|
+
evaluate_default_scope do
|
104
|
+
default_scopes.inject(base_rel) do |default_scope, scope|
|
105
|
+
default_scope.merge(base_rel.scoping { scope.call })
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def ignore_default_scope? # :nodoc:
|
112
|
+
ScopeRegistry.value_for(:ignore_default_scope, self)
|
113
|
+
end
|
114
|
+
|
115
|
+
def ignore_default_scope=(ignore) # :nodoc:
|
116
|
+
ScopeRegistry.set_value_for(:ignore_default_scope, self, ignore)
|
117
|
+
end
|
118
|
+
|
119
|
+
# The ignore_default_scope flag is used to prevent an infinite recursion
|
120
|
+
# situation where a default scope references a scope which has a default
|
121
|
+
# scope which references a scope...
|
122
|
+
def evaluate_default_scope # :nodoc:
|
123
|
+
return if ignore_default_scope?
|
124
|
+
|
125
|
+
begin
|
126
|
+
self.ignore_default_scope = true
|
127
|
+
yield
|
128
|
+
ensure
|
129
|
+
self.ignore_default_scope = false
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module Elasticsearch
|
2
|
+
module Persistence
|
3
|
+
module Scoping
|
4
|
+
module Named
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
def all(options={})
|
9
|
+
if current_scope
|
10
|
+
current_scope.clone
|
11
|
+
else
|
12
|
+
default_scoped.size(10000)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def default_scoped # :nodoc:
|
17
|
+
relation.merge(build_default_scope)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Collects attributes from scopes that should be applied when creating
|
21
|
+
# an AR instance for the particular class this is called on.
|
22
|
+
def scope_attributes # :nodoc:
|
23
|
+
all.scope_for_create
|
24
|
+
end
|
25
|
+
|
26
|
+
# Are there default attributes associated with this scope?
|
27
|
+
def scope_attributes? # :nodoc:
|
28
|
+
current_scope || default_scopes.any?
|
29
|
+
end
|
30
|
+
|
31
|
+
def scope(name, body, &block)
|
32
|
+
if dangerous_class_method?(name)
|
33
|
+
raise ArgumentError, "You tried to define a scope named \"#{name}\" " \
|
34
|
+
"on the model \"#{self.name}\", but Active Record already defined " \
|
35
|
+
"a class method with the same name."
|
36
|
+
end
|
37
|
+
|
38
|
+
extension = Module.new(&block) if block
|
39
|
+
|
40
|
+
singleton_class.send(:define_method, name) do |*args|
|
41
|
+
scope = all.scoping { body.call(*args) }
|
42
|
+
scope = scope.extending(extension) if extension
|
43
|
+
|
44
|
+
scope || all
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
BLACKLISTED_CLASS_METHODS = %w(private public protected allocate new name parent superclass)
|
49
|
+
|
50
|
+
private
|
51
|
+
def dangerous_class_method?(method_name)
|
52
|
+
BLACKLISTED_CLASS_METHODS.include?(method_name.to_s) || class_method_defined_within?(method_name, Model, self.superclass)
|
53
|
+
end
|
54
|
+
|
55
|
+
def class_method_defined_within?(name, klass, superklass = klass.superclass) # :nodoc
|
56
|
+
if klass.respond_to?(name, true)
|
57
|
+
if superklass.respond_to?(name, true)
|
58
|
+
klass.method(name).owner != superklass.method(name).owner
|
59
|
+
else
|
60
|
+
true
|
61
|
+
end
|
62
|
+
else
|
63
|
+
false
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Elasticsearch
|
2
|
+
module Persistence
|
3
|
+
module Scoping
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
include Default
|
8
|
+
include Named
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
def current_scope
|
13
|
+
ScopeRegistry.value_for(:current_scope, base_class.to_s)
|
14
|
+
end
|
15
|
+
|
16
|
+
def current_scope=(scope) #:nodoc:
|
17
|
+
ScopeRegistry.set_value_for(:current_scope, base_class.to_s, scope)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class ScopeRegistry # :nodoc:
|
22
|
+
extend ActiveSupport::PerThreadRegistry
|
23
|
+
|
24
|
+
VALID_SCOPE_TYPES = [:current_scope, :ignore_default_scope]
|
25
|
+
|
26
|
+
def initialize
|
27
|
+
@registry = Hash.new { |hash, key| hash[key] = {} }
|
28
|
+
end
|
29
|
+
|
30
|
+
# Obtains the value for a given +scope_name+ and +variable_name+.
|
31
|
+
def value_for(scope_type, variable_name)
|
32
|
+
raise_invalid_scope_type!(scope_type)
|
33
|
+
@registry[scope_type][variable_name]
|
34
|
+
end
|
35
|
+
|
36
|
+
# Sets the +value+ for a given +scope_type+ and +variable_name+.
|
37
|
+
def set_value_for(scope_type, variable_name, value)
|
38
|
+
raise_invalid_scope_type!(scope_type)
|
39
|
+
@registry[scope_type][variable_name] = value
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def raise_invalid_scope_type!(scope_type)
|
45
|
+
if !VALID_SCOPE_TYPES.include?(scope_type)
|
46
|
+
raise ArgumentError, "Invalid scope type '#{scope_type}' sent to the registry. Scope types must be included in VALID_SCOPE_TYPES"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,157 @@
|
|
1
|
+
require 'elasticsearch'
|
2
|
+
require 'elasticsearch/model/indexing'
|
3
|
+
require 'hashie'
|
4
|
+
require 'jbuilder'
|
5
|
+
|
6
|
+
require 'rails/instrumentation/railtie' if defined?(Rails)
|
7
|
+
|
8
|
+
require 'elasticsearch/rails_compatibility'
|
9
|
+
|
10
|
+
require 'elasticsearch/persistence/version'
|
11
|
+
|
12
|
+
require 'elasticsearch/persistence/client'
|
13
|
+
require 'elasticsearch/persistence/scoping'
|
14
|
+
require 'elasticsearch/persistence/scoping/default'
|
15
|
+
require 'elasticsearch/persistence/scoping/named'
|
16
|
+
require 'elasticsearch/persistence/inheritence'
|
17
|
+
require 'elasticsearch/persistence/querying'
|
18
|
+
require 'elasticsearch/persistence/query_cache'
|
19
|
+
|
20
|
+
require 'elasticsearch/persistence/repository/response/results'
|
21
|
+
require 'elasticsearch/persistence/repository/naming'
|
22
|
+
require 'elasticsearch/persistence/repository/serialize'
|
23
|
+
require 'elasticsearch/persistence/repository/store'
|
24
|
+
require 'elasticsearch/persistence/repository/find'
|
25
|
+
require 'elasticsearch/persistence/repository/search'
|
26
|
+
require 'elasticsearch/persistence/repository/class'
|
27
|
+
require 'elasticsearch/persistence/repository'
|
28
|
+
|
29
|
+
require 'elasticsearch/persistence/relation/finder_methods'
|
30
|
+
require 'elasticsearch/persistence/relation/query_methods'
|
31
|
+
require 'elasticsearch/persistence/relation/search_option_methods'
|
32
|
+
require 'elasticsearch/persistence/relation/spawn_methods'
|
33
|
+
require 'elasticsearch/persistence/relation/delegation'
|
34
|
+
require 'elasticsearch/persistence/relation/merger'
|
35
|
+
|
36
|
+
require 'elasticsearch/persistence/relation'
|
37
|
+
require 'elasticsearch/persistence/null_relation'
|
38
|
+
|
39
|
+
|
40
|
+
module Elasticsearch
|
41
|
+
# Persistence for Ruby domain objects and models in Elasticsearch
|
42
|
+
# ===============================================================
|
43
|
+
#
|
44
|
+
# `Elasticsearch::Persistence` contains modules for storing and retrieving Ruby domain objects and models
|
45
|
+
# in Elasticsearch.
|
46
|
+
#
|
47
|
+
# == Repository
|
48
|
+
#
|
49
|
+
# The repository patterns allows to store and retrieve Ruby objects in Elasticsearch.
|
50
|
+
#
|
51
|
+
# require 'elasticsearch/persistence'
|
52
|
+
#
|
53
|
+
# class Note
|
54
|
+
# def to_hash; {foo: 'bar'}; end
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
# repository = Elasticsearch::Persistence::Repository.new
|
58
|
+
#
|
59
|
+
# repository.save Note.new
|
60
|
+
# # => {"_index"=>"repository", "_type"=>"note", "_id"=>"mY108X9mSHajxIy2rzH2CA", ...}
|
61
|
+
#
|
62
|
+
# Customize your repository by including the main module in a Ruby class
|
63
|
+
# class MyRepository
|
64
|
+
# include Elasticsearch::Persistence::Repository
|
65
|
+
#
|
66
|
+
# index 'my_notes'
|
67
|
+
# klass Note
|
68
|
+
#
|
69
|
+
# client Elasticsearch::Client.new log: true
|
70
|
+
# end
|
71
|
+
#
|
72
|
+
# repository = MyRepository.new
|
73
|
+
#
|
74
|
+
# repository.save Note.new
|
75
|
+
# # 2014-04-04 22:15:25 +0200: POST http://localhost:9200/my_notes/note [status:201, request:0.009s, query:n/a]
|
76
|
+
# # 2014-04-04 22:15:25 +0200: > {"foo":"bar"}
|
77
|
+
# # 2014-04-04 22:15:25 +0200: < {"_index":"my_notes","_type":"note","_id":"-d28yXLFSlusnTxb13WIZQ", ...}
|
78
|
+
#
|
79
|
+
# == Model
|
80
|
+
#
|
81
|
+
# The active record pattern allows to use the interface familiar from ActiveRecord models:
|
82
|
+
#
|
83
|
+
# require 'elasticsearch/persistence'
|
84
|
+
#
|
85
|
+
# class Article
|
86
|
+
# attribute :title, String, mapping: { analyzer: 'snowball' }
|
87
|
+
# end
|
88
|
+
#
|
89
|
+
# article = Article.new id: 1, title: 'Test'
|
90
|
+
# article.save
|
91
|
+
#
|
92
|
+
# Article.find(1)
|
93
|
+
#
|
94
|
+
# article.update_attributes title: 'Update'
|
95
|
+
#
|
96
|
+
# article.destroy
|
97
|
+
#
|
98
|
+
module Persistence
|
99
|
+
extend ActiveSupport::Autoload
|
100
|
+
|
101
|
+
eager_autoload do
|
102
|
+
autoload :Client
|
103
|
+
autoload :Model
|
104
|
+
autoload :Repository
|
105
|
+
autoload :Scoping
|
106
|
+
autoload :Relation
|
107
|
+
autoload :NullRelation
|
108
|
+
autoload :Querying
|
109
|
+
autoload :Inheritence
|
110
|
+
autoload :QueryCache
|
111
|
+
|
112
|
+
autoload_under 'relation' do
|
113
|
+
autoload :QueryMethods
|
114
|
+
autoload :QueryBuilder
|
115
|
+
autoload :SearchOptionMethods
|
116
|
+
autoload :FinderMethods
|
117
|
+
autoload :SpawnMethods
|
118
|
+
autoload :Delegation
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
module Model
|
123
|
+
extend ActiveSupport::Autoload
|
124
|
+
|
125
|
+
autoload :GatewayDelegation
|
126
|
+
#autoload :Callbacks
|
127
|
+
end
|
128
|
+
|
129
|
+
module Repository
|
130
|
+
extend ActiveSupport::Autoload
|
131
|
+
autoload :Class
|
132
|
+
autoload :Find
|
133
|
+
autoload :Search
|
134
|
+
autoload :Serialize
|
135
|
+
autoload :Store
|
136
|
+
autoload :Naming
|
137
|
+
end
|
138
|
+
|
139
|
+
module Scoping
|
140
|
+
extend ActiveSupport::Autoload
|
141
|
+
|
142
|
+
eager_autoload do
|
143
|
+
autoload :Named
|
144
|
+
autoload :Default
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def self.eager_load!
|
149
|
+
super
|
150
|
+
Elasticsearch::Persistence::Scoping.eager_load!
|
151
|
+
end
|
152
|
+
|
153
|
+
extend Client::ClassMethods
|
154
|
+
extend QueryCache::CacheMethods
|
155
|
+
|
156
|
+
end
|
157
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
|
3
|
+
if defined?(Rails)
|
4
|
+
if Rails::VERSION::MAJOR >= 4
|
5
|
+
require 'active_support'
|
6
|
+
require 'active_support/rails'
|
7
|
+
require 'active_support/per_thread_registry'
|
8
|
+
elsif Rails::VERSION::MAJOR == 3
|
9
|
+
require 'active_support/dependencies/autoload'
|
10
|
+
require 'active_support/concern'
|
11
|
+
require 'active_support/core_ext/class/attribute'
|
12
|
+
require 'active_support/core_ext/module/delegation'
|
13
|
+
require 'active_support/deprecation'
|
14
|
+
require 'active_support/inflector'
|
15
|
+
require 'elasticsearch/per_thread_registry'
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require "rails/generators/elasticsearch_generator"
|
2
|
+
|
3
|
+
module Elasticsearch
|
4
|
+
module Generators
|
5
|
+
class ModelGenerator < ::Rails::Generators::NamedBase
|
6
|
+
source_root File.expand_path('../templates', __FILE__)
|
7
|
+
|
8
|
+
desc "Creates an Elasticsearch::Persistence model"
|
9
|
+
argument :attributes, type: :array, default: [], banner: "attribute:type attribute:type"
|
10
|
+
|
11
|
+
check_class_collision
|
12
|
+
|
13
|
+
def create_model_file
|
14
|
+
@padding = attributes.map { |a| a.name.size }.max
|
15
|
+
template "model.rb.tt", File.join("app/models", class_path, "#{file_name}.rb")
|
16
|
+
end
|
17
|
+
|
18
|
+
hook_for :test_framework
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|