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
Public Class Methods
# File lib/view_model/access_control/tree.rb, line 54 def always(&block) self::AlwaysPolicy.instance_exec(&block) end
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
# 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
# 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
ViewModel::Callbacks#inherited
# File lib/view_model/access_control/tree.rb, line 23 def inherited(subclass) super subclass.initialize_as_tree_access_control end
# 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
# 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
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
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
# 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
# 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
# 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
# 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
# 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
# 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
# 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
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
# 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