aboutsummaryrefslogtreecommitdiffstats
path: root/app/policies
diff options
context:
space:
mode:
authorRobert2017-05-23 23:23:34 +0200
committerRobert2017-05-24 07:13:21 +0200
commita47fe543ca8213d4b32d51c209b42e8f1a762776 (patch)
tree30a5006b516b0ed188d97d10b2eaa8f620dc9335 /app/policies
parent97467efb3cfb8893f842988ece7f95e7f5b3b9b8 (diff)
downloadchouette-core-a47fe543ca8213d4b32d51c209b42e8f1a762776.tar.bz2
Refs: #3446; metaprogrammed policy criteria
Diffstat (limited to 'app/policies')
-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