has_alter_ego

has_alter_ego makes it possible to keep seed and live data transparently in parallel. In contrast to other seed data approaches has_alter_ego synchronizes the seed definitions with your database objects automagically unless you've overridden it in the database.

Installation

Rails 2.3.x

As a gem

Add the following line to your config/environment.rb file: config.gem "has_alter_ego" Then gem install has_alter_ego script/generate has_alter_ego rake db:migrate

As a plugin

script/plugin install git://github.com/aduffeck/has_alter_ego.git
script/generate has_alter_ego
rake db:migrate

Rails 3

As a gem

Add the following line to your Gemfile file: gem "has_alter_ego" Then bundle install rails generate has_alter_ego rake db:migrate

As a plugin

rails plugin install git://github.com/aduffeck/has_alter_ego.git
rails generate has_alter_ego
rake db:migrate

Usage

General

The seed data is defined in YAML files called after the model's table. The files are expected in db/fixtures/alter_egos.

Say you have a Model Car. has_alter_ego is enabled with the has_alter_ego method:

create_table :cars do |t|
  t.string :brand
  t.string :model
end


class Car < ActiveRecord::Base
  has_alter_ego
end

You could then create a file db/fixtures/alter_egos/cars.yml with the seed data:

1:
  brand: Lotus
  model: Elise

2:
  brand: Porsche
  model: 911

3:
  brand: Ferrari
  model: F50

4:
  brand: Corvette
  model: C5

and you'd automagically have those objects available in your database.

Car.find(1)
=> #<Car id: 1, brand: "Lotus", model: "Elise">

Whenever the seed definition changes the objects in the database inherit the changes unless they have been overridden. When a seed object was destroyed in the database it will not be added again.

Note: has_alter_ego reserves the first n IDs for seed objects (default=1000), so the next non-seed object will get the ID 1001. The number of reserved objects can be set with the optional :reserved_space parameter, e.g.

has_alter_ego :reserved_space => 5000

You always have to make sure that no seed IDs clash with IDs in the database.

Advanced stuff

You can check if an object was created from seed definition with has_alter_ego?:

@car = Car.find(1)
@car.has_alter_ego?
=> true

Car.new.has_alter_ego?
=> false

The method alter_ego_state tells whether an object has been overridden. "modified" objects will no longer inherit changes to the seed data.

@car.alter_ego_state
=> "default"

@car.update_attribute(:model, "foo")
=> true
@car
=> #<Car id: 1, brand: "Lotus", model: "foo">
@car.alter_ego_state
=> "modified"

If you don't want to inherit changes for an object without actually modifying it you can use pin!:

@car.pin!
=> true
@car.alter_ego_state
=> "pinned"

reset reverts the changes in the database and activates the synchronization again: @car.reset => # @car.alter_ego_state => "default"

Smart associations

It's possible to define dynamic associations in the seed data which is helpful if the IDs of the associated objects are not known or the associations depends on the state of the objects. This is done by appending _by clauses to the association name, similar to the dynamic finders in ActiveRecord::Base.

Example: db/fixtures/car.yml: 1: brand: Lotus model: Elise category_id: 3 # Static way of specifying associations category_by_name: Sport # => @car.category = Category.find_by_name("Sport")

  sellers_by_name_and_active: [Hugo, true]         # @car.sellers = Seller.find_all_by_name_and_active("Hugo", true)
  sellers_by_name_and_active: [[Hugo, Egon], true] # @car.sellers = Seller.find_all_by_name_and_active(["Hugo", "Egon"], true)

Custom logic on seed

has_alter_ego provides a hook for adding custom logic when an object is created or updated from the seed definitions. Just add a method on_seed(attributes) to your Model and you'll have access to all the seed attributes.

Note: You should not call save from within the hook or the objects will be marked as modified.

Example: class Car < ActiveRecord::Base has_alter_ego

  def on_seed(attributes)
    self.price = attributes["price_without_vat"] * VAT_FACTOR
  end
end

Generating seed data from the database

has_alter_ego has a rake task for dumping the current database content into a seed file. It is called like this:

rake has_alter_ego::dump MODEL=Car

That will fill db/fixtures/alter_egos/cars.yml with the database objects.

Copyright (c) 2010 André Duffeck, released under the MIT license