module CanvasSync module JobBatches class ContextHash delegate_missing_to :flatten def initialize(bid, hash = nil) @bid_stack = [bid] @hash_map = {} @dirty = false @flattened = nil @hash_map[bid] = hash.with_indifferent_access if hash end # Local is "the nearest batch with a context value" # This allows for, for example, SerialBatchJob to have a modifiable context stored on it's main Batch # that can be accessed transparently from one of it's internal, context-less Batches def local_bid bid = @bid_stack[-1] while bid.present? bhash = reolve_hash(bid) return bid if bhash bid = get_parent_bid(bid) end nil end def local @hash_map[local_bid] end def set_local(new_hash) @dirty = true local.clear.merge!(new_hash) end def clear local.clear @flattened = nil @dirty = true self end def []=(key, value) @flattened = nil @dirty = true local[key] = value end def [](key) bid = @bid_stack[-1] while bid.present? bhash = reolve_hash(bid) return bhash[key] if bhash&.key?(key) bid = get_parent_bid(bid) end nil end def reload! @dirty = false @hash_map = {} self end def save!(force: false) return unless dirty? || force Batch.redis do |r| r.hset("BID-#{local_bid}", 'context', JSON.unparse(local)) end end def dirty? @dirty end def is_a?(arg) return true if Hash <= arg super end def flatten return @flattened if @flattened load_all flattened = {} @bid_stack.compact.each do |bid| flattened.merge!(@hash_map[bid]) if @hash_map[bid] end flattened.freeze @flattened = flattened.with_indifferent_access end private def get_parent_hash(bid) reolve_hash(get_parent_bid(bid)).freeze end def get_parent_bid(bid) index = @bid_stack.index(bid) raise "Invalid BID #{bid}" if index.nil? # Sanity Check - this shouldn't happen index -= 1 if index >= 0 @bid_stack[index] else pbid = Batch.redis { |r| r.hget("BID-#{bid}", "parent_bid") } @bid_stack.unshift(pbid) pbid end end def reolve_hash(bid) return nil unless bid.present? return @hash_map[bid] if @hash_map.key?(bid) context_json, editable = Batch.redis do |r| r.multi do r.hget("BID-#{bid}", "context") r.hget("BID-#{bid}", "allow_context_changes") end end if context_json.present? context_hash = JSON.parse(context_json) context_hash = context_hash.with_indifferent_access context_hash.each do |k, v| v.freeze end context_hash.freeze unless editable @hash_map[bid] = context_hash else @hash_map[bid] = nil end end def load_all while @bid_stack[0].present? get_parent_hash(@bid_stack[0]) end @hash_map end end end end