elastictastic 0.5.0
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.
- data/LICENSE +19 -0
- data/README.md +326 -0
- data/lib/elastictastic/association.rb +21 -0
- data/lib/elastictastic/bulk_persistence_strategy.rb +70 -0
- data/lib/elastictastic/callbacks.rb +30 -0
- data/lib/elastictastic/child_collection_proxy.rb +56 -0
- data/lib/elastictastic/client.rb +101 -0
- data/lib/elastictastic/configuration.rb +35 -0
- data/lib/elastictastic/dirty.rb +130 -0
- data/lib/elastictastic/discrete_persistence_strategy.rb +52 -0
- data/lib/elastictastic/document.rb +98 -0
- data/lib/elastictastic/errors.rb +7 -0
- data/lib/elastictastic/field.rb +38 -0
- data/lib/elastictastic/index.rb +19 -0
- data/lib/elastictastic/mass_assignment_security.rb +15 -0
- data/lib/elastictastic/middleware.rb +119 -0
- data/lib/elastictastic/nested_document.rb +29 -0
- data/lib/elastictastic/new_relic_instrumentation.rb +26 -0
- data/lib/elastictastic/observer.rb +3 -0
- data/lib/elastictastic/observing.rb +21 -0
- data/lib/elastictastic/parent_child.rb +115 -0
- data/lib/elastictastic/persistence.rb +67 -0
- data/lib/elastictastic/properties.rb +236 -0
- data/lib/elastictastic/railtie.rb +35 -0
- data/lib/elastictastic/resource.rb +4 -0
- data/lib/elastictastic/scope.rb +283 -0
- data/lib/elastictastic/scope_builder.rb +32 -0
- data/lib/elastictastic/scoped.rb +20 -0
- data/lib/elastictastic/search.rb +180 -0
- data/lib/elastictastic/server_error.rb +15 -0
- data/lib/elastictastic/test_helpers.rb +172 -0
- data/lib/elastictastic/util.rb +63 -0
- data/lib/elastictastic/validations.rb +45 -0
- data/lib/elastictastic/version.rb +3 -0
- data/lib/elastictastic.rb +82 -0
- data/spec/environment.rb +6 -0
- data/spec/examples/active_model_lint_spec.rb +20 -0
- data/spec/examples/bulk_persistence_strategy_spec.rb +233 -0
- data/spec/examples/callbacks_spec.rb +96 -0
- data/spec/examples/dirty_spec.rb +238 -0
- data/spec/examples/document_spec.rb +600 -0
- data/spec/examples/mass_assignment_security_spec.rb +13 -0
- data/spec/examples/middleware_spec.rb +92 -0
- data/spec/examples/observing_spec.rb +141 -0
- data/spec/examples/parent_child_spec.rb +308 -0
- data/spec/examples/properties_spec.rb +92 -0
- data/spec/examples/scope_spec.rb +491 -0
- data/spec/examples/search_spec.rb +382 -0
- data/spec/examples/spec_helper.rb +15 -0
- data/spec/examples/validation_spec.rb +65 -0
- data/spec/models/author.rb +9 -0
- data/spec/models/blog.rb +5 -0
- data/spec/models/comment.rb +5 -0
- data/spec/models/post.rb +41 -0
- data/spec/models/post_observer.rb +11 -0
- data/spec/support/fakeweb_request_history.rb +13 -0
- metadata +227 -0
@@ -0,0 +1,67 @@
|
|
1
|
+
module Elastictastic
|
2
|
+
module Persistence
|
3
|
+
def save
|
4
|
+
persisted? ? update : create
|
5
|
+
end
|
6
|
+
|
7
|
+
def destroy
|
8
|
+
if persisted?
|
9
|
+
Elastictastic.persister.destroy(self)
|
10
|
+
else
|
11
|
+
raise OperationNotAllowed, "Cannot destroy transient document: #{inspect}"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def persisted?
|
16
|
+
!!@persisted
|
17
|
+
end
|
18
|
+
|
19
|
+
def transient?
|
20
|
+
!persisted?
|
21
|
+
end
|
22
|
+
|
23
|
+
def pending_save?
|
24
|
+
!!@pending_save
|
25
|
+
end
|
26
|
+
|
27
|
+
def pending_destroy?
|
28
|
+
!!@pending_destroy
|
29
|
+
end
|
30
|
+
|
31
|
+
def persisted!
|
32
|
+
@persisted = true
|
33
|
+
@pending_save = false
|
34
|
+
end
|
35
|
+
|
36
|
+
def transient!
|
37
|
+
@persisted = @pending_destroy = false
|
38
|
+
end
|
39
|
+
|
40
|
+
def pending_save!
|
41
|
+
@pending_save = true
|
42
|
+
end
|
43
|
+
|
44
|
+
def pending_destroy!
|
45
|
+
@pending_destroy = true
|
46
|
+
end
|
47
|
+
|
48
|
+
protected
|
49
|
+
|
50
|
+
def create
|
51
|
+
Elastictastic.persister.create(self)
|
52
|
+
end
|
53
|
+
|
54
|
+
def update
|
55
|
+
Elastictastic.persister.update(self)
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def assert_transient!
|
61
|
+
if persisted?
|
62
|
+
raise IllegalModificationError,
|
63
|
+
"Cannot modify identity attribute after model has been saved."
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,236 @@
|
|
1
|
+
module Elastictastic
|
2
|
+
module Properties
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
def each_field
|
7
|
+
properties.each_pair do |field, properties|
|
8
|
+
if properties['properties']
|
9
|
+
embeds[field].clazz.each_field do |embed_field, embed_properties|
|
10
|
+
yield("#{field}.#{embed_field}", embed_properties)
|
11
|
+
end
|
12
|
+
elsif properties['fields']
|
13
|
+
properties['fields'].each_pair do |variant_field, variant_properties|
|
14
|
+
if variant_field == field
|
15
|
+
yield(field, variant_properties)
|
16
|
+
else
|
17
|
+
yield("#{field}.#{variant_field}", variant_properties)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
else
|
21
|
+
yield field, properties
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def select_fields
|
27
|
+
[].tap do |fields|
|
28
|
+
each_field do |field, properties|
|
29
|
+
fields << [field, properties] if yield(field, properties)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def all_fields
|
35
|
+
@all_fields ||= {}.tap do |fields|
|
36
|
+
each_field { |field, properties| fields[field] = properties }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def field_properties
|
41
|
+
@field_properties ||= {}
|
42
|
+
end
|
43
|
+
|
44
|
+
def properties
|
45
|
+
return @properties if defined? @properties
|
46
|
+
@properties = {}
|
47
|
+
@properties.merge!(field_properties)
|
48
|
+
embeds.each_pair do |name, embed|
|
49
|
+
@properties[name] = { 'properties' => embed.clazz.properties }
|
50
|
+
end
|
51
|
+
@properties
|
52
|
+
end
|
53
|
+
|
54
|
+
def properties_for_field(field_name)
|
55
|
+
properties[field_name.to_s]
|
56
|
+
end
|
57
|
+
|
58
|
+
def embeds
|
59
|
+
@embeds ||= {}
|
60
|
+
end
|
61
|
+
|
62
|
+
def field(*field_names, &block)
|
63
|
+
options = field_names.extract_options!
|
64
|
+
field_names.each do |field_name|
|
65
|
+
define_field(field_name, options, &block)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def define_field(field_name, options, &block)
|
70
|
+
field_name = field_name.to_s
|
71
|
+
|
72
|
+
module_eval(<<-RUBY, __FILE__, __LINE__ + 1)
|
73
|
+
def #{field_name}
|
74
|
+
read_attribute(#{field_name.inspect})
|
75
|
+
end
|
76
|
+
|
77
|
+
def #{field_name}=(value)
|
78
|
+
write_attribute(#{field_name.inspect}, value)
|
79
|
+
end
|
80
|
+
RUBY
|
81
|
+
|
82
|
+
field_properties[field_name.to_s] =
|
83
|
+
Field.process(field_name, options, &block)
|
84
|
+
end
|
85
|
+
|
86
|
+
def embed(*embed_names)
|
87
|
+
options = embed_names.extract_options!
|
88
|
+
|
89
|
+
embed_names.each do |embed_name|
|
90
|
+
define_embed(embed_name, options)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def define_embed(embed_name, options)
|
95
|
+
embed_name = embed_name.to_s
|
96
|
+
embed = Association.new(embed_name, options)
|
97
|
+
|
98
|
+
module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
99
|
+
def #{embed_name}
|
100
|
+
read_embed(#{embed_name.inspect})
|
101
|
+
end
|
102
|
+
|
103
|
+
def #{embed_name}=(value)
|
104
|
+
Util.call_or_each(value) do |check_value|
|
105
|
+
unless check_value.nil? || check_value.is_a?(#{embed.class_name})
|
106
|
+
raise TypeError, "Expected instance of class #{embed.class_name}; got \#{check_value.inspect}"
|
107
|
+
end
|
108
|
+
end
|
109
|
+
write_embed(#{embed_name.inspect}, value)
|
110
|
+
end
|
111
|
+
RUBY
|
112
|
+
|
113
|
+
embeds[embed_name] = embed
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
module InstanceMethods
|
118
|
+
def initialize(attributes = {})
|
119
|
+
super
|
120
|
+
@attributes = {}
|
121
|
+
@embeds = {}
|
122
|
+
self.attributes = attributes
|
123
|
+
end
|
124
|
+
|
125
|
+
def attributes
|
126
|
+
@attributes.with_indifferent_access
|
127
|
+
end
|
128
|
+
|
129
|
+
def attributes=(attributes)
|
130
|
+
attributes.each_pair do |field, value|
|
131
|
+
__send__(:"#{field}=", value)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def elasticsearch_doc
|
136
|
+
{}.tap do |doc|
|
137
|
+
@attributes.each_pair do |field, value|
|
138
|
+
next if value.nil?
|
139
|
+
doc[field] = Util.call_or_map(value) do |item|
|
140
|
+
serialize_value(field, item)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
@embeds.each_pair do |field, embedded|
|
144
|
+
next if embedded.nil?
|
145
|
+
doc[field] = Util.call_or_map(embedded) do |item|
|
146
|
+
item.elasticsearch_doc
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def elasticsearch_doc=(doc)
|
153
|
+
return if doc.nil?
|
154
|
+
doc.each_pair do |field_name, value|
|
155
|
+
if self.class.properties.has_key?(field_name)
|
156
|
+
embed = self.class.embeds[field_name]
|
157
|
+
if embed
|
158
|
+
embedded = Util.call_or_map(value) do |item|
|
159
|
+
embed.clazz.new.tap { |e| e.elasticsearch_doc = item }
|
160
|
+
end
|
161
|
+
write_embed(field_name, embedded)
|
162
|
+
else
|
163
|
+
deserialized = Util.call_or_map(value) do |item|
|
164
|
+
deserialize_value(field_name, item)
|
165
|
+
end
|
166
|
+
write_attribute(field_name, deserialized)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
protected
|
173
|
+
|
174
|
+
def read_attribute(field)
|
175
|
+
@attributes[field.to_s]
|
176
|
+
end
|
177
|
+
|
178
|
+
def write_attribute(field, value)
|
179
|
+
if value.nil?
|
180
|
+
@attributes.delete(field.to_s)
|
181
|
+
else
|
182
|
+
@attributes[field.to_s] = value
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
def read_attributes
|
187
|
+
@attributes
|
188
|
+
end
|
189
|
+
|
190
|
+
def write_attributes(attributes)
|
191
|
+
@attributes = attributes
|
192
|
+
end
|
193
|
+
|
194
|
+
def read_embed(field)
|
195
|
+
@embeds[field.to_s]
|
196
|
+
end
|
197
|
+
|
198
|
+
def write_embed(field, value)
|
199
|
+
@embeds[field.to_s] = value
|
200
|
+
end
|
201
|
+
|
202
|
+
private
|
203
|
+
|
204
|
+
def serialize_value(field_name, value)
|
205
|
+
type = self.class.properties_for_field(field_name)['type'].to_s
|
206
|
+
case type
|
207
|
+
when 'date'
|
208
|
+
time = value.to_time
|
209
|
+
time.to_i * 1000 + time.usec / 1000
|
210
|
+
when 'integer', 'byte', 'short', 'long'
|
211
|
+
value.to_i
|
212
|
+
when 'float', 'double'
|
213
|
+
value.to_f
|
214
|
+
when 'boolean'
|
215
|
+
!!value
|
216
|
+
else
|
217
|
+
value
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
def deserialize_value(field_name, value)
|
222
|
+
return nil if value.nil?
|
223
|
+
if self.class.properties_for_field(field_name)['type'].to_s == 'date'
|
224
|
+
if value.is_a? Fixnum
|
225
|
+
sec, usec = value / 1000, (value % 1000) * 1000
|
226
|
+
Time.at(sec, usec).utc
|
227
|
+
else
|
228
|
+
Time.parse(value)
|
229
|
+
end
|
230
|
+
else
|
231
|
+
value
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Elastictastic
|
2
|
+
class Railtie < Rails::Railtie
|
3
|
+
config.elastictastic = Elastictastic.config
|
4
|
+
|
5
|
+
initializer "elastictastic.configure_rails" do
|
6
|
+
config_path = Rails.root.join('config/elastictastic.yml').to_s
|
7
|
+
config = Elastictastic.config
|
8
|
+
app_name = Rails.application.class.name.split('::').first.underscore
|
9
|
+
config.default_index = "#{app_name}_#{Rails.env}"
|
10
|
+
|
11
|
+
if File.exist?(config_path)
|
12
|
+
yaml = YAML.load_file(config_path)[Rails.env]
|
13
|
+
if yaml
|
14
|
+
yaml.each_pair do |name, value|
|
15
|
+
config.__send__("#{name}=", value)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
Elastictastic.config.logger = Rails.logger
|
21
|
+
|
22
|
+
require 'elastictastic/new_relic_instrumentation' if defined? NewRelic
|
23
|
+
end
|
24
|
+
|
25
|
+
initializer "elastictastic.instantiate_observers" do
|
26
|
+
config.after_initialize do
|
27
|
+
::Elastictastic::Observing.instantiate_observers
|
28
|
+
|
29
|
+
ActionDispatch::Callbacks.to_prepare do
|
30
|
+
::Elastictastic.instantiate_observers
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,283 @@
|
|
1
|
+
require 'hashie'
|
2
|
+
|
3
|
+
module Elastictastic
|
4
|
+
class Scope < BasicObject
|
5
|
+
attr_reader :clazz, :index
|
6
|
+
|
7
|
+
def initialize(index, clazz, search = Search.new, parent_collection = nil)
|
8
|
+
@index, @clazz, @search, @parent_collection =
|
9
|
+
index, clazz, search, parent_collection
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize_instance(instance)
|
13
|
+
index = @index
|
14
|
+
instance.instance_eval { @index = index }
|
15
|
+
end
|
16
|
+
|
17
|
+
def params
|
18
|
+
@search.params
|
19
|
+
end
|
20
|
+
|
21
|
+
def each
|
22
|
+
if ::Kernel.block_given?
|
23
|
+
find_each { |result, hit| yield result }
|
24
|
+
else
|
25
|
+
::Enumerator.new(self, :each)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def find_each(batch_options = {}, &block)
|
30
|
+
if block
|
31
|
+
find_in_batches(batch_options) { |batch| batch.each(&block) }
|
32
|
+
else
|
33
|
+
::Enumerator.new(self, :find_each, batch_options)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def find_in_batches(batch_options = {}, &block)
|
38
|
+
return ::Enumerator.new(self, :find_in_batches, batch_options) unless block
|
39
|
+
if params.key?('size') || params.key?('from')
|
40
|
+
yield search_all
|
41
|
+
elsif params.key?('sort') || params.key('facets')
|
42
|
+
search_in_batches(&block)
|
43
|
+
else
|
44
|
+
scan_in_batches(batch_options, &block)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def count
|
49
|
+
return @count if defined? @count
|
50
|
+
populate_counts
|
51
|
+
@count
|
52
|
+
end
|
53
|
+
|
54
|
+
def empty?
|
55
|
+
count == 0
|
56
|
+
end
|
57
|
+
|
58
|
+
def any?(&block)
|
59
|
+
block ? each.any?(&block) : !empty?
|
60
|
+
end
|
61
|
+
|
62
|
+
def first
|
63
|
+
params = from(0).size(1).params
|
64
|
+
hit = ::Elastictastic.client.search(
|
65
|
+
@index,
|
66
|
+
@clazz.type,
|
67
|
+
params
|
68
|
+
)['hits']['hits'].first
|
69
|
+
materialize_hit(hit) if hit
|
70
|
+
end
|
71
|
+
|
72
|
+
def all
|
73
|
+
scoped({})
|
74
|
+
end
|
75
|
+
|
76
|
+
def all_facets
|
77
|
+
return @all_facets if defined? @all_facets
|
78
|
+
populate_counts
|
79
|
+
@all_facets ||= nil
|
80
|
+
end
|
81
|
+
|
82
|
+
def scoped(params, index = @index)
|
83
|
+
::Elastictastic::Scope.new(
|
84
|
+
@index,
|
85
|
+
@clazz,
|
86
|
+
@search.merge(Search.new(params)),
|
87
|
+
@parent_collection
|
88
|
+
)
|
89
|
+
end
|
90
|
+
|
91
|
+
def destroy_all
|
92
|
+
#FIXME support delete-by-query
|
93
|
+
::Elastictastic.client.delete(@index, @clazz.type)
|
94
|
+
end
|
95
|
+
|
96
|
+
def sync_mapping
|
97
|
+
#XXX is this a weird place to have this?
|
98
|
+
::Elastictastic.client.put_mapping(index, type, @clazz.mapping)
|
99
|
+
end
|
100
|
+
|
101
|
+
def find(*ids)
|
102
|
+
#TODO support combining this with other filters/query
|
103
|
+
force_array = ::Array === ids.first
|
104
|
+
ids = ids.flatten
|
105
|
+
if ::Hash === ids.first
|
106
|
+
find_many_in_many_indices(*ids)
|
107
|
+
elsif ids.length == 1
|
108
|
+
instance = find_one(ids.first)
|
109
|
+
force_array ? [instance] : instance
|
110
|
+
else
|
111
|
+
find_many(ids)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
Search::KEYS.each do |search_key|
|
116
|
+
module_eval <<-RUBY
|
117
|
+
def #{search_key}(*values, &block)
|
118
|
+
values << ScopeBuilder.build(&block) if block
|
119
|
+
|
120
|
+
case values.length
|
121
|
+
when 0 then ::Kernel.raise ::ArgumentError, "wrong number of arguments (0 for 1)"
|
122
|
+
when 1 then value = values.first
|
123
|
+
else value = values
|
124
|
+
end
|
125
|
+
|
126
|
+
scoped(#{search_key.inspect} => value)
|
127
|
+
end
|
128
|
+
RUBY
|
129
|
+
end
|
130
|
+
|
131
|
+
def method_missing(method, *args, &block)
|
132
|
+
if ::Enumerable.method_defined?(method)
|
133
|
+
each.__send__(method, *args, &block)
|
134
|
+
elsif @clazz.respond_to?(method)
|
135
|
+
@clazz.with_scope(self) do
|
136
|
+
@clazz.__send__(method, *args, &block)
|
137
|
+
end
|
138
|
+
else
|
139
|
+
super
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def inspect
|
144
|
+
inspected = "#{@clazz.name}:#{@index.name}"
|
145
|
+
inspected << @search.params.to_json unless @search.params.empty?
|
146
|
+
inspected
|
147
|
+
end
|
148
|
+
|
149
|
+
protected
|
150
|
+
|
151
|
+
def search(search_params = {})
|
152
|
+
::Elastictastic.client.search(
|
153
|
+
@index,
|
154
|
+
@clazz.type,
|
155
|
+
params,
|
156
|
+
search_params
|
157
|
+
)
|
158
|
+
end
|
159
|
+
|
160
|
+
private
|
161
|
+
|
162
|
+
def search_all
|
163
|
+
response = search(:search_type => 'query_then_fetch')
|
164
|
+
populate_counts(response)
|
165
|
+
materialize_hits(response['hits']['hits'])
|
166
|
+
end
|
167
|
+
|
168
|
+
def search_in_batches(&block)
|
169
|
+
from, size = 0, ::Elastictastic.config.default_batch_size
|
170
|
+
scope_with_size = self.size(size)
|
171
|
+
begin
|
172
|
+
scope = scope_with_size.from(from)
|
173
|
+
response = scope.search(:search_type => 'query_then_fetch')
|
174
|
+
populate_counts(response)
|
175
|
+
yield materialize_hits(response['hits']['hits'])
|
176
|
+
from += size
|
177
|
+
@count ||= scope.count
|
178
|
+
end while from < @count
|
179
|
+
end
|
180
|
+
|
181
|
+
def scan_in_batches(batch_options, &block)
|
182
|
+
batch_options = batch_options.symbolize_keys
|
183
|
+
scroll_options = {
|
184
|
+
:scroll => "#{batch_options[:ttl] || 60}s",
|
185
|
+
:size => batch_options[:batch_size] || ::Elastictastic.config.default_batch_size
|
186
|
+
}
|
187
|
+
scan_response = ::Elastictastic.client.search(
|
188
|
+
@index,
|
189
|
+
@clazz.type,
|
190
|
+
params,
|
191
|
+
scroll_options.merge(:search_type => 'scan')
|
192
|
+
)
|
193
|
+
|
194
|
+
@count = scan_response['hits']['total']
|
195
|
+
scroll_id = scan_response['_scroll_id']
|
196
|
+
|
197
|
+
begin
|
198
|
+
response = ::Elastictastic.client.scroll(scroll_id, scroll_options.slice(:scroll))
|
199
|
+
scroll_id = response['_scroll_id']
|
200
|
+
yield materialize_hits(response['hits']['hits'])
|
201
|
+
end until response['hits']['hits'].empty?
|
202
|
+
end
|
203
|
+
|
204
|
+
def populate_counts(response = nil)
|
205
|
+
response ||= search(:search_type => 'count')
|
206
|
+
@count ||= response['hits']['total']
|
207
|
+
if response['facets']
|
208
|
+
@all_facets ||= ::Hashie::Mash.new(response['facets'])
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
def find_one(id)
|
213
|
+
data = ::Elastictastic.client.get(index, type, id, params_for_find_one)
|
214
|
+
return nil if data['exists'] == false
|
215
|
+
case data['status']
|
216
|
+
when nil
|
217
|
+
materialize_hit(data)
|
218
|
+
when 404
|
219
|
+
nil
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
def find_many(ids)
|
224
|
+
docspec = ids.map do |id|
|
225
|
+
{ '_id' => id }.merge!(params_for_find_many)
|
226
|
+
end
|
227
|
+
materialize_hits(
|
228
|
+
::Elastictastic.client.mget(docspec, index, type)['docs']
|
229
|
+
).map { |result, hit| result }
|
230
|
+
end
|
231
|
+
|
232
|
+
def find_many_in_many_indices(ids_by_index)
|
233
|
+
docs = []
|
234
|
+
ids_by_index.each_pair do |index, ids|
|
235
|
+
::Kernel.Array(ids).each do |id|
|
236
|
+
docs << doc = {
|
237
|
+
'_id' => id.to_s,
|
238
|
+
'_type' => type,
|
239
|
+
'_index' => index
|
240
|
+
}
|
241
|
+
doc['fields'] = ::Kernel.Array(@search['fields']) if @search['fields']
|
242
|
+
end
|
243
|
+
end
|
244
|
+
materialize_hits(
|
245
|
+
::Elastictastic.client.mget(docs)['docs']
|
246
|
+
).map { |result, hit| result }
|
247
|
+
end
|
248
|
+
|
249
|
+
def params_for_find_one
|
250
|
+
params_for_find.tap do |params|
|
251
|
+
params['fields'] &&= params['fields'].join(',')
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
def params_for_find_many
|
256
|
+
params_for_find
|
257
|
+
end
|
258
|
+
|
259
|
+
def params_for_find
|
260
|
+
{}.tap do |params|
|
261
|
+
params['fields'] = ::Kernel.Array(@search['fields']) if @search['fields']
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
def materialize_hits(hits)
|
266
|
+
unless ::Kernel.block_given?
|
267
|
+
return ::Enumerator.new(self, :materialize_hits, hits)
|
268
|
+
end
|
269
|
+
hits.each do |hit|
|
270
|
+
unless hit['exists'] == false
|
271
|
+
yield materialize_hit(hit), ::Hashie::Mash.new(hit)
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
def materialize_hit(hit)
|
277
|
+
@clazz.new.tap do |result|
|
278
|
+
result.parent_collection = @parent_collection if @parent_collection
|
279
|
+
result.elasticsearch_hit = hit
|
280
|
+
end
|
281
|
+
end
|
282
|
+
end
|
283
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Elastictastic
|
2
|
+
class ScopeBuilder < BasicObject
|
3
|
+
class <<self
|
4
|
+
private :new
|
5
|
+
|
6
|
+
def build(&block)
|
7
|
+
new(&block).build
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(&block)
|
12
|
+
@block = block
|
13
|
+
end
|
14
|
+
|
15
|
+
def build
|
16
|
+
@scope = {}
|
17
|
+
instance_eval(&@block)
|
18
|
+
@scope
|
19
|
+
end
|
20
|
+
|
21
|
+
def method_missing(method, *args, &block)
|
22
|
+
args << ScopeBuilder.build(&block) if block
|
23
|
+
value =
|
24
|
+
case args.length
|
25
|
+
when 0 then {}
|
26
|
+
when 1 then args.first
|
27
|
+
else args
|
28
|
+
end
|
29
|
+
@scope[method.to_s] = value
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Elastictastic
|
2
|
+
module Scoped
|
3
|
+
def with_scope(scope)
|
4
|
+
scope_stack.push(scope)
|
5
|
+
begin
|
6
|
+
yield
|
7
|
+
ensure
|
8
|
+
scope_stack.pop
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def scope_stack
|
13
|
+
Thread.current["#{name}::scope_stack"] ||= []
|
14
|
+
end
|
15
|
+
|
16
|
+
def current_scope
|
17
|
+
scope_stack.last || default_scope
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|