Module: Esse::Repository::ClassMethods

Included in:
Esse::Repository
Defined in:
lib/esse/repository/actions.rb,
lib/esse/repository/documents.rb,
lib/esse/repository/object_document_mapper.rb,
lib/esse/repository/lazy_document_attributes.rb

Instance Method Summary collapse

Instance Method Details

#action(name, options = {}, &block) ⇒ Object



6
7
# File 'lib/esse/repository/actions.rb', line 6

def action(name, options = {}, &block)
end

#collection(collection_klass = nil, **_, &block) ⇒ void

This method returns an undefined value.

Used to define the source of data. A block is required. And its content should yield an array of each object that should be serialized. The list of arguments will be passed throught the document method.

Example:

collection AdminStore
collection do |**conditions, &block|
  User.where(conditions).find_in_batches(batch_size: 5000) do |batch|
    block.call(batch, conditions)
  end
end

Parameters:

  • name (String)

    The identification of the collection.

  • klass (Class)

    The class of the collection. (Optional when block is passed)

  • block (Proc)

    The block that will be used to iterate over the collection. (Optional when using a class)



57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/esse/repository/object_document_mapper.rb', line 57

def collection(collection_klass = nil, **_, &block)
  if collection_klass.nil? && block.nil?
    raise ArgumentError, 'a document type, followed by a collection class or block that stream the data ' \
                        'is required to define the collection'
  end

  if block.nil? && collection_klass.is_a?(Class) && !collection_klass.include?(Enumerable)
    msg = '%<arg>p is not a valid collection class.' \
          ' Collections should implement the Enumerable interface.'
    raise ArgumentError, format(msg, arg: collection_klass)
  end

  @collection_proc = collection_klass || block
end

#document(klass = nil, &block) ⇒ Object

Define the document type that will be used to serialize the data. Arguments will be same of passed through the collection. It’s allowed a block or a class with the ‘to_h` instance method. Example with block

document do |model, **context|
  {
    id: model.id,
    admin: context[:is_admin],
  }
end

Example with document class

document UserDocument


20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# File 'lib/esse/repository/object_document_mapper.rb', line 20

def document(klass = nil, &block)
  if @document_proc
    raise ArgumentError, format('Document for %p already defined', repo_name)
  end

  if block
    @document_proc = ->(model, **kwargs) { coerce_to_document(block.call(model, **kwargs)) }
  elsif klass.is_a?(Class) && klass <= Esse::Document
    @document_proc = ->(model, **kwargs) { klass.new(model, **kwargs) }
  elsif klass.is_a?(Class) && klass.instance_methods.include?(:to_h)
    @document_proc = ->(model, **kwargs) { coerce_to_document(klass.new(model, **kwargs).to_h) }
  elsif klass.is_a?(Class) && klass.instance_methods.include?(:as_json) # backward compatibility
    @document_proc = ->(model, **kwargs) { coerce_to_document(klass.new(model, **kwargs).as_json) }
  elsif klass.is_a?(Class) && klass.instance_methods.include?(:call)
    @document_proc = ->(model, **kwargs) { coerce_to_document(klass.new(model, **kwargs).call) }
  else
    msg = format("%<arg>p is not a valid document. The document should inherit from Esse::Document or respond to `to_h'", arg: klass)
    raise ArgumentError, msg
  end
end

#documents(**kwargs) ⇒ Enumerator

Wrap collection data into serialized documents

Example:

GeosIndex.documents(id: 1).first

Returns:

  • (Enumerator)

    All serialized entries



102
103
104
105
106
107
108
# File 'lib/esse/repository/object_document_mapper.rb', line 102

def documents(**kwargs)
  Enumerator.new do |yielder|
    each_serialized_batch(**kwargs) do |docs, **_collection_kargs|
      docs.each { |document| yielder.yield(document) }
    end
  end
end

#documents_for_lazy_attribute(name, ids_or_doc_headers) ⇒ Object



17
18
19
20
21
# File 'lib/esse/repository/documents.rb', line 17

def documents_for_lazy_attribute(name, ids_or_doc_headers)
  retrieve_lazy_attribute_values(name, ids_or_doc_headers).map do |doc_header, datum|
    doc_header.to_doc(name => datum)
  end
end

#each_serialized_batch(lazy_attributes: false, **kwargs) {|Array, **context| ... } ⇒ Enumerator

Wrap collection data into serialized batches

Parameters:

  • kwargs (Hash)

    The context

Yields:

  • (Array, **context)

    serialized collection and the optional context from the collection

Returns:

  • (Enumerator)

    The enumerator



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/esse/repository/object_document_mapper.rb', line 77

def each_serialized_batch(lazy_attributes: false, **kwargs)
  each_batch(**kwargs) do |*args|
    batch, collection_context = args
    collection_context ||= {}
    entries = [*batch].map { |entry| serialize(entry, **collection_context) }.compact
    if lazy_attributes
      attrs = lazy_attributes.is_a?(Array) ? lazy_attributes : lazy_document_attribute_names(lazy_attributes)
      attrs.each do |attr_name|
        retrieve_lazy_attribute_values(attr_name, entries).each do |doc_header, value|
          doc = entries.find { |d| doc_header.id.to_s == d.id.to_s && doc_header.type == d.type && doc_header.routing == d.routing }
          doc&.mutate(attr_name) { value }
        end
      end
    end

    yield entries, **kwargs
  end
end

#fetch_lazy_document_attribute(attr_name) ⇒ Object



23
24
25
26
27
28
# File 'lib/esse/repository/lazy_document_attributes.rb', line 23

def fetch_lazy_document_attribute(attr_name)
  klass, kwargs = lazy_document_attributes.fetch(attr_name)
  klass.new(**kwargs)
rescue KeyError
  raise ArgumentError, format('Attribute %<attr>p is not defined as a lazy document attribute', attr: attr_name)
end

#import(**kwargs) ⇒ Object



6
7
8
# File 'lib/esse/repository/documents.rb', line 6

def import(**kwargs)
  index.import(repo_name, **kwargs)
end

#lazy_document_attribute(attr_name, klass = nil, **kwargs, &block) ⇒ Object



30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/esse/repository/lazy_document_attributes.rb', line 30

def lazy_document_attribute(attr_name, klass = nil, **kwargs, &block)
  if attr_name.nil?
    raise ArgumentError, 'Attribute name is required to define a lazy document attribute'
  end
  if lazy_document_attribute?(attr_name.to_sym) || lazy_document_attribute?(attr_name.to_s)
    raise ArgumentError, format('Attribute %<attr>p is already defined as a lazy document attribute', attr: attr_name)
  end

  @lazy_document_attributes = lazy_document_attributes.dup
  if block
    klass = Class.new(Esse::DocumentLazyAttribute) do
      define_method(:call, &block)
    end
    @lazy_document_attributes[attr_name] = [klass, kwargs]
  elsif klass.is_a?(Class) && klass <= Esse::DocumentLazyAttribute
    @lazy_document_attributes[attr_name] = [klass, kwargs]
  elsif klass.is_a?(Class) && klass.instance_methods.include?(:call)
    @lazy_document_attributes[attr_name] = [klass, kwargs]
  elsif klass.nil?
    raise ArgumentError, format('A block or a class that responds to `call` is required to define a lazy document attribute')
  else
    raise ArgumentError, format('%<arg>p is not a valid lazy document attribute. Class should inherit from Esse::DocumentLazyAttribute or respond to `call`', arg: klass)
  end
ensure
  @lazy_document_attributes&.freeze
end

#lazy_document_attribute_names(all = true) ⇒ Object



11
12
13
14
15
16
17
18
19
20
21
# File 'lib/esse/repository/lazy_document_attributes.rb', line 11

def lazy_document_attribute_names(all = true)
  case all
  when false
    []
  when true
    lazy_document_attributes.keys
  else
    filtered = Array(all).map(&:to_s)
    lazy_document_attributes.keys.select { |name| filtered.include?(name.to_s) }
  end
end

#lazy_document_attributesObject



7
8
9
# File 'lib/esse/repository/lazy_document_attributes.rb', line 7

def lazy_document_attributes
  @lazy_document_attributes ||= {}.freeze
end

#retrieve_lazy_attribute_values(name, ids_or_doc_headers) ⇒ Object



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/esse/repository/documents.rb', line 23

def retrieve_lazy_attribute_values(name, ids_or_doc_headers)
  unless lazy_document_attribute?(name)
    raise ArgumentError, <<~MSG
      The attribute `#{name}` is not defined as a lazy document attribute.

      Define the attribute as a lazy document attribute using the `lazy_document_attribute` method.
    MSG
  end

  docs = LazyDocumentHeader.coerce_each(ids_or_doc_headers)
  return [] if docs.empty?

  result = fetch_lazy_document_attribute(name).call(docs)
  return [] unless result.is_a?(Hash)

  result.each_with_object({}) do |(key, value), memo|
    if key.is_a?(LazyDocumentHeader) && (doc = docs.find { |d| d == key || d.id == key.id })
      memo[doc] = value
    elsif (doc = docs.find { |d| d.id == key })
      memo[doc] = value
    end
  end
end

#serialize(model, **kwargs) ⇒ Esse::Document

Convert ruby object to json by using the document of the given document type.

Parameters:

  • model (Object)

    The ruby object

  • kwargs (Hash)

    The context

Returns:



114
115
116
117
118
119
120
# File 'lib/esse/repository/object_document_mapper.rb', line 114

def serialize(model, **kwargs)
  if @document_proc.nil?
    raise NotImplementedError, format('there is no %<t>p document defined for the %<k>p index', t: repo_name, k: index.to_s)
  end

  @document_proc.call(model, **kwargs)
end

#update_documents_attribute(name, ids_or_doc_headers = [], kwargs = {}) ⇒ Object



10
11
12
13
14
15
# File 'lib/esse/repository/documents.rb', line 10

def update_documents_attribute(name, ids_or_doc_headers = [], kwargs = {})
  batch = documents_for_lazy_attribute(name, ids_or_doc_headers)
  return if batch.empty?

  index.bulk(**kwargs.transform_keys(&:to_sym), update: batch)
end