icelab/formalist

View on GitHub
lib/formalist/rich_text/embedded_form_compiler.rb

Summary

Maintainability
A
0 mins
Test Coverage
require "json"

module Formalist
  module RichText

    # Our input data looks like this example, which consists of 3 elements:
    #
    # 1. A text line
    # 2. embedded form data
    # 3. Another text line
    #
    # [
    #   ["block",["unstyled","b14hd",[["inline",[[],"Before!"]]]]],
    #   ["block",["atomic","48b4f",[["entity",["formalist","1","IMMUTABLE",{"name":"image_with_caption","label":"Image with caption","data":{"image_id":"5678","caption":"Large panda"}},[["inline",[[],"ΒΆ"]]]]]]]],
    #   ["block",["unstyled","aivqi",[["inline",[[],"After!"]]]]]
    # ]
    #
    # We want to intercept the embededed form data and transform them into full
    # form ASTs, complete with validation messages.

    class EmbeddedFormCompiler
      attr_reader :embedded_forms

      def initialize(embedded_form_collection)
        @embedded_forms = embedded_form_collection
      end

      def call(ast)
        return ast if ast.nil?

        ast = ast.is_a?(String) ? JSON.parse(ast) : ast

        ast.map { |node| visit(node) }
      end
      alias_method :[], :call

      private

      def visit(node)
        name, nodes = node

        handler = :"visit_#{name}"

        if respond_to?(handler, true)
          send(handler, nodes)
        else
          [name, nodes]
        end
      end

      # We need to visit blocks in order to get to the formalist entities nested within them
      def visit_block(node)
        type, id, children = node

        ["block", [type, id, children.map { |child| visit(child) }]]
      end

      def visit_entity(node)
        type, key, mutability, entity_data, children = node

        return ["entity", node] unless type == "formalist"

        embedded_form = embedded_forms[entity_data["name"]]

        compiled_entity_data = entity_data.merge(
          "label" => embedded_form.label,
          "form" => prepare_form_ast(embedded_form, entity_data["data"])
        )

        ["entity", [type, key, mutability, compiled_entity_data, children]]
      end

      def prepare_form_ast(embedded_form, data)
        # Run the raw data through the validation schema
        validation = embedded_form.schema.(data)

        # And then through the embedded form's input processor (which may add
        # extra system-generated information necessary for the form to render
        # fully)
        input = embedded_form.input_processor.(validation.to_h)

        embedded_form.form.fill(input: input, errors: validation.errors.to_h).to_ast
      end
    end
  end
end