active_column 0.1.1 → 0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,267 +1,129 @@
1
1
  module ActiveColumn
2
2
 
3
- class IrreversibleMigration < ActiveColumnError
4
- end
3
+ class Migration
5
4
 
6
- class DuplicateMigrationVersionError < ActiveColumnError#:nodoc:
7
- def initialize(version)
8
- super("Multiple migrations have the version number #{version}")
9
- end
10
- end
5
+ @@verbose = true
11
6
 
12
- class DuplicateMigrationNameError < ActiveColumnError#:nodoc:
13
- def initialize(name)
14
- super("Multiple migrations have the name #{name}")
7
+ def self.verbose=(verbose)
8
+ @@verbose = verbose
15
9
  end
16
- end
17
10
 
18
- class UnknownMigrationVersionError < ActiveColumnError #:nodoc:
19
- def initialize(version)
20
- super("No migration with version number #{version}")
11
+ def self.verbose
12
+ @@verbose
21
13
  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
14
 
15
+ # Returns the raw connection to Cassandra
32
16
  def self.connection
33
- $cassandra
17
+ ActiveColumn.connection
34
18
  end
35
19
 
36
20
  def self.migrate(direction)
37
21
  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
22
 
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)
23
+ case direction
24
+ when :up then announce "migrating"
25
+ when :down then announce "reverting"
83
26
  end
84
- end
85
27
 
86
- def self.rollback(migrations_path, steps = 1)
87
- move(:down, migrations_path, steps)
88
- end
28
+ result = nil
29
+ time = Benchmark.measure { result = send("#{direction}") }
89
30
 
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
31
+ case direction
32
+ when :up then announce "migrated (%.4fs)" % time.real; write
33
+ when :down then announce "reverted (%.4fs)" % time.real; write
34
+ end
101
35
 
102
- def self.run(direction, migrations_path, target_version)
103
- self.new(direction, migrations_path, target_version).run
36
+ result
104
37
  end
105
38
 
106
- def self.migrations_path
107
- 'ks/migrate'
39
+ # Creates a new column family with the given name. Column family configurations can be set within
40
+ # a block like this:
41
+ #
42
+ # create_column_family(:users) do |cf|
43
+ # cf.comment = 'Users column family'
44
+ # cf.comparator_type = 'TimeUUIDType'
45
+ # end
46
+ #
47
+ # A complete list of available configuration settings is here:
48
+ #
49
+ # http://github.com/fauna/cassandra/blob/master/vendor/0.7/gen-rb/cassandra_types.rb
50
+ #
51
+ # Scroll down to the CfDef definition.
52
+ def self.create_column_family(name, &block)
53
+ ActiveColumn.column_family_tasks.create(name, &block)
108
54
  end
109
55
 
110
- def self.schema_migrations_column_family
111
- :schema_migrations
56
+ # Drops the given column family
57
+ def self.drop_column_family(name)
58
+ ActiveColumn.column_family_tasks.drop(name)
112
59
  end
113
60
 
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
61
+ # Renames the column family from the old name to the new name
62
+ def self.rename_column_family(old_name, new_name)
63
+ ActiveColumn.column_family_tasks.rename(old_name, new_name)
117
64
  end
118
65
 
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
66
+ def self.write(text="")
67
+ puts(text) if verbose
140
68
  end
141
69
 
142
- public
70
+ def self.announce(message)
71
+ version = defined?(@version) ? @version : nil
143
72
 
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
73
+ text = "#{version} #{name}: #{message}"
74
+ length = [0, 75 - text.length].max
75
+ write "== %s %s" % [text, "=" * length]
153
76
  end
154
77
 
155
- def current_version
156
- migrated.last || 0
78
+ def self.say(message, subitem=false)
79
+ write "#{subitem ? " ->" : "--"} #{message}"
157
80
  end
158
81
 
159
- def current_migration
160
- migrations.detect { |m| m.version == current_version }
82
+ def self.say_with_time(message)
83
+ say(message)
84
+ result = nil
85
+ time = Benchmark.measure { result = yield }
86
+ say "%.4fs" % time.real, :subitem
87
+ say("#{result} rows", :subitem) if result.is_a?(Integer)
88
+ result
161
89
  end
162
90
 
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
91
+ def self.suppress_messages
92
+ save, self.verbose = verbose, false
93
+ yield
94
+ ensure
95
+ self.verbose = save
170
96
  end
171
97
 
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
98
+ end
217
99
 
218
- if klasses.detect { |m| m.name == name.camelize }
219
- raise DuplicateMigrationNameError.new(name.camelize)
220
- end
100
+ # MigrationProxy is used to defer loading of the actual migration classes
101
+ # until they are needed
102
+ class MigrationProxy
221
103
 
222
- migration = MigrationProxy.new
223
- migration.name = name.camelize
224
- migration.version = version
225
- migration.filename = file
226
- klasses << migration
227
- end
104
+ attr_accessor :name, :version, :filename
228
105
 
229
- migrations = migrations.sort_by { |m| m.version }
230
- down? ? migrations.reverse : migrations
231
- end
106
+ def migrate(*args)
107
+ migration.migrate *args
232
108
  end
233
109
 
234
- def pending_migrations
235
- already_migrated = migrated
236
- migrations.reject { |m| already_migrated.include?(m.version.to_i) }
110
+ def announce(*args)
111
+ migration.announce *args
237
112
  end
238
113
 
239
- def migrated
240
- @migrated_versions ||= self.class.get_all_versions
114
+ def write(*args)
115
+ migration.write *args
241
116
  end
242
117
 
243
118
  private
244
119
 
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
120
+ def migration
121
+ @migration ||= load_migration
261
122
  end
262
123
 
263
- def down?
264
- @direction == :down
124
+ def load_migration
125
+ require(File.expand_path(filename))
126
+ eval(name)
265
127
  end
266
128
 
267
129
  end
@@ -0,0 +1,231 @@
1
+ module ActiveColumn
2
+ class IrreversibleMigration < ActiveColumnError
3
+ end
4
+
5
+ class DuplicateMigrationVersionError < ActiveColumnError#:nodoc:
6
+ def initialize(version)
7
+ super("Multiple migrations have the version number #{version}")
8
+ end
9
+ end
10
+
11
+ class DuplicateMigrationNameError < ActiveColumnError#:nodoc:
12
+ def initialize(name)
13
+ super("Multiple migrations have the name #{name}")
14
+ end
15
+ end
16
+
17
+ class UnknownMigrationVersionError < ActiveColumnError #:nodoc:
18
+ def initialize(version)
19
+ super("No migration with version number #{version}")
20
+ end
21
+ end
22
+
23
+ class IllegalMigrationNameError < ActiveColumnError#:nodoc:
24
+ def initialize(name)
25
+ super("Illegal name for migration file: #{name}\n\t(only lower case letters, numbers, and '_' allowed)")
26
+ end
27
+ end
28
+
29
+ class Migrator
30
+
31
+ def self.migrate(migrations_path, target_version = nil)
32
+ case
33
+ when target_version.nil?
34
+ up(migrations_path, target_version)
35
+ when current_version == 0 && target_version == 0
36
+ when current_version > target_version
37
+ down(migrations_path, target_version)
38
+ else
39
+ up(migrations_path, target_version)
40
+ end
41
+ end
42
+
43
+ def self.rollback(migrations_path, steps = 1)
44
+ move(:down, migrations_path, steps)
45
+ end
46
+
47
+ def self.forward(migrations_path, steps = 1)
48
+ move(:up, migrations_path, steps)
49
+ end
50
+
51
+ def self.up(migrations_path, target_version = nil)
52
+ self.new(:up, migrations_path, target_version).migrate
53
+ end
54
+
55
+ def self.down(migrations_path, target_version = nil)
56
+ self.new(:down, migrations_path, target_version).migrate
57
+ end
58
+
59
+ def self.run(direction, migrations_path, target_version)
60
+ self.new(direction, migrations_path, target_version).run
61
+ end
62
+
63
+ def self.migrations_path
64
+ 'ks/migrate'
65
+ end
66
+
67
+ def self.schema_migrations_column_family
68
+ :schema_migrations
69
+ end
70
+
71
+ def self.get_all_versions
72
+ cas = ActiveColumn.connection
73
+ cas.get(schema_migrations_column_family, 'all').map {|(name, _value)| name.to_i}.sort
74
+ end
75
+
76
+ def self.current_version
77
+ sm_cf = schema_migrations_column_family
78
+ if ActiveColumn.column_family_tasks.exists?(sm_cf)
79
+ get_all_versions.max || 0
80
+ else
81
+ 0
82
+ end
83
+ end
84
+
85
+ private
86
+
87
+ def self.move(direction, migrations_path, steps)
88
+ migrator = self.new(direction, migrations_path)
89
+ start_index = migrator.migrations.index(migrator.current_migration)
90
+
91
+ if start_index
92
+ finish = migrator.migrations[start_index + steps]
93
+ version = finish ? finish.version : 0
94
+ send(direction, migrations_path, version)
95
+ end
96
+ end
97
+
98
+ public
99
+
100
+ def initialize(direction, migrations_path, target_version = nil)
101
+ cf_tasks = ActiveColumn.column_family_tasks
102
+ sm_cf = self.class.schema_migrations_column_family
103
+
104
+ unless cf_tasks.exists?(sm_cf)
105
+ cf_tasks.create(sm_cf) do |cf|
106
+ cf.comparator_type = 'LongType'
107
+ end
108
+ end
109
+
110
+ @direction, @migrations_path, @target_version = direction, migrations_path, target_version
111
+ end
112
+
113
+ def current_version
114
+ migrated.last || 0
115
+ end
116
+
117
+ def current_migration
118
+ migrations.detect { |m| m.version == current_version }
119
+ end
120
+
121
+ def run
122
+ target = migrations.detect { |m| m.version == @target_version }
123
+ raise UnknownMigrationVersionError.new(@target_version) if target.nil?
124
+ unless (up? && migrated.include?(target.version.to_i)) || (down? && !migrated.include?(target.version.to_i))
125
+ target.migrate(@direction)
126
+ record_version_state_after_migrating(target)
127
+ end
128
+ end
129
+
130
+ def migrate
131
+ current = migrations.detect { |m| m.version == current_version }
132
+ target = migrations.detect { |m| m.version == @target_version }
133
+
134
+ if target.nil? && !@target_version.nil? && @target_version > 0
135
+ raise UnknownMigrationVersionError.new(@target_version)
136
+ end
137
+
138
+ start = up? ? 0 : (migrations.index(current) || 0)
139
+ finish = migrations.index(target) || migrations.size - 1
140
+ runnable = migrations[start..finish]
141
+
142
+ # skip the last migration if we're headed down, but not ALL the way down
143
+ runnable.pop if down? && !target.nil?
144
+
145
+ runnable.each do |migration|
146
+ #puts "Migrating to #{migration.name} (#{migration.version})"
147
+
148
+ # On our way up, we skip migrating the ones we've already migrated
149
+ next if up? && migrated.include?(migration.version.to_i)
150
+
151
+ # On our way down, we skip reverting the ones we've never migrated
152
+ if down? && !migrated.include?(migration.version.to_i)
153
+ migration.announce 'never migrated, skipping'; migration.write
154
+ next
155
+ end
156
+
157
+ migration.migrate(@direction)
158
+ record_version_state_after_migrating(migration)
159
+ end
160
+ end
161
+
162
+ def migrations
163
+ @migrations ||= begin
164
+ files = Dir["#{@migrations_path}/[0-9]*_*.rb"]
165
+
166
+ migrations = files.inject([]) do |klasses, file|
167
+ version, name = file.scan(/([0-9]+)_([_a-z0-9]*).rb/).first
168
+
169
+ raise IllegalMigrationNameError.new(file) unless version
170
+ version = version.to_i
171
+
172
+ if klasses.detect { |m| m.version == version }
173
+ raise DuplicateMigrationVersionError.new(version)
174
+ end
175
+
176
+ if klasses.detect { |m| m.name == name.camelize }
177
+ raise DuplicateMigrationNameError.new(name.camelize)
178
+ end
179
+
180
+ migration = MigrationProxy.new
181
+ migration.name = to_camel name
182
+ migration.version = version
183
+ migration.filename = file
184
+ klasses << migration
185
+ end
186
+
187
+ migrations = migrations.sort_by { |m| m.version }
188
+ down? ? migrations.reverse : migrations
189
+ end
190
+ end
191
+
192
+ def pending_migrations
193
+ already_migrated = migrated
194
+ migrations.reject { |m| already_migrated.include?(m.version.to_i) }
195
+ end
196
+
197
+ def migrated
198
+ @migrated_versions ||= self.class.get_all_versions
199
+ end
200
+
201
+ private
202
+
203
+ def record_version_state_after_migrating(migration)
204
+ cas = ActiveColumn.connection
205
+ sm_cf = self.class.schema_migrations_column_family
206
+
207
+ @migrated_versions ||= []
208
+ if down?
209
+ @migrated_versions.delete(migration.version)
210
+ cas.remove sm_cf, 'all', migration.version
211
+ else
212
+ @migrated_versions.push(migration.version).sort!
213
+ cas.insert sm_cf, 'all', { migration.version => migration.name }
214
+ end
215
+ end
216
+
217
+ def up?
218
+ @direction == :up
219
+ end
220
+
221
+ def down?
222
+ @direction == :down
223
+ end
224
+
225
+ def to_camel(lower_case_and_underscored_word)
226
+ lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
227
+ end
228
+
229
+ end
230
+
231
+ end