Class: ReactiveRecord::ServerDataCache::CacheItem

Inherits:
Object
  • Object
show all
Defined in:
lib/reactive_record/server_data_cache.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(db_cache, acting_user, klass, preloaded_records) ⇒ CacheItem

Returns a new instance of CacheItem.



221
222
223
224
225
226
227
228
229
230
# File 'lib/reactive_record/server_data_cache.rb', line 221

def initialize(db_cache, acting_user, klass, preloaded_records)
  @db_cache = db_cache
  @acting_user = acting_user
  @vector = @absolute_vector = [klass]
  @value = klass
  @parent = nil
  @root = self
  @preloaded_records = preloaded_records
  @db_cache.add_item_to_cache self
end

Instance Attribute Details

#absolute_vectorObject (readonly)

Returns the value of attribute absolute_vector.



201
202
203
# File 'lib/reactive_record/server_data_cache.rb', line 201

def absolute_vector
  @absolute_vector
end

#acting_userObject (readonly)

Returns the value of attribute acting_user.



203
204
205
# File 'lib/reactive_record/server_data_cache.rb', line 203

def acting_user
  @acting_user
end

#rootObject (readonly)

Returns the value of attribute root.



202
203
204
# File 'lib/reactive_record/server_data_cache.rb', line 202

def root
  @root
end

#vectorObject (readonly)

Returns the value of attribute vector.



200
201
202
# File 'lib/reactive_record/server_data_cache.rb', line 200

def vector
  @vector
end

Class Method Details

.new(db_cache, acting_user, klass, preloaded_records) ⇒ Object



213
214
215
216
217
218
219
# File 'lib/reactive_record/server_data_cache.rb', line 213

def self.new(db_cache, acting_user, klass, preloaded_records)
  klass = ServerDataCache.get_model(klass)
  if existing = ServerDataCache.timing(:root_lookup) { db_cache.cache.detect { |cached_item| cached_item.vector == [klass] } }
    return existing
  end
  super
end

Instance Method Details

#aggregation?(method) ⇒ Boolean

Returns:

  • (Boolean)


289
290
291
292
293
294
295
296
# File 'lib/reactive_record/server_data_cache.rb', line 289

def aggregation?(method)
  if method.is_a?(String) && @value.class.respond_to?(:reflect_on_aggregation)
    aggregation = @value.class.reflect_on_aggregation(method.to_sym)
    if aggregation && !(aggregation.klass < ActiveRecord::Base) && @value.send(method)
      aggregation
    end
  end
end

#apply_method(method) ⇒ Object



328
329
330
331
332
333
334
335
336
# File 'lib/reactive_record/server_data_cache.rb', line 328

def apply_method(method)
  if method.is_a? Array and method.first == "find_by_id"
    method[0] = "find"
  elsif method.is_a? String and method =~ /^\*[0-9]+$/
    method = "*"
  end
  new_vector = vector + [method]
  timing('apply_method lookup') { @db_cache.cache_reps[new_vector] } || apply_method_to_cache(method)
end

#apply_method_to_cache(method) ⇒ Object



240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
# File 'lib/reactive_record/server_data_cache.rb', line 240

def apply_method_to_cache(method)
  @db_cache.cache.inject(nil) do |representative, cache_item|
    if cache_item.vector == vector
      if method == "*"
        # apply_star does the security check if value is present
        cache_item.apply_star || representative
      elsif method == "*all"
        # if we secure the collection then we assume its okay to read the ids
        secured_value = cache_item.value.__secure_collection_check(@acting_user)
        cache_item.build_new_cache_item(timing(:active_record) { secured_value.collect { |record| record.id } }, method, method)
      elsif method == "*count"
        secured_value = cache_item.value.__secure_collection_check(@acting_user)
        cache_item.build_new_cache_item(timing(:active_record) { cache_item.value.__secure_collection_check(@acting_user).count }, method, method)
      elsif preloaded_value = @preloaded_records[cache_item.absolute_vector + [method]]
        # no security check needed since we already evaluated this
        cache_item.build_new_cache_item(preloaded_value, method, method)
      elsif aggregation = cache_item.aggregation?(method)
        # aggregations are not protected
        cache_item.build_new_cache_item(aggregation.mapping.collect { |attribute, accessor| cache_item.value[attribute] }, method, method)
      else
        if !cache_item.value || cache_item.value.is_a?(Array)
          # seeing as we just returning representative, no check is needed (its already checked)
          representative
        else
          begin
            secured_method = "__secure_remote_access_to_#{[*method].first}"

            # order is important.  This check must be first since scopes can have same name as attributes!
            if cache_item.value.respond_to? secured_method
              cache_item.build_new_cache_item(timing(:active_record) { cache_item.value.send(secured_method, cache_item.value, @acting_user, *([*method][1..-1])) }, method, method)
            elsif (cache_item.value.class < ActiveRecord::Base) && cache_item.value.attributes.has_key?(method) # TODO: second check is not needed, its built into  check_permmissions,  check should be does class respond to check_permissions...
              cache_item.value.check_permission_with_acting_user(@acting_user, :view_permitted?, method)
              cache_item.build_new_cache_item(timing(:active_record) { cache_item.value.send(*method) }, method, method)
            else
              raise "method missing"
            end
          rescue Exception => e # this check may no longer be needed as we are quite explicit now on which methods we apply
            # ReactiveRecord::Pry::rescued(e)
            ::Rails.logger.debug "\033[0;31;1mERROR: HyperModel exception caught when applying #{method} to db object #{cache_item.value}: #{e}\033[0;30;21m"
            raise e, "HyperModel fetching records failed, exception caught when applying #{method} to db object #{cache_item.value}: #{e}", e.backtrace
          end
        end
      end
    else
      representative
    end
  end
end

#apply_starObject



298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
# File 'lib/reactive_record/server_data_cache.rb', line 298

def apply_star
  if @value && @value.__secure_collection_check(@acting_user) && @value.length > 0
    i = -1
    @value.inject(nil) do |representative, current_value|
      i += 1
      if preloaded_value = @preloaded_records[@absolute_vector + ["*#{i}"]]
        build_new_cache_item(preloaded_value, "*", "*#{i}")
      else
        build_new_cache_item(current_value, "*", "*#{i}")
      end
    end
  else
    build_new_cache_item([], "*", "*")
  end
end

#as_hash(children = nil) ⇒ Object



352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
# File 'lib/reactive_record/server_data_cache.rb', line 352

def as_hash(children = nil)
  unless children
    return {} if @value.is_a?(Class) && (@value < ActiveRecord::Base)
    children = [@value.is_a?(BigDecimal) ? @value.to_f : @value]
  end
  if @parent
    if method == "*"
      if @value.is_a? Array  # this happens when a scope is empty there is test case, but
        @parent.as_hash({})  # does it work for all edge cases?
      else
        @parent.as_hash({@value.id => children})
      end
    elsif (@value.class < ActiveRecord::Base) && children.is_a?(Hash)
      id = method.is_a?(Array) && method.first == "new" ? [nil] : [@value.id]
      # c = children.merge(id: id)
      # if @value.attributes.key? @value.class.inheritance_column
      #   c[@value.class.inheritance_column] = [@value[@value.class.inheritance_column]]
      # end
      @parent.as_hash(jsonize(method) => merge_inheritance_column(children.merge(id: id)))
    elsif method == '*all'
      @parent.as_hash('*all' => children.first)
    else
      @parent.as_hash(jsonize(method) => children)
    end
  else
    { method.name => children }
  end
end

#build_new_cache_item(new_value, method, absolute_method) ⇒ Object

TODO replace instance_eval with a method like clone_new_child(.…)



315
316
317
318
319
320
321
322
323
324
325
326
# File 'lib/reactive_record/server_data_cache.rb', line 315

def build_new_cache_item(new_value, method, absolute_method)
  new_parent = self
  self.clone.instance_eval do
    @vector = @vector + [method]  # don't push it on since you need a new vector!
    @absolute_vector = @absolute_vector + [absolute_method]
    @value = new_value
    @db_cache.add_item_to_cache self
    @parent = new_parent
    @root = new_parent.root
    self
  end
end

#jsonize(method) ⇒ Object



338
339
340
341
342
343
# File 'lib/reactive_record/server_data_cache.rb', line 338

def jsonize(method)
  # sadly standard json converts {[:foo, nil] => 123} to {"['foo', nil]": 123}
  # luckily [:foo, nil] does convert correctly
  # so we check the methods and force proper conversion
  method.is_a?(Array) ? method.to_json : method
end

#merge_inheritance_column(children) ⇒ Object



345
346
347
348
349
350
# File 'lib/reactive_record/server_data_cache.rb', line 345

def merge_inheritance_column(children)
  if @value.attributes.key? @value.class.inheritance_column
    children[@value.class.inheritance_column] = [@value[@value.class.inheritance_column]]
  end
  children
end

#methodObject



209
210
211
# File 'lib/reactive_record/server_data_cache.rb', line 209

def method
  @vector.last
end

#start_timing(&block) ⇒ Object



232
233
234
# File 'lib/reactive_record/server_data_cache.rb', line 232

def start_timing(&block)
  ServerDataCache.class.start_timing(&block)
end

#timing(tag, &block) ⇒ Object



236
237
238
# File 'lib/reactive_record/server_data_cache.rb', line 236

def timing(tag, &block)
  ServerDataCache.timing(tag, &block)
end

#to_jsonObject



381
382
383
# File 'lib/reactive_record/server_data_cache.rb', line 381

def to_json
  @value.to_json
end

#valueObject



205
206
207
# File 'lib/reactive_record/server_data_cache.rb', line 205

def value
  @value # which is a ActiveRecord object
end