active_column 0.0.2 → 0.1
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/.gitignore +1 -0
- data/.yardopts +1 -0
- data/Gemfile.lock +120 -17
- data/README.md +27 -143
- data/active_column.gemspec +12 -6
- data/docs/Create.md +101 -0
- data/docs/Migrate.md +100 -0
- data/docs/Query.md +43 -0
- data/lib/active_column.rb +22 -3
- data/lib/active_column/base.rb +32 -46
- data/lib/active_column/errors.rb +6 -0
- data/lib/active_column/generators/migration_generator.rb +31 -0
- data/lib/active_column/generators/templates/migration.rb.erb +11 -0
- data/lib/active_column/key_config.rb +16 -0
- data/lib/active_column/migration.rb +269 -0
- data/lib/active_column/tasks/column_family.rb +64 -0
- data/lib/active_column/tasks/keyspace.rb +59 -0
- data/lib/active_column/tasks/ks.rb +76 -0
- data/lib/active_column/version.rb +1 -1
- data/spec/active_column/base_crud_spec.rb +1 -1
- data/spec/active_column/base_finders_spec.rb +6 -6
- data/spec/active_column/migrator_spec.rb +150 -0
- data/spec/active_column/tasks/column_family_spec.rb +34 -0
- data/spec/active_column/tasks/keyspace_spec.rb +38 -0
- data/spec/spec_helper.rb +24 -4
- data/spec/support/aggregating_tweet.rb +3 -1
- data/spec/support/migrate/migrator_spec/1_migration1.rb +11 -0
- data/spec/support/migrate/migrator_spec/2_migration2.rb +11 -0
- data/spec/support/migrate/migrator_spec/3_migration3.rb +11 -0
- data/spec/support/migrate/migrator_spec/4_migration4.rb +11 -0
- data/spec/support/tweet.rb +4 -2
- data/spec/support/tweet_dm.rb +6 -4
- metadata +103 -11
data/docs/Migrate.md
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
## Data Migrations
|
2
|
+
|
3
|
+
The very first thing I would like to say about ActiveColumn Cassandra data migration is that *I stole most of the code
|
4
|
+
for this from the Rails gem (in ActiveSupport)*. I made the necessary changes to update a Cassandra database
|
5
|
+
instead of a relational DB. These changes were sort of significant, but I just wanted to give credit where credit
|
6
|
+
is due.
|
7
|
+
|
8
|
+
With that out of the way, we can discuss how you would use ActiveColumn to perform data migrations.
|
9
|
+
|
10
|
+
### Creating keyspaces
|
11
|
+
|
12
|
+
First we will create our project's keyspaces.
|
13
|
+
|
14
|
+
1. Make sure your cassandra 0.7 (or above) server is running.
|
15
|
+
|
16
|
+
2. Make sure you have your _config/cassandra.yml_ file created. The [README](../README.md) has an example of
|
17
|
+
this file.
|
18
|
+
|
19
|
+
The ActiveColumn gem gives you several rake tasks within the **ks:** namespace. "ks" stands for keyspace, which is
|
20
|
+
the equivalent of a database in MySQL (or other relational dbs). To see the available tasks, run this rake command:
|
21
|
+
|
22
|
+
<pre>
|
23
|
+
rake -T ks
|
24
|
+
</pre>
|
25
|
+
|
26
|
+
3. Create your databases with the **ks:create:all** rake task:
|
27
|
+
|
28
|
+
<pre>
|
29
|
+
rake ks:create:all
|
30
|
+
</pre>
|
31
|
+
|
32
|
+
Voila! You have now successfully created your keyspaces. Now let's generate some migration files.
|
33
|
+
|
34
|
+
### Creating and running migrations
|
35
|
+
|
36
|
+
4. ActiveColumn includes a generator to help you create blank migration files. To create a new migration, run this
|
37
|
+
command:
|
38
|
+
|
39
|
+
<pre>
|
40
|
+
rails g active_column:migration NameOfYourMigration
|
41
|
+
</pre>
|
42
|
+
|
43
|
+
The name of the migration might be something like "CreateUsersColumnFamily". After you run this command, you should see
|
44
|
+
a new file that is located here:
|
45
|
+
|
46
|
+
<pre>
|
47
|
+
ks/migrate/20101229183849_create_users_column_family.rb
|
48
|
+
</pre>
|
49
|
+
|
50
|
+
Note that the date stamp on the file will be different depending on when you create the migration. The migration file
|
51
|
+
will look like this:
|
52
|
+
|
53
|
+
<pre>
|
54
|
+
class CreateUsersColumnFamily < ActiveColumn::Migration
|
55
|
+
|
56
|
+
def self.up
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.down
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
</pre>
|
66
|
+
|
67
|
+
5. Edit your new migration file to do what you want it to. For this migration, it would probably wind up looking like
|
68
|
+
this:
|
69
|
+
|
70
|
+
<pre>
|
71
|
+
class CreateUsersColumnFamily < ActiveColumn::Migration
|
72
|
+
|
73
|
+
def self.up
|
74
|
+
create_column_family :users
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.down
|
78
|
+
drop_column_family :users
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
</pre>
|
83
|
+
|
84
|
+
6. Run the migrate rake task (for development):
|
85
|
+
|
86
|
+
<pre>
|
87
|
+
rake ks:migrate
|
88
|
+
</pre>
|
89
|
+
|
90
|
+
This will create the column family for your development environment. But you also need it in your test environment.
|
91
|
+
For now, you have to do this like the following. However, soon this will be updated to work more like ActiveRecord
|
92
|
+
migrations.
|
93
|
+
|
94
|
+
7. Run the migrate rake task (for test):
|
95
|
+
|
96
|
+
<pre>
|
97
|
+
RAILS_ENV=test rake ks:migrate
|
98
|
+
</pre>
|
99
|
+
|
100
|
+
And BAM! You have your development and test keyspaces set up correctly.
|
data/docs/Query.md
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
### Finding data
|
2
|
+
|
3
|
+
Ok, congratulations - now you have a bunch of fantastic data in Cassandra. How do you get it out? ActiveColumn can
|
4
|
+
help you here too.
|
5
|
+
|
6
|
+
Here is how you look up data that have a simple key:
|
7
|
+
|
8
|
+
<pre>
|
9
|
+
tweets = Tweet.find( 'mwynholds', :reversed => true, :count => 3 )
|
10
|
+
</pre>
|
11
|
+
|
12
|
+
This code will find the last 10 tweets for the 'mwynholds' user in reverse order. It comes back as a hash of arrays,
|
13
|
+
and would looks like this if represented in JSON:
|
14
|
+
|
15
|
+
<pre>
|
16
|
+
{
|
17
|
+
'mwynholds': [ { 'user_id': 'mwynholds', 'message': 'I\'m going to bed now' },
|
18
|
+
{ 'user_id': 'mwynholds', 'message': 'It\'s lunch time' },
|
19
|
+
{ 'user_id': 'mwynholds', 'message': 'Just woke up' } ]
|
20
|
+
}
|
21
|
+
</pre>
|
22
|
+
|
23
|
+
Here are some other examples and their return values:
|
24
|
+
|
25
|
+
<pre>
|
26
|
+
Tweet.find( [ 'mwynholds', 'all' ], :count => 2 )
|
27
|
+
|
28
|
+
{
|
29
|
+
'mwynholds': [ { 'user_id': 'mwynholds', 'message': 'Good morning' },
|
30
|
+
{ 'user_id': 'mwynholds', 'message': 'Good afternoon' } ],
|
31
|
+
'all': [ { 'user_id': 'mwynholds', 'message': 'Good morning' },
|
32
|
+
'user_id': 'bmurray', 'message': 'Who ya gonna call!' } ]
|
33
|
+
}
|
34
|
+
</pre>
|
35
|
+
|
36
|
+
<pre>
|
37
|
+
Tweet.find( { 'user_id' => 'all', 'recipient_id' => [ 'fsinatra', 'dmartin' ] }, :reversed => true, :count => 1 )
|
38
|
+
|
39
|
+
{
|
40
|
+
'all:fsinatra' => [ { 'user_id': 'mwynholds', 'recipient_ids' => [ 'fsinatra', 'dmartin' ], 'message' => 'Here we come Vegas!' } ],
|
41
|
+
'all:dmartin' => [ { 'user_id': 'fsinatra', 'recipient_ids' => [ 'dmartin' ], 'message' => 'Vegas was fun' } ]
|
42
|
+
}
|
43
|
+
</pre>
|
data/lib/active_column.rb
CHANGED
@@ -1,8 +1,27 @@
|
|
1
|
+
require 'cassandra/0.7'
|
2
|
+
require 'active_support'
|
3
|
+
require 'active_support/core_ext/string'
|
4
|
+
|
1
5
|
module ActiveColumn
|
2
6
|
|
3
|
-
autoload :
|
4
|
-
autoload :
|
5
|
-
autoload :
|
7
|
+
autoload :Base, 'active_column/base'
|
8
|
+
autoload :Connection, 'active_column/connection'
|
9
|
+
autoload :KeyConfig, 'active_column/key_config'
|
10
|
+
autoload :Version, 'active_column/version'
|
11
|
+
|
12
|
+
require 'active_column/errors'
|
13
|
+
require 'active_column/migration'
|
14
|
+
|
15
|
+
module Tasks
|
16
|
+
autoload :Keyspace, 'active_column/tasks/keyspace'
|
17
|
+
autoload :ColumnFamily, 'active_column/tasks/column_family'
|
18
|
+
|
19
|
+
require 'active_column/tasks/ks'
|
20
|
+
end
|
21
|
+
|
22
|
+
module Generators
|
23
|
+
require 'active_column/generators/migration_generator'
|
24
|
+
end
|
6
25
|
|
7
26
|
extend Connection
|
8
27
|
|
data/lib/active_column/base.rb
CHANGED
@@ -1,59 +1,33 @@
|
|
1
1
|
module ActiveColumn
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
def self.included(base)
|
4
|
+
base.extend ClassMethods
|
5
|
+
end
|
6
6
|
|
7
|
-
|
8
|
-
@attributes = attrs
|
9
|
-
end
|
7
|
+
module ClassMethods
|
10
8
|
|
11
|
-
def
|
12
|
-
return @column_family if column_family.nil?
|
9
|
+
def column_family(column_family = nil)
|
10
|
+
return @column_family || self.name.tableize.to_sym if column_family.nil?
|
13
11
|
@column_family = column_family
|
14
12
|
end
|
15
13
|
|
16
|
-
def
|
14
|
+
def key(key, options = {})
|
17
15
|
@keys ||= []
|
18
16
|
@keys << KeyConfig.new(key, options)
|
19
17
|
end
|
20
18
|
|
21
|
-
def
|
22
|
-
|
23
|
-
key_parts = self.class.keys.each_with_object( {} ) do |key_config, key_parts|
|
24
|
-
key_parts[key_config.key] = get_keys(key_config)
|
25
|
-
end
|
26
|
-
keys = self.class.generate_keys(key_parts)
|
27
|
-
|
28
|
-
keys.each do |key|
|
29
|
-
ActiveColumn.connection.insert(self.class.column_family, key, value)
|
30
|
-
end
|
31
|
-
|
32
|
-
self
|
19
|
+
def keys
|
20
|
+
@keys
|
33
21
|
end
|
34
22
|
|
35
|
-
def
|
23
|
+
def find(key_parts, options = {})
|
36
24
|
keys = generate_keys key_parts
|
37
25
|
ActiveColumn.connection.multi_get(column_family, keys, options).each_with_object( {} ) do |(user, row), results|
|
38
|
-
results[user] = row.to_a.collect { |(_uuid, col)| new(JSON.
|
26
|
+
results[user] = row.to_a.collect { |(_uuid, col)| new(ActiveSupport::JSON.decode(col)) }
|
39
27
|
end
|
40
28
|
end
|
41
29
|
|
42
|
-
def
|
43
|
-
@attributes.to_json(*a)
|
44
|
-
end
|
45
|
-
|
46
|
-
private
|
47
|
-
|
48
|
-
def self.keys
|
49
|
-
@keys
|
50
|
-
end
|
51
|
-
|
52
|
-
def get_keys(key_config)
|
53
|
-
key_config.func.nil? ? attributes[key_config.key] : self.send(key_config.func)
|
54
|
-
end
|
55
|
-
|
56
|
-
def self.generate_keys(key_parts)
|
30
|
+
def generate_keys(key_parts)
|
57
31
|
if keys.size == 1
|
58
32
|
key_config = keys.first
|
59
33
|
value = key_parts.is_a?(Hash) ? key_parts[key_config.key] : key_parts
|
@@ -73,17 +47,29 @@ module ActiveColumn
|
|
73
47
|
|
74
48
|
end
|
75
49
|
|
76
|
-
|
77
|
-
|
50
|
+
def initialize(attrs = {})
|
51
|
+
attrs.each do |attr, value|
|
52
|
+
send("#{attr}=", value) if respond_to?("#{attr}=")
|
53
|
+
end
|
54
|
+
end
|
78
55
|
|
79
|
-
|
80
|
-
|
81
|
-
|
56
|
+
def save()
|
57
|
+
value = { SimpleUUID::UUID.new => ActiveSupport::JSON.encode(self) }
|
58
|
+
key_parts = self.class.keys.each_with_object( {} ) do |key_config, key_parts|
|
59
|
+
key_parts[key_config.key] = self.send(key_config.func)
|
82
60
|
end
|
61
|
+
keys = self.class.generate_keys(key_parts)
|
83
62
|
|
84
|
-
|
85
|
-
|
63
|
+
keys.each do |key|
|
64
|
+
ActiveColumn.connection.insert(self.class.column_family, key, value)
|
86
65
|
end
|
66
|
+
|
67
|
+
self
|
68
|
+
end
|
69
|
+
|
70
|
+
class Base
|
71
|
+
include ActiveColumn
|
87
72
|
end
|
88
73
|
|
89
|
-
end
|
74
|
+
end
|
75
|
+
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
require 'rails/generators/named_base'
|
3
|
+
|
4
|
+
module ActiveColumn
|
5
|
+
module Generators
|
6
|
+
class MigrationGenerator < Rails::Generators::NamedBase
|
7
|
+
|
8
|
+
source_root File.expand_path("../templates", __FILE__)
|
9
|
+
|
10
|
+
def self.banner
|
11
|
+
"rails g active_column:migration NAME"
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.desc(description = nil)
|
15
|
+
<<EOF
|
16
|
+
Description:
|
17
|
+
Create an empty Cassandra migration file in 'ks/migrate'. Very similar to Rails database migrations.
|
18
|
+
|
19
|
+
Example:
|
20
|
+
`rails g active_column:migration CreateFooColumnFamily`
|
21
|
+
EOF
|
22
|
+
end
|
23
|
+
|
24
|
+
def create
|
25
|
+
timestamp = Time.now.utc.strftime("%Y%m%d%H%M%S")
|
26
|
+
template 'migration.rb.erb', "ks/migrate/#{timestamp}_#{file_name.tableize}.rb"
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,269 @@
|
|
1
|
+
module ActiveColumn
|
2
|
+
|
3
|
+
class IrreversibleMigration < ActiveColumnError
|
4
|
+
end
|
5
|
+
|
6
|
+
class DuplicateMigrationVersionError < ActiveColumnError#:nodoc:
|
7
|
+
def initialize(version)
|
8
|
+
super("Multiple migrations have the version number #{version}")
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class DuplicateMigrationNameError < ActiveColumnError#:nodoc:
|
13
|
+
def initialize(name)
|
14
|
+
super("Multiple migrations have the name #{name}")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class UnknownMigrationVersionError < ActiveColumnError #:nodoc:
|
19
|
+
def initialize(version)
|
20
|
+
super("No migration with version number #{version}")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class IllegalMigrationNameError < ActiveColumnError#:nodoc:
|
25
|
+
def initialize(name)
|
26
|
+
super("Illegal name for migration file: #{name}\n\t(only lower case letters, numbers, and '_' allowed)")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class Migration
|
31
|
+
|
32
|
+
def self.connection
|
33
|
+
$cassandra
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.migrate(direction)
|
37
|
+
return unless respond_to?(direction)
|
38
|
+
send direction
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.create_column_family(name, options = {})
|
42
|
+
ActiveColumn::Tasks::ColumnFamily.new.create(name, options)
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.drop_column_family(name)
|
46
|
+
ActiveColumn::Tasks::ColumnFamily.new.drop(name)
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
# MigrationProxy is used to defer loading of the actual migration classes
|
52
|
+
# until they are needed
|
53
|
+
class MigrationProxy
|
54
|
+
|
55
|
+
attr_accessor :name, :version, :filename
|
56
|
+
|
57
|
+
delegate :migrate, :announce, :write, :to=>:migration
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def migration
|
62
|
+
@migration ||= load_migration
|
63
|
+
end
|
64
|
+
|
65
|
+
def load_migration
|
66
|
+
require(File.expand_path(filename))
|
67
|
+
name.constantize
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
class Migrator
|
73
|
+
|
74
|
+
def self.migrate(migrations_path, target_version = nil)
|
75
|
+
case
|
76
|
+
when target_version.nil?
|
77
|
+
up(migrations_path, target_version)
|
78
|
+
when current_version == 0 && target_version == 0
|
79
|
+
when current_version > target_version
|
80
|
+
down(migrations_path, target_version)
|
81
|
+
else
|
82
|
+
up(migrations_path, target_version)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.rollback(migrations_path, steps = 1)
|
87
|
+
move(:down, migrations_path, steps)
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.forward(migrations_path, steps = 1)
|
91
|
+
move(:up, migrations_path, steps)
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.up(migrations_path, target_version = nil)
|
95
|
+
self.new(:up, migrations_path, target_version).migrate
|
96
|
+
end
|
97
|
+
|
98
|
+
def self.down(migrations_path, target_version = nil)
|
99
|
+
self.new(:down, migrations_path, target_version).migrate
|
100
|
+
end
|
101
|
+
|
102
|
+
def self.run(direction, migrations_path, target_version)
|
103
|
+
self.new(direction, migrations_path, target_version).run
|
104
|
+
end
|
105
|
+
|
106
|
+
def self.migrations_path
|
107
|
+
'ks/migrate'
|
108
|
+
end
|
109
|
+
|
110
|
+
def self.schema_migrations_column_family
|
111
|
+
:schema_migrations
|
112
|
+
end
|
113
|
+
|
114
|
+
def self.get_all_versions
|
115
|
+
cas = ActiveColumn.connection
|
116
|
+
cas.get(schema_migrations_column_family, 'all').map {|(name, _value)| name.to_i}.sort
|
117
|
+
end
|
118
|
+
|
119
|
+
def self.current_version
|
120
|
+
sm_cf = schema_migrations_column_family
|
121
|
+
cf = ActiveColumn::Tasks::ColumnFamily.new
|
122
|
+
if cf.exists?(sm_cf)
|
123
|
+
get_all_versions.max || 0
|
124
|
+
else
|
125
|
+
0
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
private
|
130
|
+
|
131
|
+
def self.move(direction, migrations_path, steps)
|
132
|
+
migrator = self.new(direction, migrations_path)
|
133
|
+
start_index = migrator.migrations.index(migrator.current_migration)
|
134
|
+
|
135
|
+
if start_index
|
136
|
+
finish = migrator.migrations[start_index + steps]
|
137
|
+
version = finish ? finish.version : 0
|
138
|
+
send(direction, migrations_path, version)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
public
|
143
|
+
|
144
|
+
def initialize(direction, migrations_path, target_version = nil)
|
145
|
+
cf = ActiveColumn::Tasks::ColumnFamily.new
|
146
|
+
sm_cf = self.class.schema_migrations_column_family
|
147
|
+
|
148
|
+
unless cf.exists?(sm_cf)
|
149
|
+
cf.create(sm_cf, :comparator_type => 'LongType')
|
150
|
+
end
|
151
|
+
|
152
|
+
@direction, @migrations_path, @target_version = direction, migrations_path, target_version
|
153
|
+
end
|
154
|
+
|
155
|
+
def current_version
|
156
|
+
migrated.last || 0
|
157
|
+
end
|
158
|
+
|
159
|
+
def current_migration
|
160
|
+
migrations.detect { |m| m.version == current_version }
|
161
|
+
end
|
162
|
+
|
163
|
+
def run
|
164
|
+
target = migrations.detect { |m| m.version == @target_version }
|
165
|
+
raise UnknownMigrationVersionError.new(@target_version) if target.nil?
|
166
|
+
unless (up? && migrated.include?(target.version.to_i)) || (down? && !migrated.include?(target.version.to_i))
|
167
|
+
target.migrate(@direction)
|
168
|
+
record_version_state_after_migrating(target)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def migrate
|
173
|
+
current = migrations.detect { |m| m.version == current_version }
|
174
|
+
target = migrations.detect { |m| m.version == @target_version }
|
175
|
+
|
176
|
+
if target.nil? && !@target_version.nil? && @target_version > 0
|
177
|
+
raise UnknownMigrationVersionError.new(@target_version)
|
178
|
+
end
|
179
|
+
|
180
|
+
start = up? ? 0 : (migrations.index(current) || 0)
|
181
|
+
finish = migrations.index(target) || migrations.size - 1
|
182
|
+
runnable = migrations[start..finish]
|
183
|
+
|
184
|
+
# skip the last migration if we're headed down, but not ALL the way down
|
185
|
+
runnable.pop if down? && !target.nil?
|
186
|
+
|
187
|
+
runnable.each do |migration|
|
188
|
+
#puts "Migrating to #{migration.name} (#{migration.version})"
|
189
|
+
|
190
|
+
# On our way up, we skip migrating the ones we've already migrated
|
191
|
+
next if up? && migrated.include?(migration.version.to_i)
|
192
|
+
|
193
|
+
# On our way down, we skip reverting the ones we've never migrated
|
194
|
+
if down? && !migrated.include?(migration.version.to_i)
|
195
|
+
migration.announce 'never migrated, skipping'; migration.write
|
196
|
+
next
|
197
|
+
end
|
198
|
+
|
199
|
+
migration.migrate(@direction)
|
200
|
+
record_version_state_after_migrating(migration)
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
def migrations
|
205
|
+
@migrations ||= begin
|
206
|
+
files = Dir["#{@migrations_path}/[0-9]*_*.rb"]
|
207
|
+
|
208
|
+
migrations = files.inject([]) do |klasses, file|
|
209
|
+
version, name = file.scan(/([0-9]+)_([_a-z0-9]*).rb/).first
|
210
|
+
|
211
|
+
raise IllegalMigrationNameError.new(file) unless version
|
212
|
+
version = version.to_i
|
213
|
+
|
214
|
+
if klasses.detect { |m| m.version == version }
|
215
|
+
raise DuplicateMigrationVersionError.new(version)
|
216
|
+
end
|
217
|
+
|
218
|
+
if klasses.detect { |m| m.name == name.camelize }
|
219
|
+
raise DuplicateMigrationNameError.new(name.camelize)
|
220
|
+
end
|
221
|
+
|
222
|
+
migration = MigrationProxy.new
|
223
|
+
migration.name = name.camelize
|
224
|
+
migration.version = version
|
225
|
+
migration.filename = file
|
226
|
+
klasses << migration
|
227
|
+
end
|
228
|
+
|
229
|
+
migrations = migrations.sort_by { |m| m.version }
|
230
|
+
down? ? migrations.reverse : migrations
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
def pending_migrations
|
235
|
+
already_migrated = migrated
|
236
|
+
migrations.reject { |m| already_migrated.include?(m.version.to_i) }
|
237
|
+
end
|
238
|
+
|
239
|
+
def migrated
|
240
|
+
@migrated_versions ||= self.class.get_all_versions
|
241
|
+
end
|
242
|
+
|
243
|
+
private
|
244
|
+
|
245
|
+
def record_version_state_after_migrating(migration)
|
246
|
+
cas = ActiveColumn.connection
|
247
|
+
sm_cf = self.class.schema_migrations_column_family
|
248
|
+
|
249
|
+
@migrated_versions ||= []
|
250
|
+
if down?
|
251
|
+
@migrated_versions.delete(migration.version)
|
252
|
+
cas.remove sm_cf, 'all', migration.version
|
253
|
+
else
|
254
|
+
@migrated_versions.push(migration.version).sort!
|
255
|
+
cas.insert sm_cf, 'all', { migration.version => migration.name }
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
def up?
|
260
|
+
@direction == :up
|
261
|
+
end
|
262
|
+
|
263
|
+
def down?
|
264
|
+
@direction == :down
|
265
|
+
end
|
266
|
+
|
267
|
+
end
|
268
|
+
|
269
|
+
end
|