exodus 1.0.0
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/.gitignore +19 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +145 -0
- data/Rakefile +14 -0
- data/exodus.gemspec +24 -0
- data/lib/exodus.rb +68 -0
- data/lib/exodus/version.rb +3 -0
- metadata +119 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Thomas Dmytryk
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,145 @@
|
|
1
|
+
Exodus - a migration framework for MongoDb
|
2
|
+
=============
|
3
|
+
|
4
|
+
# Intro
|
5
|
+
|
6
|
+
## A migration Framework for a schemaless database ???
|
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.
|
9
|
+
|
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
|
+
|
12
|
+
* It allows you to know at any time which migration has been ran on any given system
|
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
|
15
|
+
|
16
|
+
|
17
|
+
# Installation
|
18
|
+
|
19
|
+
gem install exodus
|
20
|
+
|
21
|
+
# Configuration
|
22
|
+
|
23
|
+
You need to configure 3 things before using Exodus: the database name, the mongodb connection and the config file location
|
24
|
+
|
25
|
+
Exodus.configure do |config|
|
26
|
+
config.db = 'migration_db'
|
27
|
+
config.connection = Mongo::MongoClient.new("127.0.0.1", 27017, :pool_size => 5, :pool_timeout => 5)
|
28
|
+
config.config_file = File.dirname(__FILE__) + '/config/config.yml'
|
29
|
+
end
|
30
|
+
|
31
|
+
... And you're all set!
|
32
|
+
|
33
|
+
|
34
|
+
# Basic knowledge
|
35
|
+
|
36
|
+
* All Migrations have to be ruby classes that inherits from Migration class.
|
37
|
+
* Migrations have a direction (UP or DOWN)
|
38
|
+
* UP means the migration has been migrated
|
39
|
+
* DOWN means the migration has not been run or has been rollbacked
|
40
|
+
* All migrations have a current_status and status_complete
|
41
|
+
* When current_status is equal to 0 it means the migration has not been run or has been succesfully rollbacked
|
42
|
+
* When current_status is equal to status_complete it means the migration has been succefully migrated
|
43
|
+
* We decided to keep track of migration by enumerating them.
|
44
|
+
* Migrations will run in order using the migration_number
|
45
|
+
* Migrations can be rerunnable safe, rerunnable safe migrations will run on each db:migrate even if the migration has already been run!
|
46
|
+
|
47
|
+
## To Do when writting your own
|
48
|
+
|
49
|
+
* Give it a migration_number
|
50
|
+
class MyMigration < Exodus::Migration
|
51
|
+
self.migration_number = 1
|
52
|
+
end
|
53
|
+
|
54
|
+
* Initialize it and define status_complete and description
|
55
|
+
* Write the UP method that will be call when migrating the migration
|
56
|
+
* Write the DOWN method that will be call when rolling back the migration
|
57
|
+
* If your migration contains distinct steps that you want to split up I recommand using the "step" DSL
|
58
|
+
|
59
|
+
|
60
|
+
## Good example
|
61
|
+
|
62
|
+
class RenameNameToFirstName < Exodus::Migration
|
63
|
+
self.migration_number = 1
|
64
|
+
|
65
|
+
def initialize(args = {})
|
66
|
+
super(args)
|
67
|
+
self.status_complete = 3
|
68
|
+
self.description = 'Change name to first_name'
|
69
|
+
end
|
70
|
+
|
71
|
+
def up
|
72
|
+
step("Creating first_name index", 1) do
|
73
|
+
puts Account.collection.ensure_index({:first_name => 1}, {:unique => true, :sparse => true})
|
74
|
+
end
|
75
|
+
|
76
|
+
step("Renaming", 2) do
|
77
|
+
puts Account.collection.update({'name' => {'$exists' => true}}, {'$rename' => {'name' => 'first_name'}},{:multi => true})
|
78
|
+
end
|
79
|
+
|
80
|
+
step("Dropping name index", 3) do
|
81
|
+
puts Account.collection.drop_index([[:name,1]])
|
82
|
+
end
|
83
|
+
|
84
|
+
self.status.message = "Migration Executed!"
|
85
|
+
end
|
86
|
+
|
87
|
+
def down
|
88
|
+
step("Creating name index", 2) do
|
89
|
+
puts Account.collection.ensure_index({:name => 1}, {:unique => true, :sparse => true})
|
90
|
+
end
|
91
|
+
|
92
|
+
step("Renaming", 1) do
|
93
|
+
puts Account.collection.update({'first_name' => {'$exists' => true}}, {'$rename' => {'first_name' => 'name'}},{:multi => true})
|
94
|
+
end
|
95
|
+
|
96
|
+
step("Dropping first_name index", 0) do
|
97
|
+
puts Account.collection.drop_index([[:first_name,1]])
|
98
|
+
end
|
99
|
+
|
100
|
+
self.status.message = "Rollback Executed!"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# Commands
|
105
|
+
|
106
|
+
## db:migrate
|
107
|
+
Executes all migrations that haven't run yet. You can the STEP enviroment to run only the first x ones.
|
108
|
+
|
109
|
+
rake db:migrate
|
110
|
+
rake db:migrate STEP=2
|
111
|
+
|
112
|
+
## db:rollback
|
113
|
+
Rolls back all migrations that haven't run yet. You can set the STEP enviroment variable to rollback only the last x ones.
|
114
|
+
|
115
|
+
rake db:rollback
|
116
|
+
rake db:rollback STEP=2
|
117
|
+
|
118
|
+
## db:migrate_custom
|
119
|
+
Executes all custom migrations that haven't run yet. You can pass custom migrations in parameters, otherwise custom migrations will be loaded from config/migration.yml. You can use an extra parameter to run only the last x ones.
|
120
|
+
|
121
|
+
rake db:migrate_custom
|
122
|
+
rake db:migrate_custom STEP=2
|
123
|
+
|
124
|
+
## db:rollback_custom
|
125
|
+
Executes all custom migrations that haven't run yet. You can pass custom migrations in parameters, otherwise custom migrations will be loaded from config/migration.yml. You can use an extra parameter to run only the last x ones.
|
126
|
+
|
127
|
+
rake db:rollback_custom
|
128
|
+
rake db:rollback_custom STEP=2
|
129
|
+
|
130
|
+
## db:migrate:list
|
131
|
+
Lists all the migrations.
|
132
|
+
|
133
|
+
rake db:migrate:list
|
134
|
+
|
135
|
+
## db:migrate:status
|
136
|
+
Gives a preview of what as been run on the current database.
|
137
|
+
|
138
|
+
rake db:migrate:status
|
139
|
+
|
140
|
+
## db:migrate:yml_status
|
141
|
+
Prints on the screen the content of the yml configuration file
|
142
|
+
|
143
|
+
rake db:migrate:status
|
144
|
+
|
145
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
require 'rubygems'
|
3
|
+
require 'rake'
|
4
|
+
|
5
|
+
require 'rspec/core/rake_task'
|
6
|
+
|
7
|
+
FileList.new(File.dirname(__FILE__) + '/tasks/*.rake').each { |file| import file }
|
8
|
+
|
9
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
10
|
+
spec.rspec_opts = ["-c", "-f progress"]
|
11
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
12
|
+
end
|
13
|
+
|
14
|
+
task :default => :spec
|
data/exodus.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/exodus/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ['Thomas Dmytryk']
|
6
|
+
gem.email = ['thomas@fanhattan.com', 'thomas.dmytryk@supinfo.com']
|
7
|
+
gem.description = %q{Exodus is a migration framework for Mongo}
|
8
|
+
gem.summary = %q{Exodus uses mongomapper to provide a complete migration framework}
|
9
|
+
gem.homepage = ''
|
10
|
+
gem.license = 'MIT'
|
11
|
+
|
12
|
+
gem.files = `git ls-files`.split($\)
|
13
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
14
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
15
|
+
gem.name = 'exodus'
|
16
|
+
gem.require_paths = ['lib']
|
17
|
+
gem.version = Exodus::VERSION
|
18
|
+
|
19
|
+
gem.add_dependency 'mongo_mapper'
|
20
|
+
gem.add_dependency 'bson_ext'
|
21
|
+
|
22
|
+
gem.add_development_dependency 'rspec'
|
23
|
+
gem.add_development_dependency 'rake'
|
24
|
+
end
|
data/lib/exodus.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'mongo_mapper'
|
2
|
+
Dir[File.dirname(__FILE__) + "/exodus/**/*.rb"].sort.each { |file| require file}
|
3
|
+
|
4
|
+
module Exodus
|
5
|
+
class << self
|
6
|
+
attr_reader :migrations_info
|
7
|
+
|
8
|
+
def configuration
|
9
|
+
@migrations_info ||= MigrationInfo.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def configure
|
13
|
+
yield(configuration) if block_given?
|
14
|
+
end
|
15
|
+
|
16
|
+
# Loads and runs a number of migrations equal to step (or all of them if step is nil)
|
17
|
+
def run_migrations(direction, migrations, step = nil)
|
18
|
+
if migrations
|
19
|
+
sorted_migrations = sort_migrations(migrations)
|
20
|
+
sorted_migrations = order_with_direction(sorted_migrations, direction)
|
21
|
+
sorted_migrations = sorted_migrations.shift(step.to_i) if step
|
22
|
+
|
23
|
+
sorted_migrations.each {|migration_class, args| run_each(migration_class, direction, args)}
|
24
|
+
else
|
25
|
+
puts "no migrations given in argument!"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def sort_migrations(migrations)
|
30
|
+
migrations.sort_by {|migration,args| migration.migration_number }
|
31
|
+
end
|
32
|
+
|
33
|
+
def order_with_direction(migrations, direction)
|
34
|
+
direction == Migration::UP ? migrations : migrations.reverse
|
35
|
+
end
|
36
|
+
|
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"
|
43
|
+
end
|
44
|
+
|
45
|
+
def run_one_migration(migration_class, direction, args)
|
46
|
+
# Going throught MRD because MM request returns nil for some reason
|
47
|
+
current_migration = migration_class.load(migration_class.collection.find('status.arguments' => args).first)
|
48
|
+
current_migration ||= migration_class.new(status: {arguments: args})
|
49
|
+
|
50
|
+
if current_migration.is_runnable?(direction)
|
51
|
+
# Make sure we save all info in case of a failure
|
52
|
+
begin
|
53
|
+
current_migration.run(direction)
|
54
|
+
current_migration.status.error = nil
|
55
|
+
rescue Exception => e
|
56
|
+
current_migration.failure = e
|
57
|
+
current_migration.save!
|
58
|
+
raise
|
59
|
+
end
|
60
|
+
|
61
|
+
# save the migration
|
62
|
+
current_migration.save!
|
63
|
+
else
|
64
|
+
puts "#{current_migration.class}#{current_migration.status.arguments}(#{direction}) as Already been run (or is not runnable)."
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
metadata
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: exodus
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Thomas Dmytryk
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-05-27 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: mongo_mapper
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: bson_ext
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rspec
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: rake
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
description: Exodus is a migration framework for Mongo
|
79
|
+
email:
|
80
|
+
- thomas@fanhattan.com
|
81
|
+
- thomas.dmytryk@supinfo.com
|
82
|
+
executables: []
|
83
|
+
extensions: []
|
84
|
+
extra_rdoc_files: []
|
85
|
+
files:
|
86
|
+
- .gitignore
|
87
|
+
- Gemfile
|
88
|
+
- LICENSE
|
89
|
+
- README.md
|
90
|
+
- Rakefile
|
91
|
+
- exodus.gemspec
|
92
|
+
- lib/exodus.rb
|
93
|
+
- lib/exodus/version.rb
|
94
|
+
homepage: ''
|
95
|
+
licenses:
|
96
|
+
- MIT
|
97
|
+
post_install_message:
|
98
|
+
rdoc_options: []
|
99
|
+
require_paths:
|
100
|
+
- lib
|
101
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
102
|
+
none: false
|
103
|
+
requirements:
|
104
|
+
- - ! '>='
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: '0'
|
107
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
108
|
+
none: false
|
109
|
+
requirements:
|
110
|
+
- - ! '>='
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: '0'
|
113
|
+
requirements: []
|
114
|
+
rubyforge_project:
|
115
|
+
rubygems_version: 1.8.23
|
116
|
+
signing_key:
|
117
|
+
specification_version: 3
|
118
|
+
summary: Exodus uses mongomapper to provide a complete migration framework
|
119
|
+
test_files: []
|