shearwater 0.0.0 → 0.1.3
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/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
|