summaryrefslogtreecommitdiff
path: root/app/policies/base_policy.rb
blob: 623424c63e087aebbbba35eb47f90a4453da74b6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
class BasePolicy
  class RuleSet
    attr_reader :can_set, :cannot_set
    def initialize(can_set, cannot_set)
      @can_set = can_set
      @cannot_set = cannot_set
    end

    delegate :size, to: :to_set

    def self.empty
      new(Set.new, Set.new)
    end

    def self.none
      empty.freeze
    end

    def can?(ability)
      @can_set.include?(ability) && !@cannot_set.include?(ability)
    end

    def include?(ability)
      can?(ability)
    end

    def to_set
      @can_set - @cannot_set
    end

    def merge(other)
      @can_set.merge(other.can_set)
      @cannot_set.merge(other.cannot_set)
    end

    def can!(*abilities)
      @can_set.merge(abilities)
    end

    def cannot!(*abilities)
      @cannot_set.merge(abilities)
    end

    def freeze
      @can_set.freeze
      @cannot_set.freeze
      super
    end
  end

  def self.abilities(user, subject)
    new(user, subject).abilities
  end

  def self.class_for(subject)
    return GlobalPolicy if subject == :global
    raise ArgumentError, 'no policy for nil' if subject.nil?

    if subject.class.try(:presenter?)
      subject = subject.subject
    end

    subject.class.ancestors.each do |klass|
      next unless klass.name

      begin
        policy_class = "#{klass.name}Policy".constantize

        # NOTE: the < operator here tests whether policy_class
        # inherits from BasePolicy
        return policy_class if policy_class < BasePolicy
      rescue NameError
        nil
      end
    end

    raise "no policy for #{subject.class.name}"
  end

  attr_reader :user, :subject
  def initialize(user, subject)
    @user = user
    @subject = subject
  end

  def abilities
    return RuleSet.none if @user && @user.blocked?
    return anonymous_abilities if @user.nil?
    collect_rules { rules }
  end

  def anonymous_abilities
    collect_rules { anonymous_rules }
  end

  def anonymous_rules
    rules
  end

  def rules
    raise NotImplementedError
  end

  def delegate!(new_subject)
    @rule_set.merge(Ability.allowed(@user, new_subject))
  end

  def can?(rule)
    @rule_set.can?(rule)
  end

  def can!(*rules)
    @rule_set.can!(*rules)
  end

  def cannot!(*rules)
    @rule_set.cannot!(*rules)
  end

  private

  def collect_rules(&b)
    @rule_set = RuleSet.empty
    yield
    @rule_set
  end
end