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.
@@ -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
+