class ViewModel::ActiveRecord::AbstractCollectionUpdate::Parser

def append_action_schema # abstract

abstract
def append_action_schema # abstract
  raise 'abstract'
end

def functional_update_schema # abstract

abstract
def functional_update_schema # abstract
  raise 'abstract'
end

def functional_update_type # abstract

abstract
def functional_update_type # abstract
  raise 'abstract'
end

def initialize(association_data, blame_reference, valid_reference_keys)

def initialize(association_data, blame_reference, valid_reference_keys)
  @association_data     = association_data
  @blame_reference      = blame_reference
  @valid_reference_keys = valid_reference_keys
end

def parse(value)

def parse(value)
  case value
  when Array
    replace_update_type.new(parse_contents(value))
  when Hash
    ViewModel::Schemas.verify_schema!(functional_update_schema, value)
    functional_updates = value[ACTIONS_ATTRIBUTE].map { |action| parse_action(action) }
    functional_update_type.new(functional_updates)
  else
    raise ViewModel::DeserializationError::InvalidSyntax.new(
            "Could not parse non-array value for collection association '#{association_data}'",
            blame_reference)
  end
end

def parse_action(action)

def parse_action(action)
  type = action[ViewModel::TYPE_ATTRIBUTE]
  case type
  when FunctionalUpdate::Remove::NAME
    parse_remove_action(action)
  when FunctionalUpdate::Append::NAME
    parse_append_action(action)
  when FunctionalUpdate::Update::NAME
    parse_update_action(action)
  else
    raise ViewModel::DeserializationError::InvalidSyntax.new(
            "Unknown action type '#{type}'",
            blame_reference)
  end
end

def parse_anchor(child_hash) # final

final
referenced associations.
May only contain type and id fields, is never a reference even for
Parse an anchor for a functional_update, before/after
def parse_anchor(child_hash) # final
  child_metadata = ViewModel.extract_reference_only_metadata(child_hash)
  child_viewmodel_class =
    association_data.viewmodel_class_for_name(child_metadata.view_name)
  if child_viewmodel_class.nil?
    raise ViewModel::DeserializationError::InvalidAssociationType.new(
            association_data.association_name.to_s,
            child_metadata.view_name,
            blame_reference)
  end
  ViewModel::Reference.new(child_viewmodel_class, child_metadata.id)
end

def parse_append_action(action) # final

final
def parse_append_action(action) # final
  ViewModel::Schemas.verify_schema!(append_action_schema, action)
  values = action[VALUES_ATTRIBUTE]
  update = FunctionalUpdate::Append.new(parse_contents(values))
  if (before = action[BEFORE_ATTRIBUTE])
    update.before = parse_anchor(before)
  end
  if (after = action[AFTER_ATTRIBUTE])
    update.after = parse_anchor(after)
  end
  if before && after
    raise ViewModel::DeserializationError::InvalidSyntax.new(
            "Append may not specify both 'after' and 'before'",
            blame_reference)
  end
  update
end

def parse_contents(_values) # abstract

abstract
def parse_contents(_values) # abstract
  raise 'abstract'
end

def parse_remove_action(action) # final

final
def parse_remove_action(action) # final
  ViewModel::Schemas.verify_schema!(remove_action_schema, action)
  values = action[VALUES_ATTRIBUTE]
  FunctionalUpdate::Remove.new(parse_remove_values(values))
end

def parse_remove_values(values)

Remove values are always anchors
def parse_remove_values(values)
  # There's no reasonable interpretation of a remove update that includes data.
  # Report it as soon as we detect it.
  invalid_entries = values.reject { |h| UpdateData.reference_only_hash?(h) }
  if invalid_entries.present?
    raise ViewModel::DeserializationError::InvalidSyntax.new(
            "Removed entities must have only #{ViewModel::TYPE_ATTRIBUTE} and #{ViewModel::ID_ATTRIBUTE} fields. " \
            "Invalid entries: #{invalid_entries}",
            blame_reference)
  end
  values.map { |value| parse_anchor(value) }
end

def parse_update_action(action) # final

final
def parse_update_action(action) # final
  ViewModel::Schemas.verify_schema!(update_action_schema, action)
  values = action[VALUES_ATTRIBUTE]
  FunctionalUpdate::Update.new(parse_contents(values))
end

def remove_action_schema # abstract

abstract
def remove_action_schema # abstract
  raise 'abstract'
end

def replace_update_type # abstract

abstract
def replace_update_type # abstract
  raise 'abstract'
end

def update_action_schema # abstract

abstract
def update_action_schema # abstract
  raise 'abstract'
end