module Protector::Adapters::ActiveRecord::Relation
Patches ‘ActiveRecord::Relation`
Public Class Methods
AR 4 has awfull inconsistency when it comes to method ‘all` We have to mimic base class behaviour for relation we get from `unscoped`
# File lib/protector/adapters/active_record/relation.rb, line 197 def all self end
Public Instance Methods
Merges current relation with restriction and calls real ‘calculate`
# File lib/protector/adapters/active_record/relation.rb, line 77 def calculate(*args) return super unless protector_subject? protector_relation.unrestrict!.calculate(*args) end
# File lib/protector/adapters/active_record/relation.rb, line 35 def can?(action, field=false) protector_meta.can?(action, field) end
@note This is here cause ‘NullRelation` can return `nil` from `count`
# File lib/protector/adapters/active_record/relation.rb, line 67 def count(*args) super || 0 end
# File lib/protector/adapters/active_record/relation.rb, line 31 def creatable? new.creatable? end
# File lib/protector/adapters/active_record/relation.rb, line 103 def create_with_protector(*args, &block) return create_without_protector(*args, &block) unless protector_subject? protector_permit_strong_params(args) create_without_protector(*args) do |instance| instance.restrict!(protector_subject) block.call(instance) if block end end
# File lib/protector/adapters/active_record/relation.rb, line 114 def create_with_protector!(*args, &block) return create_without_protector!(*args, &block) unless protector_subject? protector_permit_strong_params(args) create_without_protector!(*args) do |instance| instance.restrict!(protector_subject) block.call(instance) if block end end
# File lib/protector/adapters/active_record/relation.rb, line 56 def except(*args) return super unless protector_subject? super.restrict!(protector_subject) end
Patches current relation to fulfill restriction and call real ‘exec_queries`
Patching includes:
-
turning ‘includes` (that are not referenced for eager loading) into `preload`
-
delaying built-in preloading to the stage where selection is restricted
-
merging current relation with restriction (of self and every eager association)
# File lib/protector/adapters/active_record/relation.rb, line 132 def exec_queries_with_protector(*args) return @records if loaded? return exec_queries_without_protector unless protector_subject? subject = protector_subject relation = protector_relation.unrestrict! relation = protector_substitute_includes(subject, relation) # Preserve associations from internal loading. We are going to handle that # ourselves respecting security scopes FTW! associations, relation.preload_values = relation.preload_values, [] @records = relation.send(:exec_queries).each { |record| record.restrict!(subject) } # Now we have @records restricted properly so let's preload associations! associations.each do |association| if ::ActiveRecord::Associations::Preloader.method_defined? :preload ::ActiveRecord::Associations::Preloader.new.preload(@records, association) else ::ActiveRecord::Associations::Preloader.new(@records, association).run end end @loaded = true @records end
Merges current relation with restriction and calls real ‘exists?`
# File lib/protector/adapters/active_record/relation.rb, line 83 def exists?(*args) return super unless protector_subject? protector_relation.unrestrict!.exists?(*args) end
# File lib/protector/adapters/active_record/relation.rb, line 24 def includes!(*args) self.includes_values += args self end
Forwards protection subject to the new instance
# File lib/protector/adapters/active_record/relation.rb, line 89 def new_with_protector(*args, &block) return new_without_protector(*args, &block) unless protector_subject? protector_permit_strong_params(args) unless block_given? new_without_protector(*args).restrict!(protector_subject) else new_without_protector(*args) do |instance| block.call instance.restrict!(protector_subject) end end end
# File lib/protector/adapters/active_record/relation.rb, line 61 def only(*args) return super unless protector_subject? super.restrict!(protector_subject) end
Indexes ‘includes` format by actual entity class
Turns ‘{foo: :bar}` into `[[Foo, :foo], [Bar, {foo: :bar}]`
@param [Symbol, Array, Hash] inclusion Inclusion description in the AR format @param [Array] results Resulting set @param [Array] base Association
path ([:foo, :bar]) @param [Class] klass Base
class
# File lib/protector/adapters/active_record/relation.rb, line 211 def protector_expand_inclusion(inclusion, results=[], base=[], klass=@klass) if inclusion.is_a?(Hash) protector_expand_inclusion_hash(inclusion, results, base, klass) else Array(inclusion).each do |i| if i.is_a?(Hash) protector_expand_inclusion_hash(i, results, base, klass) else results << [ klass.reflect_on_association(i.to_sym).klass, i.to_sym ] end end end results end
Gets {Protector::DSL::Meta::Box} of this relation
# File lib/protector/adapters/active_record/relation.rb, line 40 def protector_meta(subject=protector_subject) @klass.protector_meta.evaluate(subject) end
Makes instance of Relation
duck-type compatible to AR::Base to allow proper protection block execution with itself
# File lib/protector/adapters/active_record/relation.rb, line 191 def protector_mimic_base! return unless Protector::Adapters::ActiveRecord.modern? class <<self # AR 4 has awfull inconsistency when it comes to method `all` # We have to mimic base class behaviour for relation we get from `unscoped` def all self end end end
# File lib/protector/adapters/active_record/relation.rb, line 44 def protector_relation result = self.clone result = protector_meta.eval_scope_procs(result) if protector_meta.relation result end
Swaps ‘includes` with `preload` if it’s not referenced or merges security scope of proper class otherwise
# File lib/protector/adapters/active_record/relation.rb, line 161 def protector_substitute_includes(subject, relation) if relation.eager_loading? protector_expand_inclusion(relation.includes_values + relation.eager_load_values).each do |klass, path| # AR drops default_scope for eagerly loadable associations # https://github.com/inossidabile/protector/issues/3 # and so should we meta = klass.protector_meta.evaluate(subject) if meta.scoped? unscoped = klass.unscoped # `unscoped` gets us a relation but Protector scope is supposed # to work with AR::Base. Some versions of AR have those uncompatible # so we have to workaround it :( unscoped.protector_mimic_base! # Finally we merge unscoped basic relation extended with protection scope relation = relation.merge meta.eval_scope_procs(unscoped) end end else relation.preload_values += includes_values relation.includes_values = [] end relation end
# File lib/protector/adapters/active_record/relation.rb, line 18 def references_values tables_in_string(to_sql) end
@note This is here cause ‘NullRelation` can return `nil` from `sum`
# File lib/protector/adapters/active_record/relation.rb, line 72 def sum(*args) super || 0 end
@note Unscoped relation drops properties and therefore should be re-restricted
# File lib/protector/adapters/active_record/relation.rb, line 51 def unscoped return super unless protector_subject? super.restrict!(protector_subject) end
Private Instance Methods
# File lib/protector/adapters/active_record/relation.rb, line 240 def protector_expand_inclusion_hash(inclusion, results=[], base=[], klass=@klass) inclusion.each do |key, value| model = klass.reflect_on_association(key.to_sym).klass value = [value] unless value.is_a?(Array) nest = [key] + base value.each do |v| if v.is_a?(Hash) protector_expand_inclusion_hash(v, results, nest) else results << [ model.reflect_on_association(v.to_sym).klass, nest.reduce(v) { |a, n| { n => a } } ] end end results << [model, base.reduce(key) { |a, n| { n => a } }] end end
# File lib/protector/adapters/active_record/relation.rb, line 232 def protector_permit_strong_params(args) # strong_parameters integration if Protector.config.strong_parameters? && args.first.respond_to?(:permit) Protector::ActiveRecord::Adapters::StrongParameters.sanitize! args, true, protector_meta end end