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,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Elasticsearch
4
+ module Persistence
5
+ module NullRelation # :nodoc:
6
+ def pluck(*column_names)
7
+ []
8
+ end
9
+
10
+ def delete_all
11
+ 0
12
+ end
13
+
14
+ def update_all(_updates)
15
+ 0
16
+ end
17
+
18
+ def delete(_id_or_array)
19
+ 0
20
+ end
21
+
22
+ def empty?
23
+ true
24
+ end
25
+
26
+ def none?
27
+ true
28
+ end
29
+
30
+ def any?
31
+ false
32
+ end
33
+
34
+ def one?
35
+ false
36
+ end
37
+
38
+ def many?
39
+ false
40
+ end
41
+
42
+ def exists?(_conditions = :none)
43
+ false
44
+ end
45
+
46
+ def or(other)
47
+ other.spawn
48
+ end
49
+
50
+
51
+ def exec_queries
52
+ @records = OpenStruct.new(klass: Elasticsearch::Persistence::Repository::Class, total: 0, results: []).freeze
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,68 @@
1
+ require "active_support/core_ext/module"
2
+
3
+ module Elasticsearch
4
+ module Persistence
5
+ module QueryCache
6
+ module CacheMethods
7
+ mattr_accessor :force_cache
8
+ mattr_accessor :cache_store
9
+ mattr_accessor :cache_store_expire_in
10
+
11
+ @@cache_store_expire_in = 300
12
+
13
+ @@force_cache = false
14
+
15
+ def cache
16
+ Elasticsearch::Persistence.force_cache = true
17
+ lm = yield
18
+ Elasticsearch::Persistence.force_cache = false
19
+ lm
20
+ end
21
+
22
+ def setup_store!
23
+ case Elasticsearch::Persistence.cache_store
24
+ when :redis_store
25
+ ActiveSupport::Cache::RedisStore
26
+ when :memory_store
27
+ ActiveSupport::Cache::MemoryStore
28
+ else
29
+ ActiveSupport::Cache::MemoryStore
30
+ end.new(namespace: "elasticsearch", expires_in: Elasticsearch::Persistence.cache_store_expire_in)
31
+ end
32
+ end
33
+
34
+ def store
35
+ @query_cache ||= Elasticsearch::Persistence.setup_store!
36
+ end
37
+
38
+ def cache_query(query, klass)
39
+ cache_key = sha(query)
40
+ Elasticsearch::Persistence.force_cache
41
+ result = if store.exist?(cache_key) && Elasticsearch::Persistence.force_cache
42
+ ActiveSupport::Notifications.instrument "cache.query.elasticsearch",
43
+ name: klass.name,
44
+ query: query
45
+
46
+ store.fetch cache_key
47
+ else
48
+ res = []
49
+ ActiveSupport::Notifications.instrument "query.elasticsearch",
50
+ name: klass.name,
51
+ query: query do
52
+ res = yield
53
+ end
54
+
55
+ store.write(cache_key, res) if Elasticsearch::Persistence.force_cache
56
+ res
57
+ end
58
+ result.dup
59
+ end
60
+
61
+ private
62
+
63
+ def sha(str)
64
+ Digest::SHA256.new.hexdigest(str)
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,21 @@
1
+ module Elasticsearch
2
+ module Persistence
3
+ module Querying
4
+ delegate :first, :first!, :last, :last!, :exists?, :has_field?, :any?, :many?, to: :all
5
+ delegate :order, :limit, :size, :sort, :where, :rewhere, :eager_load, :includes, :create_with, :none, :unscope, to: :all
6
+ delegate :or_filter, :filter, :fields, :source, :highlight, :aggregation, to: :all
7
+ delegate :skip_callbacks, :routing, to: :all
8
+ delegate :search_options, :routing, to: :all
9
+ delegate :must, :must_not, :should, :where_not, :query_string, to: :all
10
+
11
+ def fetch_results(es)
12
+ unless es.count?
13
+ gateway.search(es.to_elastic, es.search_options)
14
+ else
15
+ gateway.count(es.to_elastic, es.search_options)
16
+ end
17
+ end
18
+
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,130 @@
1
+ module Elasticsearch
2
+ module Persistence
3
+ module Delegation # :nodoc:
4
+ module DelegateCache
5
+ def relation_delegate_class(klass) # :nodoc:
6
+ @relation_delegate_cache[klass]
7
+ end
8
+
9
+ def initialize_relation_delegate_cache # :nodoc:
10
+ @relation_delegate_cache = cache = {}
11
+ [
12
+ Elasticsearch::Persistence::Relation,
13
+ ].each do |klass|
14
+ delegate = Class.new(klass) {
15
+ include ClassSpecificRelation
16
+ }
17
+ const_set klass.name.gsub("::", "_"), delegate
18
+ cache[klass] = delegate
19
+ end
20
+ end
21
+
22
+ def self.extended(child_class)
23
+ child_class.initialize_relation_delegate_cache
24
+ super
25
+ end
26
+ end
27
+
28
+ extend ActiveSupport::Concern
29
+
30
+ # This module creates compiled delegation methods dynamically at runtime, which makes
31
+ # subsequent calls to that method faster by avoiding method_missing. The delegations
32
+ # may vary depending on the klass of a relation, so we create a subclass of Relation
33
+ # for each different klass, and the delegations are compiled into that subclass only.
34
+
35
+ BLACKLISTED_ARRAY_METHODS = [
36
+ :compact!, :flatten!, :reject!, :reverse!, :rotate!, :map!,
37
+ :shuffle!, :slice!, :sort!, :sort_by!, :delete_if,
38
+ :keep_if, :pop, :shift, :delete_at, :compact, :select!,
39
+ ].to_set # :nodoc:
40
+
41
+ delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to_ary, :join, to: :to_a
42
+ delegate :inner_hits, :aggregations, :highlights, :total, to: :to_a
43
+
44
+ delegate :mapping, :index_name, :document_type, :to => :klass
45
+
46
+ module ClassSpecificRelation # :nodoc:
47
+ extend ActiveSupport::Concern
48
+
49
+ included do
50
+ @delegation_mutex = Mutex.new
51
+ end
52
+
53
+ module ClassMethods # :nodoc:
54
+ def name
55
+ superclass.name
56
+ end
57
+
58
+ def delegate_to_scoped_klass(method)
59
+ @delegation_mutex.synchronize do
60
+ return if method_defined?(method)
61
+
62
+ if method.to_s =~ /\A[a-zA-Z_]\w*[!?]?\z/
63
+ module_eval <<-RUBY, __FILE__, __LINE__ + 1
64
+ def #{method}(*args, &block)
65
+ scoping { @klass.#{method}(*args, &block) }
66
+ end
67
+ RUBY
68
+ else
69
+ define_method method do |*args, &block|
70
+ scoping { @klass.public_send(method, *args, &block) }
71
+ end
72
+ end
73
+ end
74
+ end
75
+
76
+ def delegate(method, opts = {})
77
+ @delegation_mutex.synchronize do
78
+ return if method_defined?(method)
79
+ super
80
+ end
81
+ end
82
+ end
83
+
84
+ protected
85
+
86
+ def method_missing(method, *args, &block)
87
+ if @klass.respond_to?(method)
88
+ self.class.delegate_to_scoped_klass(method)
89
+ scoping { @klass.public_send(method, *args, &block) }
90
+ else
91
+ super
92
+ end
93
+ end
94
+ end
95
+
96
+ module ClassMethods # :nodoc:
97
+ def create(klass, *args)
98
+ relation_class_for(klass).new(klass, *args)
99
+ end
100
+
101
+ private
102
+
103
+ def relation_class_for(klass)
104
+ klass.relation_delegate_class(self)
105
+ end
106
+ end
107
+
108
+ def respond_to?(method, include_private = false)
109
+ super || @klass.respond_to?(method, include_private) ||
110
+ array_delegable?(method)
111
+ end
112
+
113
+ protected
114
+
115
+ def array_delegable?(method)
116
+ Array.method_defined?(method) && BLACKLISTED_ARRAY_METHODS.exclude?(method)
117
+ end
118
+
119
+ def method_missing(method, *args, &block)
120
+ if @klass.respond_to?(method)
121
+ scoping { @klass.public_send(method, *args, &block) }
122
+ elsif array_delegable?(method)
123
+ to_a.public_send(method, *args, &block)
124
+ else
125
+ super
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,39 @@
1
+ module Elasticsearch
2
+ module Persistence
3
+
4
+ module FinderMethods
5
+
6
+ def first
7
+ return results.first if @loaded
8
+ spawn.first!.results.first
9
+ end
10
+
11
+ def first!
12
+ spawn.sort(Hash[default_sort_key, :asc]).spawn.size(1)
13
+ self
14
+ end
15
+
16
+ def last
17
+ return results.last if @loaded
18
+ spawn.last!.results.first
19
+ end
20
+
21
+ def last!
22
+ spawn.sort(Hash[default_sort_key, :desc]).spawn.size(1)
23
+ self
24
+ end
25
+
26
+ def count
27
+ return results.count if @loaded
28
+ spawn.count!
29
+ end
30
+
31
+ def count!
32
+ @values[:count] = true
33
+ @values.delete(:size)
34
+ spawn.to_a["count"]
35
+ end
36
+
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,179 @@
1
+ require 'active_support/core_ext/hash/keys'
2
+ require "set"
3
+
4
+ module Elasticsearch
5
+ module Persistence
6
+ class Relation
7
+ class HashMerger # :nodoc:
8
+ attr_reader :relation, :hash
9
+
10
+ def initialize(relation, hash)
11
+ hash.assert_valid_keys(*Relation::VALUE_METHODS)
12
+
13
+ @relation = relation
14
+ @hash = hash
15
+ end
16
+
17
+ def merge
18
+ Merger.new(relation, other).merge
19
+ end
20
+
21
+ # Applying values to a relation has some side effects. E.g.
22
+ # interpolation might take place for where values. So we should
23
+ # build a relation to merge in rather than directly merging
24
+ # the values.
25
+ def other
26
+ other = Relation.create(relation.klass)
27
+ hash.each { |k, v|
28
+ if k == :joins
29
+ if Hash === v
30
+ other.joins!(v)
31
+ else
32
+ other.joins!(*v)
33
+ end
34
+ elsif k == :select
35
+ other._select!(v)
36
+ else
37
+ other.send("#{k}!", v)
38
+ end
39
+ }
40
+ other
41
+ end
42
+ end
43
+
44
+ class Merger # :nodoc:
45
+ attr_reader :relation, :values, :other
46
+
47
+ def initialize(relation, other)
48
+ @relation = relation
49
+ @values = other.values
50
+ @other = other
51
+ end
52
+
53
+ NORMAL_VALUES = [:where, :first, :last, :filter]
54
+
55
+ def normal_values
56
+ NORMAL_VALUES
57
+ end
58
+
59
+ def merge
60
+ normal_values.each do |name|
61
+ value = values[name]
62
+ # The unless clause is here mostly for performance reasons (since the `send` call might be moderately
63
+ # expensive), most of the time the value is going to be `nil` or `.blank?`, the only catch is that
64
+ # `false.blank?` returns `true`, so there needs to be an extra check so that explicit `false` values
65
+ # don't fall through the cracks.
66
+
67
+ unless value.nil? || (value.blank? && false != value)
68
+ if name == :select
69
+ relation._select!(*value)
70
+ elsif name == :filter
71
+ values.each do |v|
72
+ relation.send("#{name}!", v.first, v.last)
73
+ end
74
+ else
75
+ relation.send("#{name}!", *value)
76
+ end
77
+ end
78
+ end
79
+
80
+ merge_multi_values
81
+ merge_single_values
82
+ #merge_joins
83
+
84
+ relation
85
+ end
86
+
87
+ private
88
+
89
+ def merge_joins
90
+ return if values[:joins].blank?
91
+
92
+ if other.klass == relation.klass
93
+ relation.joins!(*values[:joins])
94
+ else
95
+ joins_dependency, rest = values[:joins].partition do |join|
96
+ case join
97
+ when Hash, Symbol, Array
98
+ true
99
+ else
100
+ false
101
+ end
102
+ end
103
+
104
+ join_dependency = ActiveRecord::Associations::JoinDependency.new(other.klass,
105
+ joins_dependency,
106
+ [])
107
+ relation.joins! rest
108
+
109
+ @relation = relation.joins join_dependency
110
+ end
111
+ end
112
+
113
+ def merge_multi_values
114
+ lhs_wheres = relation.where_values
115
+ rhs_wheres = values[:where] || []
116
+
117
+ lhs_filters = relation.filter_values
118
+ rhs_filters = values[:filter] || []
119
+
120
+ removed, kept = partition_overwrites(lhs_wheres, rhs_wheres)
121
+
122
+ where_values = kept + rhs_wheres
123
+
124
+ filters_removed, filters_kept = partition_overwrites(lhs_wheres, rhs_wheres)
125
+ filter_values = rhs_filters
126
+
127
+
128
+ relation.where_values = where_values.empty? ? nil : where_values
129
+ relation.filter_values = filter_values.empty? ? nil : filter_values
130
+
131
+ if values[:reordering]
132
+ # override any order specified in the original relation
133
+ relation.reorder! values[:order]
134
+ elsif values[:order]
135
+ # merge in order_values from relation
136
+ relation.order! values[:order]
137
+ end
138
+
139
+ relation.extend(*values[:extending]) unless values[:extending].blank?
140
+ end
141
+
142
+ def merge_single_values
143
+ #relation.from_value = values[:from] unless relation.from_value
144
+ #relation.lock_value = values[:lock] unless relation.lock_value
145
+
146
+ unless values[:create_with].blank?
147
+ relation.create_with_value = (relation.create_with_value || {}).merge(values[:create_with])
148
+ end
149
+ end
150
+
151
+ def filter_binds(lhs_binds, removed_wheres)
152
+ return lhs_binds if removed_wheres.empty?
153
+
154
+ set = Set.new removed_wheres.map { |x| x.left.name.to_s }
155
+ lhs_binds.dup.delete_if { |col,_| set.include? col.name }
156
+ end
157
+
158
+ # Remove equalities from the existing relation with a LHS which is
159
+ # present in the relation being merged in.
160
+ # returns [things_to_remove, things_to_keep]
161
+ def partition_overwrites(lhs_wheres, rhs_wheres)
162
+ if lhs_wheres.empty? || rhs_wheres.empty?
163
+ return [[], lhs_wheres]
164
+ end
165
+
166
+ nodes = rhs_wheres.find_all do |w|
167
+ w.respond_to?(:operator) && w.operator == :==
168
+ end
169
+ seen = Set.new(nodes) { |node| node.left }
170
+
171
+ lhs_wheres.partition do |w|
172
+ w.respond_to?(:operator) && w.operator == :== && seen.include?(w.left)
173
+ end
174
+ end
175
+ end
176
+ end
177
+ end
178
+ end
179
+