sequel_tools 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.
@@ -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: []