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.
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"