migrake 0.1.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,3 @@
1
+ /MIGRAKE_STATUS
2
+ /Gemfile.lock
3
+ /pkg
data/.travis.yml ADDED
@@ -0,0 +1,11 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.2
4
+ - 1.9.3
5
+ - rbx-19mode
6
+ - jruby-19mode
7
+ env:
8
+ - RUBYOPT="$RUBYOPT -Itest -Ilib"
9
+ notifications:
10
+ email:
11
+ - contacto@nicolassanguinetti.info
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source :rubygems
2
+ gemspec
data/README.md ADDED
@@ -0,0 +1,115 @@
1
+ # Migrake
2
+
3
+ [![Build Status](https://secure.travis-ci.org/foca/migrake.png?branch=master)](http://travis-ci.org/foca/migrake)
4
+
5
+ A simple [Rake][rake] extension that lets you define tasks that are divided into
6
+ multiple smaller "versions". When you run the task in a given host, it will only
7
+ run the "versions" that haven't yet been run in that host. Think something
8
+ similar to [ActiveRecord::Migration][migrations] for Rake tasks.
9
+
10
+ This is specially useful to define tasks that should be run on deploy, in order
11
+ to do some sort of house cleaning that should be run once. For example, some
12
+ complex data migrations, or re-processing your user avatars because now the UI
13
+ shows them in a different size.
14
+
15
+ The important thing is these tasks should only be run once when you deploy to a
16
+ given environment, and they should be run immediately.
17
+
18
+ [rake]: http://rake.rubyforge.org
19
+ [migrations]: http://api.rubyonrails.org/classes/ActiveRecord/Migration.html
20
+
21
+ ## An example
22
+
23
+ In `lib/tasks/migrake.rake`:
24
+
25
+ ``` ruby
26
+ require "migrake"
27
+
28
+ migrake Set.new([
29
+ "data:set_default_user_state",
30
+ "twitter:purge_cache",
31
+ # etc, etc
32
+ ])
33
+ ```
34
+
35
+ This would define a task called `:migrake` that, when called, will invoke the
36
+ tasks in that set _unless they have been invoked before_. This means that after
37
+ each deploy you can run this task (with a capistrano hook, for example) and any
38
+ tasks that need to be run in this environment will be run.
39
+
40
+ ## How it works
41
+
42
+ This will keep a file named `MIGRAKE_STATUS`, that will contain the name of the
43
+ tasks that were ran in the past. Whenever you run `rake migrake` migrake will
44
+ check that file, see which tasks in the set aren't in it, and will run those
45
+ tasks (no order is guaranteed, if you need tasks to run in order, define the
46
+ dependencies in the task themselves.)
47
+
48
+ ## Overriding where to keep the MIGRAKE_STATUS file
49
+
50
+ The file will be located, by default, at whichever directory `rake` is invoked
51
+ from. In order to change that (for example, to put it in capistrano's `shared`
52
+ directory), do either of the following:
53
+
54
+ Define `Migrake.status_file_directory` before running the tasks (so, for
55
+ example, at the top of your `migrake.rake`):
56
+
57
+ ``` ruby
58
+ Migrake.status_file_directory = File.expand_path("../tmp", __FILE__)
59
+ ```
60
+
61
+ Or as an alternative, pass it as an environment variable to Rake:
62
+
63
+ ``` shell
64
+ MIGRAKE_STATUS_DIR=./some/path bundle exec rake migrake
65
+ ```
66
+
67
+ The latter lets you define it within the capistrano definition file, like this:
68
+
69
+ ``` ruby
70
+ namespace :deploy do
71
+ task :migrake do
72
+ run "MIGRAKE_STATUS_DIR=#{shared_path} bundle exec rake migrake"
73
+ end
74
+ end
75
+
76
+ after "deploy:migrations", "deploy:migrake"
77
+ ```
78
+
79
+ ## Bootstrapping a new environment
80
+
81
+ When you bootstrap a new environment you don't need to run migrake tasks that
82
+ have been already run. For this, when the `migrake` method is invoked we also
83
+ define a `migrake:ready` task, that forces all tasks defined in the migrake set
84
+ into the `MIGRAKE_STATUS` file.
85
+
86
+ This way you can just run that when you bootstrap the environment and then keep
87
+ running `rake migrake` when you deploy.
88
+
89
+ ## License
90
+
91
+ (The MIT License)
92
+
93
+ Copyright (c) 2012 [Nicolas Sanguinetti][me], with the support of [Cubox][cubox]
94
+
95
+ Permission is hereby granted, free of charge, to any person obtaining
96
+ a copy of this software and associated documentation files (the
97
+ 'Software'), to deal in the Software without restriction, including
98
+ without limitation the rights to use, copy, modify, merge, publish,
99
+ distribute, sublicense, and/or sell copies of the Software, and to
100
+ permit persons to whom the Software is furnished to do so, subject to
101
+ the following conditions:
102
+
103
+ The above copyright notice and this permission notice shall be
104
+ included in all copies or substantial portions of the Software.
105
+
106
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
107
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
108
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
109
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
110
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
111
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
112
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
113
+
114
+ [me]: http://nicolassanguinetti.info
115
+ [cubox]: http://cuboxlabs.com
data/Rakefile ADDED
@@ -0,0 +1,15 @@
1
+ require "rake/testtask"
2
+ require "rubygems/package_task"
3
+
4
+ gem_spec = eval(File.read("./migrake.gemspec"))
5
+ Gem::PackageTask.new(gem_spec) do |pkg|
6
+ pkg.need_zip = false
7
+ pkg.need_tar = false
8
+ end
9
+
10
+ Rake::TestTask.new do |t|
11
+ t.pattern = "test/*_test.rb"
12
+ t.verbose = true
13
+ end
14
+
15
+ task default: :test
data/examples/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "migrake"
2
+
3
+ migrake Set.new(%w(a b c d))
4
+
5
+ %w(a b c d).each do |t|
6
+ desc "Print #{t}"
7
+ task t do
8
+ puts t
9
+ end
10
+ end
@@ -0,0 +1,45 @@
1
+ require "migrake"
2
+
3
+ module Migrake
4
+ module DSL
5
+ # Public: Define the rake tasks required to run migrake. This defines the
6
+ # following tasks:
7
+ #
8
+ # - A task to create the `Migrake.status_file_directory` unless it exists.
9
+ # - A task to create the MIGRAKE_STATUS file in the aforementioned
10
+ # directory, unless it exists.
11
+ # - A `migrake:ready` task to bootstrap new environments by writing all
12
+ # the tasks to the store.
13
+ # - The `migrake` task, that will run any tasks not run before.
14
+ #
15
+ # tasks - A Set of tasks to be run.
16
+ #
17
+ # Returns the `migrake` Rake::Task.
18
+ def migrake(tasks)
19
+ dir = Migrake.status_file_directory
20
+ status_file = dir.join("MIGRAKE_STATUS")
21
+
22
+ namespace :migrake do
23
+ directory dir.to_s
24
+
25
+ file status_file.to_s => dir.to_s do
26
+ touch status_file
27
+ end
28
+
29
+ task :run_tasks => status_file.to_s do
30
+ Migrake::Runner.run(tasks, Migrake::Store.new(status_file))
31
+ end
32
+
33
+ desc "Tell migrake that all defined tasks have already been run"
34
+ task :ready => status_file.to_s do
35
+ Migrake::Store.new(status_file).write(tasks)
36
+ end
37
+ end
38
+
39
+ desc "Run the tasks defined by migrake"
40
+ task migrake: "migrake:run_tasks"
41
+ end
42
+ end
43
+ end
44
+
45
+ extend Migrake::DSL
@@ -0,0 +1,3 @@
1
+ module Migrake
2
+ VERSION = "0.1.0"
3
+ end
data/lib/migrake.rb ADDED
@@ -0,0 +1,82 @@
1
+ require "rake/task"
2
+ require "migrake/dsl"
3
+ require "migrake/version"
4
+ require "yaml"
5
+ require "set"
6
+ require "pathname"
7
+
8
+ module Migrake
9
+ # Public: Change the directory where we keep the MIGRAKE_STATUS file.
10
+ #
11
+ # dir - A filesystem path.
12
+ #
13
+ # Returns the directory path.
14
+ def self.status_file_directory=(dir)
15
+ @status_dir = Pathname(dir)
16
+ end
17
+
18
+ # Public: Get the directory where we keep the MIGRAKE_STATUS file. If no
19
+ # directory is set, we will try to fetch it from the ENV, using the
20
+ # MIGRAKE_STATUS_DIR environment variable. If that isn't set either, we
21
+ # default to the working directory.
22
+ #
23
+ # Returns a Pathname.
24
+ def self.status_file_directory
25
+ @status_dir ||= Pathname(ENV.fetch("MIGRAKE_STATUS_DIR", Dir.pwd))
26
+ end
27
+
28
+ # The Runner is responsible for selecting the rake tasks to be run and run
29
+ # them, unless they have already been run in this host.
30
+ module Runner
31
+ # Public: Run all the rake tasks in `set` that haven't been stored in
32
+ # `store`.
33
+ #
34
+ # set - A Set with tasks to be run.
35
+ # store - A Migrake::Store with the tasks that have already been run.
36
+ #
37
+ # Returns the Set of tasks that were run.
38
+ def self.run(set, store)
39
+ (set - store.all).each do |task|
40
+ Rake::Task[task].invoke
41
+ store.put(task)
42
+ end
43
+ end
44
+ end
45
+
46
+ # The Store handles the file where tasks that have already been run are stored
47
+ # so they aren't run again.
48
+ class Store
49
+ # Public: Initialize the store.
50
+ #
51
+ # path - The path to the file where we store the information.
52
+ def initialize(path)
53
+ @path = path
54
+ end
55
+
56
+ # Public: Add one task to the store. This immediately writes the file to
57
+ # disk, to preserve consistency.
58
+ #
59
+ # task - A string with a task's name.
60
+ #
61
+ # Returns nil.
62
+ def put(task)
63
+ write(all << task)
64
+ end
65
+
66
+ # Public: Load all the tasks from the store.
67
+ #
68
+ # Returns a Set.
69
+ def all
70
+ @all ||= YAML.load(@path.read) || Set.new
71
+ end
72
+
73
+ # Public: Write a whole set of tasks to the store, replacing what is in it.
74
+ #
75
+ # set - A Set of tasks.
76
+ #
77
+ # Returns nil.
78
+ def write(set)
79
+ @path.open("w+") { |f| f.puts YAML.dump(set) }
80
+ end
81
+ end
82
+ end
data/migrake.gemspec ADDED
@@ -0,0 +1,19 @@
1
+ # For some reason, 'require "migrake/version"' doesn't work in jruby (1.6.6, on
2
+ # 1.9 mode), even with RUBYOPT=-Ilib, so resorting to this hackery.
3
+ require File.expand_path("../lib/migrake/version", __FILE__)
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "migrake"
7
+ s.version = Migrake::VERSION
8
+ s.description = "Migrake allows you to run rake tasks that only need to be run once in a given environment each time you deploy"
9
+ s.summary = "Migrake is like ActiveRecord::Migration for Rake"
10
+ s.authors = ["Nicolas Sanguinetti"]
11
+ s.email = "hi@nicolassanguinetti.info"
12
+ s.homepage = "http://github.com/foca/migrake"
13
+ s.has_rdoc = false
14
+ s.files = `git ls-files`.split "\n"
15
+ s.platform = Gem::Platform::RUBY
16
+
17
+ s.add_dependency("rake", ">= 0.9.2")
18
+ s.add_development_dependency("minitest")
19
+ end
@@ -0,0 +1,54 @@
1
+ require_relative "test_helper"
2
+ require "rake"
3
+
4
+ describe Migrake::Runner do
5
+ it "runs tasks that aren't in the store" do
6
+ define_tasks "a", "b", "c"
7
+ define_store "a"
8
+
9
+ expect_tasks_ran "b", "c"
10
+
11
+ Migrake::Runner.run(Set.new(["a", "b", "c"]), store)
12
+
13
+ store.verify
14
+ end
15
+
16
+ it "runs no tasks if all the tasks are in the store" do
17
+ define_tasks "a", "b", "c"
18
+ define_store "a", "b", "c"
19
+ # setting no expectation for tasks run passes only if no tasks are run
20
+
21
+ Migrake::Runner.run(Set.new(["a", "b", "c"]), store)
22
+
23
+ store.verify
24
+ end
25
+
26
+ it "doesn't mind stored tasks that don't exist anymore" do
27
+ define_tasks "a"
28
+ define_store "b", "c"
29
+
30
+ expect_tasks_ran "a"
31
+
32
+ Migrake::Runner.run(Set.new(["a", "b", "c"]), store)
33
+
34
+ store.verify
35
+ end
36
+
37
+ def store
38
+ @store ||= MiniTest::Mock.new
39
+ end
40
+
41
+ def define_store(*tasks)
42
+ store.expect :all, Set.new(tasks)
43
+ end
44
+
45
+ def define_tasks(*tasks)
46
+ tasks.each { |task| Rake::Task.define_task(task) }
47
+ end
48
+
49
+ def expect_tasks_ran(*tasks)
50
+ Set.new(tasks).each do |task|
51
+ store.expect :put, nil, [task]
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,45 @@
1
+ require_relative "test_helper"
2
+ require "tempfile"
3
+
4
+ describe Migrake::Store do
5
+ it "can read a stored set" do
6
+ set = Set.new(["a", "b", "c"])
7
+ save_in_source set
8
+
9
+ store = Migrake::Store.new(source)
10
+ assert_equal set, store.all
11
+ end
12
+
13
+ it "an empty file means an empty set" do
14
+ store = Migrake::Store.new(source)
15
+ assert_equal Set.new, store.all
16
+ end
17
+
18
+ it "can add an entry to the store" do
19
+ store = Migrake::Store.new(source)
20
+ store.put("test")
21
+
22
+ assert_equal Set.new(["test"]), store.all
23
+ end
24
+
25
+ it "can write a set replacing whatever is there on the file" do
26
+ save_in_source Set.new(["d", "e", "f", "g"])
27
+
28
+ store = Migrake::Store.new(source)
29
+ store.write Set.new(["a", "b", "c"])
30
+
31
+ assert_equal Set.new(["a", "b", "c"]), store.all
32
+ end
33
+
34
+ after do
35
+ source.unlink # just to be polite
36
+ end
37
+
38
+ def save_in_source(set)
39
+ source.open("w+") { |f| f.puts YAML.dump(set) }
40
+ end
41
+
42
+ def source
43
+ @source ||= Pathname(Tempfile.new("store").path)
44
+ end
45
+ end
@@ -0,0 +1,9 @@
1
+ require "minitest/autorun"
2
+ require "migrake"
3
+
4
+ begin
5
+ require "purdytest"
6
+ rescue LoadError
7
+ # Oh well, no colorized tests for you. You can always use minitest/pride if
8
+ # you want :P
9
+ end
metadata ADDED
@@ -0,0 +1,80 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: migrake
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Nicolas Sanguinetti
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-04-15 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rake
16
+ requirement: &70142092483920 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 0.9.2
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70142092483920
25
+ - !ruby/object:Gem::Dependency
26
+ name: minitest
27
+ requirement: &70142092483500 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *70142092483500
36
+ description: Migrake allows you to run rake tasks that only need to be run once in
37
+ a given environment each time you deploy
38
+ email: hi@nicolassanguinetti.info
39
+ executables: []
40
+ extensions: []
41
+ extra_rdoc_files: []
42
+ files:
43
+ - .gitignore
44
+ - .travis.yml
45
+ - Gemfile
46
+ - README.md
47
+ - Rakefile
48
+ - examples/Rakefile
49
+ - lib/migrake.rb
50
+ - lib/migrake/dsl.rb
51
+ - lib/migrake/version.rb
52
+ - migrake.gemspec
53
+ - test/runner_test.rb
54
+ - test/store_test.rb
55
+ - test/test_helper.rb
56
+ homepage: http://github.com/foca/migrake
57
+ licenses: []
58
+ post_install_message:
59
+ rdoc_options: []
60
+ require_paths:
61
+ - lib
62
+ required_ruby_version: !ruby/object:Gem::Requirement
63
+ none: false
64
+ requirements:
65
+ - - ! '>='
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ required_rubygems_version: !ruby/object:Gem::Requirement
69
+ none: false
70
+ requirements:
71
+ - - ! '>='
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ requirements: []
75
+ rubyforge_project:
76
+ rubygems_version: 1.8.11
77
+ signing_key:
78
+ specification_version: 3
79
+ summary: Migrake is like ActiveRecord::Migration for Rake
80
+ test_files: []