ardb 0.0.1 → 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.
- data/ardb.gemspec +4 -0
- data/bin/ardb +7 -0
- data/lib/ardb.rb +93 -1
- data/lib/ardb/adapter/base.rb +24 -0
- data/lib/ardb/adapter/mysql.rb +22 -0
- data/lib/ardb/adapter/postgresql.rb +49 -0
- data/lib/ardb/adapter/sqlite.rb +36 -0
- data/lib/ardb/cli.rb +113 -0
- data/lib/ardb/migration_helpers.rb +74 -0
- data/lib/ardb/runner.rb +65 -0
- data/lib/ardb/runner/create_command.rb +21 -0
- data/lib/ardb/runner/drop_command.rb +21 -0
- data/lib/ardb/runner/generate_command.rb +64 -0
- data/lib/ardb/runner/migrate_command.rb +48 -0
- data/lib/ardb/test_helpers.rb +24 -0
- data/lib/ardb/version.rb +1 -1
- data/test/helper.rb +17 -1
- data/test/unit/adapter/base_tests.rb +41 -0
- data/test/unit/adapter/mysql_tests.rb +40 -0
- data/test/unit/adapter/postgresql_tests.rb +39 -0
- data/test/unit/adapter/sqlite_tests.rb +39 -0
- data/test/unit/ardb_tests.rb +55 -0
- data/test/unit/config_tests.rb +42 -0
- data/test/unit/migration_helpers_tests.rb +59 -0
- data/test/unit/runner/create_command_tests.rb +18 -0
- data/test/unit/runner/drop_command_tests.rb +17 -0
- data/test/unit/runner/generate_command_tests.rb +46 -0
- data/test/unit/runner/migrate_command_tests.rb +62 -0
- data/test/unit/runner_tests.rb +72 -0
- data/test/unit/test_helpers_tests.rb +14 -0
- metadata +99 -6
data/ardb.gemspec
CHANGED
data/bin/ardb
ADDED
data/lib/ardb.rb
CHANGED
@@ -1,3 +1,95 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
require 'ns-options'
|
3
|
+
require 'pathname'
|
4
|
+
require 'singleton'
|
5
|
+
|
1
6
|
require "ardb/version"
|
2
7
|
|
3
|
-
module Ardb
|
8
|
+
module Ardb
|
9
|
+
NotConfiguredError = Class.new(RuntimeError)
|
10
|
+
|
11
|
+
def self.config; Config; end
|
12
|
+
def self.configure(&block); Config.define(&block); end
|
13
|
+
|
14
|
+
def self.adapter; Adapter.current; end
|
15
|
+
|
16
|
+
def self.validate!
|
17
|
+
if !self.config.required_set?
|
18
|
+
raise NotConfiguredError, "missing required configs"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.init(connection=true)
|
23
|
+
validate!
|
24
|
+
Adapter.init
|
25
|
+
|
26
|
+
# setup AR
|
27
|
+
ActiveRecord::Base.logger = self.config.logger
|
28
|
+
ActiveRecord::Base.establish_connection(self.config.db.to_hash) if connection
|
29
|
+
end
|
30
|
+
|
31
|
+
class Config
|
32
|
+
include NsOptions::Proxy
|
33
|
+
|
34
|
+
namespace :db do
|
35
|
+
option :adapter, String, :required => true
|
36
|
+
option :database, String, :required => true
|
37
|
+
option :encoding, String, :required => false
|
38
|
+
option :url, String, :required => false
|
39
|
+
option :username, String, :required => false
|
40
|
+
option :password, String, :required => false
|
41
|
+
end
|
42
|
+
|
43
|
+
option :root_path, Pathname, :required => true
|
44
|
+
option :logger, :required => true
|
45
|
+
option :migrations_path, String, :default => proc{ default_migrations_path }
|
46
|
+
option :schema_path, String, :default => proc{ default_schema_path }
|
47
|
+
|
48
|
+
def self.default_migrations_path; root_path.join("db/migrations"); end
|
49
|
+
def self.default_schema_path; root_path.join("db/schema.rb"); end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
class Adapter
|
54
|
+
include Singleton
|
55
|
+
|
56
|
+
attr_reader :current
|
57
|
+
|
58
|
+
def init
|
59
|
+
@current = Adapter.send(Ardb.config.db.adapter)
|
60
|
+
end
|
61
|
+
|
62
|
+
def reset
|
63
|
+
@current = nil
|
64
|
+
end
|
65
|
+
|
66
|
+
def sqlite
|
67
|
+
require 'ardb/adapter/sqlite'
|
68
|
+
Adapter::Sqlite.new
|
69
|
+
end
|
70
|
+
alias_method :sqlite3, :sqlite
|
71
|
+
|
72
|
+
def postgresql
|
73
|
+
require 'ardb/adapter/postgresql'
|
74
|
+
Adapter::Postgresql.new
|
75
|
+
end
|
76
|
+
|
77
|
+
def mysql
|
78
|
+
require 'ardb/adapter/mysql'
|
79
|
+
Adapter::Mysql.new
|
80
|
+
end
|
81
|
+
alias_method :mysql2, :mysql
|
82
|
+
|
83
|
+
# nice singleton api
|
84
|
+
|
85
|
+
def self.method_missing(method, *args, &block)
|
86
|
+
self.instance.send(method, *args, &block)
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.respond_to?(method)
|
90
|
+
super || self.instance.respond_to?(method)
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Ardb; end
|
2
|
+
class Ardb::Adapter; end
|
3
|
+
class Ardb::Adapter::Base
|
4
|
+
|
5
|
+
attr_reader :config_settings, :database
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@config_settings = Ardb.config.db.to_hash
|
9
|
+
@database = Ardb.config.db.database
|
10
|
+
end
|
11
|
+
|
12
|
+
def foreign_key_add_sql(*args); raise NotImplementedError; end
|
13
|
+
def foreign_key_drop_sql(*args); raise NotImplementedError; end
|
14
|
+
|
15
|
+
def create_db(*args); raise NotImplementedError; end
|
16
|
+
def drop_db(*args); raise NotImplementedError; end
|
17
|
+
|
18
|
+
def drop_tables(*args); raise NotImplementedError; end
|
19
|
+
|
20
|
+
def ==(other_adapter)
|
21
|
+
self.class == other_adapter.class
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'ardb'
|
2
|
+
require 'ardb/adapter/base'
|
3
|
+
|
4
|
+
class Ardb::Adapter
|
5
|
+
|
6
|
+
class Mysql < Base
|
7
|
+
|
8
|
+
def foreign_key_add_sql
|
9
|
+
"ALTER TABLE :from_table"\
|
10
|
+
" ADD CONSTRAINT :name"\
|
11
|
+
" FOREIGN KEY (:from_column)"\
|
12
|
+
" REFERENCES :to_table (:to_column)"
|
13
|
+
end
|
14
|
+
|
15
|
+
def foreign_key_drop_sql
|
16
|
+
"ALTER TABLE :from_table"\
|
17
|
+
" DROP FOREIGN KEY :name"
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'ardb'
|
2
|
+
require 'ardb/adapter/base'
|
3
|
+
|
4
|
+
class Ardb::Adapter
|
5
|
+
|
6
|
+
class Postgresql < Base
|
7
|
+
|
8
|
+
def public_schema_settings
|
9
|
+
self.config_settings.merge({
|
10
|
+
:database => 'postgres',
|
11
|
+
:schema_search_path => 'public'
|
12
|
+
})
|
13
|
+
end
|
14
|
+
|
15
|
+
def create_db
|
16
|
+
ActiveRecord::Base.establish_connection(self.public_schema_settings)
|
17
|
+
ActiveRecord::Base.connection.create_database(self.database, self.config_settings)
|
18
|
+
ActiveRecord::Base.establish_connection(self.config_settings)
|
19
|
+
end
|
20
|
+
|
21
|
+
def drop_db
|
22
|
+
ActiveRecord::Base.establish_connection(self.public_schema_settings)
|
23
|
+
ActiveRecord::Base.connection.drop_database(self.database)
|
24
|
+
end
|
25
|
+
|
26
|
+
def drop_tables
|
27
|
+
ActiveRecord::Base.connection.tap do |conn|
|
28
|
+
tables = conn.execute "SELECT table_name"\
|
29
|
+
" FROM information_schema.tables"\
|
30
|
+
" WHERE table_schema = 'public';"
|
31
|
+
tables.each{ |row| conn.execute "DROP TABLE #{row['table_name']} CASCADE" }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def foreign_key_add_sql
|
36
|
+
"ALTER TABLE :from_table"\
|
37
|
+
" ADD CONSTRAINT :name"\
|
38
|
+
" FOREIGN KEY (:from_column)"\
|
39
|
+
" REFERENCES :to_table (:to_column)"
|
40
|
+
end
|
41
|
+
|
42
|
+
def foreign_key_drop_sql
|
43
|
+
"ALTER TABLE :from_table"\
|
44
|
+
" DROP CONSTRAINT :name"
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'ardb'
|
4
|
+
require 'ardb/adapter/base'
|
5
|
+
|
6
|
+
class Ardb::Adapter
|
7
|
+
|
8
|
+
class Sqlite < Base
|
9
|
+
|
10
|
+
def db_file_path
|
11
|
+
if (path = Pathname.new(self.database)).absolute?
|
12
|
+
path.to_s
|
13
|
+
else
|
14
|
+
Ardb.config.root_path.join(path).to_s
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def validate!
|
19
|
+
if File.exist?(self.db_file_path)
|
20
|
+
raise Ardb::Runner::CmdError, "#{self.database} already exists"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def create_db
|
25
|
+
validate!
|
26
|
+
FileUtils.mkdir_p File.dirname(self.db_file_path)
|
27
|
+
ActiveRecord::Base.establish_connection(self.config_settings)
|
28
|
+
end
|
29
|
+
|
30
|
+
def drop_db
|
31
|
+
FileUtils.rm(self.db_file_path) if File.exist?(self.db_file_path)
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
data/lib/ardb/cli.rb
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
require 'ardb/version'
|
2
|
+
require 'ardb/runner'
|
3
|
+
|
4
|
+
module Ardb
|
5
|
+
|
6
|
+
class CLI
|
7
|
+
|
8
|
+
def self.run(*args)
|
9
|
+
self.new.run(*args)
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@cli = CLIRB.new do
|
14
|
+
option 'root_path', 'root path Ardb should use (`Dir.pwd`)', {
|
15
|
+
:abbrev => 'p', :value => String
|
16
|
+
}
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def run(*args)
|
21
|
+
begin
|
22
|
+
@cli.parse!(args)
|
23
|
+
Ardb::Runner.new(@cli.args, @cli.opts).run
|
24
|
+
rescue CLIRB::HelpExit
|
25
|
+
puts help
|
26
|
+
rescue CLIRB::VersionExit
|
27
|
+
puts Ardb::VERSION
|
28
|
+
rescue Ardb::Runner::UnknownCmdError => err
|
29
|
+
$stderr.puts "#{err.message}\n\n"
|
30
|
+
$stderr.puts help
|
31
|
+
exit(1)
|
32
|
+
rescue Ardb::NotConfiguredError, Ardb::Runner::CmdError => err
|
33
|
+
$stderr.puts "#{err.message}"
|
34
|
+
exit(1)
|
35
|
+
rescue Ardb::Runner::CmdFail => err
|
36
|
+
exit(1)
|
37
|
+
rescue CLIRB::Error => exception
|
38
|
+
$stderr.puts "#{exception.message}\n\n"
|
39
|
+
$stderr.puts help
|
40
|
+
exit(1)
|
41
|
+
rescue Exception => exception
|
42
|
+
$stderr.puts "#{exception.class}: #{exception.message}"
|
43
|
+
$stderr.puts exception.backtrace.join("\n")
|
44
|
+
exit(1)
|
45
|
+
end
|
46
|
+
exit(0)
|
47
|
+
end
|
48
|
+
|
49
|
+
def help
|
50
|
+
"Usage: ardb [options] COMMAND\n"\
|
51
|
+
"\n"\
|
52
|
+
"Options:"\
|
53
|
+
"#{@cli}"
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
class CLIRB # Version 1.0.0, https://github.com/redding/cli.rb
|
59
|
+
Error = Class.new(RuntimeError);
|
60
|
+
HelpExit = Class.new(RuntimeError); VersionExit = Class.new(RuntimeError)
|
61
|
+
attr_reader :argv, :args, :opts, :data
|
62
|
+
|
63
|
+
def initialize(&block)
|
64
|
+
@options = []; instance_eval(&block) if block
|
65
|
+
require 'optparse'
|
66
|
+
@data, @args, @opts = [], [], {}; @parser = OptionParser.new do |p|
|
67
|
+
p.banner = ''; @options.each do |o|
|
68
|
+
@opts[o.name] = o.value; p.on(*o.parser_args){ |v| @opts[o.name] = v }
|
69
|
+
end
|
70
|
+
p.on_tail('--version', ''){ |v| raise VersionExit, v.to_s }
|
71
|
+
p.on_tail('--help', ''){ |v| raise HelpExit, v.to_s }
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def option(*args); @options << Option.new(*args); end
|
76
|
+
def parse!(argv)
|
77
|
+
@args = (argv || []).dup.tap do |args_list|
|
78
|
+
begin; @parser.parse!(args_list)
|
79
|
+
rescue OptionParser::ParseError => err; raise Error, err.message; end
|
80
|
+
end; @data = @args + [@opts]
|
81
|
+
end
|
82
|
+
def to_s; @parser.to_s; end
|
83
|
+
def inspect
|
84
|
+
"#<#{self.class}:#{'0x0%x' % (object_id << 1)} @data=#{@data.inspect}>"
|
85
|
+
end
|
86
|
+
|
87
|
+
class Option
|
88
|
+
attr_reader :name, :opt_name, :desc, :abbrev, :value, :klass, :parser_args
|
89
|
+
|
90
|
+
def initialize(name, *args)
|
91
|
+
settings, @desc = args.last.kind_of?(::Hash) ? args.pop : {}, args.pop || ''
|
92
|
+
@name, @opt_name, @abbrev = parse_name_values(name, settings[:abbrev])
|
93
|
+
@value, @klass = gvalinfo(settings[:value])
|
94
|
+
@parser_args = if [TrueClass, FalseClass, NilClass].include?(@klass)
|
95
|
+
["-#{@abbrev}", "--[no-]#{@opt_name}", @desc]
|
96
|
+
else
|
97
|
+
["-#{@abbrev}", "--#{@opt_name} #{@opt_name.upcase}", @klass, @desc]
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
|
103
|
+
def parse_name_values(name, custom_abbrev)
|
104
|
+
[ (processed_name = name.to_s.strip.downcase), processed_name.gsub('_', '-'),
|
105
|
+
custom_abbrev || processed_name.gsub(/[^a-z]/, '').chars.first || 'a'
|
106
|
+
]
|
107
|
+
end
|
108
|
+
def gvalinfo(v); v.kind_of?(Class) ? [nil,gklass(v)] : [v,gklass(v.class)]; end
|
109
|
+
def gklass(k); k == Fixnum ? Integer : k; end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'ardb'
|
2
|
+
|
3
|
+
module Ardb; end
|
4
|
+
module Ardb::MigrationHelpers
|
5
|
+
module_function
|
6
|
+
|
7
|
+
def foreign_key(from_table, from_column, to_table, options={})
|
8
|
+
fk = ForeignKey.new(from_table, from_column, to_table, options)
|
9
|
+
execute(fk.add_sql)
|
10
|
+
end
|
11
|
+
|
12
|
+
def drop_foreign_key(*args)
|
13
|
+
from_table, from_column = args[0..1]
|
14
|
+
options = args.last.kind_of?(Hash) ? args.last : {}
|
15
|
+
fk = ForeignKey.new(from_table, from_column, nil, options)
|
16
|
+
execute(fk.drop_sql)
|
17
|
+
end
|
18
|
+
|
19
|
+
def remove_column_with_fk(table, column)
|
20
|
+
drop_foreign_key(table, column)
|
21
|
+
remove_column(table, column)
|
22
|
+
end
|
23
|
+
|
24
|
+
class ForeignKey
|
25
|
+
attr_reader :from_table, :from_column, :to_table, :to_column, :name, :adapter
|
26
|
+
|
27
|
+
def initialize(from_table, from_column, to_table, options=nil)
|
28
|
+
options ||= {}
|
29
|
+
@from_table = from_table.to_s
|
30
|
+
@from_column = from_column.to_s
|
31
|
+
@to_table = to_table.to_s
|
32
|
+
@to_column = (options[:to_column] || 'id').to_s
|
33
|
+
@name = (options[:name] || "fk_#{@from_table}_#{@from_column}").to_s
|
34
|
+
@adapter = Ardb::Adapter.send(Ardb.config.db.adapter)
|
35
|
+
end
|
36
|
+
|
37
|
+
def add_sql
|
38
|
+
apply_data(@adapter.foreign_key_add_sql)
|
39
|
+
end
|
40
|
+
|
41
|
+
def drop_sql
|
42
|
+
apply_data(@adapter.foreign_key_drop_sql)
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def apply_data(template_sql)
|
48
|
+
template_sql.
|
49
|
+
gsub(':from_table', @from_table).
|
50
|
+
gsub(':from_column', @from_column).
|
51
|
+
gsub(':to_table', @to_table).
|
52
|
+
gsub(':to_column', @to_column).
|
53
|
+
gsub(':name', @name)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# This file will setup the AR migration command recorder for being able to change our
|
58
|
+
# stuff, require it in an initializer
|
59
|
+
|
60
|
+
module RecorderMixin
|
61
|
+
|
62
|
+
def foreign_key(*args)
|
63
|
+
record(:foreign_key, args)
|
64
|
+
end
|
65
|
+
|
66
|
+
protected
|
67
|
+
|
68
|
+
def invert_foreign_key(args)
|
69
|
+
[ :drop_foreign_key, args ]
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
data/lib/ardb/runner.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'ardb'
|
2
|
+
|
3
|
+
module Ardb; end
|
4
|
+
class Ardb::Runner
|
5
|
+
UnknownCmdError = Class.new(ArgumentError)
|
6
|
+
CmdError = Class.new(RuntimeError)
|
7
|
+
CmdFail = Class.new(RuntimeError)
|
8
|
+
|
9
|
+
attr_reader :cmd_name, :cmd_args, :opts, :root_path
|
10
|
+
|
11
|
+
def initialize(args, opts)
|
12
|
+
@opts = opts
|
13
|
+
@cmd_name = args.shift || ""
|
14
|
+
@cmd_args = args
|
15
|
+
@root_path = @opts.delete('root_path') || Dir.pwd
|
16
|
+
end
|
17
|
+
|
18
|
+
def run
|
19
|
+
setup_run
|
20
|
+
case @cmd_name
|
21
|
+
when 'migrate'
|
22
|
+
require 'ardb/runner/migrate_command'
|
23
|
+
MigrateCommand.new.run
|
24
|
+
when 'generate'
|
25
|
+
require 'ardb/runner/generate_command'
|
26
|
+
GenerateCommand.new(@cmd_args).run
|
27
|
+
when 'create'
|
28
|
+
require 'ardb/runner/create_command'
|
29
|
+
CreateCommand.new.run
|
30
|
+
when 'drop'
|
31
|
+
require 'ardb/runner/drop_command'
|
32
|
+
DropCommand.new.run
|
33
|
+
when 'null'
|
34
|
+
NullCommand.new.run
|
35
|
+
else
|
36
|
+
raise UnknownCmdError, "unknown command `#{@cmd_name}`"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def setup_run
|
43
|
+
Ardb.config.root_path = @root_path
|
44
|
+
DbConfigFile.new.require_if_exists
|
45
|
+
Ardb.init(false) # don't establish a connection
|
46
|
+
end
|
47
|
+
|
48
|
+
class DbConfigFile
|
49
|
+
PATH = 'config/db.rb'
|
50
|
+
def initialize
|
51
|
+
@path = Ardb.config.root_path.join(PATH)
|
52
|
+
end
|
53
|
+
|
54
|
+
def require_if_exists
|
55
|
+
require @path.to_s if File.exists?(@path.to_s)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class NullCommand
|
60
|
+
def run
|
61
|
+
# if this was a real command it would do something here
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|