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,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
|