module Pakyow::UI::Recordable

@api private

Constants

PRIORITY_CALLS

Attributes

calls[R]

@api private

Public Class Methods

new(*) click to toggle source
Calls superclass method
# File lib/pakyow/ui/recordable.rb, line 234
def initialize(*)
  super

  @calls = []
  cache_bindings!
end

Private Class Methods

find_through(binding_path, binding_info, options, context, calls) click to toggle source
# File lib/pakyow/ui/recordable.rb, line 164
def self.find_through(binding_path, binding_info, options, context, calls)
  if binding_path.any?
    binding_path_part = binding_path.shift
    current_options = options.dup

    if id = binding_info[binding_path_part.to_s.split(":", 2)[0].to_sym]
      # Tie the transformations to a node of a specific id, unless we're transforming the entire set.
      #
      unless calls.any? { |call| call[0] == :transform }
        current_options["id"] = id
      end
    end

    subsequent = []

    args = [[binding_path_part]]
    unless current_options.empty?
      args << current_options
    end

    context << [
      :find,
      args, [], subsequent
    ]

    find_through(binding_path, binding_info, options, subsequent, calls)
  else
    context.concat(calls)
  end
end

Public Instance Methods

cache_bindings!() click to toggle source

@api private

# File lib/pakyow/ui/recordable.rb, line 42
def cache_bindings!
  binding_nodes = if (view.object.is_a?(StringDoc::Node) || view.object.is_a?(StringDoc::MetaNode)) && view.object.significant?(:multipart_binding)
    [view.object]
  else
    view.object.find_significant_nodes(:binding)
  end

  @bindings = binding_nodes.flat_map { |node|
    [node.label(:binding), node.label(:binding_prop)]
  }.compact
end
to_a() click to toggle source
# File lib/pakyow/ui/recordable.rb, line 37
def to_a
  @calls
end
to_json(*) click to toggle source
# File lib/pakyow/ui/recordable.rb, line 33
def to_json(*)
  optimized.to_json
end

Private Instance Methods

attributes() click to toggle source
Calls superclass method
# File lib/pakyow/ui/recordable.rb, line 295
def attributes
  Attributes.from_attributes(super).tap do |subsequent|
    calls << [:attributes, [], [], subsequent]
  end
end
Also aliased as: attrs
attrs()
Alias for: attributes
call_priority(call, calls) click to toggle source
# File lib/pakyow/ui/recordable.rb, line 80
def call_priority(call, calls)
  if PRIORITY_CALLS.include?(call[0])
    # Set priority calls to a priority of -1000, which is highest priority.
    #
    -1000
  elsif call[0] == :find
    # Make priority of finds an inverse of specificity (e.g. [:post] > [:post, :title]).
    #
    -1000 + call[1][0].count
  else
    # Or just keep the same order we have now.
    #
    calls.index(call)
  end
end
from_presenter(presenter) click to toggle source
# File lib/pakyow/ui/recordable.rb, line 220
def from_presenter(presenter)
  allocate.tap { |instance|
    # Copy state from the presenter we're tracking.
    #
    presenter.instance_variables.each do |ivar|
      instance.instance_variable_set(ivar, presenter.instance_variable_get(ivar))
    end

    instance.cache_bindings!
  }
end
optimized() click to toggle source
# File lib/pakyow/ui/recordable.rb, line 56
def optimized
  calls = []

  # Combine finds when looking for the same nodes.
  #
  @calls.each do |call|
    if call[0] == :find && matching_call = calls.find { |c| c[0] == :find && c[1] == call[1] && c[2] == call[2] }
      matching_call[3].to_a.concat(call[3].to_a)
    else
      calls << call
    end
  end

  # Prioritize the calls so they are applied correctly on the client.
  #
  calls.sort! { |a, b|
    call_priority(a, calls) <=> call_priority(b, calls)
  }

  calls
end
presenter_for(view, type: view&.label(:presenter_type)) click to toggle source
Calls superclass method
# File lib/pakyow/ui/recordable.rb, line 241
def presenter_for(view, type: view&.label(:presenter_type))
  presenter = super

  if presenter.is_a?(Delegator)
    presenter.__setobj__(self.class.from_presenter(presenter.__getobj__))
  else
    presenter = self.class.from_presenter(presenter)
  end

  presenter
end
render_proc(view, render, &block) click to toggle source
Calls superclass method
# File lib/pakyow/ui/recordable.rb, line 196
def render_proc(view, render, &block)
  super(view, render) do |_, context|
    if render[:node]
      instance_exec(&block)

      # The super proc creates a new presenter instance per render, but we want each to use the
      # same starting point for calls since they all apply to the same node.
      #
      context.calls.concat(calls)
    else
      instance_exec(&block)

      if calls.any?
        # Explicitly find the node to apply the transformation to the correct node. While
        # we're at it, append any transformations caused by the `instance_exec` above.
        #
        Recordable.find_through(
          render[:binding_path].dup, object.label(:binding_info).to_h, {}, context.calls, calls
        )
      end
    end
  end
end
viewify(data) click to toggle source

FIXME: We currently viewify twice for present; once for transform, another for bind. Let's create a `Viewified` object instead… then check to see if it's already happened.

# File lib/pakyow/ui/recordable.rb, line 99
def viewify(data)
  data = if data.is_a?(Data::Proxy)
    data.to_a
  elsif data.nil?
    []
  else
    Array.ensure(data)
  end

  data.map { |object|
    binder = wrap_data_in_binder(object)
    object = binder.object

    # Map object keys to the binding name.
    #
    keys_and_binding_names = object.to_h.keys.map { |key|
      key = key.to_sym
      if key == :id || @bindings.include?(key)
        binding_name = key
      else
        plural_binding_name = Support.inflector.pluralize(key.to_s).to_sym
        singular_binding_name = Support.inflector.singularize(key.to_s).to_sym

        if @bindings.include?(plural_binding_name)
          binding_name = plural_binding_name
        elsif @bindings.include?(singular_binding_name)
          binding_name = singular_binding_name
        else
          next
        end
      end

      [key, binding_name]
    }

    # Add view-specific bindings that aren't in the object, but may exist in the binder.
    #
    @bindings.each do |binding_name|
      unless keys_and_binding_names.find { |_, k2| k2 == binding_name }
        keys_and_binding_names << [binding_name, binding_name]
      end
    end

    viewified = keys_and_binding_names.compact.uniq.each_with_object({}) { |(key, binding_name), values|
      value = binder.__value(key)

      if value.is_a?(String)
        value = ensure_html_safety(value)
      end

      if value.is_a?(Presenter::BindingParts)
        values[binding_name] = value.values(@view.find(binding_name))
      elsif !value.nil?
        values[binding_name] = value
      end
    }

    viewified
  }
end