shearwater 0.0.0 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -0,0 +1,5 @@
1
+ module Shearwater
2
+
3
+ VERSION = '0.1.3'
4
+
5
+ end
data/lib/shearwater.rb ADDED
@@ -0,0 +1,5 @@
1
+ require 'shearwater/in_memory_backend'
2
+ require 'shearwater/migrator'
3
+
4
+ module Shearwater
5
+ end
@@ -0,0 +1,7 @@
1
+ require 'bundler'
2
+
3
+ Bundler.require(:default, :test)
4
+
5
+ Dir.glob(File.join(File.dirname(__FILE__), 'support', '**', '*.rb')).each do |f|
6
+ require f
7
+ end
@@ -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,7 @@
1
+ require File.expand_path('../../environment', __FILE__)
2
+
3
+ RSpec.configure do |config|
4
+ config.before :each do
5
+ Shearwater::SpecSupport::MigrationTracker.clear!
6
+ end
7
+ end
@@ -0,0 +1,9 @@
1
+ class Migration1
2
+ def up
3
+ Shearwater::SpecSupport::MigrationTracker.migrated!(1)
4
+ end
5
+
6
+ def down
7
+ Shearwater::SpecSupport::MigrationTracker.rolled_back!(1)
8
+ end
9
+ end
@@ -0,0 +1,10 @@
1
+ class Migration2
2
+ def up
3
+ Shearwater::SpecSupport::MigrationTracker.migrated!(2)
4
+ end
5
+
6
+ def down
7
+ Shearwater::SpecSupport::MigrationTracker.rolled_back!(2)
8
+ end
9
+ end
10
+
@@ -0,0 +1,9 @@
1
+ class Migration3
2
+ def up
3
+ Shearwater::SpecSupport::MigrationTracker.migrated!(3)
4
+ end
5
+
6
+ def down
7
+ Shearwater::SpecSupport::MigrationTracker.rolled_back!(3)
8
+ end
9
+ 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.0.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-04-13 00:00:00.000000000Z
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: &10110340 !ruby/object:Gem::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: *10110340
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.11'
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: *10109880
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: &10109420 !ruby/object:Gem::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: *10109420
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.15
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