reversible_data 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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
@@ -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
+