require 'active_record' require "#{File.dirname(__FILE__)}/once" module Raccess #:nodoc: module Acts #:nodoc: module Filterable def self.included(mod) mod.extend(ClassMethods) end module ClassMethods # The model mixin to let ActiveRecord filter your model object based on # rules. The following example makes a badger visible by default, but # makes it invisible if its had more than 5 beers, unless its name is # 'Frank' # # class Badger < ActiveRecord::Base # has_many :beers # acts_as_filterable :visible => true # Defaut to visible # # def too_much_beer? # beers.size > 5 # end # # def is_frank? # name == 'Frank' # end # # invisible_if :too_much_beer? # visible_if :is_frank? # end # # That should make said badgers invisible from the entire system # def acts_as_filterable(opts = {}) # Defaults write_inheritable_attribute(:visibility_default, opts[:visible] || false) write_inheritable_attribute(:acts_as_filterable, true) Thread.current[:eval_access_control] = true if Thread.current[:eval_access_control] != false class_eval do once('@raccessAliasReadMethods') { alias :old_read_attribute :read_attribute } extend Raccess::Acts::Filterable::SingletonMethods end include Raccess::Acts::Filterable::InstanceMethods class << self alias :find_without_access_control :find def find(*args) #:nodoc: result = find_without_access_control(*args) #return result if !result.respond_to?(:visible?) return nil if result.nil? if result.kind_of?(Array) return result.collect{|row| row if row.visible? || nil}.compact else if result.visible? return result else raise ActiveRecord::RecordNotFound end end return nil end end end def flush_visiblity_rules write_inheritable_attribute(:visibility_perms, []) end def true? true end def false? false end def invisible_if(sym, opts = {}) add_rule(:invisible, sym, opts) end def visible_if(sym, opts = {}) add_rule(:visible, sym, opts) end private def add_rule(permission, sym, opts) #:nodoc: opts[:always] = false if !opts[:always] rules = read_inheritable_attribute(:visibility_perms) || [] rules << [permission, sym, opts] write_inheritable_attribute(:visibility_perms, rules) end end module SingletonMethods #:nodoc: end module InstanceMethods def visible? return true if Thread.current[:eval_access_control] == false return true if !self.class.read_inheritable_attribute(:acts_as_filterable) rules = self.class.read_inheritable_attribute(:visibility_perms) || [] return true if !rules.any? visible = self.class.read_inheritable_attribute(:visibility_default) || false rules.each do |permission, rule, opts| Thread.current[:eval_access_control] = false result = self.send(rule) Thread.current[:eval_access_control] = true if result && opts[:always] return permission == :visible end if permission == :visible visible = true if result elsif permission == :invisible visible = false if result else raise "Bad permission" end end return visible end def read_attribute(name) return old_read_attribute(name) if !Thread.current[:eval_access_control] if !self.visible? puts self.inspect raise ActiveRecord::RecordNotFound end attr = old_read_attribute(name) return attr end end end end end ActiveRecord::Base.class_eval do include Raccess::Acts::Filterable def self.security_ignore(&block) return block.call if Thread.current[:eval_access_control] == false previous_ac = Thread.current[:eval_access_control] Thread.current[:eval_access_control] = false begin ret = block.call ensure Thread.current[:eval_access_control] = previous_ac end return ret end end