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,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
- Elasticsearch::Persistence.client = Lycra.client
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 < Lycra::Document
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.split('::').map { |n| n.underscore }.join('/')}_document.rb
22
- class #{model.name}Document < Lycra::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
@@ -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
@@ -1,44 +1,14 @@
1
- # The Problem:
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
- # The Solution:
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 Adapter
21
- module Multiple
22
- module Records
23
- # Returns the class of the model corresponding to a specific `hit` in Elasticsearch results
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
- Elasticsearch::Model.search(instance_eval(&block), models)
54
+ Lycra.search(instance_eval(&block), models)
55
55
  else
56
- Elasticsearch::Model.search((qry || to_query), models)
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
@@ -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