Dumping selected attributes to YAML

Hi,

I am trying to dump an ActveRecord tree structure (created using
acts_as_tree) with elements that also have associations.

I want to dump the YAML representation as text, edit it and then
reload it. The current state of my project means that this is the
most efficient way to get bulk changes working.

After spendign a day Googling I cannot see a way to dump a subset of
the ActiveRecord attributes.

For example:

- !ruby/object:Expectation
  attributes:
    status: WRITTEN
    name: tpe2
    updated_at: 2008-03-19 14:34:28
    evidence:
    metric_table:
    order_column_name:
    goal_diff_filename:
    metric_select_column_name:
    goal_diff_options:
    id: "2"
    order_direction:
    goal_numeric_comparison:
    parent_section_id: "1"
    goal_numeric_value:
    owner: "222"
    documentation:
    reviewer:
    created_at: 2008-03-19 14:34:28
    implementor:
  attributes_cache: {}

is what I get when I dump an instance to YAML.

But I don't want the *id, created_at and updated_at fields, since
they're irrelevant to my needs and will change when I reload this
data.

I can see how to_yaml_properties works for simple objects and indeed
if I use that method to limit the instance variables I want to see
then I get:

  def to_yaml_properties
    props = self.attributes
    props.delete "id"
    props.delete "created_at"
    props.delete "updated_at"
    props.delete "parent_id"
    props.delete "name"
    props.keys.map{|prop| "@#{prop}"}
  end

- !ruby/object:Expectation
  status:
  metric_select_action_math:
  metric_select_action_index:
  evidence:
  metric_table:
  order_column_name:
  goal_diff_filename:
  metric_select_column_name:
  goal_diff_options:
  testplan_section_id:
  order_direction:
  goal_numeric_comparison:
  owner:
  goal_numeric_value:
  documentation:
  reviewer:
  implementor:
  goal_regexp:

But now this cannot be reloaded as an Expectation object because these
instance variables are now no longer in the attributes section as they
were in the previous dump

Other than running a post-dump textual deletion of these entries I am
completely stumped.

I note that the to_xml methods allow this kind of feature but to_yaml
doesn't seem to offer anything like this.

Allan

Here is a bit of code I copied from techno-weenie and made some
modifications to.

# http://rails.techno-weenie.net/tip/2005/12/23/make_fixtures
ActiveRecord::Base.class_eval do
  # Write a fixture file for testing
  def self.to_test_fixture
    self.write_fixture_file("test")
  end

  # Write a fixture file for the CMS system
  def self.to_cms_fixture
    self.write_fixture_file("cms")
  end

  private
    def self.write_fixture_file(fixture_path)
      raise "fixture_path cannot be blank!" if fixture_path.blank?

      # collect the data in an array to preserve ordering by id
      data = find(:all, :order => :id).inject([]) do |array, record|
        array << {record.id => record.attributes(:except =>
[:created_at, :updated_at])}
      end

      # add !omap to the YAML header
      string = YAML.dump data
      string = string[0..3] + "!omap" + string[4..-1]

      File.open(File.expand_path("#{fixture_path}/fixtures/
#{table_name}.yml", RAILS_ROOT), 'w') do |out|
        out.puts string
      end
      logger.info "Writing #{self.to_s.titleize} to #{fixture_path ==
"test" ? fixture_path.titleize : fixture_path.upcase} path"
    end
end

Combine those updates to ActiveRecord with this simple class and you
can easily create a YAML based CMS for use from script/console.

require 'active_record/fixtures'

class FixtureManager
  @@cms_classes = [AllTheModelsIWishToSave, InAnArray]

  def self.save_all(test = false)
    RAILS_DEFAULT_LOGGER.info "Saving CMS data"

    @@cms_classes.each do |klass|
      begin
        test ? klass.to_test_fixture : klass.to_cms_fixture
      rescue Exception => e
        RAILS_DEFAULT_LOGGER.error "Error saving: #{klass}"
        RAILS_DEFAULT_LOGGER.error e.message
        RAILS_DEFAULT_LOGGER.error e.backtrace.join("\n\t")
        return false
      end
    end

    RAILS_DEFAULT_LOGGER.info "All data saved"
    return true
  end

  def self.load_all(test = false)
    RAILS_DEFAULT_LOGGER.info "Loading CMS data"
    begin
      tables = @@cms_classes.collect{|klass| klass.table_name}
      Fixtures.create_fixtures(File.join(RAILS_ROOT, "#{test ?
"test" : "cms"}", "fixtures"), tables)
    rescue => e
      RAILS_DEFAULT_LOGGER.error e.message
      RAILS_DEFAULT_LOGGER.error e.backtrace.join("\n\t")
      return false
    end

    RAILS_DEFAULT_LOGGER.info "All data loaded"
    return true
  end
end

I will take a thorough look at this and see where it gets me.

Thanks,

Allan