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,38 @@
|
|
1
|
+
module Lycra
|
2
|
+
module Document
|
3
|
+
class Registry
|
4
|
+
include Enumerable
|
5
|
+
|
6
|
+
class << self
|
7
|
+
delegate :add, :documents, :to_a, to: :__instance
|
8
|
+
|
9
|
+
def __instance
|
10
|
+
@instance ||= new
|
11
|
+
end
|
12
|
+
|
13
|
+
def all
|
14
|
+
new(documents.reject(&:abstract?))
|
15
|
+
end
|
16
|
+
|
17
|
+
def abstract
|
18
|
+
new(documents.select(&:abstract?))
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
attr_reader :documents
|
23
|
+
delegate :each, to: :documents
|
24
|
+
|
25
|
+
def initialize(documents=[])
|
26
|
+
@documents = documents
|
27
|
+
end
|
28
|
+
|
29
|
+
def add(klass)
|
30
|
+
@documents << klass
|
31
|
+
end
|
32
|
+
|
33
|
+
def method_missing(meth, *args, **opts, &block)
|
34
|
+
documents.map { |doc| doc.send(meth, *args, **opts, &block) }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/lib/lycra/engine.rb
CHANGED
@@ -10,7 +10,10 @@ module Lycra
|
|
10
10
|
|
11
11
|
initializer "lycra.elasticsearch.client" do |app|
|
12
12
|
Elasticsearch::Model.client = Lycra.client
|
13
|
-
|
13
|
+
end
|
14
|
+
|
15
|
+
initializer "lycra.load_documents" do
|
16
|
+
Dir[File.join(Rails.root, "app/documents/**/*.rb")].each { |f| require f }
|
14
17
|
end
|
15
18
|
end
|
16
19
|
end
|
data/lib/lycra/errors.rb
CHANGED
@@ -1,4 +1,15 @@
|
|
1
1
|
module Lycra
|
2
|
+
class AttributeError < StandardError; end
|
3
|
+
class AbstractClassError < StandardError; end
|
4
|
+
|
5
|
+
class MissingSubjectError < StandardError
|
6
|
+
def initialize(document=nil)
|
7
|
+
msg = "This document was initialized with a nil subject. Make sure you set @subject before resolving or call `super` from your initializer."
|
8
|
+
|
9
|
+
super(msg)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
2
13
|
class DocumentNotFoundError < StandardError
|
3
14
|
attr_reader :model
|
4
15
|
|
@@ -10,7 +21,9 @@ module Lycra
|
|
10
21
|
You must define a corresponding document class for all models utilizing Lycra::Model. For example, if your model is called BlogPost:
|
11
22
|
|
12
23
|
# /app/documents/blog_post_document.rb
|
13
|
-
class BlogPostDocument
|
24
|
+
class BlogPostDocument
|
25
|
+
include Lycra::Document
|
26
|
+
|
14
27
|
index_name 'blog-posts'
|
15
28
|
end
|
16
29
|
MSG
|
@@ -18,8 +31,10 @@ MSG
|
|
18
31
|
msg = <<MSG
|
19
32
|
You must define a corresponding document class for your #{model.name} model. For example:
|
20
33
|
|
21
|
-
# /app/documents/#{model.name.
|
22
|
-
class #{model.name}Document
|
34
|
+
# /app/documents/#{model.name.underscore}_document.rb
|
35
|
+
class #{model.name}Document
|
36
|
+
include Lycra::Document
|
37
|
+
|
23
38
|
index_name '#{model.name.parameterize.pluralize}'
|
24
39
|
end
|
25
40
|
MSG
|
data/lib/lycra/import.rb
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
module Lycra
|
2
|
+
class Import
|
3
|
+
attr_reader :documents
|
4
|
+
|
5
|
+
def self.create(*args, &block)
|
6
|
+
new(*args).create(&block)
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.recreate(*args, &block)
|
10
|
+
new(*args).recreate(&block)
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.destroy(*args, &block)
|
14
|
+
new(*args).destroy(&block)
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.import(*args, **opts, &block)
|
18
|
+
new(*args).import(**opts, &block)
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.rotate(*args, **opts, &block)
|
22
|
+
new(*args).rotate(**opts, &block)
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.reindex(*args, **opts, &block)
|
26
|
+
new(*args).reindex(**opts, &block)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.truncate(*args, **opts, &block)
|
30
|
+
new(*args).truncate(**opts, &block)
|
31
|
+
end
|
32
|
+
|
33
|
+
def initialize(*documents)
|
34
|
+
@documents = documents
|
35
|
+
@documents = Lycra::Document::Registry.all if @documents.empty?
|
36
|
+
end
|
37
|
+
|
38
|
+
def scopes
|
39
|
+
documents.map do |doc|
|
40
|
+
if doc.import_scope.is_a?(Proc)
|
41
|
+
doc.subject_type.instance_exec(&doc.import_scope)
|
42
|
+
elsif doc.import_scope.is_a?(String) || doc.import_scope.is_a?(Symbol)
|
43
|
+
doc.subject_type.send(doc.import_scope)
|
44
|
+
else
|
45
|
+
doc.subject_type.all
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def total
|
51
|
+
scopes.sum { |scope| scope.count }
|
52
|
+
end
|
53
|
+
|
54
|
+
def create(&block)
|
55
|
+
documents.each do |document|
|
56
|
+
document.create_index! unless document.index_exists?
|
57
|
+
yield(document) if block_given?
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def recreate(&block)
|
62
|
+
documents.each do |document|
|
63
|
+
document.delete_alias! if document.alias_exists?
|
64
|
+
document.delete_index! if document.index_exists?
|
65
|
+
document.create_index!
|
66
|
+
yield(document) if block_given?
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def destroy(&block)
|
71
|
+
documents.each do |document|
|
72
|
+
document.delete_alias! if document.alias_exists?
|
73
|
+
document.delete_index! if document.index_exists?
|
74
|
+
yield(document) if block_given?
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def import(batch_size: 200, scope: nil, query: nil, &block)
|
79
|
+
recreate
|
80
|
+
|
81
|
+
documents.each do |document|
|
82
|
+
document.import! batch_size: batch_size, scope: scope, query: query, &block
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def rotate(batch_size: 200, scope: nil, query: nil, &block)
|
87
|
+
create
|
88
|
+
|
89
|
+
documents.each do |document|
|
90
|
+
document.update! batch_size: batch_size, scope: scope, query: query, &block
|
91
|
+
|
92
|
+
unless document.index_aliased?
|
93
|
+
if document.alias_exists?
|
94
|
+
old_index = document.aliased_index
|
95
|
+
document.delete_alias!
|
96
|
+
document.delete_index!(index: old_index)
|
97
|
+
end
|
98
|
+
|
99
|
+
document.create_alias!
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def reindex(batch_size: 200, scope: nil, query: nil, &block)
|
105
|
+
documents.each do |document|
|
106
|
+
document.update! batch_size: batch_size, scope: scope, query: query, &block
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def truncate(batch_size: 200, scope: nil, query: nil, &block)
|
111
|
+
documents.each do |document|
|
112
|
+
document.delete! batch_size: batch_size, scope: scope, query: query, &block
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Lycra
|
2
|
+
module Inheritance
|
3
|
+
attr_accessor :abstract_class
|
4
|
+
|
5
|
+
def self.extended(base)
|
6
|
+
base.send :delegate, :abstract?, to: :class
|
7
|
+
end
|
8
|
+
|
9
|
+
def abstract!
|
10
|
+
@abstract_class = true
|
11
|
+
end
|
12
|
+
|
13
|
+
def abstract?
|
14
|
+
!!abstract_class
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/lycra/monkeypatches.rb
CHANGED
@@ -1,44 +1,14 @@
|
|
1
|
-
#
|
2
|
-
#
|
3
|
-
# https://github.com/elastic/elasticsearch-rails/issues/421
|
4
|
-
#
|
5
|
-
# We create our indices with a timestamp, then alias the basename (without the timestamp) to the timestamped index. When
|
6
|
-
# you ask the elasticsearch-model gem for records (instead of results), and it tries to use your models to look them up,
|
7
|
-
# it decides which model to use based on the index name and document type. This is a problem for us because our documents
|
8
|
-
# use the alias name (i.e. 'my-index') as their index name, but the hits that come back when searching use the timestamped
|
9
|
-
# index name (i.e. 'my-index-123456789'), which don't match up using the `==` operator in the original implementation of
|
10
|
-
# this method.
|
1
|
+
# This is for awesome_print, which wants a to_hash method on objects to print
|
2
|
+
# them out in a console.
|
11
3
|
|
12
|
-
|
13
|
-
#
|
14
|
-
# Instead of checking whether the hit's index name and the model's index name are exactly equal, we use a simple regex pattern
|
15
|
-
# to match the index names against each other. The regex pattern used below makes the timestamp optional, and allows for both
|
16
|
-
# 'my-index' and 'my-index-123456789' to match against 'my-index'
|
4
|
+
require 'elasticsearch/model/response/result'
|
17
5
|
|
18
6
|
module Elasticsearch
|
19
7
|
module Model
|
20
|
-
module
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
#
|
25
|
-
# @see Elasticsearch::Model::Registry
|
26
|
-
#
|
27
|
-
# @api private
|
28
|
-
#
|
29
|
-
def __type_for_hit(hit)
|
30
|
-
@@__types ||= {}
|
31
|
-
|
32
|
-
@@__types[ "#{hit[:_index]}::#{hit[:_type]}" ] ||= begin
|
33
|
-
Registry.all.detect do |model|
|
34
|
-
# Original Implementation
|
35
|
-
#model.index_name == hit[:_index] && model.document_type == hit[:_type]
|
36
|
-
|
37
|
-
# Our Monkeypatch
|
38
|
-
hit[:_index] =~ /\A#{model.index_name}(-\d+)?\Z/ && model.document_type == hit[:_type]
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
8
|
+
module Response
|
9
|
+
class Result
|
10
|
+
def to_hash
|
11
|
+
to_h
|
42
12
|
end
|
43
13
|
end
|
44
14
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Lycra
|
2
|
+
class Multidoc
|
3
|
+
attr_reader :documents
|
4
|
+
|
5
|
+
def initialize(*documents)
|
6
|
+
@documents = documents.flatten
|
7
|
+
@documents = Lycra::Document::Registry.all if @documents.empty?
|
8
|
+
end
|
9
|
+
|
10
|
+
def index_name
|
11
|
+
documents.map { |d| d.alias_name }
|
12
|
+
end
|
13
|
+
|
14
|
+
def document_type
|
15
|
+
documents.map { |d| d.document_type }
|
16
|
+
end
|
17
|
+
|
18
|
+
def client
|
19
|
+
Lycra.client
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/lycra/search.rb
CHANGED
@@ -51,9 +51,9 @@ module Lycra
|
|
51
51
|
|
52
52
|
def search(qry=nil, &block)
|
53
53
|
if block_given?
|
54
|
-
|
54
|
+
Lycra.search(instance_eval(&block), models)
|
55
55
|
else
|
56
|
-
|
56
|
+
Lycra.search((qry || to_query), models)
|
57
57
|
end
|
58
58
|
end
|
59
59
|
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Lycra
|
2
|
+
module Serializer
|
3
|
+
def self.included(base)
|
4
|
+
base.send :include, Attributes
|
5
|
+
base.send :extend, Inheritance
|
6
|
+
base.send :alias_method, :serialize!, :resolve!
|
7
|
+
|
8
|
+
base.class_eval do
|
9
|
+
class << self
|
10
|
+
alias_method :serialize!, :resolve!
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def as_json(options={})
|
16
|
+
serialize!(subject).as_json(options)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Lycra
|
2
|
+
module Serializer
|
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 serializer(klass=nil)
|
11
|
+
@_lycra_serializer = klass if klass
|
12
|
+
@_lycra_serializer || ("#{name}Serializer".constantize rescue nil)
|
13
|
+
end
|
14
|
+
|
15
|
+
def serializer=(klass)
|
16
|
+
serializer klass
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
module InstanceMethods
|
21
|
+
delegate :serialize!, to: :serializer
|
22
|
+
|
23
|
+
def reload
|
24
|
+
@serializer = nil
|
25
|
+
super
|
26
|
+
end
|
27
|
+
|
28
|
+
def serializer(serializer_class=nil)
|
29
|
+
return serializer_class.new(self) if serializer_class
|
30
|
+
@serializer ||= self.class.serializer.new(self)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/lycra/types.rb
ADDED
@@ -0,0 +1,165 @@
|
|
1
|
+
module Lycra
|
2
|
+
module Types
|
3
|
+
class Type
|
4
|
+
attr_reader :value
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def klasses(*klasses)
|
8
|
+
@_klasses = klasses unless klasses.empty?
|
9
|
+
@_klasses || []
|
10
|
+
end
|
11
|
+
|
12
|
+
def type(type=nil)
|
13
|
+
@_type = type if type
|
14
|
+
@_type ||= self.name.demodulize.underscore
|
15
|
+
end
|
16
|
+
alias_method :to_s, :type
|
17
|
+
|
18
|
+
def valid?(value, required=false, nested=false)
|
19
|
+
return false if required && value.nil?
|
20
|
+
return true if value.nil?
|
21
|
+
return false if nested && !value.is_a?(Enumerable)
|
22
|
+
return true if nested && value.empty?
|
23
|
+
if nested
|
24
|
+
klasses.any? { |klass| value.all? { |val| val.is_a?(klass) } }
|
25
|
+
else
|
26
|
+
klasses.any? { |klass| value.is_a?(klass) }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def transform(value)
|
31
|
+
value # noop by default
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def initialize(value)
|
36
|
+
@value = value
|
37
|
+
end
|
38
|
+
|
39
|
+
def type(type=nil)
|
40
|
+
@type = type if type
|
41
|
+
@type ||= self.class.type
|
42
|
+
end
|
43
|
+
|
44
|
+
def valid?(required=false, nested=false)
|
45
|
+
@valid ||= self.class.valid?(@value, required, nested)
|
46
|
+
end
|
47
|
+
|
48
|
+
def transform
|
49
|
+
@transformed ||= self.class.transform(@value)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class Text < Type
|
54
|
+
klasses ::String
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.text
|
58
|
+
Text
|
59
|
+
end
|
60
|
+
|
61
|
+
class Integer < Type
|
62
|
+
klasses ::Integer
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.integer
|
66
|
+
Integer
|
67
|
+
end
|
68
|
+
|
69
|
+
class Float < Type
|
70
|
+
klasses ::Float
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.float
|
74
|
+
Float
|
75
|
+
end
|
76
|
+
|
77
|
+
class Date < Type
|
78
|
+
klasses ::Date, ::Time, ::DateTime
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.date
|
82
|
+
Date
|
83
|
+
end
|
84
|
+
|
85
|
+
class Boolean < Type
|
86
|
+
def self.valid?(value, required=false, nested=false)
|
87
|
+
[true, false, 0, 1, nil].include?(value)
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.transform(value)
|
91
|
+
return value if [true, false].include?(value)
|
92
|
+
return true if value == 1
|
93
|
+
false # 0, nil
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def self.boolean
|
98
|
+
Boolean
|
99
|
+
end
|
100
|
+
|
101
|
+
class Hash < Type
|
102
|
+
klasses ::Hash
|
103
|
+
|
104
|
+
def self.valid?(value, required=false, nested=false)
|
105
|
+
return true if super(value, required, nested)
|
106
|
+
value.is_a?(Hash) || value.respond_to?(:to_h)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def self.hash
|
111
|
+
Hash
|
112
|
+
end
|
113
|
+
|
114
|
+
class Object < Type
|
115
|
+
klasses ::Hash
|
116
|
+
|
117
|
+
def self.valid?(value, required=false, nested=false)
|
118
|
+
return true if super(value, required, nested)
|
119
|
+
value.respond_to?(:to_h)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def self.object
|
124
|
+
Object
|
125
|
+
end
|
126
|
+
|
127
|
+
class Nested < Type
|
128
|
+
def self.valid?(value, required=false, nested=false)
|
129
|
+
return true if value.nil?
|
130
|
+
value.respond_to?(:each)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def self.nested
|
135
|
+
Nested
|
136
|
+
end
|
137
|
+
|
138
|
+
def self.custom(type)
|
139
|
+
Class.new(Type) do
|
140
|
+
self.type(type)
|
141
|
+
|
142
|
+
def self.valid?(value, required=false, nested=false)
|
143
|
+
return false if required && value.nil?
|
144
|
+
return true if value.nil?
|
145
|
+
return false if nested && !value.is_a?(Enumerable)
|
146
|
+
return true if nested && value.empty?
|
147
|
+
if nested
|
148
|
+
value.all? { |val| val.is_a?(type) || val.is_a?(type.subject_type) }
|
149
|
+
else
|
150
|
+
value.is_a?(type) || value.is_a?(type.subject_type)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def self.transform(value)
|
155
|
+
return value.resolve! if value.is_a?(type)
|
156
|
+
if value.is_a?(Enumerable)
|
157
|
+
value.map { |val| type.new(val).resolve! }
|
158
|
+
else
|
159
|
+
type.new(value).resolve!
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|