Module: Protector::Adapters::ActiveRecord::Relation

Extended by:
ActiveSupport::Concern
Defined in:
lib/protector/adapters/active_record/relation.rb

Overview

Patches ActiveRecord::Relation

Instance Method Summary collapse

Instance Method Details

#calculate(*args) ⇒ Object

Merges current relation with restriction and calls real calculate



61
62
63
64
# File 'lib/protector/adapters/active_record/relation.rb', line 61

def calculate(*args)
  return super unless protector_subject?
  merge(protector_meta.relation).unrestrict!.calculate *args
end

#count(*args) ⇒ Object

Note:

This is here cause NullRelation can return nil from count



51
52
53
# File 'lib/protector/adapters/active_record/relation.rb', line 51

def count(*args)
  super || 0
end

#except(*args) ⇒ Object



40
41
42
43
# File 'lib/protector/adapters/active_record/relation.rb', line 40

def except(*args)
  return super unless protector_subject?
  super.restrict!(protector_subject)
end

#exec_queries_with_protector(*args) ⇒ Object

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)


91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/protector/adapters/active_record/relation.rb', line 91

def exec_queries_with_protector(*args)
  return @records if loaded?
  return exec_queries_without_protector unless protector_subject?

  subject  = protector_subject
  relation = merge(protector_meta.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|
    ::ActiveRecord::Associations::Preloader.new(@records, association).run
  end

  @loaded = true
  @records
end

#exists?(*args) ⇒ Boolean

Merges current relation with restriction and calls real exists?

Returns:

  • (Boolean)


67
68
69
70
# File 'lib/protector/adapters/active_record/relation.rb', line 67

def exists?(*args)
  return super unless protector_subject?
  merge(protector_meta.relation).unrestrict!.exists? *args
end

#new_with_protector(*args, &block) ⇒ Object

Forwards protection subject to the new instance



73
74
75
76
77
78
79
80
81
82
# File 'lib/protector/adapters/active_record/relation.rb', line 73

def new_with_protector(*args, &block)
  return new_without_protector(*args, &block) unless protector_subject?

  # strong_parameters integration
  if Protector.config.strong_parameters? && args.first.respond_to?(:permit)
    Protector::ActiveRecord::StrongParameters::sanitize! args, true, protector_meta
  end

  new_without_protector(*args, &block).restrict!(protector_subject)
end

#only(*args) ⇒ Object



45
46
47
48
# File 'lib/protector/adapters/active_record/relation.rb', line 45

def only(*args)
  return super unless protector_subject?
  super.restrict!(protector_subject)
end

#protector_expand_inclusion(inclusion, results = [], base = [], klass = @klass) ⇒ Object

Indexes includes format by actual entity class

Turns {foo: :bar} into [[Foo, :foo], [Bar, {foo: :bar}]

Parameters:

  • inclusion (Symbol, Array, Hash)

    Inclusion description in the AR format

  • results (Array) (defaults to: [])

    Resulting set

  • base (Array) (defaults to: [])

    Association path ([:foo, :bar])

  • klass (Class) (defaults to: @klass)

    Base class



166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
# File 'lib/protector/adapters/active_record/relation.rb', line 166

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) ⇒ Object

Gets DSL::Meta::Box of this relation



30
31
32
# File 'lib/protector/adapters/active_record/relation.rb', line 30

def protector_meta(subject=protector_subject)
  @klass.protector_meta.evaluate(subject)
end

#protector_mimic_base!Object

Makes instance of Relation duck-type compatible to AR::Base to allow proper protection block execution with itself



146
147
148
149
150
151
152
153
154
155
156
# File 'lib/protector/adapters/active_record/relation.rb', line 146

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_substitute_includes(subject, relation) ⇒ Object

Swaps includes with preload if it's not referenced or merges security scope of proper class otherwise



116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/protector/adapters/active_record/relation.rb', line 116

def protector_substitute_includes(subject, relation)
  if eager_loading?
    protector_expand_inclusion(includes_values + 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 unscoped.instance_eval(&meta.scope_proc)
      end
    end
  else
    relation.preload_values += includes_values
    relation.includes_values = []
  end

  relation
end

#sum(*args) ⇒ Object

Note:

This is here cause NullRelation can return nil from sum



56
57
58
# File 'lib/protector/adapters/active_record/relation.rb', line 56

def sum(*args)
  super || 0
end

#unscopedObject

Note:

Unscoped relation drops properties and therefore should be re-restricted



35
36
37
38
# File 'lib/protector/adapters/active_record/relation.rb', line 35

def unscoped
  return super unless protector_subject?
  super.restrict!(protector_subject)
end