Class: ReactiveRecord::ServerDataCache

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

Overview

Todo, [find, 119], owner, todos, active, *all

-> [[Todo, [find, 119], owner, todos, active, *all], [119, 123], 119, 12]

Defined Under Namespace

Classes: CacheItem

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(acting_user, preloaded_records) ⇒ ServerDataCache

Returns a new instance of ServerDataCache.



95
96
97
98
99
100
101
# File 'lib/reactive_record/server_data_cache.rb', line 95

def initialize(acting_user, preloaded_records)
  @acting_user = acting_user
  @cache = []
  @cache_reps = {}
  @requested_cache_items = Set.new
  @preloaded_records = preloaded_records
end

Instance Attribute Details

#cacheObject (readonly)

Returns the value of attribute cache.



103
104
105
# File 'lib/reactive_record/server_data_cache.rb', line 103

def cache
  @cache
end

#cache_repsObject (readonly)

Returns the value of attribute cache_reps.



104
105
106
# File 'lib/reactive_record/server_data_cache.rb', line 104

def cache_reps
  @cache_reps
end

#requested_cache_itemsObject (readonly)

Returns the value of attribute requested_cache_items.



105
106
107
# File 'lib/reactive_record/server_data_cache.rb', line 105

def requested_cache_items
  @requested_cache_items
end

Class Method Details

.[](models, associations, vectors, acting_user) ⇒ Object



168
169
170
171
172
173
174
175
176
177
178
179
180
# File 'lib/reactive_record/server_data_cache.rb', line 168

def self.[](models, associations, vectors, acting_user)
  start_timing do
    timing(:public_columns_hash) { ActiveRecord::Base.public_columns_hash }
    result = nil
    ActiveRecord::Base.transaction do
      cache = new(acting_user, timing(:save_records) { ReactiveRecord::Base.save_records(models, associations, acting_user, false, false) })
      timing(:process_vectors) { vectors.each { |vector| cache[*vector] } }
      timing(:as_json) { result = cache.as_json }
      raise ActiveRecord::Rollback, "This Rollback is intentional!"
    end
    result
  end
end

.get_model(str) ⇒ Object



115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/reactive_record/server_data_cache.rb', line 115

def self.get_model(str)
  # We don't want to open a security hole by allowing some client side string to
  # autoload a class, which would happen if we did a simple str.constantize.
  #
  # Because all AR models are loaded at boot time on the server to define the
  # ActiveRecord::Base.public_columns_hash method any model which the client has
  # access to should already be loaded.
  #
  # If str is not already loaded then we have an access violation.
  unless const_defined? str
    Hyperloop::InternalPolicy.raise_operation_access_violation(:undefined_const, "#{str} is not a loaded constant")
  end
  str.constantize
end

.load_from_json(tree, target = nil) ⇒ Object



415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
# File 'lib/reactive_record/server_data_cache.rb', line 415

def self.load_from_json(tree, target = nil)

  # have to process *all before any other items
  # we leave the "*all" key in just for debugging purposes, and then skip it below

  if sorted_collection = tree["*all"]
    target.replace sorted_collection.collect { |id| target.proxy_association.klass.find(id) }
  end

  if id_value = tree["id"] and id_value.is_a? Array
    target.id = id_value.first
  end
  tree.each do |method, value|
    method = JSON.parse(method) rescue method
    new_target = nil

    if method == "*all"
      next # its already been processed above
    elsif !target
      load_from_json(value, Object.const_get(method))
    elsif method == "*count"
      target.set_count_state(value.first)
    elsif method.is_a? Integer or method =~ /^[0-9]+$/
      new_target = target.push_and_update_belongs_to(method)
      #target << (new_target = target.proxy_association.klass.find(method))
    elsif method.is_a? Array
      if method[0] == "new"
        new_target = ReactiveRecord::Base.lookup_by_object_id(method[1])
      elsif !(target.class < ActiveRecord::Base)
        new_target = target.send(*method)
        # value is an array if scope returns nil, so we destroy the bogus record
        new_target.destroy and new_target = nil if value.is_a? Array
      else
        target.backing_record.update_simple_attribute([method], target.backing_record.convert(method, value.first))
      end
    elsif target.class.respond_to?(:reflect_on_aggregation) &&
          (aggregation = target.class.reflect_on_aggregation(method)) &&
          !(aggregation.klass < ActiveRecord::Base)
      value = [aggregation.deserialize(value.first)] unless value.first.is_a?(aggregation.klass)

      target.send "#{method}=", value.first
    elsif value.is_a? Array
      # we cannot use target.send "#{method}=" here because it might be a server method, which does not have a setter
      # a better fix might be something like target._internal_attribute_hash[method] =  ...
      target.backing_record.set_attr_value(method, value.first) unless method == :id
    elsif value.is_a? Hash and value[:id] and value[:id].first and association = target.class.reflect_on_association(method)
      # not sure if its necessary to check the id above... is it possible to for the method to be an association but not have an id?
      new_target = association.klass.find(value[:id].first)
      target.send "#{method}=", new_target
    elsif !(target.class < ActiveRecord::Base)
      new_target = target.send(*method)
      # value is an array if scope returns nil, so we destroy the bogus record
      new_target.destroy and new_target = nil if value.is_a? Array
    else
      new_target = target.send("#{method}=", target.send(method))
    end
    load_from_json(value, new_target) if new_target
  end
rescue Exception => e
  # debugger
  raise e
end

.start_timing(&block) ⇒ Object



148
149
150
151
152
153
154
155
156
157
158
159
160
# File 'lib/reactive_record/server_data_cache.rb', line 148

def self.start_timing(&block)
  @timings = Hash.new { |h, k| h[k] = 0 }
  start_time = Time.now
  yield.tap do
    ::Rails.logger.debug "********* Total Time #{total = Time.now - start_time} ***********************"
    sum = 0
    @timings.sort_by(&:last).reverse.each do |tag, time|
      ::Rails.logger.debug "            #{tag}: #{time} (#{(time/total*100).to_i})%"
      sum += time
    end
    ::Rails.logger.debug "********* Other Time ***********************"
  end
end

.timing(tag, &block) ⇒ Object



162
163
164
165
166
# File 'lib/reactive_record/server_data_cache.rb', line 162

def self.timing(tag, &block)
  start_time = Time.now
  tag = tag.to_sym
  yield.tap { @timings[tag] += (Time.now - start_time) if @timings }
end

Instance Method Details

#[](*vector) ⇒ Object



130
131
132
133
134
135
136
137
138
# File 'lib/reactive_record/server_data_cache.rb', line 130

def [](*vector)
  timing('building cache_items') do
    root = CacheItem.new(self, @acting_user, vector[0], @preloaded_records)
    vector[1..-1].inject(root) { |cache_item, method| cache_item.apply_method method if cache_item }
    final = vector[1..-1].inject(root) { |cache_item, method| cache_item.apply_method method if cache_item }
    next final unless final && final.value.respond_to?(:superclass) && final.value.superclass <= ActiveRecord::Base
    Hyperloop::InternalPolicy.raise_operation_access_violation(:invalid_vector, "attempt to insecurely access relationship #{vector.last}.")
  end
end

#add_item_to_cache(item) ⇒ Object



107
108
109
110
111
# File 'lib/reactive_record/server_data_cache.rb', line 107

def add_item_to_cache(item)
  cache << item
  cache_reps[item.vector] = item
  requested_cache_items << item
end

#as_jsonObject



186
187
188
189
190
# File 'lib/reactive_record/server_data_cache.rb', line 186

def as_json
  @requested_cache_items.inject({}) do |hash, cache_item|
    hash.deep_merge! cache_item.as_hash
  end
end

#clear_requestsObject



182
183
184
# File 'lib/reactive_record/server_data_cache.rb', line 182

def clear_requests
  @requested_cache_items = Set.new
end

#detect(&block) ⇒ Object



194
# File 'lib/reactive_record/server_data_cache.rb', line 194

def detect(&block); @cache.detect(&block); end

#inject(initial, &block) ⇒ Object



196
# File 'lib/reactive_record/server_data_cache.rb', line 196

def inject(initial, &block); @cache.inject(initial) &block; end

#select(&block) ⇒ Object



192
# File 'lib/reactive_record/server_data_cache.rb', line 192

def select(&block); @cache.select(&block); end

#start_timing(&block) ⇒ Object



140
141
142
# File 'lib/reactive_record/server_data_cache.rb', line 140

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

#timing(tag, &block) ⇒ Object



144
145
146
# File 'lib/reactive_record/server_data_cache.rb', line 144

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