hummingbird 1.0.0 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -12,5 +12,9 @@ Gemfile.lock
12
12
  # SimpleCov
13
13
  /coverage/
14
14
 
15
+ # Yard
16
+ /.yardoc/
17
+ /doc/
18
+
15
19
  # OS X
16
20
  .DS_Store
data/Rakefile CHANGED
@@ -1,5 +1,6 @@
1
1
  require 'bundler/gem_tasks'
2
2
  require 'rake/testtask'
3
+ require 'yard'
3
4
 
4
5
  Rake::TestTask.new do |t|
5
6
  t.libs << 'test'
@@ -7,4 +8,6 @@ Rake::TestTask.new do |t|
7
8
  t.verbose = true
8
9
  end
9
10
 
11
+ YARD::Rake::YardocTask.new
12
+
10
13
  task :default => :test
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
- class Hummingbird
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
- class Hummingbird
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
@@ -1,21 +1,59 @@
1
1
  require 'sequel'
2
2
 
3
- class Hummingbird
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
 
@@ -2,27 +2,75 @@ require 'hummingbird/plan_error'
2
2
 
3
3
  require 'pathname'
4
4
 
5
- class Hummingbird
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
- attr_reader :migration_dir, :planned_files
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
- class Hummingbird
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
- attr_reader :already_run_migrations, :planned_files
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
@@ -1,3 +1,4 @@
1
- class Hummingbird
2
- VERSION = "1.0.0"
1
+ module Hummingbird
2
+ # The version of Hummingbird.
3
+ VERSION = "1.0.1"
3
4
  end
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.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-18 00:00:00.000000000 Z
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: rake
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: guard
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: guard-minitest
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: -2384325587943083337
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: