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



77
78
79
80
# 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) ⇒ Boolean

Returns:

  • (Boolean)


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

def can?(action, field=false)
  protector_meta.can?(action, field)
end

#count(*args) ⇒ Object

Note:

This is here cause NullRelation can return nil from count



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

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



103
104
105
106
107
108
109
110
111
112
# 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) ⇒ Object



114
115
116
117
118
119
120
121
122
123
# 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) ⇒ Object



56
57
58
59
# 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) ⇒ 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)


132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# 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) ⇒ Boolean

Merges current relation with restriction and calls real exists?

Returns:

  • (Boolean)


83
84
85
86
# File 'lib/protector/adapters/active_record/relation.rb', line 83

def exists?(*args)
  return super unless protector_subject?
  protector_relation.unrestrict!.exists?(*args)
end

#new_with_protector(*args, &block) ⇒ Object

Forwards protection subject to the new instance



89
90
91
92
93
94
95
96
97
98
99
100
101
# 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) ⇒ Object



61
62
63
64
# 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) ⇒ 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



211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
# 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) ⇒ Object

Gets DSL::Meta::Box of this relation



40
41
42
# 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!Object

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



191
192
193
194
195
196
197
198
199
200
201
# 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_relationObject



44
45
46
47
48
# 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) ⇒ Object

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



161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
# 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

#sum(*args) ⇒ Object

Note:

This is here cause NullRelation can return nil from sum



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

def sum(*args)
  super || 0
end

#unscopedObject

Note:

Unscoped relation drops properties and therefore should be re-restricted



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

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