reversible_data 0.1.0
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/lib/reversible_data.rb +51 -0
- data/lib/reversible_data/shoulda_macros.rb +14 -0
- data/lib/reversible_data/table.rb +96 -0
- data/lib/reversible_data/table_manager.rb +31 -0
- data/test/reversible_data_test.rb +47 -0
- data/test/table_manager_test.rb +28 -0
- data/test/table_test.rb +140 -0
- data/test/test_helper.rb +25 -0
- metadata +62 -0
@@ -0,0 +1,51 @@
|
|
1
|
+
$:.unshift File.dirname(__FILE__)
|
2
|
+
|
3
|
+
require 'active_support'
|
4
|
+
require 'active_record'
|
5
|
+
|
6
|
+
module ReversibleData
|
7
|
+
|
8
|
+
autoload :Table, 'reversible_data/table'
|
9
|
+
autoload :TableManager, 'reversible_data/table_manager'
|
10
|
+
autoload :ShouldaMacros, 'reversible_data/shoulda_macros'
|
11
|
+
|
12
|
+
def self.add(name, opts = {}, &blk)
|
13
|
+
Table.new(name, opts, &blk)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.[](name)
|
17
|
+
Table.known_models[name]
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.remove(name)
|
21
|
+
table = self[name]
|
22
|
+
table.down!
|
23
|
+
table.destroy
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.remove_all
|
27
|
+
Table.known_models.each_value do |t|
|
28
|
+
t.down!
|
29
|
+
t.destroy
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.manager_for(*tables)
|
34
|
+
TableManager.new(*tables)
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.in_memory!
|
38
|
+
if connected?
|
39
|
+
ActiveRecord::Base.connection.disconnect!
|
40
|
+
ActiveRecord::Base.remove_connection
|
41
|
+
end
|
42
|
+
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.connected?
|
46
|
+
ActiveRecord::Base.connection.active?
|
47
|
+
rescue ActiveRecord::ConnectionNotEstablished
|
48
|
+
false
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module ReversibleData
|
2
|
+
module ShouldaMacros
|
3
|
+
|
4
|
+
def with_tables(*args, &blk)
|
5
|
+
table_manager = ReversibleData.manager_for(*args)
|
6
|
+
context '' do
|
7
|
+
setup { table_manager.up! }
|
8
|
+
context('', &blk)
|
9
|
+
teardown { table_manager.down! }
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
module ReversibleData
|
2
|
+
class Table
|
3
|
+
|
4
|
+
cattr_accessor :known_models
|
5
|
+
self.known_models = {}
|
6
|
+
|
7
|
+
attr_accessor :name, :table_name, :model_name, :options
|
8
|
+
|
9
|
+
def initialize(name, opts = {}, &blk)
|
10
|
+
@name = name.to_s.underscore.to_sym
|
11
|
+
@table_name = (opts[:table_name] || @name.to_s.tableize).to_s
|
12
|
+
@model_name = (opts[:class_name] || @name.to_s.classify).to_s
|
13
|
+
@migrator = blk
|
14
|
+
@model_definition = nil
|
15
|
+
default_options = {:skip_model => Object.const_defined?(@model_name),
|
16
|
+
:skip_table => (connected? && connection.table_exists?(@table_name))}
|
17
|
+
@options = opts.reverse_merge(default_options)
|
18
|
+
@blueprint = nil
|
19
|
+
self.known_models[@name] = self
|
20
|
+
end
|
21
|
+
|
22
|
+
def skip_model?
|
23
|
+
!!@options[:skip_model]
|
24
|
+
end
|
25
|
+
|
26
|
+
def skip_table?
|
27
|
+
!!@options[:skip_table]
|
28
|
+
end
|
29
|
+
|
30
|
+
def up!
|
31
|
+
create_table
|
32
|
+
create_model
|
33
|
+
end
|
34
|
+
|
35
|
+
def down!
|
36
|
+
remove_model
|
37
|
+
drop_table
|
38
|
+
end
|
39
|
+
|
40
|
+
def drop_table
|
41
|
+
return if skip_table? || !connected?
|
42
|
+
connection.drop_table(@table_name) if connection.table_exists?(@table_name)
|
43
|
+
end
|
44
|
+
|
45
|
+
def create_table(autodrop = true)
|
46
|
+
return if skip_table? || !connected?
|
47
|
+
drop_table if autodrop
|
48
|
+
return if connection.table_exists?(@table_name)
|
49
|
+
connection.create_table(table_name, &@migrator)
|
50
|
+
end
|
51
|
+
|
52
|
+
def remove_model
|
53
|
+
return if skip_model?
|
54
|
+
if Object.const_defined?(@model_name)
|
55
|
+
silence_warnings { Object.send(:remove_const, @model_name) }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def create_model(autoremove = true)
|
60
|
+
return if skip_model?
|
61
|
+
remove_model if autoremove
|
62
|
+
return if Object.const_defined?(@model_name)
|
63
|
+
@model = Class.new(ActiveRecord::Base)
|
64
|
+
@model.class_eval(&@model_definition) unless @model_definition.nil?
|
65
|
+
@model.blueprint(&@blueprint) unless @blueprint.nil? || !@model.respond_to?(:blueprint)
|
66
|
+
Object.const_set(@model_name, @model)
|
67
|
+
end
|
68
|
+
|
69
|
+
def define_model(&blk)
|
70
|
+
@model_definition = blk unless blk.nil?
|
71
|
+
end
|
72
|
+
|
73
|
+
def blueprint(&blk)
|
74
|
+
@blueprint = blk unless blk.nil?
|
75
|
+
end
|
76
|
+
|
77
|
+
def clear_model_definition!
|
78
|
+
@model_definition = nil
|
79
|
+
end
|
80
|
+
|
81
|
+
def destroy
|
82
|
+
self.known_models.delete(@name)
|
83
|
+
end
|
84
|
+
|
85
|
+
protected
|
86
|
+
|
87
|
+
def connection
|
88
|
+
ActiveRecord::Base.connection
|
89
|
+
end
|
90
|
+
|
91
|
+
def connected?
|
92
|
+
ReversibleData.connected?
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module ReversibleData
|
2
|
+
class TableManager
|
3
|
+
|
4
|
+
attr_reader :managed_tables
|
5
|
+
|
6
|
+
def initialize(*managed_tables)
|
7
|
+
@managed_tables = managed_tables.map { |n| n.to_sym }.freeze
|
8
|
+
end
|
9
|
+
|
10
|
+
def up!
|
11
|
+
@managed_tables.each do |name|
|
12
|
+
table = table_for(name)
|
13
|
+
table.up! unless table.nil?
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def down!
|
18
|
+
@managed_tables.each do |name|
|
19
|
+
table = table_for(name)
|
20
|
+
table.down! unless table.nil?
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
protected
|
25
|
+
|
26
|
+
def table_for(name)
|
27
|
+
Table.known_models[name]
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class ReversibleDataTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
should 'have add as a shortcut to Table.new' do
|
6
|
+
mock(ReversibleData::Table).new(:awesome, {})
|
7
|
+
ReversibleData.add(:awesome)
|
8
|
+
mock(ReversibleData::Table).new(:rocket, :table_name => "awesomesauces")
|
9
|
+
ReversibleData.add(:rocket, :table_name => "awesomesauces")
|
10
|
+
end
|
11
|
+
|
12
|
+
should 'have [] as a shortcut to find' do
|
13
|
+
mock(ReversibleData::Table.known_models)[:awesome]
|
14
|
+
ReversibleData[:awesome]
|
15
|
+
end
|
16
|
+
|
17
|
+
should 'have remove to remove a specific table' do
|
18
|
+
ReversibleData.add(:awesome) { |t| t.string :name }
|
19
|
+
assert !ReversibleData[:awesome].nil?
|
20
|
+
ReversibleData.remove(:awesome)
|
21
|
+
assert ReversibleData[:awesome].nil?
|
22
|
+
end
|
23
|
+
|
24
|
+
should 'let you remove all definitions with remove_all' do
|
25
|
+
ReversibleData.add(:awesome) { |t| t.string :name }
|
26
|
+
ReversibleData.add(:ninjas) { |t| t.string :name }
|
27
|
+
assert !ReversibleData[:awesome].nil?
|
28
|
+
assert !ReversibleData[:ninjas].nil?
|
29
|
+
ReversibleData.remove_all
|
30
|
+
assert ReversibleData[:awesome].nil?
|
31
|
+
assert ReversibleData[:ninjas].nil?
|
32
|
+
end
|
33
|
+
|
34
|
+
should 'let you create a Table Manager with manager_for' do
|
35
|
+
ReversibleData.add(:awesome) { |t| t.string :name }
|
36
|
+
ReversibleData.add(:ninjas) { |t| t.string :name }
|
37
|
+
mock(ReversibleData::TableManager).new(:awesome, :ninjas)
|
38
|
+
ReversibleData.manager_for(:awesome, :ninjas)
|
39
|
+
end
|
40
|
+
|
41
|
+
should 'let you establish an in-memory connection' do
|
42
|
+
assert !ReversibleData.connected?
|
43
|
+
ReversibleData.in_memory!
|
44
|
+
assert ReversibleData.connected?
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class TableManagerTest < Test::Unit::TestCase
|
4
|
+
context 'table managers in general' do
|
5
|
+
|
6
|
+
setup do
|
7
|
+
@table_a = ReversibleData.add(:a) { |t| t.string :name }
|
8
|
+
@table_b = ReversibleData.add(:b) { |t| t.string :name }
|
9
|
+
@table_c = ReversibleData.add(:c) { |t| t.string :name }
|
10
|
+
@manager = ReversibleData::TableManager.new(:a, :b, :c)
|
11
|
+
end
|
12
|
+
|
13
|
+
should 'let you create a manager for a set of tables' do
|
14
|
+
assert_equal [:a, :b, :c], @manager.managed_tables
|
15
|
+
end
|
16
|
+
|
17
|
+
should 'let you run up! on all managed tables' do
|
18
|
+
[@table_a, @table_b, @table_c].each { |t| mock(t).up! }
|
19
|
+
@manager.up!
|
20
|
+
end
|
21
|
+
|
22
|
+
should 'let you run down! on all managed tables' do
|
23
|
+
[@table_a, @table_b, @table_c].each { |t| mock(t).down! }
|
24
|
+
@manager.down!
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
data/test/table_test.rb
ADDED
@@ -0,0 +1,140 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class TableTest < Test::Unit::TestCase
|
4
|
+
context '' do
|
5
|
+
|
6
|
+
setup { ReversibleData.in_memory! }
|
7
|
+
|
8
|
+
teardown { ReversibleData.remove_all }
|
9
|
+
|
10
|
+
should 'automatically add it to a list of known models' do
|
11
|
+
table = ReversibleData::Table.new(:user) { |t| t.string :name }
|
12
|
+
assert_equal table, ReversibleData::Table.known_models[:user]
|
13
|
+
end
|
14
|
+
|
15
|
+
should 'default the table name' do
|
16
|
+
table = ReversibleData::Table.new(:user) { |t| t.string :name }
|
17
|
+
assert_equal "users", table.table_name
|
18
|
+
table = ReversibleData::Table.new(:users) { |t| t.string :name }
|
19
|
+
assert_equal "users", table.table_name
|
20
|
+
end
|
21
|
+
|
22
|
+
should 'default the model class name' do
|
23
|
+
table = ReversibleData::Table.new(:user) { |t| t.string :name }
|
24
|
+
assert_equal "User", table.model_name
|
25
|
+
table = ReversibleData::Table.new(:users) { |t| t.string :name }
|
26
|
+
assert_equal "User", table.model_name
|
27
|
+
end
|
28
|
+
|
29
|
+
should 'let you override the table name' do
|
30
|
+
table = ReversibleData::Table.new(:user, :table_name => "awesomesauce") { |t| t.string :name }
|
31
|
+
assert_equal "awesomesauce", table.table_name
|
32
|
+
end
|
33
|
+
|
34
|
+
should 'let you override the model class name' do
|
35
|
+
table = ReversibleData::Table.new(:user, :class_name => "Ninja") { |t| t.string :name }
|
36
|
+
assert_equal "Ninja", table.model_name
|
37
|
+
end
|
38
|
+
|
39
|
+
should 'let you skip creating the model' do
|
40
|
+
table = ReversibleData::Table.new(:user, :skip_model => true) { |t| t.string :name }
|
41
|
+
table.create_model
|
42
|
+
assert !defined?(User)
|
43
|
+
end
|
44
|
+
|
45
|
+
should 'let you skip creating the table' do
|
46
|
+
table = ReversibleData::Table.new(:user, :skip_table => true) { |t| t.string :name }
|
47
|
+
table.create_table
|
48
|
+
assert !ActiveRecord::Base.connection.table_exists?(:users)
|
49
|
+
end
|
50
|
+
|
51
|
+
should 'let you create the table' do
|
52
|
+
table = ReversibleData::Table.new(:user) { |t| t.string :name }
|
53
|
+
table.create_table
|
54
|
+
assert ActiveRecord::Base.connection.table_exists?(:users)
|
55
|
+
end
|
56
|
+
|
57
|
+
should 'let you drop the table' do
|
58
|
+
table = ReversibleData::Table.new(:user) { |t| t.string :name }
|
59
|
+
table.create_table
|
60
|
+
table.drop_table
|
61
|
+
assert !ActiveRecord::Base.connection.table_exists?(:users)
|
62
|
+
end
|
63
|
+
|
64
|
+
should 'let you create the model' do
|
65
|
+
assert !defined?(::User)
|
66
|
+
table = ReversibleData::Table.new(:user) { |t| t.string :name }
|
67
|
+
table.create_model
|
68
|
+
assert defined?(::User)
|
69
|
+
end
|
70
|
+
|
71
|
+
should 'let you remove the model' do
|
72
|
+
assert !defined?(::User)
|
73
|
+
table = ReversibleData::Table.new(:user) { |t| t.string :name }
|
74
|
+
table.create_model
|
75
|
+
table.remove_model
|
76
|
+
assert !defined?(::User)
|
77
|
+
end
|
78
|
+
|
79
|
+
should 'let you call up! to create the table and the model' do
|
80
|
+
assert !defined?(::User)
|
81
|
+
assert !ActiveRecord::Base.connection.table_exists?(:users)
|
82
|
+
table = ReversibleData::Table.new(:user) { |t| t.string :name }
|
83
|
+
table.up!
|
84
|
+
assert defined?(::User)
|
85
|
+
assert ActiveRecord::Base.connection.table_exists?(:users)
|
86
|
+
assert User < ActiveRecord::Base
|
87
|
+
end
|
88
|
+
|
89
|
+
should 'let you call down! to clean up' do
|
90
|
+
table = ReversibleData::Table.new(:user) { |t| t.string :name }
|
91
|
+
table.up!
|
92
|
+
assert ActiveRecord::Base.connection.table_exists?(:users)
|
93
|
+
assert User < ActiveRecord::Base
|
94
|
+
table.down!
|
95
|
+
assert !ActiveRecord::Base.connection.table_exists?(:users)
|
96
|
+
assert !defined?(::User)
|
97
|
+
end
|
98
|
+
|
99
|
+
should 'default to skipping model if the constant exists' do
|
100
|
+
::Awesome = true
|
101
|
+
table = ReversibleData::Table.new(:awesome)
|
102
|
+
assert table.skip_model?
|
103
|
+
Object.send(:remove_const, :Awesome)
|
104
|
+
end
|
105
|
+
|
106
|
+
should 'default to skipping the table if connected and the table exists' do
|
107
|
+
ActiveRecord::Base.connection.create_table(:ninjas) { |t| t.string :name }
|
108
|
+
table = ReversibleData::Table.new(:ninja)
|
109
|
+
assert table.skip_table?
|
110
|
+
ActiveRecord::Base.connection.drop_table(:ninjas)
|
111
|
+
end
|
112
|
+
|
113
|
+
should 'let you define a model block' do
|
114
|
+
table = ReversibleData::Table.new(:ninja)
|
115
|
+
table.define_model do
|
116
|
+
cattr_accessor :ninjariffic
|
117
|
+
self.ninjariffic = "felafel"
|
118
|
+
end
|
119
|
+
table.create_model
|
120
|
+
assert Ninja.respond_to?(:ninjariffic)
|
121
|
+
assert Ninja.respond_to?(:ninjariffic=)
|
122
|
+
assert_equal "felafel", Ninja.ninjariffic
|
123
|
+
table.remove_model
|
124
|
+
end
|
125
|
+
|
126
|
+
should 'let not call the blueprint if none is specified' do
|
127
|
+
table = ReversibleData::Table.new(:user) { |t| t.string :name }
|
128
|
+
table.up!
|
129
|
+
assert !User.blueprint_called
|
130
|
+
end
|
131
|
+
|
132
|
+
should 'call the blueprint if present' do
|
133
|
+
table = ReversibleData::Table.new(:user) { |t| t.string :name }
|
134
|
+
table.blueprint {}
|
135
|
+
table.up!
|
136
|
+
assert User.blueprint_called
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
140
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'test/unit'
|
3
|
+
require 'shoulda'
|
4
|
+
require 'rr'
|
5
|
+
require 'redgreen'
|
6
|
+
require 'reversible_data'
|
7
|
+
|
8
|
+
class Test::Unit::TestCase
|
9
|
+
include RR::Adapters::TestUnit
|
10
|
+
end
|
11
|
+
|
12
|
+
module MockBlueprints
|
13
|
+
|
14
|
+
def blueprint(&blk)
|
15
|
+
# Use this as a kind of hacky way to ensure it is called.
|
16
|
+
self.blueprint_called = true
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
ActiveRecord::Base.class_eval do
|
22
|
+
class_inheritable_accessor :blueprint_called
|
23
|
+
self.blueprint_called = false
|
24
|
+
extend MockBlueprints
|
25
|
+
end
|
metadata
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: reversible_data
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Darcy Laycock
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-09-25 00:00:00 +08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description:
|
17
|
+
email: sutto@sutto.net
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files: []
|
23
|
+
|
24
|
+
files:
|
25
|
+
- lib/reversible_data/shoulda_macros.rb
|
26
|
+
- lib/reversible_data/table.rb
|
27
|
+
- lib/reversible_data/table_manager.rb
|
28
|
+
- lib/reversible_data.rb
|
29
|
+
- test/reversible_data_test.rb
|
30
|
+
- test/table_manager_test.rb
|
31
|
+
- test/table_test.rb
|
32
|
+
- test/test_helper.rb
|
33
|
+
has_rdoc: false
|
34
|
+
homepage: http://sutto.net/
|
35
|
+
licenses: []
|
36
|
+
|
37
|
+
post_install_message:
|
38
|
+
rdoc_options: []
|
39
|
+
|
40
|
+
require_paths:
|
41
|
+
- lib
|
42
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: "0"
|
47
|
+
version:
|
48
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
49
|
+
requirements:
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: "0"
|
53
|
+
version:
|
54
|
+
requirements: []
|
55
|
+
|
56
|
+
rubyforge_project:
|
57
|
+
rubygems_version: 1.3.2
|
58
|
+
signing_key:
|
59
|
+
specification_version: 3
|
60
|
+
summary: Reversible Data provides migration-like functionality for tests etc - All with temporary models.
|
61
|
+
test_files: []
|
62
|
+
|