mongoid_migration 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.rdoc +26 -0
  3. data/Rakefile +37 -0
  4. data/lib/generators/mongoid_migration/USAGE +8 -0
  5. data/lib/generators/mongoid_migration/migration.rb +11 -0
  6. data/lib/generators/mongoid_migration/mongoid_migration_generator.rb +14 -0
  7. data/lib/generators/mongoid_migration/templates/mongoid_migration.rb +9 -0
  8. data/lib/mongoid_migration.rb +1 -0
  9. data/lib/mongoid_migration/migration.rb +332 -0
  10. data/lib/mongoid_migration/version.rb +3 -0
  11. data/lib/tasks/mongoid_migration_tasks.rake +68 -0
  12. data/test/dummy/Rakefile +7 -0
  13. data/test/dummy/app/assets/javascripts/application.js +7 -0
  14. data/test/dummy/app/assets/stylesheets/application.css +7 -0
  15. data/test/dummy/app/controllers/application_controller.rb +3 -0
  16. data/test/dummy/app/helpers/application_helper.rb +2 -0
  17. data/test/dummy/app/models/product.rb +5 -0
  18. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  19. data/test/dummy/config.ru +4 -0
  20. data/test/dummy/config/application.rb +51 -0
  21. data/test/dummy/config/boot.rb +10 -0
  22. data/test/dummy/config/environment.rb +5 -0
  23. data/test/dummy/config/environments/development.rb +30 -0
  24. data/test/dummy/config/environments/production.rb +60 -0
  25. data/test/dummy/config/environments/test.rb +39 -0
  26. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  27. data/test/dummy/config/initializers/inflections.rb +10 -0
  28. data/test/dummy/config/initializers/mime_types.rb +5 -0
  29. data/test/dummy/config/initializers/secret_token.rb +7 -0
  30. data/test/dummy/config/initializers/session_store.rb +8 -0
  31. data/test/dummy/config/initializers/wrap_parameters.rb +10 -0
  32. data/test/dummy/config/locales/en.yml +5 -0
  33. data/test/dummy/config/mongoid.yml +20 -0
  34. data/test/dummy/config/routes.rb +58 -0
  35. data/test/dummy/lib/generators/mongoid_migration/USAGE +8 -0
  36. data/test/dummy/lib/generators/mongoid_migration/migration.rb +11 -0
  37. data/test/dummy/lib/generators/mongoid_migration/mongoid_migration_generator.rb +14 -0
  38. data/test/dummy/lib/generators/mongoid_migration/templates/mongoid_migration.rb +9 -0
  39. data/test/dummy/lib/tasks/mongoid_migration_tasks.rake +68 -0
  40. data/test/dummy/log/development.log +233 -0
  41. data/test/dummy/log/test.log +4 -0
  42. data/test/dummy/mongodb/migrate/20111114234935_yo.rb +9 -0
  43. data/test/dummy/mongodb/migrate/20111116172729_foo.rb +9 -0
  44. data/test/dummy/public/404.html +26 -0
  45. data/test/dummy/public/422.html +26 -0
  46. data/test/dummy/public/500.html +26 -0
  47. data/test/dummy/public/favicon.ico +0 -0
  48. data/test/dummy/script/rails +6 -0
  49. data/test/mongoid_migration_test.rb +7 -0
  50. data/test/test_helper.rb +10 -0
  51. metadata +184 -0
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2011 YOURNAME
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,26 @@
1
+ = MongoidMigration
2
+
3
+ ActiveRecord::Migration ported to Mongoid to run incremental/reversible migrations to manipulate/populate data in the database.
4
+
5
+ == Rails Generator
6
+
7
+ Run the generator to generate timestamped migration files:
8
+
9
+ rails generate mongoid_migration Thing
10
+
11
+ which generates a timestamped migration file: mongodb/migrate/20111114234935_thing.rb similar to AR.
12
+
13
+ The migration has 2 class methods defined, self.up - executed when migrating up and self.down - executed when migrating down.
14
+ Obviously ActiveRecord::ConnectionAdapters::SchemaStatements methods have not been ported over, so don't attempt to use them. (they make no sense for mongodb anyways)
15
+
16
+ == The following rake tasks are available:
17
+
18
+ * rake db:mongoid:migrate # Migrate the database (options: VERSION=x, VERBOSE=false).
19
+ * rake db:mongoid:migration:down # Runs the "down" for a given migration VERSION.
20
+ * rake db:mongoid:migration:status # Display status of migrations
21
+ * rake db:mongoid:migration:up # Runs the "up" for a given migration VERSION.
22
+ * rake db:mongoid:rollback # Rolls migrations back to the previous version (specify steps w/ STEP=n).
23
+ * rake db:mongoid:version # Retrieves the current schema version number
24
+
25
+ This is the fork of ActiveRecord::Migrations ported to work with mongoid.
26
+ All thanks should go to the Rails team.
data/Rakefile ADDED
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+ begin
8
+ require 'rdoc/task'
9
+ rescue LoadError
10
+ require 'rdoc/rdoc'
11
+ require 'rake/rdoctask'
12
+ RDoc::Task = Rake::RDocTask
13
+ end
14
+
15
+ RDoc::Task.new(:rdoc) do |rdoc|
16
+ rdoc.rdoc_dir = 'rdoc'
17
+ rdoc.title = 'MongoidMigration'
18
+ rdoc.options << '--line-numbers'
19
+ rdoc.rdoc_files.include('README.rdoc')
20
+ rdoc.rdoc_files.include('lib/**/*.rb')
21
+ end
22
+
23
+
24
+
25
+ Bundler::GemHelper.install_tasks
26
+
27
+ require 'rake/testtask'
28
+
29
+ Rake::TestTask.new(:test) do |t|
30
+ t.libs << 'lib'
31
+ t.libs << 'test'
32
+ t.pattern = 'test/**/*_test.rb'
33
+ t.verbose = false
34
+ end
35
+
36
+
37
+ task :default => :test
@@ -0,0 +1,8 @@
1
+ Description:
2
+ Mongoid migration generator generates 'migration' files for mongoid.
3
+
4
+ Example:
5
+ rails generate mongoid_migration Thing
6
+
7
+ This will create migration file:
8
+ mongodb/migrate/20111114234935_thing.rb
@@ -0,0 +1,11 @@
1
+ module MongoidMigration
2
+ module Generators
3
+ module Migration
4
+ # Implement the required interface for Rails::Generators::Migration.
5
+ def next_migration_number(dirname) #:nodoc:
6
+ next_migration_number = current_migration_number(dirname) + 1
7
+ [Time.now.utc.strftime("%Y%m%d%H%M%S"), "%.14d" % next_migration_number].max
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,14 @@
1
+ require 'generators/mongoid_migration/migration'
2
+
3
+ class MongoidMigrationGenerator < Rails::Generators::NamedBase
4
+
5
+ include Rails::Generators::Migration
6
+ extend MongoidMigration::Generators::Migration
7
+
8
+ source_root File.expand_path("../templates", __FILE__)
9
+
10
+ def create_migration_file
11
+ migration_template "mongoid_migration.rb", "mongodb/migrate/#{file_name}.rb"
12
+ end
13
+
14
+ end
@@ -0,0 +1,9 @@
1
+ class <%= migration_class_name %> < MongoidMigration::Migration
2
+ def self.up
3
+
4
+ end
5
+
6
+ def self.down
7
+
8
+ end
9
+ end
@@ -0,0 +1 @@
1
+ require 'mongoid_migration/migration'
@@ -0,0 +1,332 @@
1
+ require 'active_support/core_ext/kernel/singleton_class'
2
+ require 'active_support/core_ext/module/aliasing'
3
+ require 'active_support/core_ext/module/delegation'
4
+ require 'mongoid'
5
+
6
+ module MongoidMigration
7
+ class MongoidMigrationError < StandardError
8
+ end
9
+
10
+ # Exception that can be raised to stop migrations from going backwards.
11
+ class IrreversibleMigration < MongoidMigrationError
12
+ end
13
+
14
+ class DuplicateMigrationVersionError < MongoidMigrationError#:nodoc:
15
+ def initialize(version)
16
+ super("Multiple migrations have the version number #{version}")
17
+ end
18
+ end
19
+
20
+ class DuplicateMigrationNameError < MongoidMigrationError#:nodoc:
21
+ def initialize(name)
22
+ super("Multiple migrations have the name #{name}")
23
+ end
24
+ end
25
+
26
+ class UnknownMigrationVersionError < MongoidMigrationError #:nodoc:
27
+ def initialize(version)
28
+ super("No migration with version number #{version}")
29
+ end
30
+ end
31
+
32
+ class IllegalMigrationNameError < MongoidMigrationError#:nodoc:
33
+ def initialize(name)
34
+ super("Illegal name for migration file: #{name}\n\t(only lower case letters, numbers, and '_' allowed)")
35
+ end
36
+ end
37
+
38
+ # MigrationProxy is used to defer loading of the actual migration classes
39
+ # until they are needed
40
+ class MigrationProxy
41
+
42
+ attr_accessor :name, :version, :filename
43
+
44
+ delegate :migrate, :announce, :write, :to=>:migration
45
+
46
+ private
47
+
48
+ def migration
49
+ @migration ||= load_migration
50
+ end
51
+
52
+ def load_migration
53
+ require(File.expand_path(filename))
54
+ name.constantize
55
+ end
56
+ end
57
+
58
+ class Migration
59
+
60
+ include Mongoid::Document
61
+ include Mongoid::Timestamps
62
+
63
+ validates_presence_of :version
64
+ validates_uniqueness_of :version
65
+
66
+ field :version
67
+
68
+ index :version
69
+
70
+ @@verbose = true
71
+ cattr_accessor :verbose
72
+
73
+ class << self
74
+ def up_with_benchmarks #:nodoc:
75
+ migrate(:up)
76
+ end
77
+
78
+ def down_with_benchmarks #:nodoc:
79
+ migrate(:down)
80
+ end
81
+
82
+ # Execute this migration in the named direction
83
+ def migrate(direction)
84
+ return unless respond_to?(direction)
85
+
86
+ case direction
87
+ when :up then announce "migrating"
88
+ when :down then announce "reverting"
89
+ end
90
+
91
+ result = nil
92
+ time = Benchmark.measure { result = send("#{direction}_without_benchmarks") }
93
+
94
+ case direction
95
+ when :up then announce "migrated (%.4fs)" % time.real; write
96
+ when :down then announce "reverted (%.4fs)" % time.real; write
97
+ end
98
+
99
+ result
100
+ end
101
+
102
+ # Because the method added may do an alias_method, it can be invoked
103
+ # recursively. We use @ignore_new_methods as a guard to indicate whether
104
+ # it is safe for the call to proceed.
105
+ def singleton_method_added(sym) #:nodoc:
106
+ return if defined?(@ignore_new_methods) && @ignore_new_methods
107
+
108
+ begin
109
+ @ignore_new_methods = true
110
+
111
+ case sym
112
+ when :up, :down
113
+ singleton_class.send(:alias_method_chain, sym, "benchmarks")
114
+ end
115
+ ensure
116
+ @ignore_new_methods = false
117
+ end
118
+ end
119
+
120
+ def write(text="")
121
+ puts(text) if verbose
122
+ end
123
+
124
+ def announce(message)
125
+ version = defined?(@version) ? @version : nil
126
+
127
+ text = "#{version} #{name}: #{message}"
128
+ length = [0, 75 - text.length].max
129
+ write "== %s %s" % [text, "=" * length]
130
+ end
131
+
132
+ def say(message, subitem=false)
133
+ write "#{subitem ? " ->" : "--"} #{message}"
134
+ end
135
+
136
+ def say_with_time(message)
137
+ say(message)
138
+ result = nil
139
+ time = Benchmark.measure { result = yield }
140
+ say "%.4fs" % time.real, :subitem
141
+ say("#{result} rows", :subitem) if result.is_a?(Integer)
142
+ result
143
+ end
144
+
145
+ def suppress_messages
146
+ save, self.verbose = verbose, false
147
+ yield
148
+ ensure
149
+ self.verbose = save
150
+ end
151
+ end
152
+
153
+ end
154
+
155
+ class Migrator#:nodoc:
156
+ class << self
157
+ def migrate(migrations_path, target_version = nil)
158
+ case
159
+ when target_version.nil?
160
+ up(migrations_path, target_version)
161
+ when current_version == 0 && target_version == 0
162
+ when current_version > target_version
163
+ down(migrations_path, target_version)
164
+ else
165
+ up(migrations_path, target_version)
166
+ end
167
+ end
168
+
169
+ def rollback(migrations_path, steps=1)
170
+ move(:down, migrations_path, steps)
171
+ end
172
+
173
+ def forward(migrations_path, steps=1)
174
+ move(:up, migrations_path, steps)
175
+ end
176
+
177
+ def up(migrations_path, target_version = nil)
178
+ self.new(:up, migrations_path, target_version).migrate
179
+ end
180
+
181
+ def down(migrations_path, target_version = nil)
182
+ self.new(:down, migrations_path, target_version).migrate
183
+ end
184
+
185
+ def run(direction, migrations_path, target_version)
186
+ self.new(direction, migrations_path, target_version).run
187
+ end
188
+
189
+ def migrations_path
190
+ 'mongodb/migrate'
191
+ end
192
+
193
+ def get_all_versions
194
+ Migration.all.map {|e| e.version.to_i}.sort
195
+ end
196
+
197
+ def current_version
198
+ get_all_versions.max || 0
199
+ end
200
+
201
+ private
202
+
203
+ def move(direction, migrations_path, steps)
204
+ migrator = self.new(direction, migrations_path)
205
+ start_index = migrator.migrations.index(migrator.current_migration)
206
+
207
+ if start_index
208
+ finish = migrator.migrations[start_index + steps]
209
+ version = finish ? finish.version : 0
210
+ send(direction, migrations_path, version)
211
+ end
212
+ end
213
+ end
214
+
215
+ def initialize(direction, migrations_path, target_version = nil)
216
+ @direction, @migrations_path, @target_version = direction, migrations_path, target_version
217
+ end
218
+
219
+ def current_version
220
+ migrated.last || 0
221
+ end
222
+
223
+ def current_migration
224
+ migrations.detect { |m| m.version == current_version }
225
+ end
226
+
227
+ def run
228
+ target = migrations.detect { |m| m.version == @target_version }
229
+ raise UnknownMigrationVersionError.new(@target_version) if target.nil?
230
+ unless (up? && migrated.include?(target.version.to_i)) || (down? && !migrated.include?(target.version.to_i))
231
+ target.migrate(@direction)
232
+ record_version_state_after_migrating(target.version)
233
+ end
234
+ end
235
+
236
+ def migrate
237
+ current = migrations.detect { |m| m.version == current_version }
238
+ target = migrations.detect { |m| m.version == @target_version }
239
+
240
+ if target.nil? && !@target_version.nil? && @target_version > 0
241
+ raise UnknownMigrationVersionError.new(@target_version)
242
+ end
243
+
244
+ start = up? ? 0 : (migrations.index(current) || 0)
245
+ finish = migrations.index(target) || migrations.size - 1
246
+ runnable = migrations[start..finish]
247
+
248
+ # skip the last migration if we're headed down, but not ALL the way down
249
+ runnable.pop if down? && !target.nil?
250
+
251
+ runnable.each do |migration|
252
+ Rails.logger.info "Migrating to #{migration.name} (#{migration.version})" if Rails.logger
253
+
254
+ # On our way up, we skip migrating the ones we've already migrated
255
+ next if up? && migrated.include?(migration.version.to_i)
256
+
257
+ # On our way down, we skip reverting the ones we've never migrated
258
+ if down? && !migrated.include?(migration.version.to_i)
259
+ migration.announce 'never migrated, skipping'; migration.write
260
+ next
261
+ end
262
+
263
+ begin
264
+ migration.migrate(@direction)
265
+ record_version_state_after_migrating(migration.version)
266
+ rescue => e
267
+ raise StandardError, "An error has occurred, all later migrations canceled:\n\n#{e}", e.backtrace
268
+ end
269
+ end
270
+ end
271
+
272
+ def migrations
273
+ @migrations ||= begin
274
+ files = Dir["#{@migrations_path}/[0-9]*_*.rb"]
275
+
276
+ migrations = files.inject([]) do |klasses, file|
277
+ version, name = file.scan(/([0-9]+)_([_a-z0-9]*).rb/).first
278
+
279
+ raise IllegalMigrationNameError.new(file) unless version
280
+ version = version.to_i
281
+
282
+ if klasses.detect { |m| m.version == version }
283
+ raise DuplicateMigrationVersionError.new(version)
284
+ end
285
+
286
+ if klasses.detect { |m| m.name == name.camelize }
287
+ raise DuplicateMigrationNameError.new(name.camelize)
288
+ end
289
+
290
+ migration = MigrationProxy.new
291
+ migration.name = name.camelize
292
+ migration.version = version
293
+ migration.filename = file
294
+ klasses << migration
295
+ end
296
+
297
+ migrations = migrations.sort_by { |m| m.version }
298
+ down? ? migrations.reverse : migrations
299
+ end
300
+ end
301
+
302
+ def pending_migrations
303
+ already_migrated = migrated
304
+ migrations.reject { |m| already_migrated.include?(m.version.to_i) }
305
+ end
306
+
307
+ def migrated
308
+ @migrated_versions ||= self.class.get_all_versions
309
+ end
310
+
311
+ private
312
+ def record_version_state_after_migrating(version)
313
+ @migrated_versions ||= []
314
+ if down?
315
+ @migrated_versions.delete(version)
316
+ Migration.where(version: version.to_s).delete
317
+ else
318
+ @migrated_versions.push(version).sort!
319
+ Migration.create "version" => version.to_s
320
+ end
321
+ end
322
+
323
+ def up?
324
+ @direction == :up
325
+ end
326
+
327
+ def down?
328
+ @direction == :down
329
+ end
330
+
331
+ end
332
+ end