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