module Onceler::Recordable
Public Class Methods
extended(instance)
click to toggle source
# File lib/onceler/recordable.rb, line 5 def self.extended(instance) instance.instance_eval do @__retvals = {} @__inherited_retvals = {} @__ignore_ivars = instance_variables end end
Public Instance Methods
__associations_equal?(obj1, obj2)
click to toggle source
if a nested once block updates an inherited object's associations, we want to know about it
# File lib/onceler/recordable.rb, line 74 def __associations_equal?(obj1, obj2) cache1 = obj1.instance_variable_get(:@association_cache) cache2 = obj2.instance_variable_get(:@association_cache) cache1.size == cache2.size && cache1.all? { |k, v| cache2.key?(k) && __values_equal?(v.target, cache2[k].target) } end
__data(inherit = false)
click to toggle source
# File lib/onceler/recordable.rb, line 81 def __data(inherit = false) @__data ||= {} @__data[inherit] ||= begin @__comparison_cache = {} data = [__ivars(inherit), __retvals(inherit)] begin data = Marshal.dump(data) rescue TypeError data.each do |hash| hash.each do |key, val| find_dump_error(key, val) end end raise # find_dump_error should have re-raised, but just in case... ensure __visited_dump_vars.clear end @__comparison_cache = nil data end end
__ivars(inherit = false)
click to toggle source
# File lib/onceler/recordable.rb, line 36 def __ivars(inherit = false) ivars = instance_variables - @__ignore_ivars ivars.inject({}) do |hash, key| if key.to_s !~ /\A@__/ val = instance_variable_get(key) hash[key] = val if __mutated?(key, val) || inherit end hash end end
__mutated?(key, val)
click to toggle source
we don't include inherited stuff in __data
, because we might need to interleave things from an intermediate before(:each) at run time
# File lib/onceler/recordable.rb, line 49 def __mutated?(key, val) # top-level recorders don't inherit anything, so we always want to return true return true unless @__inherited_cache # need to do both types of comparison, i.e. it's the same object in # memory (not reassigned), and nothing about it has been changed return true unless @__inherited_values[key].equal?(val) return true unless __values_equal?(@__inherited_cache[key], val) false end
__prepare_recording(recording)
click to toggle source
# File lib/onceler/recordable.rb, line 13 def __prepare_recording(recording) method = recording.name define_singleton_method(method) do if @__retvals.key?(method) @__retvals[method] else @__retvals[method] = __record(recording) end end end
__record(recording)
click to toggle source
# File lib/onceler/recordable.rb, line 24 def __record(recording) instance_eval(&recording.block) end
__retvals(inherit = false)
click to toggle source
# File lib/onceler/recordable.rb, line 28 def __retvals(inherit = false) retvals = @__inherited_retvals.merge(@__retvals) retvals.inject({}) do |hash, (key, val)| hash[key] = val if __mutated?(key, val) || inherit hash end end
__values_equal?(obj1, obj2)
click to toggle source
# File lib/onceler/recordable.rb, line 59 def __values_equal?(obj1, obj2) if ActiveRecord::Base === obj1 && ActiveRecord::Base === obj2 cache_key = [obj1, obj2] return @__comparison_cache[cache_key] if @__comparison_cache.key?(cache_key) # so as to avoid cycles while traversing AR associations @__comparison_cache[cache_key] = true @__comparison_cache[cache_key] = obj1.attributes == obj2.attributes && __associations_equal?(obj1, obj2) else obj1 == obj2 end end
__visited_dump_vars()
click to toggle source
# File lib/onceler/recordable.rb, line 103 def __visited_dump_vars @__visited_dump_vars ||= Set.new end
copy_from(other)
click to toggle source
# File lib/onceler/recordable.rb, line 142 def copy_from(other) # need two copies of things for __mutated? checks (see above) @__inherited_cache = Marshal.load(other.__data(:inherit)).inject(&:merge) ivars, retvals = Marshal.load(other.__data(:inherit)) @__inherited_retvals = retvals @__inherited_values = ivars.merge(retvals) ivars.each do |key, value| instance_variable_set(key, value) end retvals.each do |key, value| define_singleton_method(key) { value } end end
find_dump_error(key, val, prefix = "")
click to toggle source
# File lib/onceler/recordable.rb, line 107 def find_dump_error(key, val, prefix = "") return if __visited_dump_vars.include?(val) __visited_dump_vars << val Marshal.dump(val) rescue TypeError # see if anything inside val can't be dumped... sub_prefix = "#{prefix}#{key} (#<#{val.class}>) => " if val.respond_to?(:marshal_dump) find_dump_error("marshal_dump", val.marshal_dump, sub_prefix) else # instance var? val.instance_variables.each do |k| v = val.instance_variable_get(k) find_dump_error(k, v, sub_prefix) end # hash key/value? val.each_pair do |k, v| find_dump_error("hash key #{k}", k, sub_prefix) find_dump_error("[#{k.inspect}]", v, sub_prefix) end if val.respond_to?(:each_pair) # array element? val.each_with_index do |v, i| find_dump_error("[#{i}]", v, sub_prefix) end if val.respond_to?(:each_with_index) end # guess it's val proper raise TypeError.new("Unable to dump #{prefix}#{key} (#<#{val.class}>) in #{self.class.metadata[:location]}: #{$!}") end