stretchy-model 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|