stretchy-model 0.1.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.
- checksums.yaml +7 -0
- data/.rspec +1 -0
- data/.yardopts +1 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +10 -0
- data/LICENSE.txt +21 -0
- data/README.md +146 -0
- data/Rakefile +4 -0
- data/containers/Dockerfile.elasticsearch +7 -0
- data/containers/Dockerfile.opensearch +19 -0
- data/docker-compose.yml +52 -0
- data/lib/active_model/type/array.rb +13 -0
- data/lib/active_model/type/hash.rb +15 -0
- data/lib/rails/instrumentation/publishers.rb +29 -0
- data/lib/rails/instrumentation/railtie.rb +29 -0
- data/lib/stretchy/associations/associated_validator.rb +17 -0
- data/lib/stretchy/associations/elastic_relation.rb +38 -0
- data/lib/stretchy/associations.rb +161 -0
- data/lib/stretchy/common.rb +33 -0
- data/lib/stretchy/delegation/delegate_cache.rb +131 -0
- data/lib/stretchy/delegation/gateway_delegation.rb +43 -0
- data/lib/stretchy/indexing/bulk.rb +48 -0
- data/lib/stretchy/model/callbacks.rb +31 -0
- data/lib/stretchy/model/serialization.rb +20 -0
- data/lib/stretchy/null_relation.rb +53 -0
- data/lib/stretchy/persistence.rb +43 -0
- data/lib/stretchy/querying.rb +20 -0
- data/lib/stretchy/record.rb +57 -0
- data/lib/stretchy/refreshable.rb +15 -0
- data/lib/stretchy/relation.rb +169 -0
- data/lib/stretchy/relations/finder_methods.rb +39 -0
- data/lib/stretchy/relations/merger.rb +179 -0
- data/lib/stretchy/relations/query_builder.rb +265 -0
- data/lib/stretchy/relations/query_methods.rb +578 -0
- data/lib/stretchy/relations/search_option_methods.rb +34 -0
- data/lib/stretchy/relations/spawn_methods.rb +60 -0
- data/lib/stretchy/repository.rb +10 -0
- data/lib/stretchy/scoping/default.rb +134 -0
- data/lib/stretchy/scoping/named.rb +68 -0
- data/lib/stretchy/scoping/scope_registry.rb +34 -0
- data/lib/stretchy/scoping.rb +28 -0
- data/lib/stretchy/shared_scopes.rb +34 -0
- data/lib/stretchy/utils.rb +69 -0
- data/lib/stretchy/version.rb +5 -0
- data/lib/stretchy.rb +38 -0
- data/sig/stretchy.rbs +4 -0
- data/stretchy.logo.png +0 -0
- metadata +247 -0
@@ -0,0 +1,33 @@
|
|
1
|
+
module Stretchy
|
2
|
+
module Common
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
def inspect
|
6
|
+
"#<#{self.class.name} #{attributes.map { |k,v| "#{k}: #{v.blank? ? 'nil' : v}" }.join(', ')}>"
|
7
|
+
end
|
8
|
+
|
9
|
+
|
10
|
+
class_methods do
|
11
|
+
|
12
|
+
# Set the default sort key to be used in sort operations
|
13
|
+
#
|
14
|
+
def default_sort_key(field = nil)
|
15
|
+
@default_sort_key = field unless field.nil?
|
16
|
+
@default_sort_key
|
17
|
+
end
|
18
|
+
|
19
|
+
def default_size(size = 10000)
|
20
|
+
@default_size = size
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
# Return a Relation instance to chain queries
|
26
|
+
#
|
27
|
+
def relation
|
28
|
+
Relation.create(self, {})
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
# TODO: Break out modules into separate files
|
2
|
+
module Stretchy
|
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
|
+
Stretchy::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, :highlights, :total, to: :to_a
|
43
|
+
delegate :aggregations, to: :response
|
44
|
+
|
45
|
+
|
46
|
+
delegate :mapping, :index_name, :document_type, :to => :klass
|
47
|
+
|
48
|
+
module ClassSpecificRelation # :nodoc:
|
49
|
+
extend ActiveSupport::Concern
|
50
|
+
|
51
|
+
included do
|
52
|
+
@delegation_mutex = Mutex.new
|
53
|
+
end
|
54
|
+
|
55
|
+
module ClassMethods # :nodoc:
|
56
|
+
def name
|
57
|
+
superclass.name
|
58
|
+
end
|
59
|
+
|
60
|
+
def delegate_to_scoped_klass(method)
|
61
|
+
@delegation_mutex.synchronize do
|
62
|
+
return if method_defined?(method)
|
63
|
+
|
64
|
+
if method.to_s =~ /\A[a-zA-Z_]\w*[!?]?\z/
|
65
|
+
module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
66
|
+
def #{method}(*args, &block)
|
67
|
+
scoping { @klass.#{method}(*args, &block) }
|
68
|
+
end
|
69
|
+
RUBY
|
70
|
+
else
|
71
|
+
define_method method do |*args, &block|
|
72
|
+
scoping { @klass.public_send(method, *args, &block) }
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def delegate(method, opts = {})
|
79
|
+
@delegation_mutex.synchronize do
|
80
|
+
return if method_defined?(method)
|
81
|
+
super
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
protected
|
87
|
+
|
88
|
+
def method_missing(method, *args, &block)
|
89
|
+
if @klass.respond_to?(method)
|
90
|
+
self.class.delegate_to_scoped_klass(method)
|
91
|
+
scoping { @klass.public_send(method, *args, &block) }
|
92
|
+
else
|
93
|
+
super
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
module ClassMethods # :nodoc:
|
99
|
+
def create(klass, *args)
|
100
|
+
relation_class_for(klass).new(klass, *args)
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
|
105
|
+
def relation_class_for(klass)
|
106
|
+
klass.relation_delegate_class(self)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def respond_to?(method, include_private = false)
|
111
|
+
super || @klass.respond_to?(method, include_private) ||
|
112
|
+
array_delegable?(method)
|
113
|
+
end
|
114
|
+
|
115
|
+
protected
|
116
|
+
|
117
|
+
def array_delegable?(method)
|
118
|
+
Array.method_defined?(method) && BLACKLISTED_ARRAY_METHODS.exclude?(method)
|
119
|
+
end
|
120
|
+
|
121
|
+
def method_missing(method, *args, &block)
|
122
|
+
if @klass.respond_to?(method)
|
123
|
+
scoping { @klass.public_send(method, *args, &block) }
|
124
|
+
elsif array_delegable?(method)
|
125
|
+
to_a.public_send(method, *args, &block)
|
126
|
+
else
|
127
|
+
super
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Stretchy
|
2
|
+
module Delegation
|
3
|
+
module GatewayDelegation
|
4
|
+
delegate :settings,
|
5
|
+
:mappings,
|
6
|
+
:mapping,
|
7
|
+
:document_type,
|
8
|
+
:document_type=,
|
9
|
+
:index_name,
|
10
|
+
:index_name=,
|
11
|
+
:search,
|
12
|
+
:find,
|
13
|
+
:exists?,
|
14
|
+
:create_index!,
|
15
|
+
:delete_index!,
|
16
|
+
:index_exists?,
|
17
|
+
:refresh_index!,
|
18
|
+
:count,
|
19
|
+
to: :gateway
|
20
|
+
|
21
|
+
include Rails::Instrumentation::Publishers::Record
|
22
|
+
|
23
|
+
def index_name(name=nil, &block)
|
24
|
+
if name || block_given?
|
25
|
+
return (@index_name = name || block)
|
26
|
+
end
|
27
|
+
|
28
|
+
if @index_name.respond_to?(:call)
|
29
|
+
@index_name.call
|
30
|
+
else
|
31
|
+
@index_name || base_class.model_name.collection
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def gateway(&block)
|
36
|
+
@gateway ||= Stretchy::Repository.create(index_name: index_name, klass: base_class)
|
37
|
+
block.arity < 1 ? @gateway.instance_eval(&block) : block.call(@gateway) if block_given?
|
38
|
+
@gateway
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Stretchy
|
2
|
+
module Indexing
|
3
|
+
module Bulk
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
class_methods do
|
7
|
+
|
8
|
+
def bulk(records)
|
9
|
+
self.gateway.client.bulk body: records
|
10
|
+
end
|
11
|
+
|
12
|
+
# bulk_in_batches(records, size: 100) do |batch|
|
13
|
+
# # do something with the batch
|
14
|
+
# batch.each { |record| record.to_bulk(method)}
|
15
|
+
# end
|
16
|
+
def bulk_in_batches(records, size: 1000)
|
17
|
+
bulk_results = records.each_slice(size).map do |batch|
|
18
|
+
yield batch if block_given?
|
19
|
+
bulk(batch)
|
20
|
+
end
|
21
|
+
self.refresh_index!
|
22
|
+
bulk_results
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
# TODO: May only be needed for associations
|
29
|
+
def update_all(**attributes)
|
30
|
+
# self.class.bulk_in_batches(self.all, size: 1000, method: :update) do |batch|
|
31
|
+
# batch.map! { |record| attributes.each { |key, value| record.send("#{key}=", value) } }
|
32
|
+
# end
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_bulk(method = :index)
|
36
|
+
case method
|
37
|
+
when :index
|
38
|
+
{ index: { _index: self.class.index_name, data: self.as_json.except(:id) } }
|
39
|
+
when :delete
|
40
|
+
{ delete: { _index: self.class.index_name, _id: self.id } }
|
41
|
+
when :update
|
42
|
+
{ update: { _index: self.class.index_name, _id: self.id, data: { doc: self.as_json.except(:id) } } }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Stretchy
|
2
|
+
module Model
|
3
|
+
module Callbacks
|
4
|
+
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
|
8
|
+
included do
|
9
|
+
mattr_accessor :_circuit_breaker_callbacks, default: []
|
10
|
+
|
11
|
+
define_model_callbacks :create, :save, :update, :destroy
|
12
|
+
define_model_callbacks :find, :touch, only: :after
|
13
|
+
end
|
14
|
+
|
15
|
+
class_methods do
|
16
|
+
|
17
|
+
def query_must_have(*args, &block)
|
18
|
+
options = args.extract_options!
|
19
|
+
|
20
|
+
cb = block_given? ? block : options[:validate_with]
|
21
|
+
|
22
|
+
options[:message] = "does not exist in #{options[:in]}." unless options.has_key? :message
|
23
|
+
|
24
|
+
_circuit_breaker_callbacks << {name: args.first, options: options, callback: cb}
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Stretchy
|
2
|
+
module Model
|
3
|
+
module Serialization
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
def serialize(document)
|
7
|
+
Hash[document.to_hash.map { |k,v| v.upcase! if k == :title; [k,v] }]
|
8
|
+
end
|
9
|
+
|
10
|
+
def deserialize(document)
|
11
|
+
attribs = ActiveSupport::HashWithIndifferentAccess.new(document['_source']).deep_symbolize_keys
|
12
|
+
_id = __get_id_from_document(document)
|
13
|
+
attribs[:id] = _id if _id
|
14
|
+
klass.new attribs
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Stretchy
|
4
|
+
module NullRelation # :nodoc:
|
5
|
+
def pluck(*column_names)
|
6
|
+
[]
|
7
|
+
end
|
8
|
+
|
9
|
+
def delete_all
|
10
|
+
0
|
11
|
+
end
|
12
|
+
|
13
|
+
def update_all(_updates)
|
14
|
+
0
|
15
|
+
end
|
16
|
+
|
17
|
+
def delete(_id_or_array)
|
18
|
+
0
|
19
|
+
end
|
20
|
+
|
21
|
+
def empty?
|
22
|
+
true
|
23
|
+
end
|
24
|
+
|
25
|
+
def none?
|
26
|
+
true
|
27
|
+
end
|
28
|
+
|
29
|
+
def any?
|
30
|
+
false
|
31
|
+
end
|
32
|
+
|
33
|
+
def one?
|
34
|
+
false
|
35
|
+
end
|
36
|
+
|
37
|
+
def many?
|
38
|
+
false
|
39
|
+
end
|
40
|
+
|
41
|
+
def exists?(_conditions = :none)
|
42
|
+
false
|
43
|
+
end
|
44
|
+
|
45
|
+
def or(other)
|
46
|
+
other.spawn
|
47
|
+
end
|
48
|
+
|
49
|
+
def exec_queries
|
50
|
+
@records = OpenStruct.new(klass: NullRelation, total: 0, results: []).freeze
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Stretchy
|
2
|
+
module Persistence
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
class_methods do
|
6
|
+
def create(*args)
|
7
|
+
self.new(*args).save
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def save
|
12
|
+
run_callbacks :save do
|
13
|
+
if new_record?
|
14
|
+
run_callbacks :create do
|
15
|
+
response = self.class.gateway.save(self.attributes)
|
16
|
+
self.id = response['_id']
|
17
|
+
end
|
18
|
+
else
|
19
|
+
self.class.gateway.save(self.attributes)
|
20
|
+
end
|
21
|
+
self
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def destroy
|
26
|
+
run_callbacks :destroy do
|
27
|
+
delete
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def delete
|
32
|
+
self.class.gateway.delete(self.id)["result"] == 'deleted'
|
33
|
+
end
|
34
|
+
|
35
|
+
def update(*args)
|
36
|
+
run_callbacks :update do
|
37
|
+
self.assign_attributes(*args)
|
38
|
+
self.save
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Stretchy
|
2
|
+
module Querying
|
3
|
+
delegate :first, :first!, :last, :last!, :exists?, :has_field, :any?, :many?, to: :all
|
4
|
+
delegate :order, :limit, :size, :sort, :where, :rewhere, :eager_load, :includes, :create_with, :none, :unscope, to: :all
|
5
|
+
delegate :or_filter, :filter, :fields, :source, :highlight, :aggregation, to: :all
|
6
|
+
delegate :skip_callbacks, :routing, to: :all
|
7
|
+
delegate :search_options, :routing, to: :all
|
8
|
+
delegate :must, :must_not, :should, :where_not, :query_string, to: :all
|
9
|
+
|
10
|
+
def fetch_results(es)
|
11
|
+
unless es.count?
|
12
|
+
base_class.search(es.to_elastic, es.search_options)
|
13
|
+
else
|
14
|
+
base_class.count(es.to_elastic, es.search_options)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Stretchy
|
2
|
+
class Record
|
3
|
+
|
4
|
+
def self.inherited(base)
|
5
|
+
|
6
|
+
base.class_eval do
|
7
|
+
|
8
|
+
extend Stretchy::Delegation::GatewayDelegation
|
9
|
+
|
10
|
+
include ActiveModel::Model
|
11
|
+
include ActiveModel::Attributes
|
12
|
+
include ActiveModel::AttributeAssignment
|
13
|
+
include ActiveModel::Naming
|
14
|
+
include ActiveModel::Conversion
|
15
|
+
include ActiveModel::Serialization
|
16
|
+
include ActiveModel::Serializers::JSON
|
17
|
+
include ActiveModel::Validations
|
18
|
+
include ActiveModel::Validations::Callbacks
|
19
|
+
extend ActiveModel::Callbacks
|
20
|
+
|
21
|
+
|
22
|
+
|
23
|
+
include Stretchy::Model::Callbacks
|
24
|
+
include Stretchy::Indexing::Bulk
|
25
|
+
include Stretchy::Persistence
|
26
|
+
include Stretchy::Associations
|
27
|
+
include Stretchy::Refreshable
|
28
|
+
include Stretchy::Common
|
29
|
+
include Stretchy::Scoping
|
30
|
+
include Stretchy::Utils
|
31
|
+
|
32
|
+
extend Stretchy::Delegation::DelegateCache
|
33
|
+
extend Stretchy::Querying
|
34
|
+
|
35
|
+
# Set up common attributes
|
36
|
+
attribute :id, :string #, default: lambda { SecureRandom.uuid }
|
37
|
+
attribute :created_at, :datetime, default: lambda { Time.now.utc }
|
38
|
+
attribute :updated_at, :datetime, default: lambda { Time.now.utc }
|
39
|
+
|
40
|
+
# Set the default sort key to be used in sort operations
|
41
|
+
default_sort_key :created_at
|
42
|
+
|
43
|
+
# Defaults max record size returned by #all
|
44
|
+
# overriden by #size
|
45
|
+
default_size 10000
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
def initialize(attributes = {})
|
50
|
+
self.assign_attributes(attributes) if attributes
|
51
|
+
super()
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,169 @@
|
|
1
|
+
module Stretchy
|
2
|
+
# This class represents a relation to Elasticsearch documents.
|
3
|
+
# It provides methods for querying and manipulating the documents.
|
4
|
+
class Relation
|
5
|
+
|
6
|
+
# These methods can accept multiple values.
|
7
|
+
MULTI_VALUE_METHODS = [:order, :where, :or_filter, :filter, :bind, :extending, :unscope, :skip_callbacks]
|
8
|
+
|
9
|
+
# These methods can accept a single value.
|
10
|
+
SINGLE_VALUE_METHODS = [:limit, :offset, :routing, :size]
|
11
|
+
|
12
|
+
# These methods cannot be used with the `delete_all` method.
|
13
|
+
INVALID_METHODS_FOR_DELETE_ALL = [:limit, :offset]
|
14
|
+
|
15
|
+
# All value methods.
|
16
|
+
VALUE_METHODS = MULTI_VALUE_METHODS + SINGLE_VALUE_METHODS
|
17
|
+
|
18
|
+
# Include modules.
|
19
|
+
include Relations::FinderMethods, Relations::SpawnMethods, Relations::QueryMethods, Relations::SearchOptionMethods, Delegation
|
20
|
+
|
21
|
+
# Getters.
|
22
|
+
attr_reader :klass, :loaded
|
23
|
+
alias :model :klass
|
24
|
+
alias :loaded? :loaded
|
25
|
+
|
26
|
+
# Delegates to the results array.
|
27
|
+
delegate :blank?, :empty?, :any?, :many?, to: :results
|
28
|
+
|
29
|
+
# Constructor.
|
30
|
+
#
|
31
|
+
# @param klass [Class] The class of the Elasticsearch documents.
|
32
|
+
# @param values [Hash] The initial values for the relation.
|
33
|
+
def initialize(klass, values={})
|
34
|
+
@klass = klass
|
35
|
+
@values = values
|
36
|
+
@offsets = {}
|
37
|
+
@loaded = false
|
38
|
+
end
|
39
|
+
|
40
|
+
# Builds a new Elasticsearch document.
|
41
|
+
#
|
42
|
+
# @param args [Array] The arguments to pass to the document constructor.
|
43
|
+
# @return [Object] The new document.
|
44
|
+
def build(*args)
|
45
|
+
@klass.new *args
|
46
|
+
end
|
47
|
+
|
48
|
+
# Returns the results of the relation as an array.
|
49
|
+
#
|
50
|
+
# @return [Array] The results of the relation.
|
51
|
+
def to_a
|
52
|
+
|
53
|
+
load
|
54
|
+
@records
|
55
|
+
end
|
56
|
+
alias :results :to_a
|
57
|
+
|
58
|
+
def response
|
59
|
+
to_a.response
|
60
|
+
end
|
61
|
+
|
62
|
+
# Returns the results of the relation as a JSON object.
|
63
|
+
#
|
64
|
+
# @param options [Hash] The options to pass to the `as_json` method.
|
65
|
+
# @return [Hash] The results of the relation as a JSON object.
|
66
|
+
def as_json(options = nil)
|
67
|
+
to_a.as_json(options)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Returns the Elasticsearch query for the relation.
|
71
|
+
#
|
72
|
+
# @return [Hash] The Elasticsearch query for the relation.
|
73
|
+
def to_elastic
|
74
|
+
query_builder.to_elastic
|
75
|
+
end
|
76
|
+
|
77
|
+
# Creates a new Elasticsearch document.
|
78
|
+
#
|
79
|
+
# @param args [Array] The arguments to pass to the document constructor.
|
80
|
+
# @param block [Proc] The block to pass to the document constructor.
|
81
|
+
# @return [Object] The new document.
|
82
|
+
def create(*args, &block)
|
83
|
+
scoping { @klass.create!(*args, &block) }
|
84
|
+
end
|
85
|
+
|
86
|
+
# Executes a block of code within the scope of the relation.
|
87
|
+
#
|
88
|
+
# @yield The block of code to execute.
|
89
|
+
def scoping
|
90
|
+
previous, klass.current_scope = klass.current_scope, self
|
91
|
+
yield
|
92
|
+
ensure
|
93
|
+
klass.current_scope = previous
|
94
|
+
end
|
95
|
+
|
96
|
+
# Loads the results of the relation.
|
97
|
+
#
|
98
|
+
# @return [Relation] The relation object.
|
99
|
+
def load
|
100
|
+
exec_queries unless loaded?
|
101
|
+
|
102
|
+
self
|
103
|
+
end
|
104
|
+
alias :fetch :load
|
105
|
+
|
106
|
+
# Deletes Elasticsearch documents.
|
107
|
+
#
|
108
|
+
# @param opts [Hash] The options for the delete operation.
|
109
|
+
def delete(opts=nil)
|
110
|
+
end
|
111
|
+
|
112
|
+
# Executes the Elasticsearch query for the relation.
|
113
|
+
#
|
114
|
+
# @return [Array] The results of the query.
|
115
|
+
def exec_queries
|
116
|
+
# Run safety callback
|
117
|
+
klass._circuit_breaker_callbacks.each do |cb|
|
118
|
+
current_scope_values = self.send("#{cb[:options][:in]}_values")
|
119
|
+
next if skip_callbacks_values.include? cb[:name]
|
120
|
+
valid = if cb[:callback].nil?
|
121
|
+
current_scope_values.collect(&:keys).flatten.include? cb[:name]
|
122
|
+
else
|
123
|
+
cb[:callback].call(current_scope_values.collect(&:keys).flatten, current_scope_values)
|
124
|
+
end
|
125
|
+
|
126
|
+
raise Stretchy::Errors::QueryOptionMissing, "#{cb[:name]} #{cb[:options][:message]}" unless valid
|
127
|
+
end
|
128
|
+
|
129
|
+
@records = @klass.fetch_results(query_builder)
|
130
|
+
|
131
|
+
@loaded = true
|
132
|
+
@records
|
133
|
+
end
|
134
|
+
|
135
|
+
# Returns the values of the relation as a hash.
|
136
|
+
#
|
137
|
+
# @return [Hash] The values of the relation.
|
138
|
+
def values
|
139
|
+
Hash[@values]
|
140
|
+
end
|
141
|
+
|
142
|
+
# Returns a string representation of the relation.
|
143
|
+
#
|
144
|
+
# @return [String] The string representation of the relation.
|
145
|
+
def inspect
|
146
|
+
begin
|
147
|
+
entries = to_a.results.take([size_value.to_i + 1, 11].compact.min).map!(&:inspect)
|
148
|
+
message = {}
|
149
|
+
message = {total: to_a.total, max: to_a.total}
|
150
|
+
message.merge!(aggregations: results.response.aggregations.keys) unless results.response.aggregations.nil?
|
151
|
+
message = message.each_pair.collect { |k,v| "#{k}: #{v}" }
|
152
|
+
message.unshift entries.join(', ') unless entries.size.zero?
|
153
|
+
"#<#{self.class.name} #{message.join(', ')}>"
|
154
|
+
rescue StandardError => e
|
155
|
+
e
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
private
|
160
|
+
|
161
|
+
# Returns the query builder for the relation.
|
162
|
+
#
|
163
|
+
# @return [QueryBuilder] The query builder for the relation.
|
164
|
+
def query_builder
|
165
|
+
Relations::QueryBuilder.new(values)
|
166
|
+
end
|
167
|
+
|
168
|
+
end
|
169
|
+
end
|