locomotive-mongoid_migration 0.0.5

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 +43 -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 +2 -0
  9. data/lib/mongoid_migration/migration.rb +373 -0
  10. data/lib/mongoid_migration/railtie.rb +10 -0
  11. data/lib/mongoid_migration/version.rb +3 -0
  12. data/lib/tasks/mongoid_migration_tasks.rake +94 -0
  13. data/test/dummy/Rakefile +7 -0
  14. data/test/dummy/app/assets/javascripts/application.js +7 -0
  15. data/test/dummy/app/assets/stylesheets/application.css +7 -0
  16. data/test/dummy/app/controllers/application_controller.rb +3 -0
  17. data/test/dummy/app/helpers/application_helper.rb +2 -0
  18. data/test/dummy/app/models/product.rb +5 -0
  19. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  20. data/test/dummy/config.ru +4 -0
  21. data/test/dummy/config/application.rb +51 -0
  22. data/test/dummy/config/boot.rb +10 -0
  23. data/test/dummy/config/environment.rb +5 -0
  24. data/test/dummy/config/environments/development.rb +30 -0
  25. data/test/dummy/config/environments/production.rb +60 -0
  26. data/test/dummy/config/environments/test.rb +39 -0
  27. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  28. data/test/dummy/config/initializers/inflections.rb +10 -0
  29. data/test/dummy/config/initializers/mime_types.rb +5 -0
  30. data/test/dummy/config/initializers/secret_token.rb +7 -0
  31. data/test/dummy/config/initializers/session_store.rb +8 -0
  32. data/test/dummy/config/initializers/wrap_parameters.rb +10 -0
  33. data/test/dummy/config/locales/en.yml +5 -0
  34. data/test/dummy/config/mongoid.yml +20 -0
  35. data/test/dummy/config/routes.rb +58 -0
  36. data/test/dummy/lib/generators/mongoid_migration/USAGE +8 -0
  37. data/test/dummy/lib/generators/mongoid_migration/migration.rb +11 -0
  38. data/test/dummy/lib/generators/mongoid_migration/mongoid_migration_generator.rb +14 -0
  39. data/test/dummy/lib/generators/mongoid_migration/templates/mongoid_migration.rb +9 -0
  40. data/test/dummy/log/development.log +30 -0
  41. data/test/dummy/log/test.log +5 -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 +187 -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,43 @@
1
+ = MongoidMigration
2
+
3
+ ActiveRecord::Migration ported to Mongoid to run incremental/reversible migrations to manipulate/populate data in the database.
4
+
5
+ == Rails 3.x
6
+
7
+ insert this line to Gemfile:
8
+
9
+ gem "mongoid_migration", "~> 0.0.3"
10
+
11
+ == Rails Generator
12
+
13
+ Run the generator to generate timestamped migration files:
14
+
15
+ rails generate mongoid_migration Thing
16
+
17
+ which generates a timestamped migration file: mongodb/migrate/20111114234935_thing.rb similar to AR.
18
+
19
+ The migration has 2 class methods defined, self.up - executed when migrating up and self.down - executed when migrating down.
20
+ Obviously ActiveRecord::ConnectionAdapters::SchemaStatements methods have not been ported over, so don't attempt to use them. (they make no sense for mongodb anyways)
21
+
22
+ == The following rake tasks are available:
23
+
24
+ * rake db:mongoid:migrate # Migrate the database (options: VERSION=x, VERBOSE=false).
25
+ * rake db:mongoid:migration:down # Runs the "down" for a given migration VERSION.
26
+ * rake db:mongoid:migration:status # Display status of migrations
27
+ * rake db:mongoid:migration:up # Runs the "up" for a given migration VERSION.
28
+ * rake db:mongoid:rollback # Rolls migrations back to the previous version (specify steps w/ STEP=n).
29
+ * rake db:mongoid:version # Retrieves the current schema version number
30
+
31
+ == Contributing to MongoidMigration
32
+
33
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
34
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
35
+ * Fork the project
36
+ * Start a feature/bugfix branch
37
+ * Commit and push until you are happy with your contribution
38
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
39
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
40
+
41
+
42
+ This is the fork of ActiveRecord::Migrations ported to work with mongoid.
43
+ 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,2 @@
1
+ require 'mongoid_migration/migration'
2
+ require 'mongoid_migration/railtie'
@@ -0,0 +1,373 @@
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, :basename, :scope
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 copy(destination, sources, options = {})
75
+ copied = []
76
+ FileUtils.mkdir_p(destination) unless File.exists?(destination)
77
+
78
+ destination_migrations = MongoidMigration::Migrator.new(:up, destination).migrations
79
+ last = destination_migrations.last
80
+ sources.each do |scope, path|
81
+ source_migrations = MongoidMigration::Migrator.new(:up, path).migrations
82
+
83
+ source_migrations.each do |migration|
84
+ source = File.read(migration.filename)
85
+ source = "# This migration comes from #{scope} (originally #{migration.version})\n#{source}"
86
+
87
+ if duplicate = destination_migrations.detect { |m| m.name == migration.name }
88
+ if options[:on_skip] && duplicate.scope != scope.to_s
89
+ options[:on_skip].call(scope, migration)
90
+ end
91
+ next
92
+ end
93
+
94
+ migration.version = next_migration_number(last ? last.version + 1 : 0).to_i
95
+ new_path = File.join(destination, "#{migration.version}_#{migration.name.underscore}.#{scope}.rb")
96
+ old_path, migration.filename = migration.filename, new_path
97
+ last = migration
98
+
99
+ File.open(migration.filename, "w") { |f| f.write source }
100
+ copied << migration
101
+ options[:on_copy].call(scope, migration, old_path) if options[:on_copy]
102
+ destination_migrations << migration
103
+ end
104
+ end
105
+
106
+ copied
107
+ end
108
+
109
+ def next_migration_number(number)
110
+ [Time.now.utc.strftime("%Y%m%d%H%M%S"), "%.14d" % number].max
111
+ end
112
+
113
+ def up_with_benchmarks #:nodoc:
114
+ migrate(:up)
115
+ end
116
+
117
+ def down_with_benchmarks #:nodoc:
118
+ migrate(:down)
119
+ end
120
+
121
+ # Execute this migration in the named direction
122
+ def migrate(direction)
123
+ return unless respond_to?(direction)
124
+
125
+ case direction
126
+ when :up then announce "migrating"
127
+ when :down then announce "reverting"
128
+ end
129
+
130
+ result = nil
131
+ time = Benchmark.measure { result = send("#{direction}_without_benchmarks") }
132
+
133
+ case direction
134
+ when :up then announce "migrated (%.4fs)" % time.real; write
135
+ when :down then announce "reverted (%.4fs)" % time.real; write
136
+ end
137
+
138
+ result
139
+ end
140
+
141
+ # Because the method added may do an alias_method, it can be invoked
142
+ # recursively. We use @ignore_new_methods as a guard to indicate whether
143
+ # it is safe for the call to proceed.
144
+ def singleton_method_added(sym) #:nodoc:
145
+ return if defined?(@ignore_new_methods) && @ignore_new_methods
146
+
147
+ begin
148
+ @ignore_new_methods = true
149
+
150
+ case sym
151
+ when :up, :down
152
+ singleton_class.send(:alias_method_chain, sym, "benchmarks")
153
+ end
154
+ ensure
155
+ @ignore_new_methods = false
156
+ end
157
+ end
158
+
159
+ def write(text="")
160
+ puts(text) if verbose
161
+ end
162
+
163
+ def announce(message)
164
+ version = defined?(@version) ? @version : nil
165
+
166
+ text = "#{version} #{name}: #{message}"
167
+ length = [0, 75 - text.length].max
168
+ write "== %s %s" % [text, "=" * length]
169
+ end
170
+
171
+ def say(message, subitem=false)
172
+ write "#{subitem ? " ->" : "--"} #{message}"
173
+ end
174
+
175
+ def say_with_time(message)
176
+ say(message)
177
+ result = nil
178
+ time = Benchmark.measure { result = yield }
179
+ say "%.4fs" % time.real, :subitem
180
+ say("#{result} rows", :subitem) if result.is_a?(Integer)
181
+ result
182
+ end
183
+
184
+ def suppress_messages
185
+ save, self.verbose = verbose, false
186
+ yield
187
+ ensure
188
+ self.verbose = save
189
+ end
190
+ end
191
+
192
+ end
193
+
194
+ class Migrator#:nodoc:
195
+ class << self
196
+ def migrate(migrations_path, target_version = nil)
197
+ case
198
+ when target_version.nil?
199
+ up(migrations_path, target_version)
200
+ when current_version == 0 && target_version == 0
201
+ when current_version > target_version
202
+ down(migrations_path, target_version)
203
+ else
204
+ up(migrations_path, target_version)
205
+ end
206
+ end
207
+
208
+ def rollback(migrations_path, steps=1)
209
+ move(:down, migrations_path, steps)
210
+ end
211
+
212
+ def forward(migrations_path, steps=1)
213
+ move(:up, migrations_path, steps)
214
+ end
215
+
216
+ def up(migrations_path, target_version = nil)
217
+ self.new(:up, migrations_path, target_version).migrate
218
+ end
219
+
220
+ def down(migrations_path, target_version = nil)
221
+ self.new(:down, migrations_path, target_version).migrate
222
+ end
223
+
224
+ def run(direction, migrations_path, target_version)
225
+ self.new(direction, migrations_path, target_version).run
226
+ end
227
+
228
+ def migrations_path
229
+ 'mongodb/migrate'
230
+ end
231
+
232
+ def get_all_versions
233
+ Migration.all.map {|e| e.version.to_i}.sort
234
+ end
235
+
236
+ def current_version
237
+ get_all_versions.max || 0
238
+ end
239
+
240
+ private
241
+
242
+ def move(direction, migrations_path, steps)
243
+ migrator = self.new(direction, migrations_path)
244
+ start_index = migrator.migrations.index(migrator.current_migration)
245
+
246
+ if start_index
247
+ finish = migrator.migrations[start_index + steps]
248
+ version = finish ? finish.version : 0
249
+ send(direction, migrations_path, version)
250
+ end
251
+ end
252
+ end
253
+
254
+ def initialize(direction, migrations_path, target_version = nil)
255
+ @direction, @migrations_path, @target_version = direction, migrations_path, target_version
256
+ end
257
+
258
+ def current_version
259
+ migrated.last || 0
260
+ end
261
+
262
+ def current_migration
263
+ migrations.detect { |m| m.version == current_version }
264
+ end
265
+
266
+ def run
267
+ target = migrations.detect { |m| m.version == @target_version }
268
+ raise UnknownMigrationVersionError.new(@target_version) if target.nil?
269
+ unless (up? && migrated.include?(target.version.to_i)) || (down? && !migrated.include?(target.version.to_i))
270
+ target.migrate(@direction)
271
+ record_version_state_after_migrating(target.version)
272
+ end
273
+ end
274
+
275
+ def migrate
276
+ current = migrations.detect { |m| m.version == current_version }
277
+ target = migrations.detect { |m| m.version == @target_version }
278
+
279
+ if target.nil? && !@target_version.nil? && @target_version > 0
280
+ raise UnknownMigrationVersionError.new(@target_version)
281
+ end
282
+
283
+ start = up? ? 0 : (migrations.index(current) || 0)
284
+ finish = migrations.index(target) || migrations.size - 1
285
+ runnable = migrations[start..finish]
286
+
287
+ # skip the last migration if we're headed down, but not ALL the way down
288
+ runnable.pop if down? && !target.nil?
289
+
290
+ runnable.each do |migration|
291
+ Rails.logger.info "Migrating to #{migration.name} (#{migration.version})" if Rails.logger
292
+
293
+ # On our way up, we skip migrating the ones we've already migrated
294
+ next if up? && migrated.include?(migration.version.to_i)
295
+
296
+ # On our way down, we skip reverting the ones we've never migrated
297
+ if down? && !migrated.include?(migration.version.to_i)
298
+ migration.announce 'never migrated, skipping'; migration.write
299
+ next
300
+ end
301
+
302
+ begin
303
+ migration.migrate(@direction)
304
+ record_version_state_after_migrating(migration.version)
305
+ rescue => e
306
+ raise StandardError, "An error has occurred, all later migrations canceled:\n\n#{e}", e.backtrace
307
+ end
308
+ end
309
+ end
310
+
311
+ def migrations
312
+ @migrations ||= begin
313
+ files = Dir["#{@migrations_path}/[0-9]*_*.rb"]
314
+
315
+ migrations = files.inject([]) do |klasses, file|
316
+ version, name, scope = file.scan(/([0-9]+)_([_a-z0-9]*)\.?([a-z]*).rb/).first
317
+
318
+ raise IllegalMigrationNameError.new(file) unless version
319
+ version = version.to_i
320
+
321
+ if klasses.detect { |m| m.version == version }
322
+ raise DuplicateMigrationVersionError.new(version)
323
+ end
324
+
325
+ if klasses.detect { |m| m.name == name.camelize }
326
+ raise DuplicateMigrationNameError.new(name.camelize)
327
+ end
328
+
329
+ migration = MigrationProxy.new
330
+ migration.basename = File.basename(file)
331
+ migration.name = name.camelize
332
+ migration.version = version
333
+ migration.filename = file
334
+ migration.scope = scope unless scope.blank?
335
+ klasses << migration
336
+ end
337
+
338
+ migrations = migrations.sort_by { |m| m.version }
339
+ down? ? migrations.reverse : migrations
340
+ end
341
+ end
342
+
343
+ def pending_migrations
344
+ already_migrated = migrated
345
+ migrations.reject { |m| already_migrated.include?(m.version.to_i) }
346
+ end
347
+
348
+ def migrated
349
+ @migrated_versions ||= self.class.get_all_versions
350
+ end
351
+
352
+ private
353
+ def record_version_state_after_migrating(version)
354
+ @migrated_versions ||= []
355
+ if down?
356
+ @migrated_versions.delete(version)
357
+ Migration.where(version: version.to_s).delete
358
+ else
359
+ @migrated_versions.push(version).sort!
360
+ Migration.create "version" => version.to_s
361
+ end
362
+ end
363
+
364
+ def up?
365
+ @direction == :up
366
+ end
367
+
368
+ def down?
369
+ @direction == :down
370
+ end
371
+
372
+ end
373
+ end