Wednesday, February 19, 2014

Ruby's ObjectSpace and Diffy to Diagnose Unknown Object Changes

Ever diff'd ObjectSpace in Ruby/Rails?

As an example, I had a situation where I thought maybe I wasn't saving a record, and because it was a complex structure, the simplest thing seemed to be to try this:

GC.start
unsaved = ObjectSpace.each_object(ActiveRecord::Base).to_a.select(&:changed?)
raise "expected all models in memory to be saved, but the following had changes:\n#{unsaved.join("\n")}" if unsaved.size > 0

...but that didn't show anything.

Since none of the model instances where changed- I thought it would be interesting to reload every ActiveModel::Base instance, then do a before and after diff of model instances in ObjectSpace. That way in scanning the diff, I might happen to see difference between model instances without having to deep dive through a bunch of associations, comparing objects.

Seems somewhat random, but it was interesting.

First add Diffy to your Gemfile and bundle install. Then after doing some queries/updates, try this:

# Note: this is a good way to waste memory and create
# some very long strings

# First, let's garbage collect. Then inspect all
# ActiveRecord::Base instances in ObjectSpace, sort
# those inspection strings, and then split them
# into lines for Diffy.
GC.start
a = ObjectSpace.each_object(ActiveRecord::Base).to_a.map(&:inspect).sort.inspect.gsub(",", ",\n").gsub(">\",", ">\",\n")

# Now, let's reload all of them, just for kicks. :)
ObjectSpace.each_object(ActiveRecord::Base) {|m|m.reload}

# Do what we did before.
# part of ObjectSpace
b = ObjectSpace.each_object(ActiveRecord::Base).to_a.map(&:inspect).sort.inspect.gsub(",", ",\n").gsub(">\",", ">\",\n")

# Diff it!
if a != b
   puts "Changes: #{Diffy::Diff.new(a, b).to_s}"
end

No comments: