class Capsula::Encapsulator

Public Class Methods

new(items:, declarations:, keys: @items = wrap_non_wrapped(items)) click to toggle source

Main encapsulator functionality

# File lib/capsula/encapsulator.rb, line 8
def initialize items:, declarations:, keys:
  # wrap up not wrapped obects
  @items = wrap_non_wrapped(items)
  @declarations = declarations

  @keys = []
  @keys_values = {}

  ##
  # Parse all declarations by first-level plans.
  #
  # We get a nested plans structure:
  # [
  #   {fruits: [:tree, {bugs: [:locations]]},
  #   :vegetables
  # ]
  # For current objects which we will encapsulate now
  # (call it first-level objects)
  # we need to preload only :fruits and :vegetables,
  # But when the turn comes to encapsulate :fruits,
  # we will transfer plans for :fruits to fruits-encapsulator,
  # and let him deside how to encapsulate tree and bugs into fruits,
  # and then, bugs-encapsulator shoud encapsulate locations and so on.
  #
  keys.each do |i|
    if i.is_a?(Hash)
      k, v = i.first
      @keys << k
      @keys_values[k] = v
    else
      @keys << i
    end
  end

  # for preloaded objects
  @collections = {}
end

Public Instance Methods

encapsulate() click to toggle source
# File lib/capsula/encapsulator.rb, line 73
def encapsulate
  @collections.each do |plan_key, accessories|
    if accessories.has_key?(:delegator)
      # Delegate to user's encapsulator
      encapsulate_by_delegator(
        accessories[:delegator],
        plan_key
      )
    else
      # Use native encapsulator
      native_encapsulation(
        plan_key,
        accessories[:collection],
        accessories[:declaration]
      )
    end
  end
end
preload() click to toggle source
# File lib/capsula/encapsulator.rb, line 58
def preload
  @keys.each do |key|
    dec = @declarations[key]
    opt = build_encapsulator_options(key)

    if dec.has_key?(:delegate_to)
      # Delegate to user's encapsulator
      preload_by_delegator(dec[:delegate_to], key, opt)
    else
      # Use native encapsulator
      native_preload(key, dec, opt)
    end
  end
end
preload_and_encapsulate() click to toggle source
# File lib/capsula/encapsulator.rb, line 46
def preload_and_encapsulate
  if (@keys - @declarations.keys).any?
    raise StandardError.new("unknown relation keys: #{ (@keys - @declarations.keys) }")
  end

  preload

  encapsulate

  @items
end

Private Instance Methods

build_encapsulator_options(key) click to toggle source
# File lib/capsula/encapsulator.rb, line 173
def build_encapsulator_options key
  {
    plans: @keys_values[key] || []
  }
end
encapsulate_by_delegator(delegator, plan_key) click to toggle source
# File lib/capsula/encapsulator.rb, line 129
def encapsulate_by_delegator delegator, plan_key
  @items.each do |i|
    val = delegator.get_preloads_for_object(i)
    i[plan_key] = val
  end
end
get_value(_object_, key_or_lambda) click to toggle source
# File lib/capsula/encapsulator.rb, line 160
def get_value _object_, key_or_lambda
  case key_or_lambda
  when ::Symbol, ::String
    _object_.send(key_or_lambda)
  when ::Array
    _object_.send(key_or_lambda[0])
  when ::Proc
    key_or_lambda.call(_object_)
  else
    nil
  end
end
index_array_by(arr) { |elem| ... } click to toggle source

copypasted from activesupport www.rubydoc.info/github/rails/rails/Enumerable:index_by

# File lib/capsula/encapsulator.rb, line 191
def index_array_by arr
  result = {}
  arr.each { |elem| result[yield(elem)] = elem }
  result
end
native_encapsulation(plan_key, preloaded_collection, declaration) click to toggle source
# File lib/capsula/encapsulator.rb, line 136
def native_encapsulation plan_key, preloaded_collection, declaration
  col = preloaded_collection
  dec = declaration
  is_dst_key_array = dec[:dst_key].is_a?(Array)

  @items.each do |i|
    src_id = get_value(i, dec[:src_key])

    val =
      if is_dst_key_array
        # src has many dst
        col[src_id] || []
      elsif src_id.is_a?(Array)
        # if object has many links to related objects (Array)
        col.values_at(*src_id)
      else
        # only one link
        col[src_id]
      end

    i[plan_key] = val
  end
end
native_preload(key, dec, opt) click to toggle source
# File lib/capsula/encapsulator.rb, line 100
def native_preload key, dec, opt
  # collect ids for preloading
  ids =
    @items.map do |o|
      get_value(o, dec[:src_key])
    end.flatten.uniq.compact

  # preload and index objects
  col =
    if ids.any?
      preloads = dec[:dst_loader].call(ids,opt)
      dst_key = dec[:dst_key]
      if dst_key.is_a?(Array)
        # build hash: {id1 => [Obj1, ...], idN => [ObjN] }
        preloads.group_by { |o| get_value(o, dst_key.first) }
      else
        # build hash: {id1 => Obj1, ..., idN => ObjN }
        index_array_by(preloads) { |el| get_value(el, dst_key) }
      end
    else
      {}
    end

  @collections[key] = {
    collection: col,
    declaration: dec
  }
end
preload_by_delegator(delegator, key, opt) click to toggle source
# File lib/capsula/encapsulator.rb, line 94
def preload_by_delegator delegator, key, opt
  d = delegator.new(items: @items, opt: opt)
  d.collect_ids_and_load_relations
  @collections[key] = { delegator: d }
end
wrap_non_wrapped(items) click to toggle source
# File lib/capsula/encapsulator.rb, line 179
def wrap_non_wrapped items
  items.map do |item|
    if item.class == Wrapper
      item
    else
      Wrapper.new(item)
    end
  end
end