lib/protector/adapters/active_record/base.rb
module Protector
module Adapters
module ActiveRecord
# Patches `ActiveRecord::Base`
module Base
extend ActiveSupport::Concern
included do
include Protector::DSL::Base
include Protector::DSL::Entry
before_destroy :protector_ensure_destroyable
# We need this to make sure no ActiveRecord classes managed
# to cache db scheme and create corresponding methods since
# we want to modify the way they get created
ObjectSpace.each_object(Class).each do |klass|
klass.undefine_attribute_methods if klass < self
end
# Drops {Protector::DSL::Meta::Box} cache when subject changes
def restrict!(*args)
@protector_meta = nil
super
end
if !Protector::Adapters::ActiveRecord.modern?
def self.restrict!(*args)
scoped.restrict!(*args)
end
else
def self.restrict!(*args)
all.restrict!(*args)
end
end
def [](name)
# rubocop:disable ParenthesesAroundCondition
if (
!protector_subject? ||
name == self.class.primary_key ||
(self.class.primary_key.is_a?(Array) && self.class.primary_key.include?(name)) ||
protector_meta.readable?(name)
)
read_attribute(name)
else
nil
end
# rubocop:enable ParenthesesAroundCondition
end
def association(*params)
return super unless protector_subject?
super.restrict!(protector_subject)
end
end
module ClassMethods
# Storage of {Protector::DSL::Meta}
def protector_meta
ensure_protector_meta!(Protector::Adapters::ActiveRecord) do
column_names
end
end
# Wraps every `.field` method with a check against {Protector::DSL::Meta::Box#readable?}
def define_method_attribute(name)
super
# Show some <3 to composite primary keys
unless primary_key == name || Array(primary_key).include?(name)
generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
alias_method #{"#{name}_unprotected".inspect}, #{name.inspect}
def #{name}
if !protector_subject? || protector_meta.readable?(#{name.inspect})
#{name}_unprotected
else
nil
end
end
STR
end
end
end
# Gathers real changed values bypassing restrictions
def protector_changed
HashWithIndifferentAccess[changed.map { |field| [field, read_attribute(field)] }]
end
# Storage for {Protector::DSL::Meta::Box}
def protector_meta(subject=protector_subject)
@protector_meta ||= self.class.protector_meta.evaluate(subject, self)
end
# Checks if current model can be selected in the context of current subject
def visible?
return true unless protector_meta.scoped?
protector_meta.relation.where(
self.class.primary_key => id
).any?
end
# Checks if current model can be created in the context of current subject
def creatable?
protector_meta.creatable? protector_changed
end
# Checks if current model can be updated in the context of current subject
def updatable?
protector_meta.updatable? protector_changed
end
# Checks if current model can be destroyed in the context of current subject
def destroyable?
protector_meta.destroyable?
end
def can?(action, field=false)
protector_meta.can?(action, field)
end
private
def protector_ensure_destroyable
return true unless protector_subject?
destroyable?
end
end
end
end
end