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 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