Class: Hyperloop::InternalClassPolicy

Inherits:
Object
  • Object
show all
Defined in:
lib/hyper-operation/transport/policy.rb

Constant Summary collapse

EXPOSED_METHODS =
[
  :regulate_class_connection, :always_allow_connection, :regulate_instance_connections,
  :regulate_all_broadcasts, :regulate_broadcast,
  :dispatch_to, :regulate_dispatches_from, :always_dispatch_from,
  :allow_change, :allow_create, :allow_read, :allow_update, :allow_destroy
]
CHANGE_POLICIES =
[:create, :update, :destroy]

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(regulated_klass) ⇒ InternalClassPolicy

Returns a new instance of InternalClassPolicy.


5
6
7
8
9
10
11
12
13
14
15
16
17
# File 'lib/hyper-operation/transport/policy.rb', line 5

def initialize(regulated_klass)
  unless regulated_klass.is_a?(Class)
    # attempt to constantize the class in case eager_loading in production
    # has loaded the policy before the class.  THis will insure that if
    # there is a class being regulated, it is loaded first.
    begin
      regulated_klass.constantize
    rescue NameError
      nil
    end
  end
  @regulated_klass = regulated_klass
end

Instance Attribute Details

#regulated_klassObject (readonly)

Returns the value of attribute regulated_klass


19
20
21
# File 'lib/hyper-operation/transport/policy.rb', line 19

def regulated_klass
  @regulated_klass
end

Class Method Details

.allow_policy(policy, method) ⇒ Object


78
79
80
81
82
83
84
# File 'lib/hyper-operation/transport/policy.rb', line 78

def self.allow_policy(policy, method)
  define_method "allow_#{policy}" do |*args, &regulation|
    process_args(policy, [], args, regulation) do |model|
      get_ar_model(model).class_eval { define_method("#{method}_permitted?", &regulation) }
    end
  end
end

.ar_base_descendants_map_cacheObject


101
102
103
# File 'lib/hyper-operation/transport/policy.rb', line 101

def self.ar_base_descendants_map_cache
  @ar_base_descendants_map_cache ||= ActiveRecord::Base.descendants.map(&:name)
end

.regulated_klassesObject


128
129
130
# File 'lib/hyper-operation/transport/policy.rb', line 128

def self.regulated_klasses
  @regulated_klasses ||= Set.new
end

Instance Method Details

#allow_change(*args, &regulation) ⇒ Object


90
91
92
93
94
95
96
97
98
99
# File 'lib/hyper-operation/transport/policy.rb', line 90

def allow_change(*args, &regulation)
  process_args('change', [:on], args, regulation) do |model, opts|
    model = get_ar_model(model)
    opts[:on] ||= CHANGE_POLICIES
    opts[:on].each do |policy|
      check_valid_on_option policy
      model.class_eval { define_method("#{policy}_permitted?", &regulation) }
    end
  end
end

#always_allow_connection(*args) ⇒ Object


32
33
34
# File 'lib/hyper-operation/transport/policy.rb', line 32

def always_allow_connection(*args)
  regulate(ClassConnectionRegulation, nil, args) { true }
end

#always_dispatch_from(*args, &regulation) ⇒ Object


57
58
59
# File 'lib/hyper-operation/transport/policy.rb', line 57

def always_dispatch_from(*args, &regulation)
  regulate_dispatches_from(*args) { true }
end

#check_valid_on_option(policy) ⇒ Object


166
167
168
169
170
171
# File 'lib/hyper-operation/transport/policy.rb', line 166

def check_valid_on_option(policy)
  unless CHANGE_POLICIES.include? policy
    valid_policies = CHANGE_POLICIES.collect { |s| ":{s}" }.to_sentence
    raise "only #{valid_policies} are allowed on the regulate_access :on option"
  end
end

#dispatch_to(*args, &regulation) ⇒ Object


61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/hyper-operation/transport/policy.rb', line 61

def dispatch_to(*args, &regulation)
  actual_klass = if regulated_klass.is_a?(Class)
                   regulated_klass
                 else
                   begin
                     regulated_klass.constantize
                   rescue NameError
                     nil
                   end
                 end
  raise 'you can only dispatch_to Operation classes' unless actual_klass.respond_to? :dispatch_to
  actual_klass.dispatch_to(actual_klass)
  actual_klass.dispatch_to(*args, &regulation)
end

#get_ar_model(str) ⇒ Object


105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/hyper-operation/transport/policy.rb', line 105

def get_ar_model(str)
  if str.is_a?(Class)
    unless str <= ActiveRecord::Base
      Hyperloop::InternalPolicy.raise_operation_access_violation(:non_ar_class, "#{str} is not a subclass of ActiveRecord::Base")
    end
    str
  else
    # we used to cache this here, but during eager loading the cache may get partially filled and never updated
    # so this guard will fail, now performance will be suckish, as this guard, required for security, takes some ms
    # def self.ar_base_descendants_map_cache
    #   @ar_base_descendants_map_cache ||= ActiveRecord::Base.descendants.map(&:name)
    # end
    # if Rails.env.production? && !Hyperloop::InternalClassPolicy.ar_base_descendants_map_cache.include?(str)
    if Rails.application.config.eager_load && !ActiveRecord::Base.descendants.map(&:name).include?(str)
      # AR::Base.descendants is eager loaded in production -> this guard works.
      # In development it may be empty or partially filled -> this guard may fail.
      # Thus guarded here only in production.
      Hyperloop::InternalPolicy.raise_operation_access_violation(:non_ar_class, "#{str} is either not defined or is not a subclass of ActiveRecord::Base")
    end
    Object.const_get(str)
  end
end

#process_args(policy, allowed_opts, args, regulation) ⇒ Object


139
140
141
142
143
144
145
146
147
# File 'lib/hyper-operation/transport/policy.rb', line 139

def process_args(policy, allowed_opts, args, regulation)
  raise "you must provide a block to the regulate_#{policy} method" unless regulation
  *args, opts = args if args.last.is_a? Hash
  opts ||= {}
  args = process_to_opt(allowed_opts, opts, args)
  args.each do |regulated_klass|
    yield regulated_klass, opts
  end
end

#process_to_opt(allowed_opts, opts, args) ⇒ Object


149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# File 'lib/hyper-operation/transport/policy.rb', line 149

def process_to_opt(allowed_opts, opts, args)
  opts.each do |opt, value|
    unless opt == :to || allowed_opts.include?(opt)
      raise "Unknown ':#{opt} => #{value}' option in policy definition"
    end
  end
  if (to = opts[:to])
    raise "option to: :#{to} is not recognized in allow_#{policy}" unless to == :all
    raise "option to: :all cannot be used with other classes" unless args.empty?
    [ActiveRecord::Base]
  elsif args.empty?
    [@regulated_klass]
  else
    args
  end
end

#regulate(regulation_klass, policy, args, &regulation) ⇒ Object


132
133
134
135
136
137
# File 'lib/hyper-operation/transport/policy.rb', line 132

def regulate(regulation_klass, policy, args, &regulation)
  process_args(policy, regulation_klass.allowed_opts, args, regulation) do |regulated_klass, opts|
    self.class.regulated_klasses << regulated_klass.to_s
    regulation_klass.add_regulation regulated_klass, opts, &regulation
  end
end

#regulate_all_broadcasts(*args, &regulation) ⇒ Object


40
41
42
# File 'lib/hyper-operation/transport/policy.rb', line 40

def regulate_all_broadcasts(*args, &regulation)
  regulate(ChannelBroadcastRegulation, :all_broadcasts, args, &regulation)
end

#regulate_broadcast(*args, &regulation) ⇒ Object


44
45
46
# File 'lib/hyper-operation/transport/policy.rb', line 44

def regulate_broadcast(*args, &regulation)
  regulate(InstanceBroadcastRegulation, :broadcast, args, &regulation)
end

#regulate_class_connection(*args, &regulation) ⇒ Object


28
29
30
# File 'lib/hyper-operation/transport/policy.rb', line 28

def regulate_class_connection(*args, &regulation)
  regulate(ClassConnectionRegulation, :class_connection, args, &regulation)
end

#regulate_dispatches_from(*args, &regulation) ⇒ Object


48
49
50
51
52
53
54
55
# File 'lib/hyper-operation/transport/policy.rb', line 48

def regulate_dispatches_from(*args, &regulation)
  args.each do |klass|
    unless klass.respond_to? :dispatch_to
      raise 'you can only regulate_dispatches_from Operation classes'
    end
    klass._dispatch_to(self) { |sself| sself.regulated_klass if instance_eval(&regulation) }
  end
end

#regulate_instance_connections(*args, &regulation) ⇒ Object


36
37
38
# File 'lib/hyper-operation/transport/policy.rb', line 36

def regulate_instance_connections(*args, &regulation)
  regulate(InstanceConnectionRegulation, :instance_connections, args, &regulation)
end