module Protector::Adapters::ActiveRecord::Relation

Patches ‘ActiveRecord::Relation`

Public Class Methods

all() click to toggle source

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

calculate(*args) click to toggle source

Merges current relation with restriction and calls real ‘calculate`

Calls superclass method
# File lib/protector/adapters/active_record/relation.rb, line 77
def calculate(*args)
  return super unless protector_subject?
  protector_relation.unrestrict!.calculate(*args)
end
can?(action, field=false) click to toggle source
# File lib/protector/adapters/active_record/relation.rb, line 35
def can?(action, field=false)
  protector_meta.can?(action, field)
end
count(*args) click to toggle source

@note This is here cause ‘NullRelation` can return `nil` from `count`

Calls superclass method
# File lib/protector/adapters/active_record/relation.rb, line 67
def count(*args)
  super || 0
end
creatable?() click to toggle source
# File lib/protector/adapters/active_record/relation.rb, line 31
def creatable?
  new.creatable?
end
create_with_protector(*args, &block) click to toggle source
# 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
create_with_protector!(*args, &block) click to toggle source
# 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
except(*args) click to toggle source
Calls superclass method
# File lib/protector/adapters/active_record/relation.rb, line 56
def except(*args)
  return super unless protector_subject?
  super.restrict!(protector_subject)
end
exec_queries_with_protector(*args) click to toggle source

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
exists?(*args) click to toggle source

Merges current relation with restriction and calls real ‘exists?`

Calls superclass method
# File lib/protector/adapters/active_record/relation.rb, line 83
def exists?(*args)
  return super unless protector_subject?
  protector_relation.unrestrict!.exists?(*args)
end
includes!(*args) click to toggle source
# File lib/protector/adapters/active_record/relation.rb, line 24
def includes!(*args)
  self.includes_values += args
  self
end
new_with_protector(*args, &block) click to toggle source

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
only(*args) click to toggle source
Calls superclass method
# File lib/protector/adapters/active_record/relation.rb, line 61
def only(*args)
  return super unless protector_subject?
  super.restrict!(protector_subject)
end
protector_expand_inclusion(inclusion, results=[], base=[], klass=@klass) click to toggle source

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
protector_meta(subject=protector_subject) click to toggle source

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
protector_mimic_base!() click to toggle source

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
protector_relation() click to toggle source
# 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
protector_substitute_includes(subject, relation) click to toggle source

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
references_values() click to toggle source
# File lib/protector/adapters/active_record/relation.rb, line 18
def references_values
  tables_in_string(to_sql)
end
sum(*args) click to toggle source

@note This is here cause ‘NullRelation` can return `nil` from `sum`

Calls superclass method
# File lib/protector/adapters/active_record/relation.rb, line 72
def sum(*args)
  super || 0
end
unscoped() click to toggle source

@note Unscoped relation drops properties and therefore should be re-restricted

Calls superclass method
# 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

protector_expand_inclusion_hash(inclusion, results=[], base=[], klass=@klass) click to toggle source
# 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
protector_permit_strong_params(args) click to toggle source
# 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