exodus 1.0.0 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.rspec ADDED
@@ -0,0 +1,4 @@
1
+ --color
2
+ --format documentation
3
+ --default_path spec/
4
+ --pattern **/*.rb
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use ruby-1.9.3-p194@exodus
data/.travis.yml ADDED
@@ -0,0 +1,14 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.8.7
4
+ - 1.9.2
5
+ - 1.9.3
6
+ - rbx-19mode
7
+ branches:
8
+ only:
9
+ - master
10
+ notifications:
11
+ email:
12
+ recipients:
13
+ - thomas.dmytryk@supinfo.com
14
+ script: bundle exec rspec spec
data/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ ## v1.0.1
2
+
3
+ * Small refactoring
4
+
5
+ ## v1.0.0
6
+
7
+ * Initial release
data/README.md CHANGED
@@ -3,15 +3,15 @@ Exodus - a migration framework for MongoDb
3
3
 
4
4
  # Intro
5
5
 
6
- ## A migration Framework for a schemaless database ???
6
+ ## A migration Framework for a schemaless database ??
7
7
 
8
- After working with Mongo for long time now I can tell you it doesn't mean you will never need any migrations. Within the same collection Mongo allows to have documents with a complete different structure, however in some case is you might want to keep data consistency in your collections; Especially when your code is live in production and used by millions of users.
8
+ After working with Mongo for long time now I can tell you working with a schemaless database does not mean you will never need any migrations. Within the same collection Mongo allows to have documents with a complete different structure, however in some case is you might want to keep data consistency; Especially when your code is live in production and used by millions of users.
9
9
 
10
10
  There is a plenty of way to modify documents data structure and after a deep reflexion I realized it makes more sens to use migration framework. A migration framework provides a lot of advantages, such as:
11
11
 
12
12
  * It allows you to know at any time which migration has been ran on any given system
13
13
  * It's Auto runnable on deploy
14
- * When switching enviromment (dev, pre-prod, prod) you don't need to worry if the script has been ran or not. The framework takes care of it for you
14
+ * When switching enviromment (dev, pre-prod, prod) you don't need to worry if the script has been ran or not. The framework takes care of it for you
15
15
 
16
16
 
17
17
  # Installation
data/exodus.gemspec CHANGED
@@ -4,7 +4,7 @@ require File.expand_path('../lib/exodus/version', __FILE__)
4
4
  Gem::Specification.new do |gem|
5
5
  gem.authors = ['Thomas Dmytryk']
6
6
  gem.email = ['thomas@fanhattan.com', 'thomas.dmytryk@supinfo.com']
7
- gem.description = %q{Exodus is a migration framework for Mongo}
7
+ gem.description = %q{Exodus is a migration framework for MongoDb}
8
8
  gem.summary = %q{Exodus uses mongomapper to provide a complete migration framework}
9
9
  gem.homepage = ''
10
10
  gem.license = 'MIT'
data/lib/exodus.rb CHANGED
@@ -1,5 +1,8 @@
1
1
  require 'mongo_mapper'
2
- Dir[File.dirname(__FILE__) + "/exodus/**/*.rb"].sort.each { |file| require file}
2
+ require File.dirname(__FILE__) + '/exodus/config/migration_info'
3
+ require File.dirname(__FILE__) + '/exodus/migrations/migration'
4
+ require File.dirname(__FILE__) + '/exodus/migrations/migration_error'
5
+ require File.dirname(__FILE__) + '/exodus/migrations/migration_status'
3
6
 
4
7
  module Exodus
5
8
  class << self
@@ -13,39 +16,43 @@ module Exodus
13
16
  yield(configuration) if block_given?
14
17
  end
15
18
 
16
- # Loads and runs a number of migrations equal to step (or all of them if step is nil)
19
+ # Executes a number of migrations equal to step (or all of them if step is nil)
17
20
  def run_migrations(direction, migrations, step = nil)
18
21
  if migrations
19
- sorted_migrations = sort_migrations(migrations)
20
22
  sorted_migrations = order_with_direction(sorted_migrations, direction)
21
23
  sorted_migrations = sorted_migrations.shift(step.to_i) if step
22
24
 
23
- sorted_migrations.each {|migration_class, args| run_each(migration_class, direction, args)}
25
+ run_each(sorted_migrations)
24
26
  else
25
27
  puts "no migrations given in argument!"
26
28
  end
27
29
  end
28
30
 
29
- def sort_migrations(migrations)
30
- migrations.sort_by {|migration,args| migration.migration_number }
31
+ # Migrations order need to be reverted if the direction is down
32
+ # (we want the latest executed migration to be the first reverted)
33
+ def order_with_direction(migrations, direction)
34
+ sorted_migrations = sort_migrations(migrations)
35
+ direction == Migration::UP ? sorted_migrations : sorted_migrations.reverse
31
36
  end
32
37
 
33
- def order_with_direction(migrations, direction)
34
- direction == Migration::UP ? migrations : migrations.reverse
38
+ def sort_migrations(migrations)
39
+ migrations.sort_by {|migration,args| migration.migration_number }
35
40
  end
36
41
 
37
- def run_each(migration_class, direction, args = {})
38
- puts "\n"
39
- args ||= {}
40
-
41
- run_one_migration(migration_class, direction, args)
42
- puts "\n"
42
+ # Runs each migration separately, migration's arguments default value is set to an empty hash
43
+ def run_each(direction, migrations)
44
+ migrations.each do |migration_class, args|
45
+ print_tabulation { run_one_migration(migration_class, direction, args || {}) }
46
+ end
43
47
  end
44
48
 
49
+ # Looks up in the database if a migration with the same class and same arguments already exists
50
+ # Otherwise instanciate a new one
51
+ # Runs the migration if it is runnable
45
52
  def run_one_migration(migration_class, direction, args)
46
53
  # Going throught MRD because MM request returns nil for some reason
47
54
  current_migration = migration_class.load(migration_class.collection.find('status.arguments' => args).first)
48
- current_migration ||= migration_class.new(status: {arguments: args})
55
+ current_migration ||= migration_class.new(:status => {:arguments => args})
49
56
 
50
57
  if current_migration.is_runnable?(direction)
51
58
  # Make sure we save all info in case of a failure
@@ -58,11 +65,19 @@ module Exodus
58
65
  raise
59
66
  end
60
67
 
61
- # save the migration
62
68
  current_migration.save!
63
69
  else
64
70
  puts "#{current_migration.class}#{current_migration.status.arguments}(#{direction}) as Already been run (or is not runnable)."
65
71
  end
66
72
  end
73
+
74
+ private
75
+
76
+ # Prints tabulation before execting a given block
77
+ def print_tabulation
78
+ puts "\n"
79
+ yield if block_given?
80
+ puts "\n"
81
+ end
67
82
  end
68
83
  end
@@ -0,0 +1,59 @@
1
+ module Exodus
2
+ class MigrationInfo
3
+ attr_accessor :info
4
+ attr_reader :config_file, :db, :connection
5
+
6
+ def initialize(file = nil)
7
+ config_file = file if file
8
+ end
9
+
10
+ def db=(database)
11
+ MongoMapper.database = database
12
+ end
13
+
14
+ def connection=(conn)
15
+ MongoMapper.connection = conn
16
+ end
17
+
18
+ def config_file=(file)
19
+ if File.exists?(file)
20
+ @config_file = file
21
+ @info = YAML.load_file(file)
22
+ else
23
+ raise ArgumentError, "#{file} not found"
24
+ end
25
+ end
26
+
27
+ def migrate
28
+ verify_yml_syntax { @info['migration']['migrate'] }
29
+ end
30
+
31
+ def rollback
32
+ verify_yml_syntax { @info['migration']['rollback'] }
33
+ end
34
+
35
+ def migrate_custom
36
+ verify_yml_syntax { @info['migration']['custom']['migrate'] }
37
+ end
38
+
39
+ def rollback_custom
40
+ verify_yml_syntax { @info['migration']['custom']['rollback'] }
41
+ end
42
+
43
+ def to_s
44
+ @info
45
+ end
46
+
47
+ private
48
+
49
+ def verify_yml_syntax
50
+ Raise StandardError, "No configuration file specified" unless self.config_file
51
+
52
+ begin
53
+ yield if block_given?
54
+ rescue
55
+ Raise StandardError, "Syntax error detected in config file #{self.config_file}. To find the good syntax take a look at the documentation."
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,165 @@
1
+ module Exodus
2
+ class Migration
3
+ include MongoMapper::Document
4
+ UP = 'up'
5
+ DOWN = 'down'
6
+ @migrations_with_args = []
7
+
8
+ timestamps!
9
+
10
+ key :description, String
11
+ key :status_complete, Integer, :default => 1
12
+ key :rerunnable_safe, Boolean, :default => false # Be careful if the job is rerunnable_safe he will re-run on each db:migrate
13
+
14
+ has_one :status, :class_name => "Exodus::MigrationStatus", :autosave => true
15
+
16
+ class << self
17
+ attr_accessor :migration_number
18
+
19
+ # Overides #inherited to have an easy and reliable way to find all migrations
20
+ # Migrations need to have embedded callbacks on depending on the MM's version
21
+ def inherited(klass)
22
+ klass.embedded_callbacks_on if defined?(MongoMapper::Plugins::EmbeddedCallbacks::ClassMethods) #MongoMapper version compatibility
23
+ klass.migration_number = 0
24
+ @migrations_with_args << [klass]
25
+ super(klass)
26
+ end
27
+
28
+ # Using a list of migrations
29
+ # Formats and overrides migrations without arguments using ones that have given arguments
30
+ # Removes duplicates
31
+ # migrations: list of migrations => [[MyMigration, {:my_args => 'some_args'}]]
32
+ def load_all(migrations)
33
+ if migrations
34
+ migrations.each do |migration, args|
35
+ if migration && args
36
+ formated_migration = format(migration, args)
37
+ migration, args = formated_migration
38
+
39
+ unless @migrations_with_args.include?(formated_migration)
40
+ @migrations_with_args.delete_if {|loaded_migration, loaded_args| migration == loaded_migration && (loaded_args.nil? || loaded_args.empty?) }
41
+ @migrations_with_args << formated_migration
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ @migrations_with_args
48
+ end
49
+
50
+ # Using a list of migrations formats them and removes duplicates
51
+ # migrations: list of migrations => [[MyMigration, {:my_args => 'some_args'}]]
52
+ def load_custom(migrations)
53
+ migrations.map {|migration_str, args| format(migration_str, args) }.uniq
54
+ end
55
+
56
+ # Formats a given migration making sure the first argument is a class
57
+ # and the second one -if it exists- is a none empty hash
58
+ def format(migration, args = {})
59
+ migration_klass = migration.is_a?(String) ? migration.constantize : migration
60
+ args.is_a?(Hash) && args.empty? ? [migration_klass] : [migration_klass, args]
61
+ end
62
+
63
+ # Prints in the console all migrations class with their name and description
64
+ def list
65
+ puts "\n Migration n#: \t\t Name: \t\t\t\t Description:"
66
+ puts '-' * 100, "\n"
67
+
68
+ @migrations_with_args.map do|migration, args|
69
+ m = migration.new
70
+ puts "\t#{migration.migration_number} \t\t #{migration.name} \t\t #{m.description}"
71
+ end
72
+
73
+ puts "\n\n"
74
+ end
75
+
76
+ # Prints in the console all migrations that has been ran at least once with their name and description
77
+ def db_status
78
+ puts "\n Migration n#: \t Name: \t\t Direction: Arguments: Current Status: \t Last completion Date: \t\t Current Message:"
79
+ puts '-' * 175, "\n"
80
+
81
+ Migration.all.each do|migration|
82
+ puts "\t#{migration.class.migration_number} \t #{migration.class.name} \t #{migration.status.to_string}"
83
+ end
84
+
85
+ puts "\n\n"
86
+ end
87
+ end
88
+
89
+ # Makes sure status get instanciated on migration's instanciation
90
+ def initialize(args = {})
91
+ self.build_status(args[:status])
92
+ super(args)
93
+ end
94
+
95
+ # Runs the migration following the direction
96
+ # sets the status, the execution time and the last succesful_completion date
97
+ def run(direction)
98
+ self.status.direction = direction
99
+
100
+ # reset the status if the job is rerunnable and has already be completed
101
+ self.status = self.status.reset if self.rerunnable_safe && completed?(direction)
102
+ self.status.execution_time = time_it { self.send(direction) }
103
+ self.status.last_succesful_completion = Time.now
104
+ end
105
+
106
+ # Sets an error to migration status
107
+ def failure=(exception)
108
+ self.status.error = MigrationError.new(:error_message => exception.message, :error_class => exception.class, :error_backtrace => exception.backtrace)
109
+ end
110
+
111
+ # Checks if a migration can be run
112
+ def is_runnable?(direction)
113
+ rerunnable_safe || (direction == UP && status.current_status < status_complete) || (direction == DOWN && status.current_status > 0)
114
+ end
115
+
116
+ # Checks if a migration as been completed
117
+ def completed?(direction)
118
+ return false if self.status.execution_time == 0
119
+ (direction == UP && self.status.current_status == self.status_complete) || (direction == DOWN && self.status.current_status == 0)
120
+ end
121
+
122
+ protected
123
+
124
+ # Executes a given block if the status has not being processed
125
+ # Then update the status
126
+ def step(step_message = nil, step_status = 1)
127
+ unless status.status_processed?(status.direction, step_status)
128
+ self.status.message = step_message
129
+ puts "\t #{step_message}"
130
+
131
+ yield if block_given?
132
+ self.status.current_status += status.direction_to_i
133
+ end
134
+ end
135
+
136
+ # Prints a given message with the current time
137
+ def tick(msg)
138
+ puts "#{Time.now}: #{msg}"
139
+ end
140
+
141
+ # Executes a block and returns the time it took to be executed
142
+ def time_it
143
+ puts "Running #{self.class}[#{self.status.arguments}](#{self.status.direction})"
144
+
145
+ start = Time.now
146
+ yield if block_given?
147
+ end_time = Time.now - start
148
+
149
+ puts "Tasks #{self.class} executed in #{end_time} seconds. \n\n"
150
+ end_time
151
+ end
152
+
153
+ # contains the code that will be executed when run(up) will be called
154
+ def up
155
+ raise StandardError, 'Needs to be implemented in child class.'
156
+ end
157
+
158
+ # contains the code that will be executed when run(down) will be called
159
+ def down
160
+ raise StandardError, 'Needs to be implemented in child class.'
161
+ end
162
+ end
163
+ end
164
+
165
+ Dir[File.dirname(__FILE__) + "/*.rb"].sort.each { |file| require file;}
@@ -0,0 +1,11 @@
1
+ module Exodus
2
+ class MigrationError
3
+ include MongoMapper::EmbeddedDocument
4
+
5
+ key :error_message, String
6
+ key :error_class, String
7
+ key :error_backtrace, Array
8
+
9
+ embedded_in :migration_status
10
+ end
11
+ end
@@ -0,0 +1,39 @@
1
+ module Exodus
2
+ class MigrationStatus
3
+ include MongoMapper::EmbeddedDocument
4
+
5
+ key :message, String
6
+ key :current_status, Integer, :default => 0
7
+ key :execution_time, Float, :default => 0
8
+ key :last_succesful_completion, Time
9
+ key :direction, String, :default => Migration::UP
10
+ key :arguments, Hash, :default => {}
11
+
12
+ embedded_in :migration
13
+ has_one :error, :class_name => "Exodus::MigrationError", :autosave => true
14
+
15
+ def direction_to_i
16
+ self.direction == Migration::UP ? 1 : -1
17
+ end
18
+
19
+ # Checks if a status has been processed
20
+ # a Status has been processed when:
21
+ # The current status is superior or equal to the given status and the migration direction is UP
22
+ # The current status is inferior or equal to the given status and the migration direction is DOWN
23
+ def status_processed?(migration_direction, status_to_process)
24
+ (migration_direction == Migration::UP && current_status >= status_to_process) || (migration_direction == Migration::DOWN && current_status <= status_to_process)
25
+ end
26
+
27
+ def to_string
28
+ "\t#{direction}\t\t #{arguments}\t\t #{current_status} \t\t #{last_succesful_completion} \t\t #{message}"
29
+ end
30
+
31
+ # Resets a status
32
+ def reset
33
+ self.message = nil
34
+ self.current_status = 0
35
+ self.execution_time = 0
36
+ self.last_succesful_completion = nil
37
+ end
38
+ end
39
+ end
@@ -1,3 +1,3 @@
1
1
  module Exodus
2
- VERSION = "1.0.0"
2
+ VERSION = "1.0.1"
3
3
  end
@@ -0,0 +1,187 @@
1
+ require "spec_helper"
2
+ require File.dirname(__FILE__) + "/../../lib/exodus"
3
+
4
+ describe Exodus::Migration do
5
+
6
+ describe "New Oject" do
7
+ subject { Exodus::Migration.new }
8
+
9
+ it "should have a status" do
10
+ subject.status.should_not be_nil
11
+ end
12
+
13
+ it "should have default value for [status_complete, rerunnable_safe]" do
14
+ subject.status_complete.should == 1
15
+ subject.rerunnable_safe.should be_false
16
+ end
17
+ end
18
+
19
+ describe "class methods" do
20
+ subject { Exodus::Migration.new }
21
+
22
+ describe "#inherited" do
23
+ it "should add a new migrations when a new migration class is created" do
24
+ migration_size = subject.class.load_all([]).size.to_i
25
+ class Migration_test1 < Exodus::Migration; end
26
+
27
+ subject.class.load_all([]).size.should == migration_size + 1
28
+ end
29
+ end
30
+
31
+ describe "#load_all" do
32
+ it "should override migrations" do
33
+ first_migration = subject.class.load_all([]).first.first
34
+ subject.class.load_all([[first_migration, {:test_args => ['some', 'test', 'arguments']}]]).should include [first_migration, {:test_args => ['some', 'test', 'arguments']}]
35
+ end
36
+
37
+ it "should add a new migrations if the migration is not present" do
38
+ migration_classes = subject.class.load_all([]).map{|migration, args| migration}
39
+ class CompleteNewMigration < Exodus::Migration; end
40
+
41
+ reloaded_migration_classes = subject.class.load_all([[CompleteNewMigration.name]]).map{|migration, args| migration}
42
+ reloaded_migration_classes.should include CompleteNewMigration
43
+ end
44
+ end
45
+ end
46
+
47
+ describe "instance methods" do
48
+ before do
49
+ class Migration_test1 < Exodus::Migration
50
+ def up
51
+ step("Creating new APIUser entity", 1) {UserSupport.create(:name =>'testor')}
52
+ end
53
+
54
+ def down
55
+ step("Droping APIUser entity", 0) do
56
+ user = UserSupport.first
57
+ user.destroy if user
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+ subject { Migration_test1.first_or_create({}) }
64
+
65
+ describe "#run" do
66
+ it "should create a new APIUser when running it up" do
67
+ Migration_test1.collection.drop
68
+ UserSupport.collection.drop
69
+
70
+ lambda{ subject.run('up')}.should change { UserSupport.count }.by(1)
71
+ subject.status.arguments.should be_empty
72
+ subject.status.current_status.should == 1
73
+ subject.status.direction.should == 'up'
74
+ subject.status.execution_time.should > 0
75
+ subject.status.last_succesful_completion.should
76
+ subject.status.message.should == 'Creating new APIUser entity'
77
+ end
78
+
79
+ it "should delete an APIUser when running it down" do
80
+ Migration_test1.collection.drop
81
+ UserSupport.collection.drop
82
+ subject.run('up')
83
+
84
+ lambda{ subject.run('down')}.should change { UserSupport.count }.by(-1)
85
+ subject.status.arguments.should be_empty
86
+ subject.status.current_status.should == 0
87
+ subject.status.direction.should == 'down'
88
+ subject.status.execution_time.should > 0
89
+ subject.status.message.should == 'Droping APIUser entity'
90
+ end
91
+ end
92
+
93
+ describe "#failure=" do
94
+ it "should save error information" do
95
+ exception = nil
96
+
97
+ begin
98
+ raise StandardError "This is an error"
99
+ rescue Exception => e
100
+ subject.failure = e
101
+ exception = e
102
+ end
103
+
104
+ subject.status.error.error_message.should == exception.message
105
+ subject.status.error.error_class.should == exception.class.name
106
+ subject.status.error.error_backtrace.should == exception.backtrace
107
+ end
108
+ end
109
+
110
+ describe "#time_it" do
111
+ it "should execute a block and set the execution_time" do
112
+ Migration_test1.collection.drop
113
+ UserSupport.collection.drop
114
+
115
+ lambda do
116
+ time = subject.send(:time_it) {UserSupport.create(:name => 'testor')}
117
+ end.should change { UserSupport.count }.by(1)
118
+ end
119
+ end
120
+
121
+ describe "#completed?" do
122
+ it "should be false when the job is not completed" do
123
+ Migration_test1.collection.drop
124
+ UserSupport.collection.drop
125
+
126
+ subject.completed?('up').should be_false
127
+ subject.completed?('down').should be_false
128
+ end
129
+
130
+ it "should be completed up when the job has ran up" do
131
+ subject.run('up')
132
+
133
+ subject.completed?('up').should be_true
134
+ subject.completed?('down').should be_false
135
+ end
136
+
137
+ it "should be completed down when the job has ran down" do
138
+ subject.run('down')
139
+
140
+ subject.completed?('up').should be_false
141
+ subject.completed?('down').should be_true
142
+ end
143
+ end
144
+
145
+ describe "#is_runnable?" do
146
+ it "should only be runable up when it has never run before" do
147
+ Migration_test1.collection.drop
148
+ UserSupport.collection.drop
149
+
150
+ subject.is_runnable?('up').should be_true
151
+ subject.is_runnable?('down').should be_false
152
+ end
153
+
154
+ it "should not be runable up when it has ran up before" do
155
+ subject.run('up')
156
+
157
+ subject.is_runnable?('up').should be_false
158
+ subject.is_runnable?('down').should be_true
159
+ end
160
+
161
+ it "should not be runable down when it has ran down before" do
162
+ subject.run('down')
163
+
164
+ subject.is_runnable?('up').should be_true
165
+ subject.is_runnable?('down').should be_false
166
+ end
167
+
168
+ it "should be runable when if the task is safe" do
169
+ subject.rerunnable_safe = true
170
+
171
+ subject.is_runnable?('up').should be_true
172
+ subject.is_runnable?('down').should be_true
173
+ end
174
+ end
175
+ end
176
+
177
+ describe "MigrationFramework" do
178
+ describe "sort_migrations" do
179
+ it "should return the migrations sorted by migration number" do
180
+ CompleteNewMigration.migration_number = 10
181
+ sorted_migrations = Exodus::sort_migrations(Exodus::Migration.load_all([]))
182
+ migrations_number = sorted_migrations.map {|migration, args| migration.migration_number }
183
+ migrations_number.should == migrations_number.sort
184
+ end
185
+ end
186
+ end
187
+ end
@@ -0,0 +1,10 @@
1
+ require File.dirname(__FILE__) + '/../lib/exodus'
2
+ Dir["#{File.dirname(__FILE__)}/support/*.rb"].each { |f| require f }
3
+ mongo_uri = 'mongodb://exodus:exodus@dharma.mongohq.com:10048/Exodus-test'
4
+
5
+ Exodus.configure do |config|
6
+ config.db = 'Exodus-test'
7
+ config.connection = Mongo::MongoClient.from_uri(mongo_uri)
8
+ config.config_file = File.dirname(__FILE__) + '/support/config.yml'
9
+ end
10
+
@@ -0,0 +1,6 @@
1
+ migration:
2
+ migrate:
3
+ rollback:
4
+ custom:
5
+ migrate:
6
+ rollback:
@@ -0,0 +1,4 @@
1
+ class UserSupport
2
+ include MongoMapper::Document
3
+ key :name, String
4
+ end
data/tasks/exodus.rake ADDED
@@ -0,0 +1,78 @@
1
+ def time_it(task, &block)
2
+ puts "#{task} starting..."
3
+ start = Time.now
4
+ yield
5
+ puts "#{task} Done in (#{Time.now-start}s)!!"
6
+ end
7
+
8
+ def step
9
+ ENV['STEP']
10
+ end
11
+
12
+ task :require_env do
13
+ require 'csv'
14
+ require File.dirname(__FILE__) + '/../lib/exodus'
15
+ end
16
+
17
+ namespace :db do
18
+ desc "Migrate the database"
19
+ task :migrate => :require_env do
20
+ time_it "db:migrate#{" step #{step}" if step}" do
21
+ migrations = Migration.load_all(Exodus.migrations_info.migrate)
22
+ Exodus::run_migrations('up', migrations, step)
23
+ end
24
+ end
25
+
26
+ desc "Rolls the database back to the previous version"
27
+ task :rollback => :require_env do
28
+ time_it "db:rollback#{" step #{step}" if step}" do
29
+ migrations = Migration.load_all(Exodus.migrations_info.rollback)
30
+ Exodus::run_migrations('down', migrations, step)
31
+ end
32
+ end
33
+
34
+ namespace :migrate do
35
+ desc "Manually migrates specified migrations (specify migrations or use config/migration.yml)"
36
+ task :custom, [:migrations_info] => :require_env do |t, args|
37
+ time_it "db:migrate_custom#{" step #{step}" if step}" do
38
+ migrations = if args[:migrations_info]
39
+ YAML.load(args[:migrations_info])
40
+ else
41
+ Migration.load_custom(Exodus.migrations_info.migrate_custom)
42
+ end
43
+
44
+ Exodus::run_migrations('up', migrations, step)
45
+ end
46
+ end
47
+
48
+ desc "Lists all the migrations"
49
+ task :list => :require_env do
50
+ Migration.list
51
+ end
52
+
53
+ desc "Loads migration.yml and displays it"
54
+ task :yml_status => :require_env do
55
+ pp Exodus.migrations_info.to_s
56
+ end
57
+
58
+ desc "Displays the current status of migrations"
59
+ task :status => :require_env do
60
+ Migration.db_status
61
+ end
62
+ end
63
+
64
+ namespace :rollback do
65
+ desc "Manually rolls the database back using specified migrations (specify migrations or use config/migration.yml)"
66
+ task :custom, [:migrations_info] => :require_env do |t, args|
67
+ time_it "db:rollback_custom#{" step #{step}" if step}" do
68
+ migrations = if args[:migrations_info]
69
+ YAML.load(args[:migrations_info])
70
+ else
71
+ Migration.load_custom(Exodus.migrations_info.rollback_custom)
72
+ end
73
+
74
+ Exodus::run_migrations('down', migrations, step)
75
+ end
76
+ end
77
+ end
78
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: exodus
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-05-27 00:00:00.000000000 Z
12
+ date: 2013-05-28 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: mongo_mapper
@@ -75,7 +75,7 @@ dependencies:
75
75
  - - ! '>='
76
76
  - !ruby/object:Gem::Version
77
77
  version: '0'
78
- description: Exodus is a migration framework for Mongo
78
+ description: Exodus is a migration framework for MongoDb
79
79
  email:
80
80
  - thomas@fanhattan.com
81
81
  - thomas.dmytryk@supinfo.com
@@ -84,13 +84,26 @@ extensions: []
84
84
  extra_rdoc_files: []
85
85
  files:
86
86
  - .gitignore
87
+ - .rspec
88
+ - .rvmrc
89
+ - .travis.yml
90
+ - CHANGELOG.md
87
91
  - Gemfile
88
92
  - LICENSE
89
93
  - README.md
90
94
  - Rakefile
91
95
  - exodus.gemspec
92
96
  - lib/exodus.rb
97
+ - lib/exodus/config/migration_info.rb
98
+ - lib/exodus/migrations/migration.rb
99
+ - lib/exodus/migrations/migration_error.rb
100
+ - lib/exodus/migrations/migration_status.rb
93
101
  - lib/exodus/version.rb
102
+ - spec/exodus/migration_spec.rb
103
+ - spec/spec_helper.rb
104
+ - spec/support/config.yml
105
+ - spec/support/user_support.rb
106
+ - tasks/exodus.rake
94
107
  homepage: ''
95
108
  licenses:
96
109
  - MIT
@@ -116,4 +129,8 @@ rubygems_version: 1.8.23
116
129
  signing_key:
117
130
  specification_version: 3
118
131
  summary: Exodus uses mongomapper to provide a complete migration framework
119
- test_files: []
132
+ test_files:
133
+ - spec/exodus/migration_spec.rb
134
+ - spec/spec_helper.rb
135
+ - spec/support/config.yml
136
+ - spec/support/user_support.rb