lycra 0.0.7 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: ae19d389fed4af25874ae57afe396eea884e1d5b
4
- data.tar.gz: da0c6854220ff3b663919c5aed3d319282d7f7ed
2
+ SHA256:
3
+ metadata.gz: 5fc3ae92178f1c1b65aef5866d78ba48eaac5ac5a1f26874f9b9de27171af7c5
4
+ data.tar.gz: 550692ddd9612fa6b13e1899743c40f080efeb3e5f2f99153a1bc2bd4e600bd7
5
5
  SHA512:
6
- metadata.gz: e3e57b0af1517a9694811487a33a13379ed0e883e6fa761a1c075256d07f2e2983ba03d5b5abca2bd73ac2cadaad7feee1d15b4647e1f2071cd3998dde83e468
7
- data.tar.gz: 480a3a64e33b453324a6cab834cc99ba3dd4a7d983b4ccc1ed97b2386a5f506210657d5f96fb2048d4facb0e12a1c4bd3e89d68083c29962587d995815130242
6
+ metadata.gz: 1425e10b35c3ca78ae95f513535ef8c2568c1f7704045b479be24dcc699277f6ab912023b06c7c882000aa67dc298361dd9216b647cf7f6f3220640b146c0e02
7
+ data.tar.gz: 156654656586332872962fee77c87272a2cac14342d7a8dbae90e5f9f57088a94189b827536ce4e419d5d3635c32f59bb6ff2ba5954d1e59d174291b76df3826
@@ -0,0 +1,25 @@
1
+ module AwesomePrint
2
+ module LycraAttribute
3
+ def self.included(base)
4
+ base.send :alias_method, :cast_without_lycra_attribute, :cast
5
+ base.send :alias_method, :cast, :cast_with_lycra_attribute
6
+ end
7
+
8
+ def cast_with_lycra_attribute(object, type)
9
+ cast = cast_without_lycra_attribute(object, type)
10
+ return cast unless defined?(::Lycra::Attributes::Attribute)
11
+
12
+ if object.is_a?(::Lycra::Attributes::Attribute)
13
+ cast = :lycra_attribute
14
+ end
15
+
16
+ cast
17
+ end
18
+
19
+ def awesome_lycra_attribute(object)
20
+ Formatters::LycraAttributeFormatter.new(object, @inspector).format
21
+ end
22
+ end
23
+ end
24
+
25
+ AwesomePrint::Formatter.send(:include, AwesomePrint::LycraAttribute)
@@ -0,0 +1,25 @@
1
+ module AwesomePrint
2
+ module LycraAttributes
3
+ def self.included(base)
4
+ base.send :alias_method, :cast_without_lycra_attributes, :cast
5
+ base.send :alias_method, :cast, :cast_with_lycra_attributes
6
+ end
7
+
8
+ def cast_with_lycra_attributes(object, type)
9
+ cast = cast_without_lycra_attributes(object, type)
10
+ return cast unless defined?(::Lycra::Attributes::Collection)
11
+
12
+ if object.is_a?(::Lycra::Attributes::Collection)
13
+ cast = :lycra_attributes
14
+ end
15
+
16
+ cast
17
+ end
18
+
19
+ def awesome_lycra_attributes(object)
20
+ Formatters::LycraAttributesFormatter.new(object, @inspector).format
21
+ end
22
+ end
23
+ end
24
+
25
+ AwesomePrint::Formatter.send(:include, AwesomePrint::LycraAttributes)
@@ -0,0 +1,58 @@
1
+ module AwesomePrint
2
+ module Formatters
3
+ class LycraAttributeFormatter < BaseFormatter
4
+ attr_reader :attribute, :inspector, :options, :padding
5
+ # TODO mappings (maybe refactor attribute(s) to accept the document and
6
+ # behave differently if they're a document attribute or not)
7
+
8
+ def initialize(attribute, inspector, padding: 0)
9
+ @attribute = attribute
10
+ @inspector = inspector
11
+ @options = inspector.options
12
+ @padding = padding
13
+ end
14
+
15
+ def format
16
+ if options[:multiline]
17
+ [
18
+ name_and_type,
19
+ indent + (' ' * (padding + 1)) + description,
20
+ #indent + (' ' * (padding + 1)) + mappings
21
+ ].join("\n")
22
+ else
23
+ "#{name_and_type} #{description}" #{mappings}"
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def name_and_type
30
+ name = align(attribute.name.to_s, indentation + padding)
31
+ name = attribute.required? ? name.green : name.cyan
32
+ type = attribute.type.type
33
+ type = attribute.nested? ? "[#{type}]" : "#{type}"
34
+ "#{name} #{type.blue} #{resolver}"
35
+ end
36
+
37
+ def description
38
+ attribute.description || "No description"
39
+ end
40
+
41
+ def resolver
42
+ if attribute.resolver.is_a?(Symbol)
43
+ if attribute.klass.instance_methods.include?(attribute.resolver)
44
+ (attribute.klass.name.yellowish + "##{attribute.resolver.to_s}".purpleish)
45
+ else
46
+ (attribute.klass.subject_type.name.yellowish + "##{attribute.resolver.to_s}".purpleish)
47
+ end
48
+ else
49
+ attribute.resolver.to_s.yellowish
50
+ end
51
+ end
52
+
53
+ def mappings
54
+ Inspector.new(options.merge({multiline: false})).awesome(attribute.mappings)
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,26 @@
1
+ module AwesomePrint
2
+ module Formatters
3
+ class LycraAttributesFormatter < HashFormatter
4
+ attr_reader :attributes, :inspector, :options
5
+
6
+ def initialize(attributes, inspector)
7
+ @attributes = attributes
8
+ @inspector = inspector
9
+ @options = inspector.options
10
+ end
11
+
12
+ def format
13
+ padding = attributes.keys.map { |k| k.to_s.length }.max
14
+ attrs = attributes.values.map do |attr|
15
+ LycraAttributeFormatter.new(attr, inspector, padding: padding).format
16
+ end
17
+
18
+ if options[:multiline]
19
+ "#{attributes.to_s} {\n#{attrs.join("\n")}\n#{outdent}}"
20
+ else
21
+ "#{attributes.to_s} { #{attrs.join(", ")} }"
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,2 @@
1
+ require 'elasticsearch/model/adapters/lycra/document'
2
+ require 'elasticsearch/model/adapters/lycra/multiple'
@@ -0,0 +1,120 @@
1
+ module Elasticsearch
2
+ module Model
3
+ module Adapter
4
+ module Lycra
5
+ # An adapter for Lycra::Document-based models
6
+ module Document
7
+
8
+ Adapter.register self,
9
+ lambda { |klass| !!defined?(::Lycra::Document::Proxy) && klass.respond_to?(:ancestors) && klass.ancestors.include?(::Lycra::Document::Proxy) }
10
+
11
+ module Records
12
+ attr_writer :options
13
+
14
+ def options
15
+ @options ||= {}
16
+ end
17
+
18
+ # Returns an `ActiveRecord::Relation` instance
19
+ #
20
+ def records
21
+ sql_records = klass.subject_type.where(klass.subject_type.primary_key => ids)
22
+ sql_records = sql_records.includes(self.options[:includes]) if self.options[:includes]
23
+
24
+ # Re-order records based on the order from Elasticsearch hits
25
+ # by redefining `to_a`, unless the user has called `order()`
26
+ #
27
+ sql_records.instance_exec(response.response['hits']['hits']) do |hits|
28
+ ar_records_method_name = :to_a
29
+ ar_records_method_name = :records if defined?(::ActiveRecord) && ::ActiveRecord::VERSION::MAJOR >= 5
30
+
31
+ define_singleton_method(ar_records_method_name) do
32
+ if defined?(::ActiveRecord) && ::ActiveRecord::VERSION::MAJOR >= 4
33
+ self.load
34
+ else
35
+ self.__send__(:exec_queries)
36
+ end
37
+ @records.sort_by { |record| hits.index { |hit| hit['_id'].to_s == record.id.to_s } }
38
+ end if self
39
+ end
40
+
41
+ sql_records
42
+ end
43
+
44
+ # Prevent clash with `ActiveSupport::Dependencies::Loadable`
45
+ #
46
+ def load
47
+ records.__send__(:load)
48
+ end
49
+
50
+ # Intercept call to the `order` method, so we can ignore the order from Elasticsearch
51
+ #
52
+ def order(*args)
53
+ sql_records = records.__send__ :order, *args
54
+
55
+ # Redefine the `to_a` method to the original one
56
+ #
57
+ sql_records.instance_exec do
58
+ define_singleton_method(:to_a) do
59
+ if defined?(::ActiveRecord) && ::ActiveRecord::VERSION::MAJOR >= 4
60
+ self.load
61
+ else
62
+ self.__send__(:exec_queries)
63
+ end
64
+ @records
65
+ end
66
+ end
67
+
68
+ sql_records
69
+ end
70
+ end
71
+
72
+ module Callbacks
73
+ # Lycra does not use callbacks directly on the documents
74
+ end
75
+
76
+ module Importing
77
+
78
+ # Fetch batches of records from the database (used by the import method)
79
+ #
80
+ #
81
+ # @see http://api.rubyonrails.org/classes/ActiveRecord/Batches.html ActiveRecord::Batches.find_in_batches
82
+ #
83
+ def __find_in_batches(options={}, &block)
84
+ query = options.delete(:query)
85
+ named_scope = options.delete(:scope)
86
+ preprocess = options.delete(:preprocess)
87
+
88
+ scope = subject_type
89
+ scope = scope.__send__(named_scope) if named_scope
90
+ scope = scope.instance_exec(&query) if query
91
+
92
+ scope.find_in_batches(options) do |batch|
93
+ yield (preprocess ? self.__send__(preprocess, batch) : batch)
94
+ end
95
+ end
96
+
97
+ def __transform
98
+ lambda do |model|
99
+ json_data = begin
100
+ if model.respond_to?(:model_document)
101
+ # TODO model_document.as_indexed_json(model)
102
+ else
103
+ modoc = begin
104
+ "#{model.class.name}Document".constantize
105
+ rescue NameError => e
106
+ raise "Unable to locate a document class for the #{model.class.name} model"
107
+ end
108
+ modoc.as_indexed_json(model)
109
+ end
110
+ end
111
+
112
+ { index: { _id: model.id, data: json_data } }
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,70 @@
1
+ module Elasticsearch
2
+ module Model
3
+ module Adapter
4
+ module Lycra
5
+
6
+ module Multiple
7
+ Adapter.register self, lambda { |klass| klass.is_a? ::Lycra::Multidoc }
8
+
9
+ module Records
10
+ #def documents
11
+ # documents_by_type = __documents_by_type
12
+
13
+ # documents = response.response["hits"]["hits"].map do |hit|
14
+ # documents_by_type[ __type_for_hit(hit) ][ hit[:_id] ]
15
+ # end
16
+
17
+ # documents.compact
18
+ #end
19
+
20
+ def records
21
+ documents_by_type = __documents_by_type
22
+
23
+ records = response.response["hits"]["hits"].map do |hit|
24
+ documents_by_type[ __type_for_hit(hit) ][ hit[:_id] ]#.subject
25
+ end
26
+
27
+ records.compact
28
+ end
29
+
30
+ def __documents_by_type
31
+ result = __ids_by_type.map do |klass, ids|
32
+ documents = __documents_for_klass(klass, ids)
33
+ ids = documents.map(&:id).map(&:to_s)
34
+ mapped = [ klass, Hash[ids.zip(documents)] ]
35
+ mapped
36
+ end
37
+
38
+ Hash[result]
39
+ end
40
+
41
+ def __documents_for_klass(klass, ids)
42
+ return klass.subject_type.where(klass.primary_key => ids)
43
+ end
44
+
45
+ def __ids_by_type
46
+ ids_by_type = {}
47
+
48
+ response.response["hits"]["hits"].each do |hit|
49
+ type = __type_for_hit(hit)
50
+ ids_by_type[type] ||= []
51
+ ids_by_type[type] << hit[:_id]
52
+ end
53
+ ids_by_type
54
+ end
55
+
56
+ def __type_for_hit(hit)
57
+ @@__types ||= {}
58
+
59
+ @@__types[ "#{hit[:_index]}::#{hit[:_type]}" ] ||= begin
60
+ ::Lycra::Document::Registry.all.detect do |document|
61
+ hit[:_index] =~ /\A#{document.index_name}(-[a-zA-Z0-9]+)?\Z/ && document.document_type == hit[:_type]
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
data/lib/lycra.rb CHANGED
@@ -1,12 +1,23 @@
1
1
  require 'logger'
2
2
  require 'canfig'
3
- require 'elasticsearch/persistence'
4
3
  require 'elasticsearch/model'
4
+ require 'elasticsearch/model/adapters/lycra'
5
5
  require 'lycra/monkeypatches'
6
6
  require 'lycra/errors'
7
- require 'lycra/model'
7
+ require 'lycra/types'
8
+ require 'lycra/attributes'
9
+ require 'lycra/serializer'
10
+ require 'lycra/serializer/model'
11
+ require 'lycra/document'
12
+ require 'lycra/document/model'
13
+ require 'lycra/import'
14
+ require 'lycra/decorator'
15
+ require 'lycra/decorator/model'
16
+ require 'lycra/inheritance'
8
17
  require 'lycra/search'
18
+ require 'lycra/multidoc'
9
19
  require 'lycra/engine' if defined?(Rails)
20
+ require 'lycra/awesome_print' if defined?(AwesomePrint)
10
21
 
11
22
  module Lycra
12
23
  include Canfig::Module
@@ -32,7 +43,7 @@ module Lycra
32
43
  end
33
44
 
34
45
  def self.client
35
- @client ||= Elasticsearch::Client.new(
46
+ @client ||= ::Elasticsearch::Client.new(
36
47
  host: configuration.elasticsearch_url,
37
48
  log: configuration.log,
38
49
  logger: configuration.logger,
@@ -40,4 +51,10 @@ module Lycra
40
51
  tracer: configuration.tracer
41
52
  )
42
53
  end
54
+
55
+ def self.search(query_or_payload, models=[], options={})
56
+ models = Multidoc.new(models)
57
+ request = Elasticsearch::Model::Searching::SearchRequest.new(models, query_or_payload, options)
58
+ Elasticsearch::Model::Response::Response.new(models, request)
59
+ end
43
60
  end
@@ -0,0 +1,146 @@
1
+ require 'lycra/attributes/collection'
2
+ require 'lycra/attributes/attribute'
3
+
4
+ module Lycra
5
+ module Attributes
6
+ def self.included(base)
7
+ base.send :attr_reader, :resolved
8
+ base.send :attr_accessor, :subject
9
+ base.send :extend, ClassMethods
10
+ base.send :include, InstanceMethods
11
+
12
+ base.class_eval do
13
+ # TODO only for activerecord models??
14
+ attribute! :id, types.integer
15
+ end
16
+ end
17
+
18
+ module ClassMethods
19
+ def inherited(child)
20
+ super if defined?(super)
21
+
22
+ # This clones parent attribues down to inheriting child classes
23
+ child.send :instance_variable_set,
24
+ :@_lycra_attributes,
25
+ self.attributes.dup(child)
26
+ end
27
+
28
+ def types
29
+ Lycra::Types
30
+ end
31
+
32
+ def attribute(name=nil, type=nil, *args, **opts, &block)
33
+ opts = {cache: cache}.merge(opts)
34
+ attr = Attribute.new(name, type, *args, **opts.merge({klass: self}), &block)
35
+ attributes[attr.name] = attr
36
+ end
37
+
38
+ def attribute!(name=nil, type=nil, *args, **opts, &block)
39
+ attribute(name, type, *args, **opts.merge({required: true}), &block)
40
+ end
41
+
42
+ def attributes
43
+ @_lycra_attributes ||= Collection.new(self)
44
+ end
45
+
46
+ def subject_type(klass=nil)
47
+ @_lycra_subject_type = klass if klass
48
+ @_lycra_subject_type ||= (name.gsub(/(Decorator|Document|Serializer)\Z/, '').constantize rescue nil)
49
+ end
50
+
51
+ def subject_type=(klass)
52
+ subject_type klass
53
+ end
54
+
55
+ def cache(cache=nil)
56
+ @_lycra_cache = cache unless cache.nil?
57
+ @_lycra_cache
58
+ end
59
+
60
+ def resolve!(subj, *args, **context)
61
+ if subj.is_a?(subject_type)
62
+ return new(subj).resolve!(*args, **context)
63
+ elsif subj.is_a?(Enumerable) && subj.first.is_a?(subject_type)
64
+ return subj.map { |s| resolve!(s, *args, **context) }
65
+ end
66
+
67
+ raise "Invalid subject: #{subj}"
68
+ end
69
+
70
+ def method_missing(meth, *args, &block)
71
+ if subject_type && subject_type.respond_to?(meth)
72
+ result = subject_type.send(meth, *args, &block)
73
+
74
+ return result.try(:document) || new(result) if result.is_a?(subject_type)
75
+ return result if result.is_a?(ActiveRecord::Relation)
76
+ return result.map { |r| r.try(:document) || new(r) } if result.is_a?(Enumerable) && result.first.is_a?(subject_type)
77
+
78
+ return result
79
+ else
80
+ super
81
+ end
82
+ end
83
+
84
+ def respond_to_missing?(meth, priv=false)
85
+ (subject_type && subject_type.respond_to?(meth, priv)) || super
86
+ end
87
+
88
+ def inspect
89
+ "#{name}(subject: #{subject_type}, #{attributes.map { |key,attr| "#{attr.name}: #{attr.nested? ? "[#{attr.type.type}]" : attr.type.type}"}.join(', ')})"
90
+ end
91
+ end
92
+
93
+ module InstanceMethods
94
+ delegate :subject_type, to: :class
95
+
96
+ def initialize(subject)
97
+ @subject = subject
98
+ end
99
+
100
+ def attributes
101
+ @attributes ||= self.class.attributes.dup
102
+ end
103
+
104
+ def resolve!(*args, **options)
105
+ raise Lycra::MissingSubjectError.new(self) if subject.nil?
106
+ context = options.slice!(:only, :except)
107
+
108
+ @resolved = attributes.map do |key,attr|
109
+ next if (options.key?(:only) && ![options[:only]].flatten.include?(key)) ||
110
+ (options.key?(:except) && [options[:except]].flatten.include?(key))
111
+ [ key, attr.dup.resolve!(self, args, context) ]
112
+ end.compact.to_h
113
+ end
114
+
115
+ def resolved?
116
+ !!@resolved
117
+ end
118
+
119
+ def reload
120
+ @resolved = nil
121
+ attributes.values.each(&:reload)
122
+ subject.send(:reload) if subject.respond_to?(:reload)
123
+ self
124
+ end
125
+
126
+ def method_missing(meth, *args, &block)
127
+ return subject if meth == subject_type.to_s.underscore.to_sym
128
+ return attributes[meth].resolve!(self, *args, &block) if attributes.key?(meth)
129
+ return subject.send(meth, *args, &block) if subject && subject.respond_to?(meth)
130
+ super
131
+ end
132
+
133
+ def respond_to_missing?(meth, priv=false)
134
+ meth == subject_type.to_s.underscore.to_sym || attributes.key?(meth) || (subject && subject.respond_to?(meth, priv)) || super
135
+ end
136
+
137
+ def inspect
138
+ if resolved?
139
+ "#<#{self.class.name} subject: #{subject.class}, #{attributes.map { |key,attr| "#{key}: #{resolved[key].try(:to_json) || (attr.nested? ? "[#{attr.type.type}]" : attr.type.type)}"}.join(', ')}>"
140
+ else
141
+ "#<#{self.class.name} subject: #{subject.class}, #{attributes.map { |key,attr| "#{attr.name}: #{attr.nested? ? "[#{attr.type.type}]" : attr.type.type}"}.join(', ')}>"
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end