class ViewModel::AccessControl

Defines an access control discipline for a given action against a viewmodel.

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

Constants

Result

Public Class Methods

new() click to toggle source
# File lib/view_model/access_control.rb, line 39
def initialize
  @initial_editability_store = {}
end

Public Instance Methods

editable!(view, deserialize_context:, changes:) click to toggle source
# File lib/view_model/access_control.rb, line 82
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
editable_check(_traversal_env) click to toggle source

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

# File lib/view_model/access_control.rb, line 59
def editable_check(_traversal_env)
  Result::DENY
end
valid_edit_check(_traversal_env) click to toggle source

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

# File lib/view_model/access_control.rb, line 68
def valid_edit_check(_traversal_env)
  Result::DENY
end
visible!(view, context:) click to toggle source

Wrappers to check access control for a single view directly. Because the checking is run directly on one node without any tree context, it's only valid to run:

  • on root views

  • when no children could contribute to the result

# File lib/view_model/access_control.rb, line 77
def visible!(view, context:)
  run_callback(ViewModel::Callbacks::Hook::BeforeVisit, view, context)
  run_callback(ViewModel::Callbacks::Hook::AfterVisit,  view, context)
end
visible_check(_traversal_env) click to toggle source

Check that the user is permitted to view the record in its current state, in the given context.

# File lib/view_model/access_control.rb, line 45
def visible_check(_traversal_env)
  Result::DENY
end

Private Instance Methods

cleanup_editability(view) click to toggle source
# File lib/view_model/access_control.rb, line 145
def cleanup_editability(view)
  @initial_editability_store.delete(view.object_id)
end
fetch_editability(view) click to toggle source
# File lib/view_model/access_control.rb, line 137
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
raise_if_error!(result) { || ... } click to toggle source
# File lib/view_model/access_control.rb, line 149
def raise_if_error!(result)
  raise (result.error || yield) unless result.permit?
end
save_editability(view, initial_editability) click to toggle source
# File lib/view_model/access_control.rb, line 129
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