Class: ViewModel::AccessControl::Tree

Inherits:
ViewModel::AccessControl show all
Defined in:
lib/view_model/access_control/tree.rb

Overview

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.

Defined Under Namespace

Classes: Node

Constant Summary

Constants included from Callbacks

Callbacks::ALWAYS

Class Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from ViewModel::AccessControl

#editable!, #visible!

Methods included from Callbacks

#ineligible, #run_callback, wrap_deserialize, wrap_serialize

Constructor Details

#initializeTree

Returns a new instance of Tree.



78
79
80
81
82
83
84
# File 'lib/view_model/access_control/tree.rb', line 78

def initialize
  super()
  @always_policy_instance = self.class::AlwaysPolicy.new(self)
  @view_policy_instances  = self.class.view_policies.each_with_object({}) { |(name, policy), h| h[name] = policy.new(self) }
  @root_visibility_store  = {}
  @root_editability_store = {}
end

Class Attribute Details

.view_policiesObject (readonly)

Returns the value of attribute view_policies.



19
20
21
# File 'lib/view_model/access_control/tree.rb', line 19

def view_policies
  @view_policies
end

Class Method Details

.always(&block) ⇒ Object



52
53
54
# File 'lib/view_model/access_control/tree.rb', line 52

def always(&block)
  self::AlwaysPolicy.instance_exec(&block)
end

.create_policy(view_name) ⇒ Object

implementation



58
59
60
61
62
63
64
65
66
67
# File 'lib/view_model/access_control/tree.rb', line 58

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) ⇒ Object



69
70
71
# File 'lib/view_model/access_control/tree.rb', line 69

def find_or_create_policy(view_name)
  view_policies.fetch(view_name) { create_policy(view_name) }
end

.include_from(ancestor) ⇒ Object



32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/view_model/access_control/tree.rb', line 32

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) ⇒ Object



21
22
23
24
# File 'lib/view_model/access_control/tree.rb', line 21

def inherited(subclass)
  super
  subclass.initialize_as_tree_access_control
end

.initialize_as_tree_access_controlObject



26
27
28
29
30
# File 'lib/view_model/access_control/tree.rb', line 26

def initialize_as_tree_access_control
  @included_checkers = []
  @view_policies     = {}
  const_set(:AlwaysPolicy, Class.new(Node))
end

.inspectObject



73
74
75
# File 'lib/view_model/access_control/tree.rb', line 73

def inspect
  "#{super}(checks:\n#{@view_policies.values.map(&:inspect).join("\n")}\n#{self::AlwaysPolicy.inspect}\nincluded checkers: #{@included_checkers})"
end

.view(view_name, &block) ⇒ Object

Definition language



47
48
49
50
# File 'lib/view_model/access_control/tree.rb', line 47

def view(view_name, &block)
  policy = find_or_create_policy(view_name)
  policy.instance_exec(&block)
end

Instance Method Details

#cleanup_descendent_results(view) ⇒ Object



125
126
127
128
# File 'lib/view_model/access_control/tree.rb', line 125

def cleanup_descendent_results(view)
  @root_visibility_store.delete(view.object_id)
  @root_editability_store.delete(view.object_id)
end

#editable_check(traversal_env) ⇒ Object



91
92
93
# File 'lib/view_model/access_control/tree.rb', line 91

def editable_check(traversal_env)
  policy_instance_for(traversal_env.view).editable_check(traversal_env)
end

#fetch_descendent_editability(view) ⇒ Object



106
107
108
109
110
# File 'lib/view_model/access_control/tree.rb', line 106

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) ⇒ Object



119
120
121
122
123
# File 'lib/view_model/access_control/tree.rb', line 119

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) ⇒ Object



99
100
101
102
103
104
# File 'lib/view_model/access_control/tree.rb', line 99

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) ⇒ Object



112
113
114
115
116
117
# File 'lib/view_model/access_control/tree.rb', line 112

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) ⇒ Object



95
96
97
# File 'lib/view_model/access_control/tree.rb', line 95

def valid_edit_check(traversal_env)
  policy_instance_for(traversal_env.view).valid_edit_check(traversal_env)
end

#visible_check(traversal_env) ⇒ Object

Evaluation entry points



87
88
89
# File 'lib/view_model/access_control/tree.rb', line 87

def visible_check(traversal_env)
  policy_instance_for(traversal_env.view).visible_check(traversal_env)
end