lycra 0.0.7 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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