ActiveRecord: Importing YAML
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.
Comments
-
I just took a look at: http://topfunky.net/svn/plugins/ar_fixtures
And noticed something topfunky does: record_copy = self.new(record.attributes)
I think this will clean up the ug :)