class ViewModel::AccessControl

# exception to raise.
# permitted. Each edit check returns a pair of boolean success and optional
# changed. The valid_edit change determines whether an attempted change is
# check determines whether a view in its current state is eligible to be
# valid_edit. The visible determines whether a view can be seen. The editable
# Access control is based around three edit check hooks: visible, editable and
#
# Defines an access control discipline for a given action against a viewmodel.

def cleanup_editability(view)

def cleanup_editability(view)
  @initial_editability_store.delete(view.object_id)
end

def editable!(view, deserialize_context:, changes:)

def editable!(view, deserialize_context:, changes:)
  run_callback(ViewModel::Callbacks::Hook::BeforeVisit,       view, deserialize_context)
  run_callback(ViewModel::Callbacks::Hook::BeforeDeserialize, view, deserialize_context)
  run_callback(ViewModel::Callbacks::Hook::OnChange,          view, deserialize_context, changes: changes) if changes
  run_callback(ViewModel::Callbacks::Hook::AfterDeserialize,  view, deserialize_context, changes: changes)
  run_callback(ViewModel::Callbacks::Hook::AfterVisit,        view, deserialize_context)
end

def editable_check(_traversal_env)

overridden by viewmodel implementations.
denied, an error must be raised only if an edit is later attempted. To be
checking against the initial state of the viewmodel), and if editing is
given context. This must be called before any edits have taken place (thus
Check that the record is eligible to be changed in its current state, in the
def editable_check(_traversal_env)
  Result::DENY
end

def failure(err)

if/unless DSL more readable when returning a custom failure error.
Called from composed access controls via the `env`, this is used to make the
def failure(err)
  raise ArgumentError.new("Unexpected failure type: #{err}") unless err.is_a?(StandardError)
  err
end

def fetch_editability(view)

def fetch_editability(view)
  unless @initial_editability_store.has_key?(view.object_id)
    raise RuntimeError.new("No access control data recorded for view #{view.to_reference}")
  end
  @initial_editability_store.delete(view.object_id)
end

def initialize

def initialize
  @initial_editability_store = {}
end

def raise_if_error!(result)

def raise_if_error!(result)
  raise (result.error || yield) unless result.permit?
end

def save_editability(view, initial_editability)

def save_editability(view, initial_editability)
  if @initial_editability_store.has_key?(view.object_id)
    raise RuntimeError.new("Access control data already recorded for view #{view.to_reference}")
  end
  @initial_editability_store[view.object_id] = initial_editability
end

def valid_edit_check(_traversal_env)

implementations.
edit checks the opportunity to compare values. To be overridden by viewmodel
transactional backing models, the changes may be made in advance to give the
attempted changes are permitted in the given context. For viewmodels with
Once the changes to be made to the viewmodel are known, check that the
def valid_edit_check(_traversal_env)
  Result::DENY
end

def visible!(view, context:)

* when no children could contribute to the result
* on root views
valid to run:
checking is run directly on one node without any tree context, it's only
Wrappers to check access control for a single view directly. Because the
def visible!(view, context:)
  run_callback(ViewModel::Callbacks::Hook::BeforeVisit, view, context)
  run_callback(ViewModel::Callbacks::Hook::AfterVisit,  view, context)
end

def visible_check(_traversal_env)

the given context.
Check that the user is permitted to view the record in its current state, in
def visible_check(_traversal_env)
  Result::DENY
end