exodus 1.0.0 → 1.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/.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