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



65
66
67
68
# File 'lib/protector/adapters/active_record/relation.rb', line 65

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



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

def count(*args)
  super || 0
end

#except(*args) ⇒ Object



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

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)


83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/protector/adapters/active_record/relation.rb', line 83

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)


71
72
73
74
# File 'lib/protector/adapters/active_record/relation.rb', line 71

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

#only(*args) ⇒ Object



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

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



153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/protector/adapters/active_record/relation.rb', line 153

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_metaObject

Gets DSL::Meta::Box of this relation



29
30
31
32
33
34
35
36
# File 'lib/protector/adapters/active_record/relation.rb', line 29

def protector_meta
  # We don't seem to require columns here as well
  @klass.protector_meta.evaluate(
    Protector::Adapters::ActiveRecord,
    @klass,
    protector_subject
  )
end

#protector_substitute_includes(subject, relation) ⇒ Object

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



108
109
110
111
112
113
114
115
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
143
# File 'lib/protector/adapters/active_record/relation.rb', line 108

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(
        Protector::Adapters::ActiveRecord,
        klass,
        subject
      )

      if meta.scoped?
        unscoped = klass.unscoped

        # AR 4 has awfull inconsistency when it comes to method `all`
        # We have to mimic base class behaviour for relation we get from `unscoped`
        if Protector::Adapters::ActiveRecord.modern?
          class <<unscoped
            def all
              self
            end
          end
        end

        # 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



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

def sum(*args)
  super || 0
end

#unscopedObject

Note:

Unscoped relation drops properties and therefore should be re-restricted



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

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