lycra 0.0.7 → 5.0.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 +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
|