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)
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)
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)
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:)
* 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)
Check that the user is permitted to view the record in its current state, in
def visible_check(_traversal_env) Result::DENY end