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,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,5 @@
1
+ module Elasticsearch
2
+ module Persistence
3
+ VERSION = "0.1.8"
4
+ end
5
+ 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
@@ -0,0 +1,9 @@
1
+ <% module_namespacing do -%>
2
+ class <%= class_name %>
3
+ include Elasticsearch::Persistence::Model
4
+
5
+ <% attributes.each do |attribute| -%>
6
+ <%= "attribute :#{attribute.name},".ljust(@padding+12) %> <%= attribute.type %>
7
+ <% end -%>
8
+ end
9
+ <% end -%>
@@ -0,0 +1,2 @@
1
+ require "rails/generators/named_base"
2
+ require "rails/generators/active_model"