class ViewModel::AccessControl::Tree

Defines an access control discipline for a given action against a tree of viewmodels.

Extends the basic AccessControl to offer different checking based on the view type and position in a viewmodel tree.

Access checks for each given node type are specified at class level as `ComposedAccessControl`s, using `view` blocks. Checks that apply to all node types are specified in an `always` block.

In addition, node types can be marked as a 'root'. Root types may permit and veto access to their non-root tree descendents with the additional access checks `root_children_{editable,visible}_if!` and `root_children_ {editable,visible}_unless!`. The results of evaluating these checks on entry to the root node.object_id will be cached and used when evaluating `visible` and `editable` on children.

Attributes

view_policies[R]

Public Class Methods

always(&block) click to toggle source
# File lib/view_model/access_control/tree.rb, line 54
def always(&block)
  self::AlwaysPolicy.instance_exec(&block)
end
create_policy(view_name) click to toggle source

implementation

# File lib/view_model/access_control/tree.rb, line 60
def create_policy(view_name)
  policy = Class.new(Node)
  # View names are not necessarily rails constants, but we want
  # `const_set` them so they show up in stack traces.
  mangled_name = view_name.tr('.', '_')
  const_set(:"#{mangled_name}Policy", policy)
  view_policies[view_name] = policy
  policy.include_from(self::AlwaysPolicy)
  policy
end
find_or_create_policy(view_name) click to toggle source
# File lib/view_model/access_control/tree.rb, line 71
def find_or_create_policy(view_name)
  view_policies.fetch(view_name) { create_policy(view_name) }
end
include_from(ancestor) click to toggle source
# File lib/view_model/access_control/tree.rb, line 34
def include_from(ancestor)
  unless ancestor < ViewModel::AccessControl::Tree
    raise ArgumentError.new("Invalid ancestor: #{ancestor}")
  end

  @included_checkers << ancestor

  self::AlwaysPolicy.include_from(ancestor::AlwaysPolicy)
  ancestor.view_policies.each do |view_name, ancestor_policy|
    policy = find_or_create_policy(view_name)
    policy.include_from(ancestor_policy)
  end
end
inherited(subclass) click to toggle source
Calls superclass method ViewModel::Callbacks#inherited
# File lib/view_model/access_control/tree.rb, line 23
def inherited(subclass)
  super
  subclass.initialize_as_tree_access_control
end
initialize_as_tree_access_control() click to toggle source
# File lib/view_model/access_control/tree.rb, line 28
def initialize_as_tree_access_control
  @included_checkers = []
  @view_policies     = {}
  const_set(:AlwaysPolicy, Class.new(Node))
end
inspect() click to toggle source
# File lib/view_model/access_control/tree.rb, line 75
def inspect
  "#{super}(checks:\n#{@view_policies.values.map(&:inspect).join("\n")}\n#{self::AlwaysPolicy.inspect}\nincluded checkers: #{@included_checkers})"
end
new() click to toggle source
Calls superclass method ViewModel::AccessControl::new
# File lib/view_model/access_control/tree.rb, line 80
def initialize
  super()
  @always_policy_instance = self.class::AlwaysPolicy.new(self)
  @view_policy_instances  = self.class.view_policies.transform_values { |policy| policy.new(self) }
  @root_visibility_store  = {}
  @root_editability_store = {}
end
view(view_name, &block) click to toggle source

Definition language

# File lib/view_model/access_control/tree.rb, line 49
def view(view_name, &block)
  policy = find_or_create_policy(view_name)
  policy.instance_exec(&block)
end

Public Instance Methods

cleanup_descendent_results(view) click to toggle source
# File lib/view_model/access_control/tree.rb, line 129
def cleanup_descendent_results(view)
  @root_visibility_store.delete(view.object_id)
  @root_editability_store.delete(view.object_id)
end
editable_check(traversal_env) click to toggle source
# File lib/view_model/access_control/tree.rb, line 93
def editable_check(traversal_env)
  policy_instance_for(traversal_env.view).editable_check(traversal_env)
end
fetch_descendent_editability(view) click to toggle source
# File lib/view_model/access_control/tree.rb, line 109
def fetch_descendent_editability(view)
  @root_editability_store.fetch(view.object_id) do
    raise RuntimeError.new('No root access control data recorded for root')
  end
end
fetch_descendent_visibility(view) click to toggle source
# File lib/view_model/access_control/tree.rb, line 123
def fetch_descendent_visibility(view)
  @root_visibility_store.fetch(view.object_id) do
    raise RuntimeError.new('No root access control data recorded for root')
  end
end
store_descendent_editability(view, descendent_editability) click to toggle source
# File lib/view_model/access_control/tree.rb, line 101
def store_descendent_editability(view, descendent_editability)
  if @root_editability_store.has_key?(view.object_id)
    raise RuntimeError.new('Root access control data already saved for root')
  end

  @root_editability_store[view.object_id] = descendent_editability
end
store_descendent_visibility(view, descendent_visibility) click to toggle source
# File lib/view_model/access_control/tree.rb, line 115
def store_descendent_visibility(view, descendent_visibility)
  if @root_visibility_store.has_key?(view.object_id)
    raise RuntimeError.new('Root access control data already saved for root')
  end

  @root_visibility_store[view.object_id] = descendent_visibility
end
valid_edit_check(traversal_env) click to toggle source
# File lib/view_model/access_control/tree.rb, line 97
def valid_edit_check(traversal_env)
  policy_instance_for(traversal_env.view).valid_edit_check(traversal_env)
end
visible_check(traversal_env) click to toggle source

Evaluation entry points

# File lib/view_model/access_control/tree.rb, line 89
def visible_check(traversal_env)
  policy_instance_for(traversal_env.view).visible_check(traversal_env)
end

Private Instance Methods

policy_instance_for(view) click to toggle source
# File lib/view_model/access_control/tree.rb, line 140
def policy_instance_for(view)
  view_name = view.class.view_name
  @view_policy_instances.fetch(view_name) { @always_policy_instance }
end