aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--app/policies/boiv_policy.rb1
-rw-r--r--app/policies/chain.rb57
-rw-r--r--app/policies/time_table_policy.rb9
3 files changed, 62 insertions, 5 deletions
diff --git a/app/policies/boiv_policy.rb b/app/policies/boiv_policy.rb
index e29a2e6de..7f7534813 100644
--- a/app/policies/boiv_policy.rb
+++ b/app/policies/boiv_policy.rb
@@ -11,5 +11,4 @@ class BoivPolicy < ApplicationPolicy
def show?
boiv_read_offer?
end
-
end
diff --git a/app/policies/chain.rb b/app/policies/chain.rb
new file mode 100644
index 000000000..4bf96bd84
--- /dev/null
+++ b/app/policies/chain.rb
@@ -0,0 +1,57 @@
+module Policies
+ # Implements the `chain_policies` macro as follows
+ #
+ # chain_policies <method_chain>, policies:
+ #
+ # e.g.
+ #
+ # chain_policies [:archived?, :!], policies: %i{create? edit?}
+ #
+ # which would establish a precondition `not archived` for the `create?` and `edit?`
+ # method, it is semantically identical to instrumenting both methods
+ # as follows:
+ #
+ # def create? # or edit?
+ # archived?.! && <original code of method>
+ # end
+ module Chain
+
+ # A local chain store implemented to avoid any possible side effect on client policies.
+ defined_chains = {}
+
+ # Using `define_method` in order to close over `defined_chains`
+ # We need to store the chains because the methods they will apply to
+ # are not defined yet.
+ define_method :chain_policies do |*conditions, policies:|
+ policies.each do | meth_name |
+ # self represents the client Policy
+ defined_chains[[self, meth_name]] = conditions
+ end
+ end
+ # Intercept method definition and check if a policy_chain has been registered for it‥.
+ define_method :method_added do |meth_name, *args, &blk|
+ # Delete potentially registered criteria conditions to‥.
+ # (i) protect against endless recursion via (:merthod_added → :define_method → :method_added → ‥.
+ # (ii) get the condition
+ conditions = defined_chains.delete([self, meth_name])
+ return unless conditions
+
+ instrument_method(meth_name, conditions)
+ end
+
+ private
+
+ # Access to the closure is not necessary anymore, normal metaprogramming can take place :)
+ def instrument_method(meth_name, conditions)
+ orig_method = instance_method(meth_name)
+ # In case of warnings remove original method here, depends on Ruby Version, ok in 2.3.1
+ define_method meth_name do |*a, &b|
+ # Method chain describing the chained policy precondition.
+ conditions.inject(self) do | result, msg |
+ result.send msg
+ end &&
+ orig_method.bind(self).(*a, &b)
+ end
+ end
+ end
+end
diff --git a/app/policies/time_table_policy.rb b/app/policies/time_table_policy.rb
index 059edb8c6..4b2bf0cd9 100644
--- a/app/policies/time_table_policy.rb
+++ b/app/policies/time_table_policy.rb
@@ -1,27 +1,28 @@
+require_relative 'chain'
class TimeTablePolicy < BoivPolicy
+ extend Policies::Chain
+
class Scope < Scope
def resolve
scope
end
end
+ chain_policies :archived?, :!, policies: %i{create? destroy? duplicate? edit?}
+
def create?
- !archived? &&
user.has_permission?('time_tables.create') # organisation match via referential is checked in the view
end
def edit?
- !archived? &&
organisation_match? && user.has_permission?('time_tables.edit')
end
def destroy?
- !archived? &&
organisation_match? && user.has_permission?('time_tables.destroy')
end
def duplicate?
- !archived? &&
organisation_match? && create?
end