Class: OpenLoam::Policy

Inherits:
Object
  • Object
show all
Defined in:
lib/open_loam/policy.rb

Overview

Base policy. One policy class per entity, one instance per (actor, record) pair. Action checks (read?/create?/update?/destroy?) default to "any member of the current tenant"; field-level write access is declared, not coded:

class EquipmentPolicy < OpenLoam::Policy
field :daily_rate, writable: [:manager]
end

Roles come from OpenLoam::Membership (actor + current tenant -> role).

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(actor, record) ⇒ Policy

Returns a new instance of Policy.



44
45
46
47
# File 'lib/open_loam/policy.rb', line 44

def initialize(actor, record)
  @actor = actor
  @record = record
end

Instance Attribute Details

#actor ⇒ Object (readonly)

Returns the value of attribute actor.



42
43
44
# File 'lib/open_loam/policy.rb', line 42

def actor
  @actor
end

#record ⇒ Object (readonly)

Returns the value of attribute record.



42
43
44
# File 'lib/open_loam/policy.rb', line 42

def record
  @record
end

Class Method Details

.field(name, writable: nil, readable: nil) ⇒ Object



17
18
19
# File 'lib/open_loam/policy.rb', line 17

def field(name, writable: nil, readable: nil)
  field_rules[name.to_sym] = { writable: writable, readable: readable }
end

.field_rules ⇒ Object



13
14
15
# File 'lib/open_loam/policy.rb', line 13

def field_rules
  @field_rules ||= {}
end

.for(record) ⇒ Object



21
22
23
# File 'lib/open_loam/policy.rb', line 21

def for(record)
  policy_class_for(record.class).new(OpenLoam::Current.actor, record)
end

.for_model(model, actor) ⇒ Object

A policy for a MODEL rather than one record — for bulk paths (export, import) that decide the column set once, before any row is read. The blank instance is a stand-in: the field checks key off the role, but the custom-field ones need record.class to find the definitions.



29
30
31
# File 'lib/open_loam/policy.rb', line 29

def for_model(model, actor)
  policy_class_for(model).new(actor, model.new)
end

.policy_class_for(model) ⇒ Object

Falling back to the base Policy for a model with no policy class would fail OPEN — every check there answers "any member", so a missing policy would silently grant what an explicit one restricts.



36
37
38
39
# File 'lib/open_loam/policy.rb', line 36

def policy_class_for(model)
  "#{model.name}Policy".safe_constantize ||
    raise(Error, "No policy defined for #{model.name} (expected #{model.name}Policy)")
end

Instance Method Details

#create? ⇒ Boolean

Returns:

  • (Boolean)


58
# File 'lib/open_loam/policy.rb', line 58

def create? = member?

#custom_field_readable?(field_name) ⇒ Boolean

Read counterpart of custom_field_writable?: a runtime field with no readable_roles is readable by any member; with some, only by those roles.

Returns:

  • (Boolean)


106
107
108
109
110
111
112
113
# File 'lib/open_loam/policy.rb', line 106

def custom_field_readable?(field_name)
  return false unless member?

  definition = record.class.custom_field_definitions.find_by(name: field_name.to_s)
  return false unless definition

  definition.readable_by?(role)
end

#custom_field_writable?(field_name) ⇒ Boolean

Same semantics as the static field writable: declaration, but for a runtime OpenLoam::FieldDefinition (see OpenLoam::CustomFields): no writable_roles recorded means any member may write it.

Returns:

  • (Boolean)


91
92
93
94
95
96
97
98
# File 'lib/open_loam/policy.rb', line 91

def custom_field_writable?(field_name)
  return false unless member?

  definition = record.class.custom_field_definitions.find_by(name: field_name.to_s)
  return false unless definition

  definition.writable_roles.blank? || definition.writable_roles.map(&:to_sym).include?(role)
end

#destroy? ⇒ Boolean

Returns:

  • (Boolean)


60
# File 'lib/open_loam/policy.rb', line 60

def destroy? = member?

#member? ⇒ Boolean

Returns:

  • (Boolean)


55
# File 'lib/open_loam/policy.rb', line 55

def member? = role.present?

#permitted_custom_fields(field_names) ⇒ Object



100
101
102
# File 'lib/open_loam/policy.rb', line 100

def permitted_custom_fields(field_names)
  field_names.select { |f| custom_field_writable?(f) }
end

#permitted_fields(field_names) ⇒ Object



71
72
73
# File 'lib/open_loam/policy.rb', line 71

def permitted_fields(field_names)
  field_names.select { |f| writable?(f) }
end

#read? ⇒ Boolean

Returns:

  • (Boolean)


57
# File 'lib/open_loam/policy.rb', line 57

def read? = member?

#readable?(field_name) ⇒ Boolean

Field-level READ check (used by CSV export): a field with no readable: rule is readable by any member; with one, only by the listed roles.

Returns:

  • (Boolean)


77
78
79
80
81
82
# File 'lib/open_loam/policy.rb', line 77

def readable?(field_name)
  rule = self.class.field_rules[field_name.to_sym]
  return member? if rule.nil? || rule[:readable].nil?

  Array(rule[:readable]).map(&:to_sym).include?(role)
end

#readable_custom_fields(field_names) ⇒ Object



115
116
117
# File 'lib/open_loam/policy.rb', line 115

def readable_custom_fields(field_names)
  field_names.select { |f| custom_field_readable?(f) }
end

#readable_fields(field_names) ⇒ Object



84
85
86
# File 'lib/open_loam/policy.rb', line 84

def readable_fields(field_names)
  field_names.select { |f| readable?(f) }
end

#role ⇒ Object



49
50
51
52
53
# File 'lib/open_loam/policy.rb', line 49

def role
  return nil unless actor

  @role ||= OpenLoam::Membership.find_by(user_id: actor.id)&.role&.to_sym
end

#update? ⇒ Boolean

Returns:

  • (Boolean)


59
# File 'lib/open_loam/policy.rb', line 59

def update? = member?

#writable?(field_name) ⇒ Boolean

Field-level check: fields without a declared rule are writable by any member; fields with writable: only by the listed roles.

Returns:

  • (Boolean)


64
65
66
67
68
69
# File 'lib/open_loam/policy.rb', line 64

def writable?(field_name)
  rule = self.class.field_rules[field_name.to_sym]
  return member? if rule.nil? || rule[:writable].nil?

  Array(rule[:writable]).map(&:to_sym).include?(role)
end