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