hummingbird 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/.gitignore +4 -0
- data/Rakefile +3 -0
- data/hummingbird.gemspec +4 -2
- data/lib/hummingbird.rb +3 -1
- data/lib/hummingbird/configuration.rb +78 -1
- data/lib/hummingbird/database.rb +39 -1
- data/lib/hummingbird/plan.rb +76 -3
- data/lib/hummingbird/plan_error.rb +29 -2
- data/lib/hummingbird/version.rb +3 -2
- metadata +39 -6
data/.gitignore
CHANGED
data/Rakefile
CHANGED
data/hummingbird.gemspec
CHANGED
@@ -22,10 +22,12 @@ DESC
|
|
22
22
|
gem.add_dependency 'sequel'
|
23
23
|
gem.add_dependency 'optimism'
|
24
24
|
|
25
|
-
gem.add_development_dependency 'rake'
|
26
|
-
gem.add_development_dependency 'minitest'
|
27
25
|
gem.add_development_dependency 'guard'
|
28
26
|
gem.add_development_dependency 'guard-minitest'
|
27
|
+
gem.add_development_dependency 'minitest'
|
28
|
+
gem.add_development_dependency 'rake'
|
29
|
+
gem.add_development_dependency 'redcarpet'
|
29
30
|
gem.add_development_dependency 'simplecov'
|
30
31
|
gem.add_development_dependency 'sqlite3'
|
32
|
+
gem.add_development_dependency 'yard'
|
31
33
|
end
|
data/lib/hummingbird.rb
CHANGED
@@ -3,5 +3,7 @@ require 'hummingbird/configuration'
|
|
3
3
|
require 'hummingbird/plan'
|
4
4
|
require 'hummingbird/database'
|
5
5
|
|
6
|
-
|
6
|
+
# Empty module to facilitate loading {Hummingbird::Configuration},
|
7
|
+
# {Hummingbird::Database}, and {Hummingbird::Plan}.
|
8
|
+
module Hummingbird
|
7
9
|
end
|
@@ -1,10 +1,54 @@
|
|
1
1
|
require 'optimism'
|
2
2
|
|
3
|
-
|
3
|
+
module Hummingbird
|
4
|
+
# Helper class to handle reading configuration options from YAML
|
5
|
+
# files.
|
4
6
|
class Configuration
|
7
|
+
# The name of the default configuration file.
|
5
8
|
CONFIG_FILE = 'hummingbird.yml'
|
9
|
+
# The name of the default user specific configuration file. Used
|
10
|
+
# for overriding settings in the default configuration file, or
|
11
|
+
# providing values for settings missing from there.
|
6
12
|
USER_CONFIG_FILE = '.hummingbird.yml'
|
7
13
|
|
14
|
+
# Provides access to the settings found in the configuration file,
|
15
|
+
# and user specific configuration file. By default it will look
|
16
|
+
# for {CONFIG_FILE}, and {USER_CONFIG_FILE} in the specified
|
17
|
+
# `config_dir`. These can be overridden.
|
18
|
+
#
|
19
|
+
# The YAML configuration files should be in the format:
|
20
|
+
#
|
21
|
+
# ---
|
22
|
+
# basedir: 'sql'
|
23
|
+
# planfile: 'application.plan'
|
24
|
+
# migrations_dir: 'migrations-dir'
|
25
|
+
# migrations_table: 'application_migrations'
|
26
|
+
# connection_string: 'sequel connection string'
|
27
|
+
#
|
28
|
+
# See the individual access methods for details about each
|
29
|
+
# setting.
|
30
|
+
#
|
31
|
+
# @see #basedir
|
32
|
+
# @see #planfile
|
33
|
+
# @see #migrations_dir
|
34
|
+
# @see #migrations_table
|
35
|
+
# @see #connection_string
|
36
|
+
#
|
37
|
+
# @param [String] config_dir The directory in which to look for
|
38
|
+
# configuration files.
|
39
|
+
#
|
40
|
+
# @param [Hash] opts Overrides for the default configuration file
|
41
|
+
# names and locations.
|
42
|
+
#
|
43
|
+
# @option opts [String] :config_file Override the default
|
44
|
+
# configuration file name and location. This can be either a
|
45
|
+
# path relative to the `config_dir`, or an absolute path to the
|
46
|
+
# configuration file.
|
47
|
+
#
|
48
|
+
# @option opts [String] :user_config_file Override the default
|
49
|
+
# user specific configuration file name and location. This can
|
50
|
+
# be either a path relative to the `config_dir`, or an absolute
|
51
|
+
# path to the configuration file.
|
8
52
|
def initialize(config_dir,opts={})
|
9
53
|
opts[:config_file] ||= CONFIG_FILE
|
10
54
|
opts[:user_config_file] ||= USER_CONFIG_FILE
|
@@ -17,22 +61,55 @@ class Hummingbird
|
|
17
61
|
@config = Optimism.require(*config_file_names)
|
18
62
|
end
|
19
63
|
|
64
|
+
# The directory on which to base relative paths of other settings.
|
65
|
+
# This directory itself is relative to `Dir.getwd` unless
|
66
|
+
# specified as an absolute path. This defaults to '.' (the
|
67
|
+
# current working directory).
|
68
|
+
#
|
69
|
+
# @return [String] The absolute path to the directory specified in
|
70
|
+
# the config file.
|
20
71
|
def basedir
|
21
72
|
@basedir ||= File.expand_path(@config[:basedir] || '.', @config_dir)
|
22
73
|
end
|
23
74
|
|
75
|
+
# The file containing the list of migrations to be run in the
|
76
|
+
# order that they should be run. This is relative to {#basedir}
|
77
|
+
# when specified in the configuration file, unless specified as an
|
78
|
+
# absolute path. Defaults to `hummingbird.plan`.
|
79
|
+
#
|
80
|
+
# @see #basedir
|
81
|
+
#
|
82
|
+
# @return [String] The absolute path to the plan file.
|
24
83
|
def planfile
|
25
84
|
@planfile ||= File.expand_path(@config[:planfile] || 'hummingbird.plan', basedir)
|
26
85
|
end
|
27
86
|
|
87
|
+
# The base directory for all migration files. This is relative to
|
88
|
+
# {#basedir} when specified in the configuration file, unless
|
89
|
+
# specified as an absolute path. Defaults to `migrations`.
|
90
|
+
#
|
91
|
+
# @see #basedir
|
92
|
+
#
|
93
|
+
# @return [String] The absolute path to the plan file.
|
28
94
|
def migrations_dir
|
29
95
|
@migrations_dir ||= File.expand_path(@config[:migrations_dir] || 'migrations', basedir)
|
30
96
|
end
|
31
97
|
|
98
|
+
# The name of the migrations table used to keep track of which
|
99
|
+
# migrations have been successfully run, and when they were run.
|
100
|
+
# Defaults to `hummingbird_migrations`.
|
101
|
+
#
|
102
|
+
# @return [Symbol] The name of the migrations table as a symbol.
|
32
103
|
def migrations_table
|
33
104
|
@migrations_table ||= (@config[:migrations_table] || :hummingbird_migrations).to_sym
|
34
105
|
end
|
35
106
|
|
107
|
+
# The {http://sequel.rubyforge.org/ Sequel} compatible connection
|
108
|
+
# string. This has no default, and must be specified in the
|
109
|
+
# configuration file, or provided by another means.
|
110
|
+
#
|
111
|
+
# @return [String] Connection string to be passed to
|
112
|
+
# {http://sequel.rubyforge.org/ Sequel}.
|
36
113
|
def connection_string
|
37
114
|
@connection_string ||= @config[:connection_string]
|
38
115
|
end
|
data/lib/hummingbird/database.rb
CHANGED
@@ -1,21 +1,59 @@
|
|
1
1
|
require 'sequel'
|
2
2
|
|
3
|
-
|
3
|
+
module Hummingbird
|
4
|
+
# Class to handle retrieving recorded migrations, as well as running
|
5
|
+
# new migrations and recording that they have been run.
|
4
6
|
class Database
|
7
|
+
# @param [String] connection_string A
|
8
|
+
# {http://sequel.rubyforge.org/ Sequel} compatible connection string.
|
9
|
+
#
|
10
|
+
# @param [Symbol] migrations_table The name of the table used to
|
11
|
+
# keep track of migrations.
|
5
12
|
def initialize(connection_string, migrations_table)
|
6
13
|
@sequel_db = Sequel.connect(connection_string)
|
7
14
|
@migrations_table_name = migrations_table
|
8
15
|
@prepared_run_migration_insert = nil
|
9
16
|
end
|
10
17
|
|
18
|
+
# @return [true, false] Whether or not the migrations table is
|
19
|
+
# present in the database.
|
11
20
|
def initialized?
|
12
21
|
@sequel_db.tables.include?(@migrations_table_name)
|
13
22
|
end
|
14
23
|
|
24
|
+
# If the database has yet to be initialized with the migrations
|
25
|
+
# table, or if the migrations table has no recorded migrations,
|
26
|
+
# this will return an empty array.
|
27
|
+
#
|
28
|
+
# If there are recorded migrations, this will return an array
|
29
|
+
# of hashes. Where the hashes have the following format:
|
30
|
+
#
|
31
|
+
# {
|
32
|
+
# :migration_name => 'name/of/migration.sql',
|
33
|
+
# :run_on => 1350683387
|
34
|
+
# }
|
35
|
+
#
|
36
|
+
# `:migration_name` is the path of the migration file relative to
|
37
|
+
# the migrations directory. `:run_on` is the time the migration
|
38
|
+
# was run, as a unix epoch.
|
39
|
+
#
|
40
|
+
# @return [Array<Hash{Symbol => String, Number}>] The list of
|
41
|
+
# migrations that have already been run, along with when they
|
42
|
+
# were run as a unix epoch.
|
15
43
|
def already_run_migrations
|
16
44
|
initialized? ? @sequel_db[@migrations_table_name].order(:run_on).to_a : []
|
17
45
|
end
|
18
46
|
|
47
|
+
# Run the provided SQL in a transaction (provided the DB in
|
48
|
+
# question supports transactions). If the SQL successfully runs,
|
49
|
+
# then also record the migration in the migration table. The time
|
50
|
+
# recorded for the `run_on` of the migration is when the migration
|
51
|
+
# _finished_, not when it _started_.
|
52
|
+
#
|
53
|
+
# @param [String] name The name of the migration to run (as listed
|
54
|
+
# in the .plan file).
|
55
|
+
#
|
56
|
+
# @param [String] sql The SQL to run.
|
19
57
|
def run_migration(name,sql)
|
20
58
|
@prepared_run_migration_insert ||= @sequel_db[@migrations_table_name].prepare(:insert, :record_migration, migration_name: :$name, run_on: :$date)
|
21
59
|
|
data/lib/hummingbird/plan.rb
CHANGED
@@ -2,27 +2,75 @@ require 'hummingbird/plan_error'
|
|
2
2
|
|
3
3
|
require 'pathname'
|
4
4
|
|
5
|
-
|
5
|
+
module Hummingbird
|
6
|
+
# This is responsible for parsing the `.plan` file, and for
|
7
|
+
# verifying it against the migrations stored on disk.
|
6
8
|
class Plan
|
7
|
-
|
8
|
-
|
9
|
+
# @return [String] The base directory for all of the migration
|
10
|
+
# files referenced in the `.plan` file.
|
11
|
+
attr_reader :migration_dir
|
12
|
+
|
13
|
+
# This list has not been verified against the files on disk, or
|
14
|
+
# against the database in any way.
|
15
|
+
#
|
16
|
+
# @return [Array<String>] The list of all files referenced in the
|
17
|
+
# `.plan` file.
|
18
|
+
attr_reader :planned_files
|
19
|
+
|
20
|
+
# @param [String] planfile The path to the `.plan` file.
|
21
|
+
#
|
22
|
+
# @param [String] migration_dir The path to the base directory for
|
23
|
+
# all of the migration files referenced in the `.plan` file.
|
9
24
|
def initialize(planfile, migration_dir)
|
10
25
|
@planned_files = parse_plan(planfile)
|
11
26
|
@migration_dir = migration_dir
|
12
27
|
end
|
13
28
|
|
29
|
+
# @return [Array<String>] All files found under {#migration_dir}.
|
14
30
|
def migration_files
|
15
31
|
@migration_files ||= get_migration_files
|
16
32
|
end
|
17
33
|
|
34
|
+
# @return [Array<String>] All {#migration_files} that are not in
|
35
|
+
# {#planned_files}.
|
18
36
|
def files_missing_from_plan
|
19
37
|
migration_files - planned_files
|
20
38
|
end
|
21
39
|
|
40
|
+
# If this list is not empty, there is probably an error as there
|
41
|
+
# are files that have been planned to run that do not exist on
|
42
|
+
# disk.
|
43
|
+
#
|
44
|
+
# @return [Array<String>] All {#planned_files} that are not in
|
45
|
+
# {#migration_files}.
|
22
46
|
def files_missing_from_migration_dir
|
23
47
|
planned_files - migration_files
|
24
48
|
end
|
25
49
|
|
50
|
+
# The names, and SQL for migrations that have yet to be run
|
51
|
+
# according to the list of already run migrations in
|
52
|
+
# `already_run_migrations`.
|
53
|
+
#
|
54
|
+
# This delegates to {#to_be_run_migration_file_names} and attaches
|
55
|
+
# the associated SQL to each migration.
|
56
|
+
#
|
57
|
+
# @see #to_be_run_migration_file_names
|
58
|
+
# @see Hummingbird::Database#already_run_migrations
|
59
|
+
#
|
60
|
+
# @param [Array<Hash{Symbol => String}>] already_run_migrations
|
61
|
+
# This takes the same format array as output by
|
62
|
+
# {Hummingbird::Database#already_run_migrations}.
|
63
|
+
#
|
64
|
+
# @return [Array<Hash{Symbol => String}>] This is the list of
|
65
|
+
# migrations to be run, with their associated SQL as
|
66
|
+
# `[{ :migration_name => String, :sql => String }, ...]`.
|
67
|
+
#
|
68
|
+
# @raise [Hummingbird::PlanError] If any of the
|
69
|
+
# already_run_migrations are not in the list of planned files.
|
70
|
+
#
|
71
|
+
# @raise [Hummingbird::PlanError] If any of the
|
72
|
+
# if any of the files in already_run_migrations appear out of
|
73
|
+
# order relative to the planned files.
|
26
74
|
def migrations_to_be_run(already_run_migrations)
|
27
75
|
to_be_run_migration_file_names(already_run_migrations).map do |f|
|
28
76
|
{
|
@@ -32,6 +80,25 @@ class Hummingbird
|
|
32
80
|
end
|
33
81
|
end
|
34
82
|
|
83
|
+
# It compares `already_run_migrations` against the list of planned
|
84
|
+
# migrations, and return the list of migrations that have yet to
|
85
|
+
# be run.
|
86
|
+
#
|
87
|
+
# @see Hummingbird::Database#already_run_migrations
|
88
|
+
#
|
89
|
+
# @param [Array<Hash{Symbol => String}>] already_run_migrations
|
90
|
+
# This takes the same format array as output by
|
91
|
+
# {Hummingbird::Database#already_run_migrations}.
|
92
|
+
#
|
93
|
+
# @return [Array<String>] The list of migration names that have
|
94
|
+
# not yet been run.
|
95
|
+
#
|
96
|
+
# @raise [Hummingbird::PlanError] If any of the
|
97
|
+
# already_run_migrations are not in the list of planned files.
|
98
|
+
#
|
99
|
+
# @raise [Hummingbird::PlanError] If any of the
|
100
|
+
# if any of the files in already_run_migrations appear out of
|
101
|
+
# order relative to the planned files.
|
35
102
|
def to_be_run_migration_file_names(already_run_migrations)
|
36
103
|
return planned_files if already_run_migrations.empty?
|
37
104
|
|
@@ -53,6 +120,12 @@ class Hummingbird
|
|
53
120
|
files
|
54
121
|
end
|
55
122
|
|
123
|
+
# Return the contents of the specified migration file.
|
124
|
+
#
|
125
|
+
# @param [String] migration_file The path to the desired migration
|
126
|
+
# file, relative to {#migration_dir}.
|
127
|
+
#
|
128
|
+
# @return [String] The contents of the specified migration file.
|
56
129
|
def get_migration_contents(migration_file)
|
57
130
|
File.read(File.expand_path(migration_file, @migration_dir))
|
58
131
|
end
|
@@ -1,7 +1,34 @@
|
|
1
|
-
|
1
|
+
module Hummingbird
|
2
|
+
# Exception class with extra information available to examine what
|
3
|
+
# caused a validation error comparing the planned migrations against
|
4
|
+
# the recorded migrations.
|
2
5
|
class PlanError < Exception
|
3
|
-
|
6
|
+
# @see Hummingbird::Database#already_run_migrations
|
7
|
+
#
|
8
|
+
# @return [Array<Hash{Symbol => String}>] The
|
9
|
+
# {Hummingbird::Database#already_run_migrations} at the time of
|
10
|
+
# the plan error.
|
11
|
+
attr_reader :already_run_migrations
|
4
12
|
|
13
|
+
# @see Hummingbird::Plan#planned_files
|
14
|
+
#
|
15
|
+
# @return [Array<String>] The {Hummingbird::Plan#planned_files} at
|
16
|
+
# the time of the plan error.
|
17
|
+
attr_reader :planned_files
|
18
|
+
|
19
|
+
# @see Hummingbird::Database#already_run_migrations
|
20
|
+
# @see Hummingbird::Plan#planned_files
|
21
|
+
#
|
22
|
+
# @param [String] msg A user friendly explanation of what
|
23
|
+
# triggered the PlanError.
|
24
|
+
#
|
25
|
+
# @param [Array<String>] planned_files The
|
26
|
+
# {Hummingbird::Plan#planned_files} at the time of the plan
|
27
|
+
# error.
|
28
|
+
#
|
29
|
+
# @param [Array<Hash{Symbol => String}>] already_run_migrations
|
30
|
+
# The {Hummingbird::Database#already_run_migrations} at the time
|
31
|
+
# of the plan error.
|
5
32
|
def initialize(msg,planned_files,already_run_migrations)
|
6
33
|
super(msg)
|
7
34
|
@planned_files = planned_files
|
data/lib/hummingbird/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hummingbird
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.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: 2012-10-
|
12
|
+
date: 2012-10-19 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: sequel
|
@@ -44,7 +44,23 @@ dependencies:
|
|
44
44
|
- !ruby/object:Gem::Version
|
45
45
|
version: '0'
|
46
46
|
- !ruby/object:Gem::Dependency
|
47
|
-
name:
|
47
|
+
name: guard
|
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: guard-minitest
|
48
64
|
requirement: !ruby/object:Gem::Requirement
|
49
65
|
none: false
|
50
66
|
requirements:
|
@@ -76,7 +92,7 @@ dependencies:
|
|
76
92
|
- !ruby/object:Gem::Version
|
77
93
|
version: '0'
|
78
94
|
- !ruby/object:Gem::Dependency
|
79
|
-
name:
|
95
|
+
name: rake
|
80
96
|
requirement: !ruby/object:Gem::Requirement
|
81
97
|
none: false
|
82
98
|
requirements:
|
@@ -92,7 +108,7 @@ dependencies:
|
|
92
108
|
- !ruby/object:Gem::Version
|
93
109
|
version: '0'
|
94
110
|
- !ruby/object:Gem::Dependency
|
95
|
-
name:
|
111
|
+
name: redcarpet
|
96
112
|
requirement: !ruby/object:Gem::Requirement
|
97
113
|
none: false
|
98
114
|
requirements:
|
@@ -139,6 +155,22 @@ dependencies:
|
|
139
155
|
- - ! '>='
|
140
156
|
- !ruby/object:Gem::Version
|
141
157
|
version: '0'
|
158
|
+
- !ruby/object:Gem::Dependency
|
159
|
+
name: yard
|
160
|
+
requirement: !ruby/object:Gem::Requirement
|
161
|
+
none: false
|
162
|
+
requirements:
|
163
|
+
- - ! '>='
|
164
|
+
- !ruby/object:Gem::Version
|
165
|
+
version: '0'
|
166
|
+
type: :development
|
167
|
+
prerelease: false
|
168
|
+
version_requirements: !ruby/object:Gem::Requirement
|
169
|
+
none: false
|
170
|
+
requirements:
|
171
|
+
- - ! '>='
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0'
|
142
174
|
description: Write your DB migrations in SQL and run them, hold the magic.
|
143
175
|
email:
|
144
176
|
- jacob@technosorcery.net
|
@@ -205,7 +237,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
205
237
|
version: '0'
|
206
238
|
segments:
|
207
239
|
- 0
|
208
|
-
hash: -
|
240
|
+
hash: -2188429721350549035
|
209
241
|
requirements: []
|
210
242
|
rubyforge_project:
|
211
243
|
rubygems_version: 1.8.24
|
@@ -237,3 +269,4 @@ test_files:
|
|
237
269
|
- test/lib/hummingbird/version_test.rb
|
238
270
|
- test/test_helper.rb
|
239
271
|
- test/test_helper_test.rb
|
272
|
+
has_rdoc:
|