class ViewModel::AccessControl::Tree
# ‘editable` on children.
# to the root node.object_id will be cached and used when evaluating `visible` and
# {editable,visible}_unless!`. The results of evaluating these checks on entry
# checks `root_children_{editable,visible}_if!` and `root_children_
# veto access to their non-root tree descendents with the additional access
# In addition, node types can be marked as a ’root’. Root types may permit and
#
# types are specified in an ‘always` block.
# `ComposedAccessControl`s, using `view` blocks. Checks that apply to all node
# Access checks for each given node type are specified at class level as
#
# type and position in a viewmodel tree.
# Extends the basic AccessControl to offer different checking based on the view
#
# viewmodels.
# Defines an access control discipline for a given action against a tree of
def always(&block)
def always(&block) self::AlwaysPolicy.instance_exec(&block) end
def cleanup_descendent_results(view)
def cleanup_descendent_results(view) @root_visibility_store.delete(view.object_id) @root_editability_store.delete(view.object_id) end
def create_policy(view_name)
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
def editable_check(traversal_env)
def editable_check(traversal_env) policy_instance_for(traversal_env.view).editable_check(traversal_env) end
def fetch_descendent_editability(view)
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
def fetch_descendent_visibility(view)
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
def find_or_create_policy(view_name)
def find_or_create_policy(view_name) view_policies.fetch(view_name) { create_policy(view_name) } end
def include_from(ancestor)
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
def inherited(subclass)
def inherited(subclass) super subclass.initialize_as_tree_access_control end
def initialize
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
def initialize_as_tree_access_control
def initialize_as_tree_access_control @included_checkers = [] @view_policies = {} const_set(:AlwaysPolicy, Class.new(Node)) end
def inspect
def inspect "#{super}(checks:\n#{@view_policies.values.map(&:inspect).join("\n")}\n#{self::AlwaysPolicy.inspect}\nincluded checkers: #{@included_checkers})" end
def policy_instance_for(view)
def policy_instance_for(view) view_name = view.class.view_name @view_policy_instances.fetch(view_name) { @always_policy_instance } end
def store_descendent_editability(view, descendent_editability)
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
def store_descendent_visibility(view, descendent_visibility)
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
def valid_edit_check(traversal_env)
def valid_edit_check(traversal_env) policy_instance_for(traversal_env.view).valid_edit_check(traversal_env) end
def view(view_name, &block)
def view(view_name, &block) policy = find_or_create_policy(view_name) policy.instance_exec(&block) end
def visible_check(traversal_env)
def visible_check(traversal_env) policy_instance_for(traversal_env.view).visible_check(traversal_env) end