shearwater 0.0.0 → 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/shearwater/cassandra_cql_backend.rb +44 -0
- data/lib/shearwater/in_memory_backend.rb +25 -0
- data/lib/shearwater/migrator.rb +55 -0
- data/lib/shearwater/redis_backend.rb +28 -0
- data/lib/shearwater/version.rb +5 -0
- data/lib/shearwater.rb +5 -0
- data/spec/environment.rb +7 -0
- data/spec/examples/cassandra_cql_backend_spec.rb +29 -0
- data/spec/examples/migrator_spec.rb +58 -0
- data/spec/examples/spec_helper.rb +7 -0
- data/spec/migrations/001_migration_1.rb +9 -0
- data/spec/migrations/002_migration_2.rb +10 -0
- data/spec/migrations/003_migration_3.rb +9 -0
- data/spec/support/migration_tracker.rb +43 -0
- metadata +46 -14
@@ -0,0 +1,44 @@
|
|
1
|
+
module Shearwater
|
2
|
+
|
3
|
+
class CassandraCqlBackend
|
4
|
+
|
5
|
+
def initialize(connection, column_family = 'schema_migrations')
|
6
|
+
@connection, @column_family = connection, column_family
|
7
|
+
end
|
8
|
+
|
9
|
+
def migrated!(id)
|
10
|
+
execute(
|
11
|
+
"INSERT INTO #{@column_family} (version, migrated_at) VALUES (?, ?)",
|
12
|
+
id, Time.now
|
13
|
+
)
|
14
|
+
end
|
15
|
+
|
16
|
+
def rolled_back!(id)
|
17
|
+
execute("DELETE FROM #{@column_family} WHERE version = ?", id)
|
18
|
+
end
|
19
|
+
|
20
|
+
def migrated?(id)
|
21
|
+
!!execute(
|
22
|
+
"SELECT migrated_at FROM #{@column_family} WHERE version = ?", id
|
23
|
+
).fetch_row.to_hash['migrated_at']
|
24
|
+
end
|
25
|
+
|
26
|
+
def last_migration
|
27
|
+
rows = []
|
28
|
+
execute(
|
29
|
+
"SELECT version, migrated_at FROM #{@column_family}"
|
30
|
+
).fetch { |row| rows << row.to_hash }
|
31
|
+
row = rows.reject { |row| row['migrated_at'].nil? }.sort { |row1, row2| row1['version'].to_i <=> row2['version'].to_i }.last
|
32
|
+
row['version'].to_i if row
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def execute(*args)
|
38
|
+
@connection.execute(*args)
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module Shearwater
|
4
|
+
class InMemoryBackend
|
5
|
+
def initialize
|
6
|
+
@migrations = SortedSet[]
|
7
|
+
end
|
8
|
+
|
9
|
+
def migrated!(id)
|
10
|
+
@migrations << id
|
11
|
+
end
|
12
|
+
|
13
|
+
def rolled_back!(id)
|
14
|
+
@migrations.delete(id)
|
15
|
+
end
|
16
|
+
|
17
|
+
def migrated?(id)
|
18
|
+
@migrations.include?(id)
|
19
|
+
end
|
20
|
+
|
21
|
+
def last_migration
|
22
|
+
@migrations.to_a.last
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Shearwater
|
2
|
+
|
3
|
+
class Migrator
|
4
|
+
|
5
|
+
def initialize(migrations_dir, backend, options = {})
|
6
|
+
@migrations_dir, @backend = migrations_dir, backend
|
7
|
+
@verbose = !!options[:verbose]
|
8
|
+
end
|
9
|
+
|
10
|
+
def migrate
|
11
|
+
migrations.keys.sort.each do |id|
|
12
|
+
unless @backend.migrated?(id)
|
13
|
+
migration = migrations[id]
|
14
|
+
say "Migrating #{migration.class.name}"
|
15
|
+
migration.up
|
16
|
+
@backend.migrated!(id)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def rollback(step = 1)
|
22
|
+
step.times do
|
23
|
+
id = @backend.last_migration
|
24
|
+
migration = migrations[id]
|
25
|
+
say "Rolling back #{migration.class.name}"
|
26
|
+
migration.down
|
27
|
+
@backend.rolled_back!(id)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def migrations
|
34
|
+
@migrations ||= {}.tap do |migrations|
|
35
|
+
Dir.glob(File.join(@migrations_dir, '**', '*.rb')).each do |file|
|
36
|
+
if /(\d+)_(\w+)\.rb/ =~ file
|
37
|
+
id, name = $1.to_i, $2
|
38
|
+
load file
|
39
|
+
class_name = name.split('_').map { |s| s.capitalize }.join
|
40
|
+
migration = ::Object.const_get(class_name).new
|
41
|
+
migrations[id] = migration
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def say(message)
|
50
|
+
puts message if @verbose
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Shearwater
|
2
|
+
|
3
|
+
class RedisBackend
|
4
|
+
|
5
|
+
def initialize(redis, key)
|
6
|
+
@redis, @key = redis, key
|
7
|
+
end
|
8
|
+
|
9
|
+
def migrated!(id)
|
10
|
+
@redis.zadd(@key, id, id.to_s)
|
11
|
+
end
|
12
|
+
|
13
|
+
def rolled_back!(id)
|
14
|
+
@redis.zrem(@key, id.to_s)
|
15
|
+
end
|
16
|
+
|
17
|
+
def migrated?(id)
|
18
|
+
@redis.zcount(@key, id, id) > 0
|
19
|
+
end
|
20
|
+
|
21
|
+
def last_migration
|
22
|
+
id = @redis.zrange(@key, -1, -1).first
|
23
|
+
id.to_i if id
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
data/lib/shearwater.rb
ADDED
data/spec/environment.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require File.expand_path('../spec_helper', __FILE__)
|
2
|
+
require 'shearwater/cassandra_cql_backend'
|
3
|
+
|
4
|
+
class CassandraStubResults
|
5
|
+
def initialize(row_hashes)
|
6
|
+
@row_hashes = row_hashes
|
7
|
+
end
|
8
|
+
|
9
|
+
def fetch
|
10
|
+
@row_hashes.each do |row_hash|
|
11
|
+
yield row_hash
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe Shearwater::CassandraCqlBackend do
|
17
|
+
describe "#last_migration" do
|
18
|
+
it "ignores 'range ghosts' when identifying the last migration" do
|
19
|
+
connection = stub('connection')
|
20
|
+
stubbed_cassandra_results = CassandraStubResults.new([
|
21
|
+
{ 'version' => 1, 'migrated_at' => 2342342 },
|
22
|
+
{ 'version' => 2, 'migrated_at' => nil }
|
23
|
+
])
|
24
|
+
connection.stub_chain(:execute).and_return(stubbed_cassandra_results)
|
25
|
+
backend = Shearwater::CassandraCqlBackend.new(connection)
|
26
|
+
backend.last_migration.should == 1
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require File.expand_path('../spec_helper', __FILE__)
|
2
|
+
|
3
|
+
describe Shearwater::Migrator do
|
4
|
+
let(:backend) { Shearwater::InMemoryBackend.new }
|
5
|
+
let(:tracker) { Shearwater::SpecSupport::MigrationTracker }
|
6
|
+
|
7
|
+
let :migrator do
|
8
|
+
Shearwater::Migrator.new(File.expand_path('../../migrations', __FILE__), backend)
|
9
|
+
end
|
10
|
+
|
11
|
+
describe '#migrate' do
|
12
|
+
it 'should run all migrations from fresh' do
|
13
|
+
migrator.migrate
|
14
|
+
[1, 2, 3].each { |i| tracker.should have_migrated(i) }
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'should run migrations in order of id' do
|
18
|
+
migrator.migrate
|
19
|
+
tracker.events.map(&:id).should == [1, 2, 3]
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'should not run migrations that backend reports have already been run' do
|
23
|
+
backend.migrated!(1)
|
24
|
+
migrator.migrate
|
25
|
+
tracker.events.map(&:id).should == [2, 3]
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'should report run to backend' do
|
29
|
+
migrator.migrate
|
30
|
+
1.upto(3) { |i| backend.migrated?(i).should be_true }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe '#rollback' do
|
35
|
+
before do
|
36
|
+
1.upto(3) { |i| backend.migrated!(i) }
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'should roll back most recent change' do
|
40
|
+
migrator.rollback
|
41
|
+
tracker.should have_rolled_back(3)
|
42
|
+
tracker.should_not have_rolled_back(2)
|
43
|
+
tracker.should_not have_rolled_back(1)
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'should mark migration as rolled back in backend' do
|
47
|
+
migrator.rollback
|
48
|
+
backend.migrated?(3).should be_false
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'should run multiple migrations if passed' do
|
52
|
+
migrator.rollback(2)
|
53
|
+
tracker.should have_rolled_back(3)
|
54
|
+
tracker.should have_rolled_back(2)
|
55
|
+
tracker.should_not have_rolled_back(1)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Shearwater
|
2
|
+
|
3
|
+
module SpecSupport
|
4
|
+
|
5
|
+
module MigrationTracker
|
6
|
+
|
7
|
+
extend self
|
8
|
+
|
9
|
+
Event = Struct.new(:id, :action)
|
10
|
+
|
11
|
+
def migrated!(id)
|
12
|
+
events << Event.new(id, :migrate)
|
13
|
+
end
|
14
|
+
|
15
|
+
def rolled_back!(id)
|
16
|
+
events << Event.new(id, :rollback)
|
17
|
+
end
|
18
|
+
|
19
|
+
def has_migrated?(id)
|
20
|
+
event?(id, :migrate)
|
21
|
+
end
|
22
|
+
|
23
|
+
def has_rolled_back?(id)
|
24
|
+
event?(id, :rollback)
|
25
|
+
end
|
26
|
+
|
27
|
+
def clear!
|
28
|
+
events.clear
|
29
|
+
end
|
30
|
+
|
31
|
+
def events
|
32
|
+
@events ||= []
|
33
|
+
end
|
34
|
+
|
35
|
+
def event?(id, action)
|
36
|
+
events.any? { |e| e.id == id && e.action == action }
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: shearwater
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.1.3
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-11-29 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec
|
16
|
-
requirement:
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ~>
|
@@ -21,21 +21,31 @@ dependencies:
|
|
21
21
|
version: '2.0'
|
22
22
|
type: :development
|
23
23
|
prerelease: false
|
24
|
-
version_requirements:
|
25
|
-
- !ruby/object:Gem::Dependency
|
26
|
-
name: ruby-debug19
|
27
|
-
requirement: &10109880 !ruby/object:Gem::Requirement
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
28
25
|
none: false
|
29
26
|
requirements:
|
30
27
|
- - ~>
|
31
28
|
- !ruby/object:Gem::Version
|
32
|
-
version: '0
|
29
|
+
version: '2.0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: debugger
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
33
38
|
type: :development
|
34
39
|
prerelease: false
|
35
|
-
version_requirements:
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
36
46
|
- !ruby/object:Gem::Dependency
|
37
47
|
name: yard
|
38
|
-
requirement:
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
39
49
|
none: false
|
40
50
|
requirements:
|
41
51
|
- - ~>
|
@@ -43,7 +53,12 @@ dependencies:
|
|
43
53
|
version: '0.6'
|
44
54
|
type: :development
|
45
55
|
prerelease: false
|
46
|
-
version_requirements:
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0.6'
|
47
62
|
description: ! 'Shearwater is a tiny framework for managing migrations in an everything-agnostic
|
48
63
|
|
49
64
|
way. It provides a pluggable backend architecture for storing which migrations
|
@@ -57,7 +72,21 @@ email: mat.a.brown@gmail.com
|
|
57
72
|
executables: []
|
58
73
|
extensions: []
|
59
74
|
extra_rdoc_files: []
|
60
|
-
files:
|
75
|
+
files:
|
76
|
+
- lib/shearwater.rb
|
77
|
+
- lib/shearwater/cassandra_cql_backend.rb
|
78
|
+
- lib/shearwater/redis_backend.rb
|
79
|
+
- lib/shearwater/in_memory_backend.rb
|
80
|
+
- lib/shearwater/migrator.rb
|
81
|
+
- lib/shearwater/version.rb
|
82
|
+
- spec/environment.rb
|
83
|
+
- spec/examples/spec_helper.rb
|
84
|
+
- spec/examples/migrator_spec.rb
|
85
|
+
- spec/examples/cassandra_cql_backend_spec.rb
|
86
|
+
- spec/support/migration_tracker.rb
|
87
|
+
- spec/migrations/002_migration_2.rb
|
88
|
+
- spec/migrations/003_migration_3.rb
|
89
|
+
- spec/migrations/001_migration_1.rb
|
61
90
|
homepage:
|
62
91
|
licenses:
|
63
92
|
- MIT
|
@@ -79,9 +108,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
79
108
|
version: '0'
|
80
109
|
requirements: []
|
81
110
|
rubyforge_project:
|
82
|
-
rubygems_version: 1.8.
|
111
|
+
rubygems_version: 1.8.24
|
83
112
|
signing_key:
|
84
113
|
specification_version: 3
|
85
114
|
summary: Tiny everything-agnostic migrations framework
|
86
|
-
test_files:
|
115
|
+
test_files:
|
116
|
+
- spec/examples/spec_helper.rb
|
117
|
+
- spec/examples/migrator_spec.rb
|
118
|
+
- spec/examples/cassandra_cql_backend_spec.rb
|
87
119
|
has_rdoc: false
|