has_alter_ego 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +100 -0
- data/Rakefile +26 -0
- data/generators/has_alter_ego/has_alter_ego_generator.rb +8 -0
- data/generators/has_alter_ego/templates/create_alter_egos.rb +14 -0
- data/lib/has_alter_ego.rb +112 -0
- data/lib/has_alter_ego/alter_ego.rb +3 -0
- data/lib/tasks/has_alter_ego.rake +1 -0
- data/rails/init.rb +1 -0
- data/test/db/fixtures/alter_egos/bikes.yml +2 -0
- data/test/db/fixtures/alter_egos/cars.yml +15 -0
- data/test/db/fixtures/alter_egos/drinks.yml +5 -0
- data/test/has_alter_ego_test.rb +156 -0
- data/test/test_helper.rb +36 -0
- metadata +80 -0
data/README.md
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
# has_alter_ego
|
2
|
+
|
3
|
+
has_alter_ego makes it possible to keep seed and live data transparently in parallel. In contrast to other seed
|
4
|
+
data approaches has_alter_ego synchronizes the seed definitions with your database objects automagically unless you've
|
5
|
+
overridden it in the database.
|
6
|
+
|
7
|
+
# Installation
|
8
|
+
|
9
|
+
## Rails 2.3.x
|
10
|
+
### As a plugin
|
11
|
+
script/plugin install git://github.com/aduffeck/has_alter_ego.git
|
12
|
+
script/generate has_alter_ego
|
13
|
+
rake db:migrate
|
14
|
+
|
15
|
+
### As a gem
|
16
|
+
Add the following line to your config/environment.rb file:
|
17
|
+
config.gem "has_alter_ego"
|
18
|
+
Then
|
19
|
+
gem install has_alter_ego
|
20
|
+
script/generate has_alter_ego
|
21
|
+
rake db:migrate
|
22
|
+
|
23
|
+
# Usage
|
24
|
+
|
25
|
+
The seed data is defined in YAML files called after the model's table. The files are expected in db/fixtures/alter_egos.
|
26
|
+
|
27
|
+
Say you have a Model Car. has_alter_ego is enabled with the has_alter_ego method:
|
28
|
+
|
29
|
+
create_table :cars do |t|
|
30
|
+
t.string :brand
|
31
|
+
t.string :model
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
class Car < ActiveRecord::Base
|
36
|
+
has_alter_ego
|
37
|
+
end
|
38
|
+
|
39
|
+
You would then create a file db/fixtures/has_alter_ego/cars.yml with the seed data:
|
40
|
+
|
41
|
+
1:
|
42
|
+
brand: Lotus
|
43
|
+
model: Elise
|
44
|
+
|
45
|
+
2:
|
46
|
+
brand: Porsche
|
47
|
+
model: 911
|
48
|
+
|
49
|
+
3:
|
50
|
+
brand: Ferrari
|
51
|
+
model: F50
|
52
|
+
|
53
|
+
4:
|
54
|
+
brand: Corvette
|
55
|
+
model: C5
|
56
|
+
|
57
|
+
and you'd automagically have those objects available in your database.
|
58
|
+
|
59
|
+
Car.find(1)
|
60
|
+
=> #<Car id: 1, brand: "Lotus", model: "Elise">
|
61
|
+
|
62
|
+
Whenever the seed definition changes the objects in the database inherit the changes unless they have been overridden.
|
63
|
+
You can check if an object was created from seed definition with *has_alter_ego?*:
|
64
|
+
|
65
|
+
@car = Car.find(1)
|
66
|
+
@car.has_alter_ego?
|
67
|
+
=> true
|
68
|
+
|
69
|
+
Car.new.has_alter_ego?
|
70
|
+
=> false
|
71
|
+
|
72
|
+
The method *alter_ego_state* tells whether an object has been overridden. "modified" objects will no longer inherit
|
73
|
+
changes to the seed data.
|
74
|
+
|
75
|
+
@car.alter_ego_state
|
76
|
+
=> "default"
|
77
|
+
|
78
|
+
@car.update_attribute(:model, "foo")
|
79
|
+
=> true
|
80
|
+
@car
|
81
|
+
=> #<Car id: 1, brand: "Lotus", model: "foo">
|
82
|
+
@car.alter_ego_state
|
83
|
+
=> "modified"
|
84
|
+
|
85
|
+
If you don't want to inherit changes for an object without actually modifying it you can use *pin!*:
|
86
|
+
|
87
|
+
@car.pin!
|
88
|
+
=> true
|
89
|
+
@car.alter_ego_state
|
90
|
+
=> "pinned"
|
91
|
+
|
92
|
+
|
93
|
+
*reset* reverts the changes in the database and activates the synchronization again:
|
94
|
+
@car.reset
|
95
|
+
=> #<Car id: 1, brand: "Lotus", model: "Elise">
|
96
|
+
@car.alter_ego_state
|
97
|
+
=> "default"
|
98
|
+
|
99
|
+
|
100
|
+
Copyright (c) 2010 André Duffeck, released under the MIT license
|
data/Rakefile
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
require "rake/gempackagetask"
|
5
|
+
|
6
|
+
desc 'Default: run unit tests.'
|
7
|
+
task :default => :test
|
8
|
+
|
9
|
+
desc 'Test the has_alter_ego plugin.'
|
10
|
+
Rake::TestTask.new(:test) do |t|
|
11
|
+
t.libs << 'lib'
|
12
|
+
t.libs << 'test'
|
13
|
+
t.pattern = 'test/**/*_test.rb'
|
14
|
+
t.verbose = true
|
15
|
+
end
|
16
|
+
|
17
|
+
desc 'Generate documentation for the has_alter_ego plugin.'
|
18
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
19
|
+
rdoc.rdoc_dir = 'rdoc'
|
20
|
+
rdoc.title = 'Schizophrenia'
|
21
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
22
|
+
rdoc.rdoc_files.include('README')
|
23
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
24
|
+
end
|
25
|
+
|
26
|
+
Rake::GemPackageTask.new(eval(File.read("has_alter_ego.gemspec"))) { |pkg| }
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class CreateAlterEgos < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table :alter_egos do |t|
|
4
|
+
t.string :alter_ego_object_id
|
5
|
+
t.string :alter_ego_object_type, :limit => 40
|
6
|
+
t.string :state
|
7
|
+
end
|
8
|
+
add_index :alter_egos, [:alter_ego_object_id, :alter_ego_object_type]
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.down
|
12
|
+
drop_table :alter_egos
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "has_alter_ego", "alter_ego")
|
2
|
+
|
3
|
+
module HasAlterEgo
|
4
|
+
module ActiveRecordAdapater
|
5
|
+
def self.included(base)
|
6
|
+
base.send :extend, ClassMethods
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
def has_alter_ego opts = {}
|
11
|
+
opts.reverse_merge!({:reserved_space => 1000})
|
12
|
+
|
13
|
+
class_eval do
|
14
|
+
has_one :alter_ego, :as => :alter_ego_object
|
15
|
+
alias_method :save_without_alter_ego, :save
|
16
|
+
send :include, InstanceMethods
|
17
|
+
reserve_space(opts[:reserved_space])
|
18
|
+
parse_yml
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Reserve the first n IDs for stubbed objects
|
23
|
+
def reserve_space space
|
24
|
+
return unless self.columns_hash[self.primary_key].klass == Fixnum
|
25
|
+
return if self.last and self.last[self.primary_key] >= space
|
26
|
+
|
27
|
+
o = self.new
|
28
|
+
o[self.primary_key] = space
|
29
|
+
o.save_without_alter_ego
|
30
|
+
o.destroy
|
31
|
+
return
|
32
|
+
end
|
33
|
+
|
34
|
+
def parse_yml
|
35
|
+
yml = get_yml
|
36
|
+
yml.keys.each do |o|
|
37
|
+
parse_yml_for o
|
38
|
+
end
|
39
|
+
yml
|
40
|
+
end
|
41
|
+
|
42
|
+
def parse_yml_for primary_key
|
43
|
+
yml = get_yml
|
44
|
+
db_object = self.find(primary_key) rescue nil
|
45
|
+
if db_object
|
46
|
+
raise "There is already a #{db_object.class} with id #{db_object.id} in the database." unless db_object.has_alter_ego?
|
47
|
+
if db_object.alter_ego.state == 'default'
|
48
|
+
yml[primary_key].keys.each do |attr|
|
49
|
+
db_object[attr] = yml[primary_key][attr]
|
50
|
+
end
|
51
|
+
db_object.save_without_alter_ego
|
52
|
+
end
|
53
|
+
else
|
54
|
+
db_object = self.new
|
55
|
+
db_object[self.primary_key] = primary_key
|
56
|
+
yml[primary_key].keys.each do |attr|
|
57
|
+
db_object[attr] = yml[primary_key][attr]
|
58
|
+
end
|
59
|
+
db_object.build_alter_ego
|
60
|
+
db_object.alter_ego.state = 'default'
|
61
|
+
db_object.save_without_alter_ego
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def get_yml
|
68
|
+
filename = File.join(RAILS_ROOT, "db", "fixtures", "alter_egos", self.table_name + ".yml")
|
69
|
+
return {} unless File.exists?(filename)
|
70
|
+
yml = File.open(filename) do |yf|
|
71
|
+
YAML::load( yf )
|
72
|
+
end
|
73
|
+
yml
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
module InstanceMethods
|
78
|
+
def has_alter_ego?
|
79
|
+
return self.alter_ego.present?
|
80
|
+
end
|
81
|
+
|
82
|
+
def alter_ego_state
|
83
|
+
self.alter_ego.state if self.alter_ego
|
84
|
+
end
|
85
|
+
|
86
|
+
def save perform_validation = true
|
87
|
+
if self.alter_ego
|
88
|
+
self.alter_ego.state = 'modified'
|
89
|
+
self.alter_ego.save
|
90
|
+
end
|
91
|
+
save_without_alter_ego perform_validation
|
92
|
+
end
|
93
|
+
|
94
|
+
def pin!
|
95
|
+
self.alter_ego.state = 'pinned'
|
96
|
+
self.alter_ego.save
|
97
|
+
end
|
98
|
+
|
99
|
+
def reset
|
100
|
+
self.alter_ego.state = 'default'
|
101
|
+
self.alter_ego.save
|
102
|
+
|
103
|
+
self.class.parse_yml_for self[self.class.primary_key]
|
104
|
+
self.reload
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
class ActiveRecord::Base
|
111
|
+
include HasAlterEgo::ActiveRecordAdapater
|
112
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
|
data/rails/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'has_alter_ego'
|
@@ -0,0 +1,156 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class Car < ActiveRecord::Base
|
4
|
+
has_many :tires
|
5
|
+
has_alter_ego
|
6
|
+
end
|
7
|
+
|
8
|
+
class Bike < ActiveRecord::Base
|
9
|
+
end
|
10
|
+
|
11
|
+
class Scooter < ActiveRecord::Base
|
12
|
+
end
|
13
|
+
|
14
|
+
class Tire < ActiveRecord::Base
|
15
|
+
end
|
16
|
+
|
17
|
+
class Drink < ActiveRecord::Base
|
18
|
+
set_primary_key :name
|
19
|
+
has_alter_ego
|
20
|
+
end
|
21
|
+
|
22
|
+
class HasAlterEgoTest < Test::Unit::TestCase
|
23
|
+
def test_space_gets_reserved
|
24
|
+
c = Car.create
|
25
|
+
assert_equal 1001, c.id
|
26
|
+
|
27
|
+
assert_equal 1, Scooter.create.id
|
28
|
+
Scooter.has_alter_ego :reserved_space => 500
|
29
|
+
assert_equal 501, Scooter.create.id
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_has_alter_ego?
|
33
|
+
assert Car.find(1).alter_ego
|
34
|
+
assert !Car.new.has_alter_ego?
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_create_objects_from_yml
|
38
|
+
assert_equal 4, Car.count
|
39
|
+
c1 = Car.find(1)
|
40
|
+
assert_equal "Lotus", c1.brand
|
41
|
+
assert_equal "Elise", c1.model
|
42
|
+
assert c1.has_alter_ego?
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_exception_is_raised_if_id_is_already_in_use
|
46
|
+
bike = Bike.create
|
47
|
+
assert_raise RuntimeError do
|
48
|
+
Bike.has_alter_ego
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_object_attributes_are_updated_if_not_modified
|
53
|
+
c = Car.find(1)
|
54
|
+
assert_equal "Lotus", c.brand
|
55
|
+
assert_equal "default", c.alter_ego_state
|
56
|
+
|
57
|
+
c.brand = "Toyota"
|
58
|
+
c.save_without_alter_ego
|
59
|
+
c.reload
|
60
|
+
assert_equal "Toyota", c.brand
|
61
|
+
assert_equal "default", c.alter_ego_state
|
62
|
+
|
63
|
+
Car.parse_yml
|
64
|
+
c.reload
|
65
|
+
assert_equal "Lotus", c.brand
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_object_attributes_are_not_updated_if_pinned
|
69
|
+
c = Car.find(2)
|
70
|
+
c.reset
|
71
|
+
assert_equal "Porsche", c.brand
|
72
|
+
|
73
|
+
c.brand = "VW"
|
74
|
+
c.save_without_alter_ego
|
75
|
+
c.pin!
|
76
|
+
c.reload
|
77
|
+
assert_equal "VW", c.brand
|
78
|
+
assert_equal "pinned", c.alter_ego_state
|
79
|
+
|
80
|
+
Car.parse_yml
|
81
|
+
c.reload
|
82
|
+
assert_equal "VW", c.brand
|
83
|
+
end
|
84
|
+
|
85
|
+
def test_object_attributes_are_not_updated_if_modified
|
86
|
+
c = Car.find(2)
|
87
|
+
assert_equal "Porsche", c.brand
|
88
|
+
|
89
|
+
c.brand = "VW"
|
90
|
+
c.save
|
91
|
+
c.reload
|
92
|
+
assert_equal "VW", c.brand
|
93
|
+
assert_equal "modified", c.alter_ego_state
|
94
|
+
|
95
|
+
Car.parse_yml
|
96
|
+
c.reload
|
97
|
+
assert_equal "VW", c.brand
|
98
|
+
end
|
99
|
+
|
100
|
+
def test_reset
|
101
|
+
c = Car.find(3)
|
102
|
+
assert_equal "Ferrari", c.brand
|
103
|
+
assert_equal "default", c.alter_ego_state
|
104
|
+
|
105
|
+
c.brand = "Fiat"
|
106
|
+
c.save
|
107
|
+
c.reload
|
108
|
+
|
109
|
+
assert_equal "Fiat", c.brand
|
110
|
+
assert_equal "modified", c.alter_ego_state
|
111
|
+
c.reset
|
112
|
+
c.reload
|
113
|
+
|
114
|
+
assert_equal "Ferrari", c.brand
|
115
|
+
assert_equal "default", c.alter_ego_state
|
116
|
+
end
|
117
|
+
|
118
|
+
def test_associations_do_not_change_state
|
119
|
+
c = Car.find(4)
|
120
|
+
assert_equal "default", c.alter_ego_state
|
121
|
+
assert_equal 0, c.tires.size
|
122
|
+
|
123
|
+
4.times do
|
124
|
+
c.tires << Tire.new
|
125
|
+
end
|
126
|
+
c.reload
|
127
|
+
assert_equal "default", c.alter_ego_state
|
128
|
+
assert_equal 4, c.tires.size
|
129
|
+
end
|
130
|
+
|
131
|
+
def test_different_primary_key
|
132
|
+
assert_equal 2, Drink.all.size
|
133
|
+
assert_equal "none", Drink.find("water").color
|
134
|
+
|
135
|
+
water = Drink.find("water")
|
136
|
+
assert water.has_alter_ego?
|
137
|
+
assert_equal "default", water.alter_ego_state
|
138
|
+
|
139
|
+
water.color = "blue"
|
140
|
+
water.save
|
141
|
+
water.reload
|
142
|
+
assert_equal "blue", water.color
|
143
|
+
assert_equal "modified", water.alter_ego_state
|
144
|
+
|
145
|
+
water.reset
|
146
|
+
water.reload
|
147
|
+
assert_equal "none", water.color
|
148
|
+
assert_equal "default", water.alter_ego_state
|
149
|
+
|
150
|
+
orangejuice = Drink.new
|
151
|
+
orangejuice.name = "orangejuice"
|
152
|
+
orangejuice.color = "yellow"
|
153
|
+
orangejuice.save
|
154
|
+
assert !orangejuice.has_alter_ego?
|
155
|
+
end
|
156
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'active_support'
|
3
|
+
require 'active_support/test_case'
|
4
|
+
require 'active_record'
|
5
|
+
require 'test/unit'
|
6
|
+
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
|
7
|
+
|
8
|
+
require File.dirname(__FILE__) + '/../lib/has_alter_ego'
|
9
|
+
RAILS_ROOT = File.dirname(__FILE__)
|
10
|
+
|
11
|
+
silence_stream(STDOUT) do
|
12
|
+
ActiveRecord::Schema.define do
|
13
|
+
create_table :alter_egos do |t|
|
14
|
+
t.string :alter_ego_object_id
|
15
|
+
t.string :alter_ego_object_type, :limit => 40
|
16
|
+
t.string :state
|
17
|
+
end
|
18
|
+
|
19
|
+
create_table :cars do |t|
|
20
|
+
t.string :brand
|
21
|
+
t.string :model
|
22
|
+
end
|
23
|
+
|
24
|
+
create_table :bikes
|
25
|
+
create_table :scooters
|
26
|
+
|
27
|
+
create_table :tires do |t|
|
28
|
+
t.integer :car_id
|
29
|
+
end
|
30
|
+
|
31
|
+
create_table :drinks, :id => false do |t|
|
32
|
+
t.string :name
|
33
|
+
t.string :color
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
metadata
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: has_alter_ego
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- "Andr\xC3\xA9 Duffeck"
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-06-09 00:00:00 +02:00
|
19
|
+
default_executable:
|
20
|
+
dependencies: []
|
21
|
+
|
22
|
+
description: " has_alter_ego makes it possible to keep seed and live data transparently in parallel. In contrast to other seed\n data approaches has_alter_ego synchronizes the seed definitions with your database objects automagically unless you've\n overridden it in the database.\n"
|
23
|
+
email:
|
24
|
+
- aduffeck@suse.de
|
25
|
+
executables: []
|
26
|
+
|
27
|
+
extensions: []
|
28
|
+
|
29
|
+
extra_rdoc_files: []
|
30
|
+
|
31
|
+
files:
|
32
|
+
- lib/has_alter_ego/alter_ego.rb
|
33
|
+
- lib/has_alter_ego.rb
|
34
|
+
- lib/tasks/has_alter_ego.rake
|
35
|
+
- README.md
|
36
|
+
- Rakefile
|
37
|
+
- rails/init.rb
|
38
|
+
- generators/has_alter_ego/templates/create_alter_egos.rb
|
39
|
+
- generators/has_alter_ego/has_alter_ego_generator.rb
|
40
|
+
- test/db/fixtures/alter_egos/bikes.yml
|
41
|
+
- test/db/fixtures/alter_egos/cars.yml
|
42
|
+
- test/db/fixtures/alter_egos/drinks.yml
|
43
|
+
- test/test_helper.rb
|
44
|
+
- test/has_alter_ego_test.rb
|
45
|
+
has_rdoc: false
|
46
|
+
homepage: http://github.com/aduffeck/has_alter_ego
|
47
|
+
licenses: []
|
48
|
+
|
49
|
+
post_install_message:
|
50
|
+
rdoc_options: []
|
51
|
+
|
52
|
+
require_paths:
|
53
|
+
- lib
|
54
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
55
|
+
none: false
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
hash: 3
|
60
|
+
segments:
|
61
|
+
- 0
|
62
|
+
version: "0"
|
63
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
64
|
+
none: false
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
hash: 3
|
69
|
+
segments:
|
70
|
+
- 0
|
71
|
+
version: "0"
|
72
|
+
requirements: []
|
73
|
+
|
74
|
+
rubyforge_project:
|
75
|
+
rubygems_version: 1.3.7
|
76
|
+
signing_key:
|
77
|
+
specification_version: 3
|
78
|
+
summary: A Rails plugin which makes it possible to keep seed and live data in parallel
|
79
|
+
test_files:
|
80
|
+
- test/has_alter_ego_test.rb
|