zerosum dirt(nap)

evolution through a series of accidents

zerosum dirt(nap)

ActiveRecord: Importing YAML

February 20, 2008 by nap · Comments

Backing up ActiveRecord models to YAML is easy to do, and can be convenient for creating ‘templates’ that can later be imported to restore basic project structure in a new database. However, the import process can be a little tricky, as the instantiated objects don’t seem to respond correctly to `new_record?`

Here’s what our YAML might look like. Note that it’s a serialized `CustomWidget`, which is an ActiveRecord model that inherits from `ActiveRecord::Base`.

--- 

- !ruby/object:CustomWidget 
  attributes: 
    name: DoSomething
    updated_at: 2008-01-28 05:02:54
    description: Does something.
    created_at: 2008-01-28 05:02:54
  attributes_cache: {}

We can automatically instantiate the appropriate Ruby objects from this using the following code:

class CustomWidget < ActiveRecord::Base
end

YAML::add_private_type("CustomWidget") do |type, value|
  CustomWidget.new(value)
end

obj = YAML::load(File.open("template.yml"))[0]
=> #<CustomWidget name: "DoSomething", created_at: "2008-01-28 05:02:54", updated_at: "2008-01-28 05:02:54", description: "Does something.">
obj.save
=> true

So what we’ve done is taken YAML from one project and tried to use it to import the same data into another database (note that IDs are stripped). We define a transfer type for the YAML, providing a mapping to the class, and then we call `YAML::load` to load it up. This returns an array, in this case it contains a single `CustomWidget` object. Everything looks swell. We call `save` on the new object and it returns true.

However, if we check the `count` of `CustomWidgets` that exist in the database, we’ll find that it’s unaltered; no new record has been saved. It turns out that this is because the imported `CustomWidget` doesn’t report true when `new_record?` is called.

obj.new_record?
=> nil

In order to save itself to the database and get a new ID, the `CustomWidget` instance has to first respond correctly to inquiries as to whether or not it’s new. We’d like `new_record?` to return false. The quick-fix secret is to manually toggle the `@new_record` instance variable. If we do that, calling `save` will return true and actually save the object to the database as well this time. Big yay, right?

CustomWidget.count
=> 0
obj.instance_variable_set("@new_record", true)
obj.new_record?
=> true
obj.save
=> true
CustomWidget.count
=> 1

Yes, this is an ugly hack. Did I say ugly? I meant gross.

blog comments powered by Disqus