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.
@@ -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,6 @@
1
+ if defined?(AwesomePrint)
2
+ require 'awesome_print/formatters/lycra_attribute_formatter'
3
+ require 'awesome_print/formatters/lycra_attributes_formatter'
4
+ require 'awesome_print/ext/lycra_attribute'
5
+ require 'awesome_print/ext/lycra_attributes'
6
+ end
@@ -0,0 +1,12 @@
1
+ module Lycra
2
+ module Decorator
3
+ def self.included(base)
4
+ base.send :include, Attributes
5
+ base.send :extend, Inheritance
6
+ end
7
+
8
+ def as_json(options={})
9
+ resolve!(subject).as_json(options)
10
+ end
11
+ end
12
+ 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,11 @@
1
+ require 'lycra/document/proxy'
2
+
3
+ module Lycra
4
+ module Document
5
+ def self.included(base)
6
+ base.send :include, Attributes
7
+ base.send :extend, Inheritance
8
+ base.send :include, Proxy
9
+ end
10
+ end
11
+ 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