sequel_tools 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b25c97f4906a511718907a4eed3dd0961f546b81
4
+ data.tar.gz: 2d97d6cc1192d996208546842fcd1ba0090fd9b1
5
+ SHA512:
6
+ metadata.gz: 7be946fb5573e87ffb93a02ebc9b5b1dc497953117ad720df53c73ba028517ad63dfdf2c7a42b061e60aa188f1e0cb9705f6be63814dbedce3d05603a4c349fa
7
+ data.tar.gz: 837f5ad9998ec57cf5faf1750940831d9033c224638a2247f578bb5e05e0ce833e1344f50d633350d3afd9c81a4f88e1f4e32888875ea3e80af67b66df0ad3fc
@@ -0,0 +1,13 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ /spec/sample_project/db/migrations/
10
+ /spec/sample_project/db/seeds.rb
11
+
12
+ # rspec failure tracking
13
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
@@ -0,0 +1,6 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.4.1
5
+ before_install: scripts/ci/travis-build.sh
6
+ #before_install: gem install bundler -v 1.16.0
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen-string-literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ #git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
6
+
7
+ # Specify your gem's dependencies in sequel_tools.gemspec
8
+ gemspec
@@ -0,0 +1,39 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ sequel_tools (0.1.0)
5
+ sequel
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ diff-lcs (1.3)
11
+ pg (0.21.0)
12
+ rake (12.3.0)
13
+ rspec (3.7.0)
14
+ rspec-core (~> 3.7.0)
15
+ rspec-expectations (~> 3.7.0)
16
+ rspec-mocks (~> 3.7.0)
17
+ rspec-core (3.7.0)
18
+ rspec-support (~> 3.7.0)
19
+ rspec-expectations (3.7.0)
20
+ diff-lcs (>= 1.2.0, < 2.0)
21
+ rspec-support (~> 3.7.0)
22
+ rspec-mocks (3.7.0)
23
+ diff-lcs (>= 1.2.0, < 2.0)
24
+ rspec-support (~> 3.7.0)
25
+ rspec-support (3.7.0)
26
+ sequel (5.3.0)
27
+
28
+ PLATFORMS
29
+ ruby
30
+
31
+ DEPENDENCIES
32
+ bundler
33
+ pg
34
+ rake
35
+ rspec
36
+ sequel_tools!
37
+
38
+ BUNDLED WITH
39
+ 1.16.0
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 Rodrigo Rosenfeld Rosas
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,99 @@
1
+ # SequelTools
2
+
3
+ SequelTools brings some tooling around Sequel migrations and database management, providing tasks
4
+ to create, drop and migrate the database, plus dumping and restoring from the last migrated schema.
5
+ It can also display which migrations are applied and which ones are missing. It's highly
6
+ customizable and supports multiple databases or environments. It integrates well with Rake as well.
7
+
8
+ Currently only PostgreSQL is supported out-of-the-box for some tasks, but it should allow you to
9
+ specify the database vendor specific commands to support your vendor of choice without requiring
10
+ changes to SequelTools itself. Other vendors can be supported through additional gems or you may
11
+ want to submit pull requests for your preferred DB vendor if you prefer so that it would be
12
+ supported out-of-the-box by this gem.
13
+
14
+ The idea behind SequelTools is to create a collection of supported actions, which depend on the
15
+ database adapter/vendor. Those supported actions can then be translated to Rake tasks as a possible
16
+ interface.
17
+
18
+ ## Installation
19
+
20
+ Add this line to your application's Gemfile:
21
+
22
+ ```ruby
23
+ gem 'sequel_tools'
24
+ ```
25
+
26
+ And then execute:
27
+
28
+ bundle
29
+
30
+ ## Usage
31
+
32
+ Here's a sample Rakefile supporting migrate actions:
33
+
34
+ ```ruby
35
+ require 'bundler/setup'
36
+ require 'sequel_tools'
37
+
38
+ base_config = SequelTools.base_config(
39
+ project_root: File.expand_path(__dir__),
40
+ dbadapter: 'postgres',
41
+ dbname: 'mydb',
42
+ username: 'myuser',
43
+ password: 'secret',
44
+ maintenancedb: 'postgres' # for tasks such as creating the database
45
+ )
46
+
47
+ namespace 'db' do
48
+ SequelTools.inject_rake_tasks base_config.merge(dump_schema_on_migrate: true), self
49
+ end
50
+
51
+ namespace 'dbtest' do
52
+ SequelTools.inject_rake_tasks base_config.merge(dbname: 'mydb_test'), self
53
+ end
54
+ ```
55
+
56
+ Then you are able to run several tasks (`rake -T` will list all supported):
57
+
58
+ rake db:create
59
+ rake db:new_migration[migration_name]
60
+ rake db:migrate
61
+ rake dbtest:setup
62
+
63
+ ## Development and running tests
64
+
65
+ The tests assume the database `sequel_tools_test_pw` exists and can be only accessed using a
66
+ username and password. It also assumes a valid user/passwd is `user_tools_user/secret`. The
67
+ database `sequel_tools_test` is also required to exist and it should be possible to access it
68
+ using the `trust` authentication method, without requiring a password. You may achieve that by
69
+ adding these lines to the start of your `pg_hba.conf`:
70
+
71
+ ```
72
+ host sequel_tools_test_pw all 127.0.0.1/32 md5
73
+ host sequel_tools_test all 127.0.0.1/32 trust
74
+ ```
75
+
76
+ Then feel free to run the tests:
77
+
78
+ bundle exec rspec
79
+
80
+ The default strategy is a safe one, which uses `Open3.capture3` to actually run
81
+ `bundle exec rake ...` whenever we want to test the Rake integration and we do that many times
82
+ in the tests. Running `bundle exec` is slow, so it adds a lot to the test suite total execution
83
+ time. Alternatively, although less robust, you may run the tests using a fork-rake approach,
84
+ which avoids calling `bundle exec` each time we want to run a Rake task. Just define
85
+ the `FORK_RAKE` environment variable:
86
+
87
+ FORK_RAKE=1 bundle exec rspec
88
+
89
+ In my environment this would complete the full suite in about half the time.
90
+
91
+ ## Contributing
92
+
93
+ Bug reports and pull requests are welcome on GitHub at
94
+ [https://github.com/rosenfeld/sequel_tools](https://github.com/rosenfeld/sequel_tools).
95
+
96
+ ## License
97
+
98
+ The gem is available as open source under the terms of the
99
+ [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,8 @@
1
+ # frozen-string-literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task :default => :spec
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "sequel_tools"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,42 @@
1
+ # frozen-string-literal: true
2
+
3
+ require 'sequel_tools/version'
4
+
5
+ module SequelTools
6
+ DEFAULT_CONFIG = {
7
+ project_root: nil,
8
+ pg_dump: 'pg_dump', # command used to run pg_dump
9
+ maintenancedb: 'postgres', # DB to connect to for creating/dropping databases
10
+ db_migrations_location: 'db/migrations',
11
+ schema_location: 'db/migrations/schema.sql',
12
+ seeds_location: 'db/seeds.rb',
13
+ dbname: nil,
14
+ dbhost: 'localhost',
15
+ dbadapter: 'postgres',
16
+ dbport: nil,
17
+ username: nil,
18
+ password: nil,
19
+ dump_schema_on_migrate: false,
20
+ } # unfrozen on purpose so that one might want to update the defaults
21
+
22
+ REQUIRED_KEYS = [ :project_root, :dbadapter, :dbname, :username ]
23
+ class MissingConfigError < StandardError; end
24
+ def self.base_config(extra_config = {})
25
+ config = DEFAULT_CONFIG.merge extra_config
26
+ REQUIRED_KEYS.each do |key|
27
+ raise MissingConfigError, "Expected value for #{key} config is missing" if config[key].nil?
28
+ end
29
+ [:db_migrations_location, :schema_location, :seeds_location].each do |k|
30
+ config[k] = File.expand_path config[k], config[:project_root]
31
+ end
32
+ config
33
+ end
34
+
35
+ def self.inject_rake_tasks(config = {}, rake_context)
36
+ require_relative 'sequel_tools/actions_manager'
37
+ require_relative 'sequel_tools/all_actions'
38
+ actions_manager = ActionsManager.new config
39
+ actions_manager.load_all
40
+ actions_manager.export_as_rake_tasks rake_context
41
+ end
42
+ end
@@ -0,0 +1,10 @@
1
+ # frozen-string-literal: true
2
+
3
+ require 'sequel'
4
+ require_relative '../actions_manager'
5
+
6
+ SequelTools::ActionsManager::Action.register :connect_db, nil do |args, context|
7
+ next if context[:db]
8
+ context[:db] = Sequel.connect context[:uri_builder].call context[:config]
9
+ end
10
+
@@ -0,0 +1,21 @@
1
+ # frozen-string-literal: true
2
+
3
+ require 'sequel'
4
+ require_relative '../actions_manager'
5
+
6
+ class SequelTools::ActionsManager
7
+ Action.register :create, 'Create database' do |args, context|
8
+ begin
9
+ Action[:connect_db].run({}, context)
10
+ rescue
11
+ c = context[:config]
12
+ db = Sequel.connect context[:uri_builder].call(c, c[:maintenancedb])
13
+ db << "create database #{c[:dbname]}"
14
+ db.disconnect
15
+ else
16
+ puts 'Database already exists - aborting'
17
+ exit 1
18
+ end
19
+ end
20
+ end
21
+
@@ -0,0 +1,14 @@
1
+ # frozen-string-literal: true
2
+
3
+ require 'sequel'
4
+ require_relative '../actions_manager'
5
+ require_relative '../migration_utils'
6
+
7
+ class SequelTools::ActionsManager
8
+ desc = 'Run specified migration down if not already applied'
9
+ Action.register :down, desc, arg_names: [ :version ] do |args, context|
10
+ MigrationUtils.apply_migration context, args[:version], :down
11
+ Action[:schema_dump].run({}, context) if context[:config][:dump_schema_on_migrate]
12
+ end
13
+ end
14
+
@@ -0,0 +1,22 @@
1
+ # frozen-string-literal: true
2
+
3
+ require 'sequel'
4
+ require_relative '../actions_manager'
5
+
6
+ class SequelTools::ActionsManager
7
+ Action.register :drop, 'Drop database' do |args, context|
8
+ begin
9
+ Action[:connect_db].run({}, context)
10
+ rescue
11
+ puts 'Database does not exist - aborting.'
12
+ exit 1
13
+ else
14
+ context.delete(:db).disconnect
15
+ c = context[:config]
16
+ db = Sequel.connect context[:uri_builder].call(c, c[:maintenancedb])
17
+ db << "drop database #{c[:dbname]}"
18
+ db.disconnect
19
+ end
20
+ end
21
+ end
22
+
@@ -0,0 +1,19 @@
1
+ # frozen-string-literal: true
2
+
3
+ require 'sequel'
4
+ require_relative '../actions_manager'
5
+
6
+ class SequelTools::ActionsManager
7
+ desc = 'Migrate to specified version (last by default)'
8
+ Action.register :migrate, desc, arg_names: [ :version ] do |args, context|
9
+ Action[:connect_db].run({}, context)
10
+ db = context[:db]
11
+ config = context[:config]
12
+ Sequel.extension :migration unless Sequel.respond_to? :migration
13
+ options = {}
14
+ options[:target] = args[:version].to_i if args[:version]
15
+ Sequel::Migrator.run db, config[:db_migrations_location], options
16
+ Action[:schema_dump].run({}, context) if config[:dump_schema_on_migrate]
17
+ end
18
+ end
19
+
@@ -0,0 +1,28 @@
1
+ # frozen-string-literal: true
2
+
3
+ require_relative '../actions_manager'
4
+
5
+ class SequelTools::ActionsManager
6
+ desc = 'Creates a new migration'
7
+ Action.register :new_migration, desc, arg_names: [ :name ] do |args, context|
8
+ (puts 'Migration name is missing - aborting'; exit 1) unless name = args[:name]
9
+ require 'time'
10
+ migrations_path = context[:config][:db_migrations_location]
11
+ filename = "#{migrations_path}/#{Time.now.strftime '%Y%m%d%H%M%S'}_#{name}.rb"
12
+ File.write filename, <<~MIGRATIONS_TEMPLATE_END
13
+ # documentation available at http://sequel.jeremyevans.net/rdoc/files/doc/migration_rdoc.html
14
+ Sequel.migration do
15
+ change do
16
+ # create_table(:table_name) do
17
+ # primary_key :id
18
+ # String :name, null: false
19
+ # end
20
+ end
21
+ # or call up{} and down{}
22
+ end
23
+ MIGRATIONS_TEMPLATE_END
24
+ puts "The new migration file was created under #{filename.inspect}"
25
+ end
26
+ end
27
+
28
+
@@ -0,0 +1,14 @@
1
+ # frozen-string-literal: true
2
+
3
+ require 'sequel'
4
+ require_relative '../actions_manager'
5
+
6
+ class SequelTools::ActionsManager
7
+ desc = 'Run specified migration down and up (redo)'
8
+ Action.register :redo, desc, arg_names: [ :version ] do |args, context|
9
+ Action[:down].run(args, context)
10
+ Action[:up].run(args, context)
11
+ Action[:schema_dump].run({}, context) if context[:config][:dump_schema_on_migrate]
12
+ end
13
+ end
14
+
@@ -0,0 +1,18 @@
1
+ # frozen-string-literal: true
2
+
3
+ require_relative '../actions_manager'
4
+
5
+ class SequelTools::ActionsManager
6
+ desc = 'Recreate database from scratch by running all migrations and seeds in a new database'
7
+ Action.register :reset, desc, arg_names: [ :version ] do |args, context|
8
+ begin
9
+ Action[:drop].run({}, context)
10
+ rescue
11
+ end
12
+ Action[:create].run({}, context)
13
+ Action[:migrate].run({}, context)
14
+ Action[:seed].run({}, context)
15
+ end
16
+ end
17
+
18
+
@@ -0,0 +1,17 @@
1
+ # frozen-string-literal: true
2
+
3
+ require 'sequel'
4
+ require_relative '../actions_manager'
5
+ require_relative '../migration_utils'
6
+
7
+ class SequelTools::ActionsManager
8
+ Action.register :rollback, 'Rollback last applied migration' do |args, context|
9
+ unless last_found_migration = MigrationUtils.last_found_migration(context)
10
+ puts 'No existing migrations are applied - cannot rollback'
11
+ exit 1
12
+ end
13
+ MigrationUtils.apply_migration context, last_found_migration, :down
14
+ Action[:schema_dump].run({}, context) if context[:config][:dump_schema_on_migrate]
15
+ end
16
+ end
17
+
@@ -0,0 +1,22 @@
1
+ # frozen-string-literal: true
2
+
3
+ require_relative '../actions_manager'
4
+
5
+ class SequelTools::ActionsManager
6
+ Action.register :schema_dump, 'Store current db schema' do |args, context|
7
+ begin
8
+ Action[:connect_db].run({}, context)
9
+ rescue
10
+ puts 'Database does not exist - aborting.'
11
+ exit 1
12
+ else
13
+ c = context[:config]
14
+ unless action = Action[:"schema_dump_#{c[:dbadapter]}"]
15
+ puts "Dumping the db schema is not currently supported for #{c[:dbadapter]}. Aborting"
16
+ exit 1
17
+ end
18
+ action.run({}, context)
19
+ end
20
+ end
21
+ end
22
+
@@ -0,0 +1,32 @@
1
+ # frozen-string-literal: true
2
+
3
+ require_relative '../actions_manager'
4
+ require_relative '../pg_helper'
5
+
6
+ class SequelTools::ActionsManager
7
+ Action.register :schema_dump_postgres, nil do |args, context|
8
+ c = context[:config]
9
+ pg_dump = c[:pg_dump]
10
+ schema_location = c[:schema_location]
11
+
12
+ stdout, stderr, success = PgHelper.run_pg_command c, "#{pg_dump} -s"
13
+ return unless success
14
+ content = stdout
15
+ if content =~ /schema_(migrations|info)/
16
+ stdout, stderr, success =
17
+ PgHelper.run_pg_command c, "#{pg_dump} -a -t schema_migrations -t schema_info --inserts"
18
+ unless success
19
+ puts 'failed to dump data for schema_migrations and schema_info. Aborting.'
20
+ exit 1
21
+ end
22
+ # to make it easier to see the git diffs and to reduce conflicts when working in separate
23
+ # branches, we use a separate line for each value in schema_migrations:
24
+ regex = /(?<=INSERT INTO schema_migrations VALUES \()(.*?)(?=\))/i
25
+ migration_data = stdout.sub(regex){ |m| "\n #{m.split(',').join(",\n ")}\n" }
26
+ content = [content, migration_data].join "\n\n"
27
+ end
28
+ require 'fileutils'
29
+ FileUtils.mkdir_p File.dirname schema_location
30
+ File.write schema_location, content
31
+ end
32
+ end
@@ -0,0 +1,23 @@
1
+ # frozen-string-literal: true
2
+
3
+ require_relative '../actions_manager'
4
+
5
+ class SequelTools::ActionsManager
6
+ description = 'Populates an empty database with previously dumped schema'
7
+ Action.register :schema_load, description do |args, context|
8
+ begin
9
+ Action[:connect_db].run({}, context)
10
+ rescue
11
+ puts 'Database does not exist - aborting.'
12
+ exit 1
13
+ else
14
+ schema_location = context[:config][:schema_location]
15
+ unless File.exist? schema_location
16
+ puts "Schema file '#{schema_location}' does not exist. Aborting."
17
+ exit 1
18
+ end
19
+ context[:db] << File.read(schema_location)
20
+ end
21
+ end
22
+ end
23
+
@@ -0,0 +1,20 @@
1
+ # frozen-string-literal: true
2
+
3
+ require_relative '../actions_manager'
4
+
5
+ class SequelTools::ActionsManager
6
+ Action.register :seed, 'Load seeds from seeds.rb' do |args, context|
7
+ begin
8
+ Action[:connect_db].run({}, context)
9
+ rescue
10
+ puts 'Database does not exist - aborting.'
11
+ exit 1
12
+ else
13
+ if File.exist?(seeds_location = context[:config][:seeds_location])
14
+ ::DB = context[:db]
15
+ load seeds_location
16
+ end
17
+ end
18
+ end
19
+ end
20
+
@@ -0,0 +1,20 @@
1
+ # frozen-string-literal: true
2
+
3
+ require_relative '../actions_manager'
4
+
5
+ class SequelTools::ActionsManager
6
+ description = 'Creates and populates a database with previously dumped schema and seeds'
7
+ Action.register :setup, description do |args, context|
8
+ begin
9
+ Action[:connect_db].run({}, context)
10
+ rescue
11
+ Action[:create].run({}, context)
12
+ Action[:schema_load].run({}, context)
13
+ Action[:seed].run({}, context)
14
+ else
15
+ puts 'Database already exists - aborting.'
16
+ exit 1
17
+ end
18
+ end
19
+ end
20
+
@@ -0,0 +1,26 @@
1
+ # frozen-string-literal: true
2
+
3
+ require 'sequel'
4
+ require_relative '../actions_manager'
5
+ require_relative '../migration_utils'
6
+
7
+ class SequelTools::ActionsManager
8
+ description = 'Show migrations status (applied and missing in migrations path vs unapplied)'
9
+ Action.register :status, description do |args, context|
10
+ unapplied, files_missing = MigrationUtils.migrations_differences context
11
+ path = context[:config][:db_migrations_location]
12
+ unless files_missing.empty?
13
+ puts "The following migrations were applied to the database but can't be found in #{path}:"
14
+ puts files_missing.map{|fn| " - #{fn}" }.join("\n")
15
+ puts
16
+ end
17
+ if unapplied.empty?
18
+ puts 'No pending migrations in the database' if files_missing.empty?
19
+ else
20
+ puts 'Unapplied migrations:'
21
+ puts unapplied.map{|fn| " - #{fn}" }.join("\n")
22
+ puts
23
+ end
24
+ end
25
+ end
26
+
@@ -0,0 +1,14 @@
1
+ # frozen-string-literal: true
2
+
3
+ require 'sequel'
4
+ require_relative '../actions_manager'
5
+ require_relative '../migration_utils'
6
+
7
+ class SequelTools::ActionsManager
8
+ desc = 'Run specified migration up if not already applied'
9
+ Action.register :up, desc, arg_names: [ :version ] do |args, context|
10
+ MigrationUtils.apply_migration context, args[:version], :up
11
+ Action[:schema_dump].run({}, context) if context[:config][:dump_schema_on_migrate]
12
+ end
13
+ end
14
+
@@ -0,0 +1,13 @@
1
+ # frozen-string-literal: true
2
+
3
+ require 'sequel'
4
+ require_relative '../actions_manager'
5
+ require_relative '../migration_utils'
6
+
7
+ class SequelTools::ActionsManager
8
+ Action.register :version, 'Displays current version' do |args, context|
9
+ puts MigrationUtils.current_version(context) || 'No migrations applied'
10
+ end
11
+ end
12
+
13
+
@@ -0,0 +1,88 @@
1
+ # frozen-string-literal: true
2
+
3
+ module SequelTools
4
+ class ActionsManager
5
+ attr_reader :actions, :context
6
+
7
+ URI_BUILDER = ->(config, dbname = config[:dbname]) do
8
+ c = config
9
+ uri_parts = [ "#{c[:dbadapter]}://" ]
10
+ if user = c[:username]
11
+ uri_parts << user
12
+ uri_parts << ':' << c[:password] if c[:password]
13
+ uri_parts << '@'
14
+ end
15
+ uri_parts << c[:dbhost]
16
+ uri_parts << ':' << c[:dbport] if c[:dbport]
17
+ uri_parts << '/' << dbname
18
+ uri_parts.join('')
19
+ end
20
+
21
+ def initialize(config)
22
+ @actions = []
23
+ @context = { config: config, uri_builder: URI_BUILDER }
24
+ end
25
+
26
+ def load_all
27
+ @actions.concat Action.registered
28
+ end
29
+
30
+ def export_as_rake_tasks(rake_context)
31
+ actions.each do |action|
32
+ ctx = context
33
+ rake_context.instance_eval do
34
+ desc action.description unless action.description.nil?
35
+ task action.name, action.arg_names do |t, args|
36
+ action.run args, ctx
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ class Action
43
+ attr_reader :name, :description, :arg_names, :block
44
+
45
+ def initialize(name, description, arg_names: [], &block)
46
+ @name, @description, @arg_names, @block = name, description, arg_names, block
47
+ end
48
+
49
+ def run(args, context)
50
+ @block.call args, context
51
+ end
52
+
53
+ @@registered = []
54
+ @@registered_by_name = {}
55
+ class AlreadyRegisteredAction < StandardError; end
56
+ def self.register(name, description, arg_names: [], &block)
57
+ if @@registered_by_name[name]
58
+ raise AlreadyRegisteredAction, "Attempt to register #{name} twice"
59
+ end
60
+ @@registered <<
61
+ (@@registered_by_name[name] = Action.new name, description, arg_names: arg_names, &block)
62
+ end
63
+
64
+ def self.registered
65
+ @@registered
66
+ end
67
+
68
+ def self.[](name)
69
+ @@registered_by_name[name]
70
+ end
71
+
72
+ def self.unregister(name)
73
+ return unless action = @@registered_by_name.delete(name)
74
+ @@registered.delete action
75
+ action
76
+ end
77
+
78
+ def self.replace(name, description, arg_names: [], &block)
79
+ unregister name
80
+ register name, description, arg_names: arg_names, &block
81
+ end
82
+
83
+ def self.register_action(action)
84
+ register action.name, action.description, arg_names: action.arg_names, &action.block
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,19 @@
1
+ # frozen-string-literal: true
2
+
3
+ require_relative 'actions/connect_db'
4
+ require_relative 'actions/create_db'
5
+ require_relative 'actions/drop_db'
6
+ require_relative 'actions/schema_dump_postgres'
7
+ require_relative 'actions/schema_dump'
8
+ require_relative 'actions/schema_load'
9
+ require_relative 'actions/new_migration'
10
+ require_relative 'actions/migrate'
11
+ require_relative 'actions/reset'
12
+ require_relative 'actions/setup'
13
+ require_relative 'actions/seed'
14
+ require_relative 'actions/up'
15
+ require_relative 'actions/down'
16
+ require_relative 'actions/redo'
17
+ require_relative 'actions/version'
18
+ require_relative 'actions/rollback'
19
+ require_relative 'actions/status'
@@ -0,0 +1,52 @@
1
+ # frozen-string-literal: true
2
+
3
+ require_relative 'actions_manager'
4
+ require 'sequel'
5
+
6
+ class MigrationUtils
7
+ def self.apply_migration(context, version, direction)
8
+ ( puts 'migration version is missing - aborting.'; exit 1 ) if version.nil?
9
+ filename = "#{File.basename version, '.rb'}.rb"
10
+ migrator = find_migrator context, direction
11
+ migrator.migration_tuples.delete_if{|(m, fn, dir)| fn != filename }
12
+ unless (size = migrator.migration_tuples.size) == 1
13
+ puts "Expected a single unapplied migration for #{filename} but found #{size}. Aborting."
14
+ exit 1
15
+ end
16
+ migrator.run
17
+ end
18
+
19
+ def self.find_migrator(context, direction = :up)
20
+ Sequel.extension :migration unless Sequel.respond_to? :migration
21
+ SequelTools::ActionsManager::Action[:connect_db].run({}, context) unless context[:db]
22
+ options = { allow_missing_migration_files: true }
23
+ options[:target] = 0 if direction == :down
24
+ config = context[:config]
25
+ Sequel::Migrator.migrator_class(config[:db_migrations_location]).
26
+ new(context[:db], config[:db_migrations_location], options)
27
+ end
28
+
29
+ def self.current_version(context)
30
+ migrator = find_migrator(context)
31
+ migrator.ds.order(Sequel.desc(migrator.column)).get migrator.column
32
+ end
33
+
34
+ def self.last_found_migration(context)
35
+ migrations_path = context[:config][:db_migrations_location]
36
+ migrator = find_migrator(context)
37
+ migrator.ds.order(Sequel.desc(migrator.column)).select_map(migrator.column).find do |fn|
38
+ File.exist?("#{migrations_path}/#{fn}")
39
+ end
40
+ end
41
+
42
+ def self.migrations_differences(context)
43
+ config = context[:config]
44
+ migrations_path = config[:db_migrations_location]
45
+ existing = Dir["#{migrations_path}/*.rb"].map{|fn| File.basename fn }.sort
46
+ migrator = find_migrator context
47
+ migrated = migrator.ds.select_order_map(migrator.column)
48
+ unapplied = existing - migrated
49
+ files_missing = migrated - existing
50
+ [ unapplied, files_missing ]
51
+ end
52
+ end
@@ -0,0 +1,24 @@
1
+ # frozen-string-literal: true
2
+
3
+ class PgHelper
4
+ def self.run_pg_command(config, cmd, connect_database: nil)
5
+ require 'open3'
6
+ require 'tempfile'
7
+ Tempfile.open 'pgpass' do |file|
8
+ c = config
9
+ file.chmod 0600
10
+ file.write "#{c[:dbhost]}:#{c[:dbport]}:#{c[:dbname]}:#{c[:username]}:#{c[:password]}"
11
+ file.close
12
+ env = {
13
+ 'PGDATABASE' => connect_database || c[:dbname],
14
+ 'PGHOST' => c[:dbhost],
15
+ 'PGPORT' => c[:dbport].to_s,
16
+ 'PGUSER' => c[:username],
17
+ 'PGPASSFILE' => file.path
18
+ }
19
+ stdout, stderr, status = Open3.capture3 env, cmd
20
+ puts "#{cmd} failed: #{[stderr, stdout].join "\n\n"}" if status != 0
21
+ [ stdout, stderr, status == 0 ]
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,5 @@
1
+ # frozen-string-literal: true
2
+
3
+ module SequelTools
4
+ VERSION = '0.1.0'
5
+ end
@@ -0,0 +1,16 @@
1
+ #!/bin/bash
2
+
3
+ # This script enable PG 9.6, creates the sequel_tools_test_pw database, which can be only
4
+ # accessed using a password through localhost, and the sequel_tools_test database which can be
5
+ # accessed using the trust method. It also creates the sequel_tools_user user with password
6
+ # 'secret'. This way we can test accessing the database both using a username and password or
7
+ # other methods defined by pg_hba.conf, such as trust, which doesn't require a password.
8
+
9
+ sudo /etc/init.d/postgresql stop
10
+ PGVERSION=9.6
11
+ PGHBA=/etc/postgresql/$PGVERSION/main/pg_hba.conf
12
+ sudo bash -c "sed -i '1i host sequel_tools_test_pw all 127.0.0.1/32 md5' $PGHBA"
13
+ sudo /etc/init.d/postgresql start $PGVERSION
14
+ psql -c "create user sequel_tools_user superuser password 'secret'" postgres
15
+ psql -U sequel_tools_user -c "create database sequel_tools_test" postgres
16
+ psql -U sequel_tools_user -c "create database sequel_tools_test_pw" postgres
@@ -0,0 +1,39 @@
1
+ # frozen-string-literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'sequel_tools/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = 'sequel_tools'
9
+ spec.version = SequelTools::VERSION
10
+ spec.authors = ['Rodrigo Rosenfeld Rosas']
11
+ spec.email = ['rr.rosas@gmail.com']
12
+
13
+ spec.summary = %q{Add Rake tasks to manage Sequel migrations}
14
+ spec.description = <<~DESCRIPTION_END
15
+ Offer tooling for common database operations, such as running Sequel migrations, store and
16
+ load from the database schema, rollback, redo some migration, rerun all migrations, display
17
+ applied and missing migrations. It allows integration with Rake, while being lazily evaluated
18
+ to not slow down other Rake tasks. It's also configurable in order to support more actions
19
+ and database vendors. Some tasks are currently implemented for PostgreSQL only for the time
20
+ being out of the box. It should be possible to complement this gem with another one to
21
+ support other database vendors, while taking advantage of the build blocks provided by this
22
+ tool set.
23
+ DESCRIPTION_END
24
+ spec.homepage = 'https://github.com/rosenfeld/sequel_tools'
25
+ spec.license = 'MIT'
26
+
27
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
28
+ f.match(%r{^spec/})
29
+ end
30
+ #spec.bindir = 'exe'
31
+ #spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
32
+ spec.require_paths = ['lib']
33
+
34
+ spec.add_runtime_dependency 'sequel'
35
+ spec.add_development_dependency 'pg'
36
+ spec.add_development_dependency 'bundler'
37
+ spec.add_development_dependency 'rake'
38
+ spec.add_development_dependency 'rspec'
39
+ end
metadata ADDED
@@ -0,0 +1,157 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sequel_tools
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Rodrigo Rosenfeld Rosas
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-12-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: sequel
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: pg
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: |
84
+ Offer tooling for common database operations, such as running Sequel migrations, store and
85
+ load from the database schema, rollback, redo some migration, rerun all migrations, display
86
+ applied and missing migrations. It allows integration with Rake, while being lazily evaluated
87
+ to not slow down other Rake tasks. It's also configurable in order to support more actions
88
+ and database vendors. Some tasks are currently implemented for PostgreSQL only for the time
89
+ being out of the box. It should be possible to complement this gem with another one to
90
+ support other database vendors, while taking advantage of the build blocks provided by this
91
+ tool set.
92
+ email:
93
+ - rr.rosas@gmail.com
94
+ executables: []
95
+ extensions: []
96
+ extra_rdoc_files: []
97
+ files:
98
+ - ".gitignore"
99
+ - ".rspec"
100
+ - ".travis.yml"
101
+ - Gemfile
102
+ - Gemfile.lock
103
+ - LICENSE.txt
104
+ - README.md
105
+ - Rakefile
106
+ - bin/console
107
+ - bin/setup
108
+ - lib/sequel_tools.rb
109
+ - lib/sequel_tools/actions/connect_db.rb
110
+ - lib/sequel_tools/actions/create_db.rb
111
+ - lib/sequel_tools/actions/down.rb
112
+ - lib/sequel_tools/actions/drop_db.rb
113
+ - lib/sequel_tools/actions/migrate.rb
114
+ - lib/sequel_tools/actions/new_migration.rb
115
+ - lib/sequel_tools/actions/redo.rb
116
+ - lib/sequel_tools/actions/reset.rb
117
+ - lib/sequel_tools/actions/rollback.rb
118
+ - lib/sequel_tools/actions/schema_dump.rb
119
+ - lib/sequel_tools/actions/schema_dump_postgres.rb
120
+ - lib/sequel_tools/actions/schema_load.rb
121
+ - lib/sequel_tools/actions/seed.rb
122
+ - lib/sequel_tools/actions/setup.rb
123
+ - lib/sequel_tools/actions/status.rb
124
+ - lib/sequel_tools/actions/up.rb
125
+ - lib/sequel_tools/actions/version.rb
126
+ - lib/sequel_tools/actions_manager.rb
127
+ - lib/sequel_tools/all_actions.rb
128
+ - lib/sequel_tools/migration_utils.rb
129
+ - lib/sequel_tools/pg_helper.rb
130
+ - lib/sequel_tools/version.rb
131
+ - scripts/ci/travis-build.sh
132
+ - sequel_tools.gemspec
133
+ homepage: https://github.com/rosenfeld/sequel_tools
134
+ licenses:
135
+ - MIT
136
+ metadata: {}
137
+ post_install_message:
138
+ rdoc_options: []
139
+ require_paths:
140
+ - lib
141
+ required_ruby_version: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ required_rubygems_version: !ruby/object:Gem::Requirement
147
+ requirements:
148
+ - - ">="
149
+ - !ruby/object:Gem::Version
150
+ version: '0'
151
+ requirements: []
152
+ rubyforge_project:
153
+ rubygems_version: 2.6.13
154
+ signing_key:
155
+ specification_version: 4
156
+ summary: Add Rake tasks to manage Sequel migrations
157
+ test_files: []