database_patcher 1.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d7b3e04fe1740e4f747547954c1978d8818d1500
4
+ data.tar.gz: 9c71855450bcbb288f71731ace7d0c962f2103ee
5
+ SHA512:
6
+ metadata.gz: f70beb3dfe9419064fdfd4929ff2468fc522ba344162333a3a7853aef9696745a771b342aedd8af19efb5981968c334779d8ffe25883fb6556cbd2296aa2cd2b
7
+ data.tar.gz: 62aff77bd1afc2a54b3da3ec9468349e0f5b461dd0a1a5eb63ae6436da6bf5fc663276d494ec51ff2efe705fdc81bf2ffc8cac30c9a83b6f541381b5d18452cd
data/.env ADDED
@@ -0,0 +1 @@
1
+ DATABASE_URL="postgres://localhost"
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1.2
4
+ before_install: gem install bundler -v 1.11.2
@@ -0,0 +1,49 @@
1
+ # Contributor Code of Conduct
2
+
3
+ As contributors and maintainers of this project, and in the interest of
4
+ fostering an open and welcoming community, we pledge to respect all people who
5
+ contribute through reporting issues, posting feature requests, updating
6
+ documentation, submitting pull requests or patches, and other activities.
7
+
8
+ We are committed to making participation in this project a harassment-free
9
+ experience for everyone, regardless of level of experience, gender, gender
10
+ identity and expression, sexual orientation, disability, personal appearance,
11
+ body size, race, ethnicity, age, religion, or nationality.
12
+
13
+ Examples of unacceptable behavior by participants include:
14
+
15
+ * The use of sexualized language or imagery
16
+ * Personal attacks
17
+ * Trolling or insulting/derogatory comments
18
+ * Public or private harassment
19
+ * Publishing other's private information, such as physical or electronic
20
+ addresses, without explicit permission
21
+ * Other unethical or unprofessional conduct
22
+
23
+ Project maintainers have the right and responsibility to remove, edit, or
24
+ reject comments, commits, code, wiki edits, issues, and other contributions
25
+ that are not aligned to this Code of Conduct, or to ban temporarily or
26
+ permanently any contributor for other behaviors that they deem inappropriate,
27
+ threatening, offensive, or harmful.
28
+
29
+ By adopting this Code of Conduct, project maintainers commit themselves to
30
+ fairly and consistently applying these principles to every aspect of managing
31
+ this project. Project maintainers who do not follow or enforce the Code of
32
+ Conduct may be permanently removed from the project team.
33
+
34
+ This code of conduct applies both within project spaces and in public spaces
35
+ when an individual is representing the project or its community.
36
+
37
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
38
+ reported by contacting a project maintainer at adamluzsi@gmail.com. All
39
+ complaints will be reviewed and investigated and will result in a response that
40
+ is deemed necessary and appropriate to the circumstances. Maintainers are
41
+ obligated to maintain confidentiality with regard to the reporter of an
42
+ incident.
43
+
44
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
45
+ version 1.3.0, available at
46
+ [http://contributor-covenant.org/version/1/3/0/][version]
47
+
48
+ [homepage]: http://contributor-covenant.org
49
+ [version]: http://contributor-covenant.org/version/1/3/0/
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in database_patcher.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,63 @@
1
+ # DatabasePatcher
2
+
3
+ This is a tool for making database schema changes in our project.
4
+ This is a CLI tool
5
+
6
+ This tool has two convention to follow
7
+ * It will use the project_root/db/patches folder for look up any change request
8
+ * It will create and maintain an :installed_patches table
9
+
10
+ ## Installation
11
+
12
+ Add this line to your application's Gemfile:
13
+
14
+ ```ruby
15
+ gem 'database_patcher'
16
+ ```
17
+
18
+ And then execute:
19
+
20
+ $ bundle
21
+
22
+ Or install it yourself as:
23
+
24
+ $ gem install database_patcher
25
+
26
+ ## Usage
27
+
28
+ ```
29
+ The following commands supported:
30
+
31
+ Command: help
32
+ Description: show command specific help message
33
+
34
+ Command: create_patch
35
+ aliases: add, new
36
+ Description: create a new patch file with this tools convention
37
+
38
+ Command: initialize
39
+ aliases: init, setup
40
+ Description: this will create initial directory and the default installed_patches table in the database
41
+
42
+ Command: apply_pending_patches
43
+ aliases: apply, up
44
+ Description: apply all pending db patch
45
+
46
+ Command: execute_all_remove_patch
47
+ aliases: apply, up
48
+ Description: execute the down patches and remove all db patch
49
+
50
+ Command: rollback
51
+ aliases: revert, step_back
52
+ Description: execute the last patch down part, and remove the db patch registration
53
+
54
+ ```
55
+
56
+ ### Example use
57
+
58
+ $ database_patcher create_patch create some test table
59
+ #> return the patch path
60
+ # use <your favorite editor> to edit the file/files
61
+ $ database_patcher apply_pending_patches
62
+
63
+ Here is a [cheat sheet](http://sequel.jeremyevans.net/rdoc/files/doc/cheat_sheet_rdoc.html) if you want use the ruby syntax for creating patches
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "database_patcher"
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
data/bin/setup ADDED
@@ -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,29 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'database_patcher/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "database_patcher"
8
+ spec.version = DatabasePatcher::VERSION
9
+ spec.authors = ["Adam Luzsi"]
10
+ spec.email = ["adamluzsi@gmail.com"]
11
+
12
+ spec.summary = %q{Simple tool for managing database patches}
13
+ spec.description = %q{Simple tool for managing database patches}
14
+ spec.homepage = "https://github.com/adamluzsi/database_patcher.rb"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ spec.bindir = "exe"
18
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.11"
22
+ spec.add_development_dependency "rake", "~> 10.0"
23
+ spec.add_development_dependency "rspec", "~> 3.0"
24
+ spec.add_development_dependency "dotenv"
25
+ spec.add_development_dependency "pg"
26
+
27
+ spec.add_dependency 'sequel'
28
+
29
+ end
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require 'database_patcher'
3
+ DatabasePatcher::CLI.start(ARGV)
@@ -0,0 +1,53 @@
1
+ require 'optparse'
2
+ require 'database_patcher'
3
+ module DatabasePatcher::CLI
4
+ extend(self)
5
+
6
+ def start(argv)
7
+ display_help(argv)
8
+ command_name = argv[0]
9
+ args = argv[1..-1]
10
+ command = find_command_for(command_name)
11
+ command.optparse.parse!(args)
12
+ command.on_call.call(args)
13
+ end
14
+
15
+ protected
16
+
17
+ def find_command_for(command_name)
18
+ command = commands.find{|c| c.names.include?(command_name) }
19
+ if command.nil?
20
+ $stderr.puts("command not found: #{command_name}")
21
+ exit
22
+ end
23
+ command
24
+ end
25
+
26
+ def display_help(argv)
27
+ if argv.any?{|str| str =~ /\bh(?:elp)/ }
28
+ options_parser.parse!(argv)
29
+ end
30
+ end
31
+
32
+ def options_parser
33
+ OptionParser.new do |opts|
34
+ opts.banner.concat(' <COMMAND_NAME>')
35
+ opts.banner.concat("\n\n")
36
+ opts.banner.concat("The following commands supported: \n\n")
37
+
38
+ commands.each do |subclass|
39
+ opts.banner.concat("\tCommand: #{subclass.names.first}\n")
40
+ aliases = [subclass.names[1..-1]].flatten
41
+ unless aliases.empty?
42
+ opts.banner.concat("\taliases: #{aliases.join(', ')}\n")
43
+ end
44
+ opts.banner.concat("\tDescription: #{subclass.desc}\n")
45
+ opts.banner.concat("\n")
46
+ end
47
+ end
48
+ end
49
+
50
+ def commands
51
+ DatabasePatcher::Command.subclasses
52
+ end
53
+ end
@@ -0,0 +1,20 @@
1
+ require 'database_patcher'
2
+ class DatabasePatcher::Command::CreatePatch < DatabasePatcher::Command
3
+ names 'create_patch', 'add', 'new'
4
+ desc 'create a new patch file with this tools convention'
5
+
6
+ options = {}
7
+ options[:type] = 'ruby'
8
+ on '-t', '--type', 'this can be sql/ruby/rb. this will determine what will be the extension for the patch. Default is "ruby"' do |type|
9
+ options[:type] = type
10
+ end
11
+
12
+ options[:idempotent] = false
13
+ on '-i', '--idempotent', 'this will tell the patcher that the next patch does not require down part and so it is allowed to create as a simple file.' do
14
+ options[:idempotent] = true
15
+ end
16
+
17
+ on_call do |args|
18
+ DatabasePatcher::PatchCreator.new(options[:type], options[:idempotent],args.join('_')).make
19
+ end
20
+ end
@@ -0,0 +1,10 @@
1
+ require 'database_patcher'
2
+ class DatabasePatcher::Command::Down < DatabasePatcher::Command
3
+ names 'execute_all_remove_patch', 'apply', 'up'
4
+ desc 'execute the down patches and remove all db patch'
5
+
6
+ on_call do |*_|
7
+ DatabasePatcher::Initializer.new.init
8
+ DatabasePatcher::PatchApplier.new.down
9
+ end
10
+ end
@@ -0,0 +1,38 @@
1
+ require 'database_patcher'
2
+ class DatabasePatcher::Command::Help < DatabasePatcher::Command
3
+ names 'help'
4
+ optparse.banner.concat(' <COMMAND_NAME>')
5
+ desc 'show command specific help message'
6
+
7
+ on_call do |args|
8
+ commands = DatabasePatcher::Command.subclasses
9
+
10
+ options_parser = OptionParser.new do |opts|
11
+ opts.banner.concat(' <COMMAND_NAME>')
12
+ opts.banner.concat("\n\n")
13
+ opts.banner.concat("The following commands supported: \n\n")
14
+
15
+ commands.each do |subclass|
16
+ opts.banner.concat("\tCommand: #{subclass.names.first}\n")
17
+ opts.banner.concat("\taliases: #{subclass.names[1..-1].join(', ')}\n")
18
+ opts.banner.concat("\tDescription: #{subclass.desc}\n")
19
+ opts.banner.concat("\n")
20
+ end
21
+ end
22
+
23
+ command_name = args[0]
24
+ parser = if command_name.nil?
25
+ options_parser
26
+ else
27
+ command = commands.find { |c| c.names.include?(command_name) }
28
+ if command.nil?
29
+ $stderr.puts("command not found: #{command_name}")
30
+ exit
31
+ end
32
+ command.optparse
33
+ end
34
+
35
+ puts(parser.help)
36
+ exit
37
+ end
38
+ end
@@ -0,0 +1,9 @@
1
+ require 'database_patcher'
2
+ class DatabasePatcher::Command::Init < DatabasePatcher::Command
3
+ names 'initialize', 'init', 'setup'
4
+ desc 'this will create initial directory and the default installed_patches table in the database'
5
+
6
+ on_call do |*_|
7
+ DatabasePatcher::Initializer.new.init
8
+ end
9
+ end
@@ -0,0 +1,10 @@
1
+ require 'database_patcher'
2
+ class DatabasePatcher::Command::RollBack < DatabasePatcher::Command
3
+ names 'rollback', 'revert', 'step_back'
4
+ desc 'execute the last patch down part, and remove the db patch registration'
5
+
6
+ on_call do |*_|
7
+ DatabasePatcher::Initializer.new.init
8
+ DatabasePatcher::PatchApplier.new.rollback
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ require 'database_patcher'
2
+ class DatabasePatcher::Command::Up < DatabasePatcher::Command
3
+ names 'apply_pending_patches', 'apply', 'up'
4
+ desc 'apply all pending db patch'
5
+
6
+ on_call do |*_|
7
+ DatabasePatcher::Initializer.new.init
8
+ DatabasePatcher::PatchApplier.new.up
9
+ end
10
+ end
@@ -0,0 +1,53 @@
1
+ require "optparse"
2
+ require 'database_patcher'
3
+ class DatabasePatcher::Command
4
+ class << self
5
+ def inherited(subclass)
6
+ subclasses << subclass
7
+ end
8
+
9
+ def subclasses
10
+ @subclasses ||= []
11
+ end
12
+
13
+ def on_call(&block)
14
+ @on_call ||= block unless block.nil?
15
+ @on_call || -> {}
16
+ end
17
+
18
+ def names(*new_names)
19
+ unless new_names.empty?
20
+ @names = new_names
21
+ optparse.banner = OptionParser.new.banner + " #{new_names.first}"
22
+ end
23
+ @names
24
+ end
25
+
26
+ def desc(message = nil)
27
+ @desc = message unless message.nil?
28
+ @desc
29
+ end
30
+
31
+ def options(hash = nil)
32
+ @options ||= {}
33
+ @options.merge!(hash) if hash.is_a?(Hash)
34
+ @options
35
+ end
36
+
37
+ def on(*args,&block)
38
+ optparse.on(*args,&block)
39
+ end
40
+
41
+ def optparse
42
+ @optparse ||= OptionParser.new
43
+ end
44
+
45
+ end
46
+
47
+ require 'database_patcher/Command/help'
48
+ require 'database_patcher/Command/create_patch'
49
+ require 'database_patcher/Command/init'
50
+ require 'database_patcher/Command/up'
51
+ require 'database_patcher/Command/down'
52
+ require 'database_patcher/Command/rollback'
53
+ end
@@ -0,0 +1,26 @@
1
+ require 'digest'
2
+ require 'logger'
3
+ require 'sequel'
4
+ require 'database_patcher'
5
+ module DatabasePatcher::DB
6
+ extend self
7
+
8
+ def create_connection
9
+ new_connection = Sequel.connect(DatabasePatcher::Environment.database_url)
10
+ new_connection.sql_log_level = :info
11
+ new_connection.loggers << new_logger
12
+ new_connection
13
+ end
14
+
15
+ def new_logger
16
+ logger = Logger.new($stdout)
17
+ logger.level = Logger::Severity::ERROR
18
+ logger
19
+ end
20
+
21
+ def valid_connection?(connection)
22
+ !connection.nil? && connection.test_connection
23
+ rescue
24
+ false
25
+ end
26
+ end
@@ -0,0 +1,34 @@
1
+ require 'database_patcher'
2
+ module DatabasePatcher::Environment
3
+ extend self
4
+
5
+ def database_url
6
+ ENV['DATABASE_URL'] || raise('missing database url from ENV (DATABASE_URL)')
7
+ end
8
+
9
+ def patch_folder_path
10
+ folder_path = ENV['DATABASE_PATCH_FOLDER_PATH'] || File.join('db','patches')
11
+ if folder_path[0] == File::Separator
12
+ folder_path
13
+ else
14
+ File.join(project_root_folder, folder_path)
15
+ end
16
+ end
17
+
18
+ def project_root_folder
19
+ if bundler_loaded
20
+ Bundler.root.to_s
21
+ else
22
+ Dir.pwd.to_s
23
+ end
24
+ end
25
+
26
+ protected
27
+
28
+ def bundler_loaded
29
+ require('bundler')
30
+ true
31
+ rescue LoadError
32
+ false
33
+ end
34
+ end
@@ -0,0 +1,51 @@
1
+ require 'database_patcher'
2
+ class DatabasePatcher::Fetcher
3
+ def initialize(connection)
4
+ @connection = connection
5
+ end
6
+
7
+ def get_intalled_patches
8
+ patches = get_patches.reverse
9
+ installed_patches = []
10
+ already_applied_patch_timestamps = get_already_applied_patch_timestamps
11
+
12
+ patches.each do |patch|
13
+ break unless already_applied_patch_timestamps.include?(patch.timestamp)
14
+ installed_patches.push(patch)
15
+ end
16
+
17
+ installed_patches
18
+ end
19
+
20
+ def get_pending_patches
21
+ patches = get_patches
22
+ pending_patches = []
23
+ already_applied_patch_timestamps = get_already_applied_patch_timestamps
24
+
25
+ patches.each do |patch|
26
+ break if already_applied_patch_timestamps.include?(patch.timestamp)
27
+ pending_patches.push(patch)
28
+ end
29
+
30
+ pending_patches
31
+ end
32
+
33
+ protected
34
+
35
+ attr_reader :connection
36
+
37
+ def get_patches
38
+ Dir.glob(File.join(DatabasePatcher::Environment.patch_folder_path, '*')).reduce([]) do |patches, current_path|
39
+ patches << DatabasePatcher::PatchEntity.factory(current_path)
40
+ patches
41
+ end.sort_by(&:timestamp)
42
+ end
43
+
44
+ def installed_patches
45
+ connection[:installed_patches].all
46
+ end
47
+
48
+ def get_already_applied_patch_timestamps
49
+ installed_patches.map { |record| record[:timestamp] }
50
+ end
51
+ end
@@ -0,0 +1,28 @@
1
+ require 'database_patcher'
2
+ class DatabasePatcher::Initializer
3
+ def init
4
+ create_patch_folder
5
+ check_table_exists
6
+ rescue Sequel::DatabaseError
7
+ connection.create_table(:installed_patches) do
8
+ Integer :timestamp
9
+ String :md5_down, size: 32
10
+ String :md5_up, size: 32
11
+ String :comment
12
+ end
13
+ end
14
+
15
+ protected
16
+
17
+ def check_table_exists
18
+ connection[:installed_patches].first
19
+ end
20
+
21
+ def create_patch_folder
22
+ FileUtils.mkpath(DatabasePatcher::Environment.patch_folder_path)
23
+ end
24
+
25
+ def connection
26
+ @connection ||= DatabasePatcher::DB.create_connection
27
+ end
28
+ end
@@ -0,0 +1,34 @@
1
+ require 'database_patcher'
2
+ class DatabasePatcher::PatchApplier
3
+ def up
4
+ connection = DatabasePatcher::DB.create_connection
5
+ connection.transaction do
6
+ fetcher = DatabasePatcher::Fetcher.new(connection)
7
+ fetcher.get_pending_patches.each do |pending_patch|
8
+ pending_patch.up(connection)
9
+ end
10
+ end
11
+ end
12
+
13
+ def down
14
+ connection = DatabasePatcher::DB.create_connection
15
+ connection.transaction do
16
+ fetcher = DatabasePatcher::Fetcher.new(connection)
17
+ fetcher.get_intalled_patches.each do |installed_patche|
18
+ installed_patche.down(connection)
19
+ end
20
+ end
21
+ end
22
+
23
+ def rollback
24
+ connection = DatabasePatcher::DB.create_connection
25
+ connection.transaction do
26
+ fetcher = DatabasePatcher::Fetcher.new(connection)
27
+ fetcher.get_intalled_patches.each do |patch|
28
+ patch.down(connection)
29
+ break
30
+ end
31
+ end
32
+ end
33
+
34
+ end
@@ -0,0 +1,43 @@
1
+ require 'fileutils'
2
+ require 'database_patcher'
3
+ class DatabasePatcher::PatchCreator
4
+ def initialize(type, idenpotent, file_name_description)
5
+ @type = type
6
+ @idenpotent = !!idenpotent
7
+ @file_name_description = file_name_description
8
+ end
9
+
10
+ def make
11
+ if @idenpotentg
12
+ file_path = base_path + extension
13
+ FileUtils.touch(file_path)
14
+ puts(file_path)
15
+ else
16
+ FileUtils.mkpath(base_path)
17
+ FileUtils.touch(File.join(base_path,'up' + extension))
18
+ FileUtils.touch(File.join(base_path,'down' + extension))
19
+ puts(base_path)
20
+ end
21
+ end
22
+
23
+ protected
24
+
25
+ def base_path
26
+ File.join(DatabasePatcher::Environment.patch_folder_path, basename)
27
+ end
28
+
29
+ def basename
30
+ [Time.now.to_i.to_s,@file_name_description].compact.join('_').gsub(/ +/,'_')
31
+ end
32
+
33
+ def extension
34
+ case @type
35
+ when 'ruby','rb'
36
+ '.rb'
37
+ when 'sql'
38
+ '.sql'
39
+ else
40
+ raise("unknown format: #{@type}")
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,23 @@
1
+ require 'database_patcher'
2
+ class DatabasePatcher::PatchEntity::File < DatabasePatcher::PatchEntity
3
+ def up(connection)
4
+ execute_file(connection, @path)
5
+ register_this_patch(connection)
6
+ end
7
+
8
+ def down(connection)
9
+ unregister_this_patch(connection)
10
+ end
11
+
12
+ def md5_down
13
+ ''
14
+ end
15
+
16
+ def md5_up
17
+ md5(::File.read(@path))
18
+ end
19
+
20
+ def comment
21
+ get_comment(@path)
22
+ end
23
+ end
@@ -0,0 +1,51 @@
1
+ require 'database_patcher'
2
+ class DatabasePatcher::PatchEntity::Folder < DatabasePatcher::PatchEntity
3
+ def up(connection)
4
+ execute_from_folder('up', connection)
5
+ register_this_patch(connection)
6
+ end
7
+
8
+ def down(connection)
9
+ execute_from_folder('down', connection)
10
+ unregister_this_patch(connection)
11
+ end
12
+
13
+ def md5_up
14
+ md5(::File.read(file_path_for('up')))
15
+ end
16
+
17
+ def md5_down
18
+ md5(::File.read(file_path_for('down')))
19
+ end
20
+
21
+ def comment
22
+ comments = []
23
+
24
+ up_comment = get_comment(file_path_for('up'))
25
+ down_comment = get_comment(file_path_for('down'))
26
+
27
+ if up_comment != ''
28
+ comments << 'UP:'
29
+ comments << up_comment
30
+ end
31
+
32
+ if down_comment != ''
33
+ comments << "" if up_comment != ''
34
+ comments << 'DOWN:'
35
+ comments << down_comment
36
+ end
37
+
38
+ comments.join("\n")
39
+ end
40
+
41
+ protected
42
+
43
+ def execute_from_folder(patch_type, connection)
44
+ execute_file(connection, file_path_for(patch_type))
45
+ end
46
+
47
+ def file_path_for(patch_type)
48
+ file_selector = ::File.join(@path, "#{patch_type}.{#{accepted_extensions.join(',')}}")
49
+ file_path = Dir.glob(file_selector).first
50
+ end
51
+ end
@@ -0,0 +1,96 @@
1
+ require 'digest/md5'
2
+ require 'database_patcher'
3
+ class DatabasePatcher::PatchEntity
4
+ require 'database_patcher/patch_entity/file'
5
+ require 'database_patcher/patch_entity/folder'
6
+
7
+ def self.factory(path)
8
+ if ::File.directory?(path)
9
+ self::Folder.new(path)
10
+ else
11
+ self::File.new(path)
12
+ end
13
+ end
14
+
15
+ def initialize(path)
16
+ @path = path
17
+ end
18
+
19
+ def timestamp
20
+ basename = ::File.basename(@path, '.*')
21
+ basename.scan(/^\d+/).flatten.first.to_i
22
+ end
23
+
24
+ def up(_connection)
25
+ raise
26
+ end
27
+
28
+ def down(_connection)
29
+ raise
30
+ end
31
+
32
+ def execute_file(connection, path)
33
+ case ::File.extname(path)
34
+
35
+ when '.rb', '.ru'
36
+ connection.instance_eval(::File.read(path))
37
+
38
+ when '.sql'
39
+ connection.run(::File.read(path))
40
+
41
+ else
42
+ raise_unknown_extension_for(path)
43
+
44
+ end
45
+ end
46
+
47
+ def register_this_patch(connection)
48
+ connection[:installed_patches].insert(patch_record)
49
+ end
50
+
51
+ def unregister_this_patch(connection)
52
+ connection[:installed_patches].where(uniq_indentifier).delete
53
+ end
54
+
55
+ def uniq_indentifier
56
+ {
57
+ timestamp: timestamp,
58
+ md5_down: md5_down,
59
+ md5_up: md5_up
60
+ }
61
+ end
62
+
63
+ def patch_record
64
+ uniq_indentifier.merge(comment: comment)
65
+ end
66
+
67
+ def md5(string)
68
+ ::Digest::MD5.hexdigest(string)
69
+ end
70
+
71
+ def get_comment(file_path)
72
+ case ::File.extname(file_path)
73
+
74
+ when '.rb', '.ru'
75
+ extract_comments(file_path, '#')
76
+
77
+ when '.sql'
78
+ extract_comments(file_path, '--')
79
+
80
+ else
81
+ raise_unknown_extension_for(file_path)
82
+ end
83
+ end
84
+
85
+ def extract_comments(file_path, comment_seperator)
86
+ ::File.read(file_path).scan(/#{comment_seperator}(.*)/).flatten.map(&:strip).join("\n")
87
+ end
88
+
89
+ def accepted_extensions
90
+ %w(rb ru sql)
91
+ end
92
+
93
+ def raise_unknown_extension_for(path)
94
+ raise("unknown patch file extension: #{::File.extname(path)} (#{path})")
95
+ end
96
+ end
@@ -0,0 +1,3 @@
1
+ module DatabasePatcher
2
+ VERSION = "1.1.0"
3
+ end
@@ -0,0 +1,13 @@
1
+ require 'fileutils'
2
+ module DatabasePatcher
3
+ require 'database_patcher/db'
4
+ require 'database_patcher/cli'
5
+ require 'database_patcher/fetcher'
6
+ require 'database_patcher/version'
7
+ require 'database_patcher/command'
8
+ require 'database_patcher/environment'
9
+ require 'database_patcher/initializer'
10
+ require 'database_patcher/patch_entity'
11
+ require 'database_patcher/patch_creator'
12
+ require 'database_patcher/patch_applier'
13
+ end
metadata ADDED
@@ -0,0 +1,159 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: database_patcher
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Adam Luzsi
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-05-26 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.11'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.11'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: dotenv
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: pg
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
+ - !ruby/object:Gem::Dependency
84
+ name: sequel
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: Simple tool for managing database patches
98
+ email:
99
+ - adamluzsi@gmail.com
100
+ executables:
101
+ - database_patcher
102
+ extensions: []
103
+ extra_rdoc_files: []
104
+ files:
105
+ - ".env"
106
+ - ".gitignore"
107
+ - ".rspec"
108
+ - ".travis.yml"
109
+ - CODE_OF_CONDUCT.md
110
+ - Gemfile
111
+ - README.md
112
+ - Rakefile
113
+ - bin/console
114
+ - bin/setup
115
+ - database_patcher.gemspec
116
+ - exe/database_patcher
117
+ - lib/database_patcher.rb
118
+ - lib/database_patcher/cli.rb
119
+ - lib/database_patcher/command.rb
120
+ - lib/database_patcher/command/create_patch.rb
121
+ - lib/database_patcher/command/down.rb
122
+ - lib/database_patcher/command/help.rb
123
+ - lib/database_patcher/command/init.rb
124
+ - lib/database_patcher/command/rollback.rb
125
+ - lib/database_patcher/command/up.rb
126
+ - lib/database_patcher/db.rb
127
+ - lib/database_patcher/environment.rb
128
+ - lib/database_patcher/fetcher.rb
129
+ - lib/database_patcher/initializer.rb
130
+ - lib/database_patcher/patch_applier.rb
131
+ - lib/database_patcher/patch_creator.rb
132
+ - lib/database_patcher/patch_entity.rb
133
+ - lib/database_patcher/patch_entity/file.rb
134
+ - lib/database_patcher/patch_entity/folder.rb
135
+ - lib/database_patcher/version.rb
136
+ homepage: https://github.com/adamluzsi/database_patcher.rb
137
+ licenses: []
138
+ metadata: {}
139
+ post_install_message:
140
+ rdoc_options: []
141
+ require_paths:
142
+ - lib
143
+ required_ruby_version: !ruby/object:Gem::Requirement
144
+ requirements:
145
+ - - ">="
146
+ - !ruby/object:Gem::Version
147
+ version: '0'
148
+ required_rubygems_version: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ requirements: []
154
+ rubyforge_project:
155
+ rubygems_version: 2.4.8
156
+ signing_key:
157
+ specification_version: 4
158
+ summary: Simple tool for managing database patches
159
+ test_files: []