Search code examples
ruby-on-railsrubyserializationunmarshalling

ApplicationRecord#save! does not work on models created with Marshal.load


I wanted to copy some data from one Rails server (same application, different DB) to another... for various reasons I chose to experiment with a hacky method in the console: Marshal.dump(MyModel.all) on one server and File.open ... { |f| Marshal.load(f).each(&:save!) } in the other.

I was suprised (seemed too good to be true) to see that the latter resulted in [true, true, true...] which made me think the records were saved. However, there was no change to the DB.

I had to use Marshal.load(f).map { |x| MyModel.new(x.attributes) }.each(&:save!). This did write the records to the DB.

I'm guessing there is something I don't understand about Marshal.load; why is it that I had to create a new instance of ostensibly the same class in order to actually save the records? Since I did get true back from each call to save!, what was actually happening there?


Solution

  • If you have your deserialization wrapped in a transaction, it is totally possible to have save!s to all return true with no actual changes made - because the transaction gets rolled back at some later execution point.

    Something like (pseudocode)

    ActiveRecord::Base.transaction do
      File.open("/path/to/dump") do |f|
        Marshal.load(f.read).map(&:save!) # [true, true, true, ...]
      end
      raise ActiveRecord::Rollback
    end
    

    Although Marshal is quite powerful (and deals with simple ActiveRecord models and relations reasonably well, at least in theory), it is still not almighty. And AR models can go quite fat, with a lot of state (depending on other models' state in worst case) sitting in callbacks etc etc etc. Plus don't forget there is a persistence layer too - if underlying storages are different (even different versions of the same database) something that is valid for one of them might not be valid for another. So, in general, I think there is no way to say what's going wrong in your case without additional digging.

    But I'd say Marshal is just not the tool for the job (at least, while you try to serialize the whole fat objects; serializing/deserializing of the attributes looks way better, but do you need Marshal in this case at all?)