lycra 0.0.7 → 5.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/lib/awesome_print/ext/lycra_attribute.rb +25 -0
- data/lib/awesome_print/ext/lycra_attributes.rb +25 -0
- data/lib/awesome_print/formatters/lycra_attribute_formatter.rb +58 -0
- data/lib/awesome_print/formatters/lycra_attributes_formatter.rb +26 -0
- data/lib/elasticsearch/model/adapters/lycra.rb +2 -0
- data/lib/elasticsearch/model/adapters/lycra/document.rb +120 -0
- data/lib/elasticsearch/model/adapters/lycra/multiple.rb +70 -0
- data/lib/lycra.rb +20 -3
- data/lib/lycra/attributes.rb +146 -0
- data/lib/lycra/attributes/attribute.rb +143 -0
- data/lib/lycra/attributes/collection.rb +38 -0
- data/lib/lycra/awesome_print.rb +6 -0
- data/lib/lycra/decorator.rb +12 -0
- data/lib/lycra/decorator/model.rb +33 -0
- data/lib/lycra/document.rb +11 -0
- data/lib/lycra/document/model.rb +37 -0
- data/lib/lycra/document/proxy.rb +485 -0
- data/lib/lycra/document/registry.rb +38 -0
- data/lib/lycra/engine.rb +4 -1
- data/lib/lycra/errors.rb +18 -3
- data/lib/lycra/import.rb +116 -0
- data/lib/lycra/inheritance.rb +17 -0
- data/lib/lycra/monkeypatches.rb +7 -37
- data/lib/lycra/multidoc.rb +22 -0
- data/lib/lycra/search.rb +2 -2
- data/lib/lycra/serializer.rb +19 -0
- data/lib/lycra/serializer/model.rb +35 -0
- data/lib/lycra/types.rb +165 -0
- data/lib/lycra/version.rb +2 -2
- data/spec/spec_helper.rb +13 -8
- data/spec/support/foo.rb +120 -0
- metadata +53 -31
- data/app/documents/lycra/document.rb +0 -191
- data/lib/lycra/model.rb +0 -62
@@ -0,0 +1,143 @@
|
|
1
|
+
module Lycra
|
2
|
+
module Attributes
|
3
|
+
class Attribute
|
4
|
+
attr_reader :resolved, :required, :klass
|
5
|
+
|
6
|
+
def initialize(name=nil, type=nil, *args, **opts, &block)
|
7
|
+
@name = name
|
8
|
+
@name ||= opts[:name]
|
9
|
+
|
10
|
+
@nested_type = type.is_a?(Array)
|
11
|
+
@type = [type].flatten.compact.first
|
12
|
+
@type ||= [opts[:type]].flatten.compact.first
|
13
|
+
if @type.ancestors.include?(Lycra::Attributes)
|
14
|
+
@type = Lycra::Types.custom(@type)
|
15
|
+
end
|
16
|
+
|
17
|
+
@klass = opts[:klass]
|
18
|
+
|
19
|
+
@mappings = opts[:mappings] || opts[:mapping]
|
20
|
+
|
21
|
+
@resolver = args.find { |arg| arg.is_a?(Proc) || arg.is_a?(Symbol) }
|
22
|
+
@resolver = opts[:resolve] if opts.key?(:resolve)
|
23
|
+
@resolver = opts[:resolver] if opts.key?(:resolver)
|
24
|
+
|
25
|
+
@description = args.find { |arg| arg.is_a?(String) }
|
26
|
+
@description = opts[:description] if opts.key?(:description)
|
27
|
+
|
28
|
+
@required = opts[:required] || false
|
29
|
+
@cache = opts[:cache] || false
|
30
|
+
|
31
|
+
instance_exec &block if block_given?
|
32
|
+
end
|
33
|
+
|
34
|
+
def name(name=nil)
|
35
|
+
@name = name if name
|
36
|
+
@name
|
37
|
+
end
|
38
|
+
|
39
|
+
def type(type=nil)
|
40
|
+
if type
|
41
|
+
@nested_type = type.is_a?(Array)
|
42
|
+
@type = [type].flatten.compact.first
|
43
|
+
if @type.ancestors.include?(Lycra::Attributes)
|
44
|
+
@type = Lycra::Types.custom(@type)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
@type
|
48
|
+
end
|
49
|
+
|
50
|
+
def nested?
|
51
|
+
!!@nested_type
|
52
|
+
end
|
53
|
+
|
54
|
+
def mappings(mappings=nil)
|
55
|
+
@mappings = mappings if mappings
|
56
|
+
|
57
|
+
map_type = type.type
|
58
|
+
if map_type.is_a?(Class) && map_type.ancestors.include?(Lycra::Attributes)
|
59
|
+
map_type = @nested_type ? "nested" : "object"
|
60
|
+
end
|
61
|
+
|
62
|
+
{type: map_type}.merge(@mappings || {})
|
63
|
+
end
|
64
|
+
alias_method :mapping, :mappings
|
65
|
+
|
66
|
+
def description(description=nil)
|
67
|
+
@description = description if description
|
68
|
+
@description
|
69
|
+
end
|
70
|
+
|
71
|
+
def resolver
|
72
|
+
@resolver ||= name.to_sym
|
73
|
+
end
|
74
|
+
|
75
|
+
def required!
|
76
|
+
@required = true
|
77
|
+
end
|
78
|
+
|
79
|
+
def required?
|
80
|
+
!!@required
|
81
|
+
end
|
82
|
+
|
83
|
+
def resolve!(document, *args, **ctxt)
|
84
|
+
@resolved ||= begin
|
85
|
+
# TODO wrap this whole block in cache if caching is enabled
|
86
|
+
if resolver.is_a?(Proc)
|
87
|
+
result = resolver.call(document.subject, args, ctxt)
|
88
|
+
elsif resolver.is_a?(Symbol)
|
89
|
+
if document.methods.include?(resolver)
|
90
|
+
result = document.send(resolver)
|
91
|
+
else
|
92
|
+
result = document.subject.send(resolver)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
rslvd = type.new(result)
|
97
|
+
|
98
|
+
unless rslvd.valid?(required?, nested?)
|
99
|
+
rslvd_type = rslvd.type
|
100
|
+
rslvd_type = "array[#{rslvd.type}]" if nested?
|
101
|
+
raise Lycra::AttributeError,
|
102
|
+
"Invalid value #{rslvd.value} (#{rslvd.value.class.name}) " +
|
103
|
+
"for type '#{rslvd_type}' in field #{name} on #{document}"
|
104
|
+
end
|
105
|
+
|
106
|
+
rslvd.transform
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def resolved?
|
111
|
+
instance_variable_defined? :@resolved
|
112
|
+
end
|
113
|
+
|
114
|
+
def reload
|
115
|
+
remove_instance_variable :@resolved
|
116
|
+
self
|
117
|
+
end
|
118
|
+
|
119
|
+
def as_json(options={})
|
120
|
+
{
|
121
|
+
name: name,
|
122
|
+
type: type.type,
|
123
|
+
required: required,
|
124
|
+
description: description,
|
125
|
+
mappings: mappings,
|
126
|
+
resolver: resolver.is_a?(Symbol) ? resolver : resolver.to_s
|
127
|
+
}
|
128
|
+
end
|
129
|
+
|
130
|
+
private
|
131
|
+
|
132
|
+
def resolve(resolver=nil, &block)
|
133
|
+
@resolver = resolver if resolver
|
134
|
+
@resolver = block if block_given?
|
135
|
+
@resolver
|
136
|
+
end
|
137
|
+
|
138
|
+
def types
|
139
|
+
Lycra::Types
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Lycra
|
2
|
+
module Attributes
|
3
|
+
class Collection
|
4
|
+
include Enumerable
|
5
|
+
|
6
|
+
attr_reader :attributes
|
7
|
+
|
8
|
+
def initialize(klass, attributes={})
|
9
|
+
@klass = klass
|
10
|
+
@attributes = attributes
|
11
|
+
end
|
12
|
+
|
13
|
+
def dup(klass=nil)
|
14
|
+
self.class.new(klass || @klass, attributes.map { |k,attr|
|
15
|
+
duped = attr.dup
|
16
|
+
duped.instance_variable_set(:@klass, klass || @klass)
|
17
|
+
[k, duped]
|
18
|
+
}.to_h)
|
19
|
+
end
|
20
|
+
|
21
|
+
def each(&block)
|
22
|
+
@attributes.each(&block)
|
23
|
+
end
|
24
|
+
|
25
|
+
def method_missing(meth, *args, &block)
|
26
|
+
if @attributes.respond_to?(meth)
|
27
|
+
@attributes.send(meth, *args, &block)
|
28
|
+
else
|
29
|
+
super
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def respond_to_missing?(meth, include_private=false)
|
34
|
+
@attributes.respond_to?(meth, include_private) || super
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Lycra
|
2
|
+
module Decorator
|
3
|
+
module Model
|
4
|
+
def self.included(base)
|
5
|
+
base.send :extend, ClassMethods
|
6
|
+
base.send :include, InstanceMethods
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
def decorator(klass=nil)
|
11
|
+
@_lycra_decorator = klass if klass
|
12
|
+
@_lycra_decorator || ("#{name}Decorator".constantize rescue nil)
|
13
|
+
end
|
14
|
+
|
15
|
+
def decorator=(klass)
|
16
|
+
decorator klass
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
module InstanceMethods
|
21
|
+
def reload
|
22
|
+
@decorator = nil
|
23
|
+
super
|
24
|
+
end
|
25
|
+
|
26
|
+
def decorator(decorator_class=nil)
|
27
|
+
return decorator_class.new(self) if decorator_class
|
28
|
+
@decorator ||= self.class.decorator.new(self)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Lycra
|
2
|
+
module Document
|
3
|
+
module Model
|
4
|
+
def self.included(base)
|
5
|
+
base.send :extend, ClassMethods
|
6
|
+
base.send :include, InstanceMethods
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
delegate :__lycra__, :index_name, :document_type, :import, :search, to: :document
|
11
|
+
|
12
|
+
def document(klass=nil)
|
13
|
+
@_lycra_document = klass if klass
|
14
|
+
@_lycra_document || ("#{name}Document".constantize rescue nil)
|
15
|
+
end
|
16
|
+
|
17
|
+
def document=(klass)
|
18
|
+
document klass
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
module InstanceMethods
|
23
|
+
delegate :__lycra__, :as_indexed_json, :indexed, :indexed?, :index!, to: :document
|
24
|
+
|
25
|
+
def reload
|
26
|
+
@document = nil
|
27
|
+
super
|
28
|
+
end
|
29
|
+
|
30
|
+
def document(document_class=nil)
|
31
|
+
return document_class.new(self) if document_class
|
32
|
+
@document ||= self.class.document.new(self)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,485 @@
|
|
1
|
+
require 'lycra/document/registry'
|
2
|
+
|
3
|
+
module Lycra
|
4
|
+
module Document
|
5
|
+
module Proxy
|
6
|
+
def self.included(base)
|
7
|
+
base.send :extend, ClassMethods
|
8
|
+
base.send :include, InstanceMethods
|
9
|
+
|
10
|
+
base.class_eval do
|
11
|
+
def self.__lycra__(&block)
|
12
|
+
@__lycra__ ||= ClassProxy.new(self)
|
13
|
+
@__lycra__.instance_eval(&block) if block_given?
|
14
|
+
@__lycra__
|
15
|
+
end
|
16
|
+
|
17
|
+
def __lycra__(&block)
|
18
|
+
@__lycra__ ||= InstanceProxy.new(self)
|
19
|
+
@__lycra__.instance_eval(&block) if block_given?
|
20
|
+
@__lycra__
|
21
|
+
end
|
22
|
+
|
23
|
+
self.__lycra__.class_eval do
|
24
|
+
include ::Elasticsearch::Model::Importing::ClassMethods
|
25
|
+
include ::Elasticsearch::Model::Adapter.from_class(base).importing_mixin
|
26
|
+
end
|
27
|
+
|
28
|
+
Registry.add(base)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
module ClassMethods
|
33
|
+
delegate :alias_name, :index_name, :document_type, :search,
|
34
|
+
:alias_exists?, :index_exists?, :index_aliased?, :aliased_index,
|
35
|
+
:index_fingerprint, to: :__lycra__
|
36
|
+
|
37
|
+
def inherited(child)
|
38
|
+
super if defined?(super)
|
39
|
+
|
40
|
+
# resets the proxy so it gets recreated for the new class
|
41
|
+
child.send :instance_variable_set, :@__lycra__, nil
|
42
|
+
child.send :instance_variable_set, :@_lycra_import_scope, self.import_scope
|
43
|
+
|
44
|
+
child.class_eval do
|
45
|
+
self.__lycra__.class_eval do
|
46
|
+
include ::Elasticsearch::Model::Importing::ClassMethods
|
47
|
+
include ::Elasticsearch::Model::Adapter.from_class(child).importing_mixin
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
Registry.add(child)
|
52
|
+
end
|
53
|
+
|
54
|
+
def import_scope(scope=nil, &block)
|
55
|
+
@_lycra_import_scope = scope if scope
|
56
|
+
@_lycra_import_scope = block if block_given?
|
57
|
+
@_lycra_import_scope
|
58
|
+
end
|
59
|
+
|
60
|
+
def import_scope=(scope)
|
61
|
+
import_scope scope
|
62
|
+
end
|
63
|
+
|
64
|
+
def create_alias!(options={})
|
65
|
+
raise Lycra::AbstractClassError, "Cannot create aliases using an abstract class" if abstract?
|
66
|
+
__lycra__.create_alias!(options)
|
67
|
+
end
|
68
|
+
|
69
|
+
def create_alias(options={})
|
70
|
+
create_alias!(options)
|
71
|
+
rescue => e
|
72
|
+
Lycra.configuration.logger.error(e.message)
|
73
|
+
return false
|
74
|
+
end
|
75
|
+
|
76
|
+
def create_index!(options={})
|
77
|
+
raise Lycra::AbstractClassError, "Cannot create indices using an abstract class" if abstract?
|
78
|
+
__lycra__.create_index!(options)
|
79
|
+
__lycra__.create_alias!(options) unless alias_exists?
|
80
|
+
end
|
81
|
+
|
82
|
+
def create_index(options={})
|
83
|
+
create_index!(options)
|
84
|
+
rescue => e
|
85
|
+
Lycra.configuration.logger.error(e.message)
|
86
|
+
return false
|
87
|
+
end
|
88
|
+
|
89
|
+
def delete_alias!(options={})
|
90
|
+
raise Lycra::AbstractClassError, "Cannot delete aliases using an abstract class" if abstract?
|
91
|
+
__lycra__.delete_alias!(options)
|
92
|
+
end
|
93
|
+
|
94
|
+
def delete_alias(options={})
|
95
|
+
delete_alias!(options)
|
96
|
+
rescue => e
|
97
|
+
Lycra.configuration.logger.error(e.message)
|
98
|
+
return false
|
99
|
+
end
|
100
|
+
|
101
|
+
def delete_index!(options={})
|
102
|
+
raise Lycra::AbstractClassError, "Cannot delete indices using an abstract class" if abstract?
|
103
|
+
__lycra__.delete_alias!(options) if alias_exists?
|
104
|
+
__lycra__.delete_index!(options)
|
105
|
+
end
|
106
|
+
|
107
|
+
def delete_index(options={})
|
108
|
+
delete_index!(options)
|
109
|
+
rescue => e
|
110
|
+
Lycra.configuration.logger.error(e.message)
|
111
|
+
return false
|
112
|
+
end
|
113
|
+
|
114
|
+
def refresh_index!(options={})
|
115
|
+
raise Lycra::AbstractClassError, "Cannot refresh indices using an abstract class" if abstract?
|
116
|
+
__lycra__.refresh_index!(options)
|
117
|
+
end
|
118
|
+
|
119
|
+
def refresh_index(options={})
|
120
|
+
refresh_index!(options)
|
121
|
+
rescue => e
|
122
|
+
Lycra.configuration.logger.error(e.message)
|
123
|
+
return false
|
124
|
+
end
|
125
|
+
|
126
|
+
def import!(options={}, &block)
|
127
|
+
raise Lycra::AbstractClassError, "Cannot import using an abstract class" if abstract?
|
128
|
+
|
129
|
+
options[:scope] ||= import_scope if import_scope.is_a?(String) || import_scope.is_a?(Symbol)
|
130
|
+
options[:query] ||= import_scope if import_scope.is_a?(Proc)
|
131
|
+
|
132
|
+
__lycra__.import(options, &block)
|
133
|
+
end
|
134
|
+
|
135
|
+
def import(options={}, &block)
|
136
|
+
import!(options, &block)
|
137
|
+
rescue => e
|
138
|
+
Lycra.configuration.logger.error(e.message)
|
139
|
+
return false
|
140
|
+
end
|
141
|
+
|
142
|
+
def update!(options={}, &block)
|
143
|
+
raise Lycra::AbstractClassError, "Cannot update using an abstract class" if abstract?
|
144
|
+
|
145
|
+
scope = options[:scope] || options[:query] || import_scope
|
146
|
+
if scope.is_a?(Proc)
|
147
|
+
scope = subject_type.instance_exec(&scope)
|
148
|
+
elsif scope.is_a?(String) || scope.is_a?(Symbol)
|
149
|
+
scope = subject_type.send(scope)
|
150
|
+
elsif scope.nil?
|
151
|
+
scope = subject_type.all
|
152
|
+
end
|
153
|
+
|
154
|
+
scope.find_in_batches(batch_size: (options[:batch_size] || 200)).each do |batch|
|
155
|
+
json_options = options.select { |k,v| [:only,:except].include?(k) }
|
156
|
+
items = batch.map do |record|
|
157
|
+
{ update: {
|
158
|
+
_index: index_name,
|
159
|
+
_type: document_type,
|
160
|
+
_id: record.id,
|
161
|
+
data: {
|
162
|
+
doc: new(record).resolve!(json_options)
|
163
|
+
}.stringify_keys
|
164
|
+
}.stringify_keys
|
165
|
+
}.stringify_keys
|
166
|
+
end
|
167
|
+
|
168
|
+
updated = __lycra__.client.bulk(body: items)
|
169
|
+
|
170
|
+
missing = updated['items'].map do |miss|
|
171
|
+
if miss['update'].key?('error') &&
|
172
|
+
miss['update']['error']['type'] == 'document_missing_exception'
|
173
|
+
|
174
|
+
update = miss['update']
|
175
|
+
item = items.find { |i| i['update']['_id'].to_s == miss['update']['_id'] }['update']
|
176
|
+
if json_options.empty?
|
177
|
+
data = item['data']['doc']
|
178
|
+
else
|
179
|
+
data = new(subject_type.find(update['_id'])).resolve!
|
180
|
+
end
|
181
|
+
|
182
|
+
{ index: {
|
183
|
+
_index: update['_index'],
|
184
|
+
_type: update['_type'],
|
185
|
+
_id: update['_id'],
|
186
|
+
data: data
|
187
|
+
}.stringify_keys
|
188
|
+
}.stringify_keys
|
189
|
+
else
|
190
|
+
nil
|
191
|
+
end
|
192
|
+
end.compact
|
193
|
+
|
194
|
+
if missing.count > 0
|
195
|
+
indexed = __lycra__.client.bulk body: missing
|
196
|
+
|
197
|
+
updated['items'] = updated['items'].map do |item|
|
198
|
+
miss = indexed['items'].find { |i| i['index']['_id'] == item['update']['_id'] }
|
199
|
+
miss || item
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
yield(updated) if block_given?
|
204
|
+
end
|
205
|
+
|
206
|
+
return true
|
207
|
+
end
|
208
|
+
|
209
|
+
def update(options={}, &block)
|
210
|
+
update!(options, &block)
|
211
|
+
rescue => e
|
212
|
+
Lycra.configuration.logger.error(e.message)
|
213
|
+
return false
|
214
|
+
end
|
215
|
+
|
216
|
+
def delete!(options={}, &block)
|
217
|
+
raise Lycra::AbstractClassError, "Cannot delete using an abstract class" if abstract?
|
218
|
+
|
219
|
+
scope = options[:scope] || options[:query] || import_scope
|
220
|
+
if scope.is_a?(Proc)
|
221
|
+
scope = subject_type.instance_exec(&scope)
|
222
|
+
elsif scope.is_a?(String) || scope.is_a?(Symbol)
|
223
|
+
scope = subject_type.send(scope)
|
224
|
+
elsif scope.nil?
|
225
|
+
scope = subject_type.all
|
226
|
+
end
|
227
|
+
|
228
|
+
scope.find_in_batches(batch_size: (options[:batch_size] || 200)).each do |batch|
|
229
|
+
items = batch.map do |record|
|
230
|
+
{ delete: {
|
231
|
+
_index: index_name,
|
232
|
+
_type: document_type,
|
233
|
+
_id: record.id
|
234
|
+
}.stringify_keys
|
235
|
+
}.stringify_keys
|
236
|
+
end
|
237
|
+
|
238
|
+
deleted = __lycra__.client.bulk(body: items)
|
239
|
+
|
240
|
+
yield(deleted) if block_given?
|
241
|
+
end
|
242
|
+
|
243
|
+
return true
|
244
|
+
end
|
245
|
+
|
246
|
+
def delete(options={}, &block)
|
247
|
+
delete!(options, &block)
|
248
|
+
rescue => e
|
249
|
+
Lycra.configuration.logger.error(e.message)
|
250
|
+
return false
|
251
|
+
end
|
252
|
+
|
253
|
+
def as_indexed_json(subj, options={})
|
254
|
+
resolve!(subj).as_json(options)
|
255
|
+
end
|
256
|
+
|
257
|
+
def as_json(options={})
|
258
|
+
{ index: index_name,
|
259
|
+
document: document_type,
|
260
|
+
subject: subject_type.name }
|
261
|
+
.merge(attributes.map { |k,a| [a.name, a.type.type] }.to_h)
|
262
|
+
.as_json(options)
|
263
|
+
end
|
264
|
+
|
265
|
+
def inspect
|
266
|
+
"#{name}(index: #{index_name}, document: #{document_type}, subject: #{subject_type}, #{attributes.map { |key,attr| "#{attr.name}: #{attr.nested? ? "[#{attr.type.type}]" : attr.type.type}"}.join(', ')})"
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
module InstanceMethods
|
271
|
+
delegate :index_name, :document_type, to: :class
|
272
|
+
|
273
|
+
def as_indexed_json(options={})
|
274
|
+
resolve!.as_json(options)
|
275
|
+
end
|
276
|
+
|
277
|
+
def index!(options={})
|
278
|
+
raise Lycra::AbstractClassError, "Cannot index using an abstract class" if abstract?
|
279
|
+
|
280
|
+
@indexed = nil
|
281
|
+
__lycra__.index_document(options)
|
282
|
+
end
|
283
|
+
|
284
|
+
def update!(options={})
|
285
|
+
raise Lycra::AbstractClassError, "Cannot update using an abstract class" if abstract?
|
286
|
+
|
287
|
+
@indexed = nil
|
288
|
+
__lycra__.update_document(options)
|
289
|
+
end
|
290
|
+
|
291
|
+
def update_attributes!(*attrs, **options)
|
292
|
+
raise Lycra::AbstractClassError, "Cannot update using an abstract class" if abstract?
|
293
|
+
|
294
|
+
if attrs.empty?
|
295
|
+
document_attrs = resolve!
|
296
|
+
else
|
297
|
+
document_attrs = resolve!(only: attrs)
|
298
|
+
end
|
299
|
+
|
300
|
+
@indexed = nil
|
301
|
+
__lycra__.update_document_attributes(document_attrs, options)
|
302
|
+
rescue Elasticsearch::Transport::Transport::Errors::NotFound => e
|
303
|
+
index!(options)
|
304
|
+
end
|
305
|
+
|
306
|
+
def _indexed
|
307
|
+
@indexed ||= self.class.search({query: {terms: {_id: [subject.id]}}}).results.first
|
308
|
+
end
|
309
|
+
|
310
|
+
def indexed
|
311
|
+
_indexed&._source&.to_h
|
312
|
+
end
|
313
|
+
|
314
|
+
def indexed?
|
315
|
+
!!indexed
|
316
|
+
end
|
317
|
+
|
318
|
+
def _indexed?
|
319
|
+
!!@indexed
|
320
|
+
end
|
321
|
+
|
322
|
+
def reload
|
323
|
+
super if defined?(super)
|
324
|
+
@indexed = nil
|
325
|
+
self
|
326
|
+
end
|
327
|
+
|
328
|
+
def as_json(options={})
|
329
|
+
resolve! unless resolved?
|
330
|
+
|
331
|
+
{ index: self.class.index_name,
|
332
|
+
document: self.class.document_type,
|
333
|
+
subject: self.class.subject_type.name,
|
334
|
+
resolved: resolved.map { |k,a| [k, a.as_json] }.to_h,
|
335
|
+
indexed: indexed? && indexed.map { |k,a| [k, a.as_json] }.to_h }
|
336
|
+
.as_json(options)
|
337
|
+
end
|
338
|
+
|
339
|
+
def inspect
|
340
|
+
attr_str = "#{attributes.map { |key,attr| "#{key}: #{(resolved? && resolved[key].try(:to_json)) || (_indexed? && indexed[key.to_s].try(:to_json)) || (attr.nested? ? "[#{attr.type.type}]" : attr.type.type)}"}.join(', ')}>"
|
341
|
+
"#<#{self.class.name} index: #{self.class.index_name}, document: #{self.class.document_type}, subject: #{self.class.subject_type}, #{attr_str}"
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
module BaseProxy
|
346
|
+
attr_reader :target
|
347
|
+
|
348
|
+
def initialize(target)
|
349
|
+
@target = target
|
350
|
+
end
|
351
|
+
|
352
|
+
def client=(client)
|
353
|
+
@client = client
|
354
|
+
end
|
355
|
+
|
356
|
+
def client
|
357
|
+
@client ||= Lycra.client
|
358
|
+
end
|
359
|
+
|
360
|
+
def method_missing(meth, *args, &block)
|
361
|
+
return target.send(meth, *args, &block) if target.respond_to?(meth)
|
362
|
+
super
|
363
|
+
end
|
364
|
+
|
365
|
+
def respond_to_missing?(meth, priv=false)
|
366
|
+
target.respond_to?(meth, priv) || super
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
370
|
+
class ClassProxy
|
371
|
+
include BaseProxy
|
372
|
+
delegate :subject_type, :import_scope, to: :target
|
373
|
+
|
374
|
+
# this is copying their (annoying) pattern
|
375
|
+
class_eval do
|
376
|
+
include ::Elasticsearch::Model::Indexing::ClassMethods
|
377
|
+
include ::Elasticsearch::Model::Searching::ClassMethods
|
378
|
+
end
|
379
|
+
|
380
|
+
def index_fingerprint(hashed=nil)
|
381
|
+
@_lycra_index_fingerprint = hashed if hashed
|
382
|
+
@_lycra_index_fingerprint ||= Digest::MD5.hexdigest(mappings.to_s)
|
383
|
+
|
384
|
+
if @_lycra_index_fingerprint.is_a?(Proc)
|
385
|
+
instance_exec(&@_lycra_index_fingerprint)
|
386
|
+
else
|
387
|
+
@_lycra_index_fingerprint
|
388
|
+
end
|
389
|
+
end
|
390
|
+
|
391
|
+
def index_fingerprint=(hashed)
|
392
|
+
index_fingerprint hashed
|
393
|
+
end
|
394
|
+
|
395
|
+
def alias_name(index_alias=nil)
|
396
|
+
@_lycra_alias_name = index_alias if index_alias
|
397
|
+
@_lycra_alias_name ||= document_type.pluralize
|
398
|
+
end
|
399
|
+
|
400
|
+
def alias_name=(index_alias)
|
401
|
+
alias_name index_alias
|
402
|
+
end
|
403
|
+
|
404
|
+
def index_name(index=nil)
|
405
|
+
@_lycra_index_name = index if index
|
406
|
+
@_lycra_index_name ||= "#{alias_name}-#{index_fingerprint}"
|
407
|
+
end
|
408
|
+
|
409
|
+
def index_name=(index)
|
410
|
+
index_name index
|
411
|
+
end
|
412
|
+
|
413
|
+
def document_type(type=nil)
|
414
|
+
@_lycra_document_type = type if type
|
415
|
+
@_lycra_document_type ||= target.name.demodulize.gsub(/Document\Z/, '').underscore
|
416
|
+
end
|
417
|
+
|
418
|
+
def document_type=(type)
|
419
|
+
document_type type
|
420
|
+
end
|
421
|
+
|
422
|
+
def mapping(mapping=nil)
|
423
|
+
@_lycra_mapping = mapping if mapping
|
424
|
+
{ document_type.to_s.underscore.to_sym => (@_lycra_mapping || {}).merge({
|
425
|
+
properties: attributes.map { |name, type| [name, type.mapping] }.to_h
|
426
|
+
}) }
|
427
|
+
end
|
428
|
+
alias_method :mappings, :mapping
|
429
|
+
|
430
|
+
def settings(settings=nil)
|
431
|
+
@_lycra_settings = settings if settings
|
432
|
+
@_lycra_settings || {}
|
433
|
+
end
|
434
|
+
|
435
|
+
def search(query_or_payload, options={})
|
436
|
+
options = {index: alias_name}.merge(options)
|
437
|
+
super(query_or_payload, options)
|
438
|
+
end
|
439
|
+
|
440
|
+
def alias_exists?
|
441
|
+
client.indices.exists_alias? name: alias_name
|
442
|
+
end
|
443
|
+
|
444
|
+
def aliased_index
|
445
|
+
client.indices.get_alias(name: alias_name).keys.first
|
446
|
+
end
|
447
|
+
|
448
|
+
def index_aliased?
|
449
|
+
alias_exists? && aliased_index == index_name
|
450
|
+
end
|
451
|
+
|
452
|
+
def create_alias!(options={})
|
453
|
+
# TODO custom error classes
|
454
|
+
raise "Alias already exists" if alias_exists?
|
455
|
+
client.indices.put_alias name: alias_name, index: index_name
|
456
|
+
end
|
457
|
+
|
458
|
+
def delete_alias!(options={})
|
459
|
+
# TODO custom error classes
|
460
|
+
raise "Alias does not exists" unless alias_exists?
|
461
|
+
client.indices.delete_alias name: alias_name, index: aliased_index
|
462
|
+
end
|
463
|
+
end
|
464
|
+
|
465
|
+
class InstanceProxy
|
466
|
+
include BaseProxy
|
467
|
+
delegate :index_name, :document_type, to: :klass_proxy
|
468
|
+
delegate :subject_type, to: :klass
|
469
|
+
|
470
|
+
# this is copying their (annoying) pattern
|
471
|
+
class_eval do
|
472
|
+
include ::Elasticsearch::Model::Indexing::InstanceMethods
|
473
|
+
end
|
474
|
+
|
475
|
+
def klass
|
476
|
+
target.class
|
477
|
+
end
|
478
|
+
|
479
|
+
def klass_proxy
|
480
|
+
klass.__lycra__
|
481
|
+
end
|
482
|
+
end
|
483
|
+
end
|
484
|
+
end
|
485
|
+
end
|