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



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

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



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

def count(*args)
  super || 0
end

#creatable?Boolean

Returns:

  • (Boolean)


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

def creatable?
  new.creatable?
end

#create_with_protector(*args, &block) ⇒ Object



90
91
92
93
94
95
96
97
# File 'lib/protector/adapters/active_record/relation.rb', line 90

def create_with_protector(*args, &block)
  return create_without_protector(*args, &block) unless protector_subject?

  create_without_protector(*args) do |instance|
    instance.restrict!(protector_subject)
    block.call(instance) if block
  end
end

#create_with_protector!(*args, &block) ⇒ Object



99
100
101
102
103
104
105
106
# File 'lib/protector/adapters/active_record/relation.rb', line 99

def create_with_protector!(*args, &block)
  return create_without_protector!(*args, &block) unless protector_subject?

  create_without_protector!(*args) do |instance|
    instance.restrict!(protector_subject)
    block.call(instance) if block
  end
end

#except(*args) ⇒ Object



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

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)


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

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)


73
74
75
76
# File 'lib/protector/adapters/active_record/relation.rb', line 73

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



79
80
81
82
83
84
85
86
87
88
# File 'lib/protector/adapters/active_record/relation.rb', line 79

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



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

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



190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
# File 'lib/protector/adapters/active_record/relation.rb', line 190

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



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

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



170
171
172
173
174
175
176
177
178
179
180
# File 'lib/protector/adapters/active_record/relation.rb', line 170

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



140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/protector/adapters/active_record/relation.rb', line 140

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

#sum(*args) ⇒ Object

Note:

This is here cause NullRelation can return nil from sum



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

def sum(*args)
  super || 0
end

#unscopedObject

Note:

Unscoped relation drops properties and therefore should be re-restricted



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

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