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 ADDED
@@ -0,0 +1,19 @@
1
+ .DS_Store
2
+ */.DS_Store
3
+ *.gem
4
+ *.rbc
5
+ .bundle
6
+ .config
7
+ .yardoc
8
+ Gemfile.lock
9
+ InstalledFiles
10
+ _yardoc
11
+ coverage
12
+ doc/
13
+ lib/bundler/man
14
+ pkg
15
+ rdoc
16
+ spec/reports
17
+ test/tmp
18
+ test/version_tmp
19
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in exodus.gemspec
4
+ gemspec
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
@@ -0,0 +1,3 @@
1
+ module Exodus
2
+ VERSION = "1.0.0"
3
+ 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: []