Permalink
Browse files

initial

  • Loading branch information...
1 parent 37afb2b commit 17786228ad132b36d2c22246fa425af7c4ce0365 @kristianmandrup committed Nov 19, 2011
View
Binary file not shown.
@@ -0,0 +1,78 @@
+module CanTango
+ class AbilityCache
+ autoload_modules :BaseCache, :SessionCache, :Reader, :Writer, :RulesCache, :Key
+
+ include CanTango::Helpers::Debug
+ include CanTango::Helpers::RoleMethods
+
+ attr_reader :rules_cached, :ability
+ attr_writer :key_method_names, :cache_key
+
+ delegate :session, :cached?, :to => :ability
+
+ def initialize ability, options = {}
+ @ability = ability
+ @cache_key = options[:cache_key]
+ @key_method_names = options[:key_method_names]
+ debug "Creating cache with key: #{cache_key.inspect} on #{key_method_names.inspect}"
+ end
+
+ def empty?
+ cached_rules.blank?
+ end
+
+ def key_method_names
+ @key_method_names ||= [roles_list_meth, role_groups_list_meth]
+ end
+
+ def cache_key
+ @cache_key ||= :cache
+ end
+
+ def cache_rules!
+ writer.save(key, reader.prepared_rules) if cached?
+ end
+
+ def cached_rules
+ @rules ||= reader.prepared_rules if cached?
+ end
+
+ def compiler
+ @compiler ||= Kompiler.new
+ end
+
+ def reader
+ @reader ||= Reader.new(self)
+ end
+
+ def writer
+ @writer ||= Writer.new(self)
+ end
+
+ def cached_rules?
+ key.same?(session) && cached?
+ end
+
+ def key
+ @key ||= Key.new ability.user, ability.subject, key_method_names
+ end
+
+ def rules_cache
+ @rules_cache ||= RulesCache.new(session).instance
+ end
+
+ def invalidate!
+ raise "no session" if !session
+ rules_cache.invalidate! session[cache_key]
+ end
+
+ def compile_on?
+ return false if !compile_adapter?
+ CanTango.config.cache_engine.compile?
+ end
+
+ def compile_adapter?
+ CanTango.config.adapters.registered?(:compiler)
+ end
+ end
+end
Binary file not shown.
@@ -0,0 +1,84 @@
+module CanTango
+ class AbilityCache
+ class Key
+ attr_reader :user, :subject
+
+ def initialize user, subject = nil, method_names = nil
+ @user = user
+ @subject = subject || user
+ @method_names = method_names if method_names
+ end
+
+ def method_names
+ @method_names ||= [:roles_list, :role_groups_list]
+ end
+
+ def self.create_for ability
+ self.new ability.user, ability.subject
+ end
+
+ def value
+ raise "No key could be generated for User:#{user.inspect} and Subject:#{subject} - key field: #{user_key_field}" if hash_values.empty?
+ @value ||= hash_values.hash
+ end
+
+ def same? session
+ raise "No session available" if !session
+ session[:cache_key] && (value == session[:cache_key].value)
+ end
+
+ def to_s
+ "key hash: #{value}"
+ end
+
+ protected
+
+ def hash_values
+ @hash_values ||= [user_key, subject_roles_hash, permissions_key].compact
+ end
+
+ def permissions_key
+ return subject.send(:permissions_hash) if permissions_key? && subject.respond_to?(:permissions_hash)
+ subject.send(:permissions) if permissions_key?
+ end
+
+ def permissions_key?
+ subject.respond_to?(:permissions) && permission_engine.modes.include?(:cache) && permission_engine.on?
+ end
+
+ def user_key
+ # raise "#{user.class} must have a method ##{user_key_field}. You can configure this with CanTango.config#user.unique_key_field" if !user.respond_to?(user_key_field)
+ user.send(user_key_field) if user.respond_to? user_key_field
+ end
+
+ def user_key_field
+ CanTango.config.user.unique_key_field || :email
+ end
+
+ def subject_roles_hash
+ role_hash_values.empty? ? nil : role_hash_values.hash
+ end
+
+ def role_hash_values
+ @role_hash_values ||= method_names.inject([]) do |result, meth_name|
+ result << subject.send(meth_name) if use_in_hash? meth_name
+ result
+ end
+ end
+
+ private
+
+ def permission_engine
+ CanTango.config.engine(:user_ac)
+ end
+
+ def use_in_hash? meth_name
+ subject.respond_to?(meth_name) && CanTango.config.permits.enabled_types.include?(meth_map[meth_name])
+ end
+
+ def meth_map
+ {:roles_list => :role, :role_groups_list => :role_group }
+ end
+ end
+ end
+end
@@ -0,0 +1,38 @@
+require 'sourcify'
+
+module CanTango
+ class AbilityCache
+ class Kompiler
+ class CacheMarshallError < StandardError; end
+
+ def compile! rules_raw
+ return if !rules_raw
+ rules_compiled = rules_raw.map do |rule|
+ rule.block = rule.block.to_source if rule.block.class == Proc && sourcify?
+ rule
+ end
+ end
+
+ def decompile! rules_compiled
+ return if !rules_compiled
+ rules_raw = rules_compiled.map do |rule|
+ compiler_check! rule
+ rule.block = eval("#{rule.block}") if sourcify?
+ rule
+ end
+ end
+
+ protected
+
+ def sourcify?
+ @sourcify ||= Proc.new {}.respond_to?(:to_source)
+ end
+
+ def compiler_check! rule
+ if rule.block && !CanTango.config.adapters.registered?(:compiler)
+ raise "You can NOT marshal dynamic rules (with Procs) unless you register the :compiler adapter"
+ end
+ end
+ end
+ end
+end
@@ -0,0 +1,32 @@
+module CanTango
+ class AbilityCache
+ class Reader
+ include CanTango::Helpers::Debug
+
+ attr_reader :cache
+
+ def initialize cache
+ @cache = cache
+ end
+
+ def prepared_rules
+ debug "reading rules from cache: #{cache.key}"
+ cache.compile_on? ? compiler.decompile!(loaded_rules) : loaded_rules
+ end
+
+ protected
+
+ def loaded_rules
+ rules_cache.load(cache.key)
+ end
+
+ def compiler
+ cache.compiler
+ end
+
+ def rules_cache
+ cache.rules_cache
+ end
+ end
+ end
+end
@@ -0,0 +1,48 @@
+module CanTango
+ class Ability
+ class Cache
+ class Writer
+ include CanTango::Helpers::Debug
+
+ attr_reader :cache, :cache_key
+
+ delegate :cache_key, :to => :cache
+
+ def initialize cache
+ @cache = cache
+ end
+
+ def save key, rules
+ cache.invalidate!
+ return if prepared_rules.blank?
+ debug "writing #{prepared_rules.size} rules to cache: #{key}"
+ cache.rules_cache.save cache.key, prepared_rules
+ session[cache_key] = cache.key if session
+ end
+
+ protected
+
+ def prepared_rules
+ cache.compile_on? ? compiler.compile!(rules) : rules
+ end
+
+ def rules
+ return cache.cached_rules if cache.cached_rules?
+ cache.ability.send :rules # bad!? FIXME
+ end
+
+ def compiler
+ cache.compiler
+ end
+
+ def session
+ cache.session
+ end
+
+ def rules_cache
+ cache.rules_cache
+ end
+ end
+ end
+ end
+end
View
@@ -0,0 +1,5 @@
+module CanTango
+ module Cache
+ autoload_modules :Base, :Moneta, :Session
+ end
+end
View
@@ -0,0 +1,24 @@
+module CanTango
+ module Cache
+ class Base
+ attr_reader :name, :options
+
+ def initialize name, options = {}
+ @name, @options = [name, options]
+
+ options.each_pair do |name, value|
+ var = :"@#{name}"
+ self.instance_variable_set(var, value)
+ end
+ end
+
+ def load key
+ raise NotImplementedError
+ end
+
+ def save key, rules
+ raise NotImplementedError
+ end
+ end
+ end
+end
@@ -0,0 +1,30 @@
+require 'moneta'
+require 'active_support/inflector'
+
+module CanTango
+ module Cache
+ class Moneta < Base
+ # one cache store is shared for all store instances (w different names)
+ attr_reader :store
+
+ # for a YamlStore, the name is the name of the yml file
+ def initialize name, options = {}
+ super
+ @store = CanTango::Cache::MonetaCache.instance
+ @store.configure_with options
+ end
+
+ def load key
+ store.load! key
+ end
+
+ def save key, rules
+ store.save! key, rules
+ end
+
+ def invalidate! key
+ store.delete key
+ end
+ end
+ end
+end
@@ -0,0 +1,44 @@
+module CanTango
+ module Cache
+ class Session < Base
+ attr_accessor :session
+ attr_reader :key
+
+ # will be called with :session => session (pointing to user session)
+ def initialize name, options = {}
+ super # will set all instance vars using options hash
+ @cache = cache
+ @cache.configure_with :type => :memory
+ raise "SessionCache must be initialized with a :session option" if !session
+ session[cache_key] = @cache
+ end
+
+ def cache_key
+ @cache_key ||= :rules_cache
+ end
+
+ def store
+ session[cache_key]
+ end
+
+ def load key
+ raise "no key" if key.nil?
+ store.load! key
+ end
+
+ def save key, rules
+ raise "no key" if key.nil?
+ store.save!(key, rules)
+ end
+
+ def invalidate! key
+ raise "no key" if key.nil?
+ store.delete(key)
+ end
+
+ def cache
+ CanTango::Cache::HashCache.instance
+ end
+ end
+ end
+end
Oops, something went wrong.

0 comments on commit 1778622

Please sign in to comment.