0

I'm getting weird behavior when I try to save an object in memory to the database and then cache that object with Dalli.

class Infraction << ActiveRecord::Base
  has_many :infraction_locations
  has_many :tracked_points, through: :infraction_locations
end

class TrackedPoint << ActiveRecord::Base
  has_many :infraction_locations
  has_many :infractions, through: :infraction_locations
end

class  InfractionLocation << ActiveRecord::Base
  belongs_to :infraction
  belongs_to :tracked_point
  belongs_to :rule
end

This works:

i = Infraction.create
i.tracked_points << TrackedPoint.create(location_id: 1)
i.save
Rails.cache.write "my_key", i

This also works:

i = Infraction.new
i.tracked_points << TrackedPoint.create(location_id: 1)
i.save
Rails.cache.write "my_key", i

Notice that the objects (in the second case just the TrackedPoint), is saved to the database implicitly by the call to create.

I've also found that reloading i allows me to write the object to the cache. So this works:

i = Infraction.new
i.tracked_points << TrackedPoint.new(location_id: 1)
i.save
i.reload
Rails.cache.write "my_key", i

This fails:

i = Infraction.new
i.tracked_points << TrackedPoint.new(location_id: 1)
i.save
Rails.cache.write "my_key", i

However, if I do some weird duping, I can get the failing example to work:

i = Infraction.new
i.tracked_points << TrackedPoint.new(location_id: 1)
i.save
copy = i.dup
copy.tracked_points = i.tracked_points.to_a
Rails.cache.write "my_key", copy

In my failing example, I can cache the infraction (i) before I save it to the database, like this:

i = Infraction.new
i.tracked_points << TrackedPoint.new(location_id: 1)
Rails.cache.write "what", i

Per Dave's idea, I tried build instead of << for the TrackedPoint as well as adding a accepts_nested_attributes_for :tracked_points to Infraction, but neither of those worked.

I am getting the marshalling/serializer error in the log:

You are trying to cache a Ruby object which cannot be serialized to memcached.

I am running Rails 3.2.13 and Dalli 2.7.0

EDIT

See also: Cacheing an ActiveRecord Object that has_many through:

Community
  • 1
  • 1
Tyler DeWitt
  • 23,366
  • 38
  • 119
  • 196

2 Answers2

0

My best guess, just by looking at the code differences.

In the first two examples you're creating the associated object with TrackedPoint.create which immediately persists it in the database. So assigning the association via "<<" works as there is an id for that object.

In the third you use TrackedPoint.new and then assign the object. This would leverage a nested creation. So you need "accepts_nested_attributes_for" in the model. The proper way IIRC is to use "build" to properly instantiate the association of a new object. My guess is you're seeing some weird case when you dup it where rails is creating the TrackedPoint object so its no longer a nested attributes case, its just direct assignment of an existing object to the association.

engineerDave
  • 3,887
  • 26
  • 28
  • Thanks for the ideas, but I get the same result. I noticed that reloading the infraction also allows me to save the object to the cache. I've updated my question with some more examples. – Tyler DeWitt Jan 12 '14 at 17:10
  • you might have to write a custom _dump & _load method for your class. From the Marshal Doc. "If your class has special serialization needs (for example, if you want to serialize in some specific format), or if it contains objects that would otherwise not be serializable, you can implement your own serialization strategy." – engineerDave Jan 13 '14 at 15:43
0

Turns out it was a problem with squeel.

There is something called an AliasTracker that is not being Marshalled correctly. A monkey patch that appears to fix this issue is:

module ActiveRecord
  module Associations
    class AliasTracker
      def marshal_dump(*)
        nil
      end

      def marshal_load(*)
        nil
      end
    end
  end
end

More discussion and answer from here: https://github.com/activerecord-hackery/squeel/issues/232

Tyler DeWitt
  • 23,366
  • 38
  • 119
  • 196