storey 0.4.2 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -0
- data/lib/storey/builds_dump_command.rb +36 -0
- data/lib/storey/builds_load_command.rb +31 -0
- data/lib/storey/dumper.rb +5 -9
- data/lib/storey/duplicator.rb +44 -46
- data/lib/storey/exceptions.rb +2 -0
- data/lib/storey/ruby_dumper.rb +1 -5
- data/lib/storey/schema_name.rb +38 -0
- data/lib/storey/sql_dumper.rb +23 -11
- data/lib/storey/utils.rb +23 -0
- data/lib/storey/version.rb +1 -1
- data/lib/storey.rb +22 -27
- data/spec/spec_helper.rb +2 -0
- data/spec/storey/builds_dump_command_spec.rb +87 -0
- data/spec/storey/builds_load_command_spec.rb +56 -0
- data/spec/storey/create_spec.rb +27 -16
- data/spec/storey/duplicator_spec.rb +12 -0
- data/spec/storey/schema_name_spec.rb +92 -0
- data/spec/storey/utils_spec.rb +60 -0
- data/storey.gemspec +1 -1
- metadata +16 -6
- data/spec/storey/command_line_switches_spec.rb +0 -22
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f43d4bfca6dbcd859c187fb920e7e296d6c1809b
|
4
|
+
data.tar.gz: 322eb8906efac105f0a0c153bc4d8c47209eea28
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 27fb55a9705fe0a214855fb5c9a6a08f8a0fc4983cb877daff4daf2c2e0bdceab06ed6cdb006e95e72e4c200dc706198ea872f56f78524198aafcf87b02157d3
|
7
|
+
data.tar.gz: e29bed608be63691d0d7a62efa278b8b57eb7ea82d0154665522a479cd74c14fc61e40eac2e7712b4407ef74806c60a426345b9385a0d512aaa3780b55987171
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
# v0.5.0
|
2
|
+
|
3
|
+
- Removed attr_accessors from Storey::Duplicator (they were never meant to be part of the public API anyway)
|
4
|
+
- Validate the schema name when creating schemas
|
5
|
+
- Clean source and target files after duplicating [#18](https://github.com/ramontayag/storey/issues/18)
|
6
|
+
|
1
7
|
# v0.4.2
|
2
8
|
|
3
9
|
- `rake storey:migrate VERSION=xxxx` now works and uses the `VERSION` environment variable
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Storey
|
2
|
+
class BuildsDumpCommand
|
3
|
+
|
4
|
+
easy_class_to_instance
|
5
|
+
|
6
|
+
def initialize(options={})
|
7
|
+
@options = options
|
8
|
+
if @options[:database].blank?
|
9
|
+
raise ArgumentError, 'database must be supplied'
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def execute
|
14
|
+
|
15
|
+
switches = {}
|
16
|
+
switches['schema-only'] = nil if @options[:structure_only]
|
17
|
+
switches['no-privileges'] = nil
|
18
|
+
switches['no-owner'] = nil
|
19
|
+
switches[:file] = Shellwords.escape(@options[:file])
|
20
|
+
|
21
|
+
if @options[:schemas]
|
22
|
+
schemas = @options[:schemas].split(',')
|
23
|
+
schemas_switches = schemas.map do |part|
|
24
|
+
Utils.command_line_switches_from({schema: Shellwords.escape(part) })
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
command_parts = ['pg_dump',
|
29
|
+
Utils.command_line_switches_from(switches),
|
30
|
+
schemas_switches,
|
31
|
+
@options[:database]]
|
32
|
+
command_parts.compact.join(' ')
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Storey
|
2
|
+
class BuildsLoadCommand
|
3
|
+
|
4
|
+
easy_class_to_instance
|
5
|
+
|
6
|
+
def initialize(options={})
|
7
|
+
@options = options
|
8
|
+
end
|
9
|
+
|
10
|
+
def execute
|
11
|
+
switches = {}
|
12
|
+
if @options[:file].present?
|
13
|
+
switches[:file] = Shellwords.escape(@options[:file])
|
14
|
+
end
|
15
|
+
switches[:dbname] = @options[:database]
|
16
|
+
switches[:username] = @options[:username] if @options[:username].present?
|
17
|
+
switches[:host] = @options[:host] if @options[:host].present?
|
18
|
+
switches[:port] = @options[:port] if @options[:port].present?
|
19
|
+
if @options[:password].present?
|
20
|
+
switches[:password] = @options[:password]
|
21
|
+
else
|
22
|
+
switches['no-password'] = nil
|
23
|
+
end
|
24
|
+
switches[:command] = %Q("#{@options[:command]}") if @options[:command].present?
|
25
|
+
command_parts = ['psql',
|
26
|
+
Utils.command_line_switches_from(switches)]
|
27
|
+
command_parts.join(' ')
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
data/lib/storey/dumper.rb
CHANGED
@@ -1,12 +1,9 @@
|
|
1
1
|
module Storey
|
2
|
-
|
2
|
+
class Dumper
|
3
3
|
|
4
|
+
easy_class_to_instance
|
4
5
|
delegate :dump, to: :dumper
|
5
6
|
|
6
|
-
def self.dump(*args)
|
7
|
-
self.new(*args).dump
|
8
|
-
end
|
9
|
-
|
10
7
|
def initialize(options={})
|
11
8
|
@options = options
|
12
9
|
end
|
@@ -17,10 +14,9 @@ module Storey
|
|
17
14
|
|
18
15
|
def dumper_class
|
19
16
|
schema_format = Rails.configuration.active_record.schema_format || :ruby
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
end
|
17
|
+
class_name = "#{schema_format.to_s.classify}Dumper"
|
18
|
+
namespace = self.class.name.deconstantize.constantize
|
19
|
+
namespace.const_get class_name
|
24
20
|
end
|
25
21
|
|
26
22
|
end
|
data/lib/storey/duplicator.rb
CHANGED
@@ -1,86 +1,84 @@
|
|
1
1
|
module Storey
|
2
2
|
class Duplicator
|
3
3
|
|
4
|
-
attr_accessor(:source_schema,
|
5
|
-
:target_schema,
|
6
|
-
:source_file,
|
7
|
-
:target_file,
|
8
|
-
:structure_only,
|
9
|
-
:file_prefix,
|
10
|
-
:dump_path,
|
11
|
-
:source_dump_path,
|
12
|
-
:target_dump_path)
|
13
|
-
|
14
4
|
def initialize(from_schema, to_schema, options={})
|
15
5
|
unless from_schema
|
16
6
|
fail SchemaNotFound, "cannot duplicate from nil schema"
|
17
7
|
end
|
18
8
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
self.target_file = File.join(
|
32
|
-
self.target_dump_path,
|
33
|
-
"#{self.file_prefix}_#{self.target_schema}.sql"
|
34
|
-
)
|
9
|
+
@dump_dir = File.join(Rails.root, 'tmp', 'schema_dumps')
|
10
|
+
@source_dump_dir = File.join(@dump_dir, 'source')
|
11
|
+
@target_dump_dir = File.join(@dump_dir, 'target')
|
12
|
+
@structure_only = options[:structure_only] || false
|
13
|
+
|
14
|
+
@source_schema = suffixify(from_schema)
|
15
|
+
@target_schema = suffixify(to_schema)
|
16
|
+
@file_prefix = "#{Time.now.to_i}_#{rand(100000)}"
|
17
|
+
@source_file = File.join(@source_dump_dir,
|
18
|
+
"#{@file_prefix}_#{@source_schema}.sql")
|
19
|
+
@target_file = File.join(@target_dump_dir,
|
20
|
+
"#{@file_prefix}_#{@target_schema}.sql")
|
35
21
|
end
|
36
22
|
|
37
23
|
def perform!
|
38
24
|
dump_schema
|
39
25
|
replace_occurrences
|
40
26
|
load_schema
|
27
|
+
clean_source
|
28
|
+
clean_target
|
41
29
|
end
|
42
30
|
|
43
31
|
private
|
44
32
|
|
33
|
+
def clean_source
|
34
|
+
FileUtils.rm(@source_file)
|
35
|
+
end
|
36
|
+
|
37
|
+
def clean_target
|
38
|
+
FileUtils.rm(@target_file)
|
39
|
+
end
|
40
|
+
|
45
41
|
def dump_schema(options={})
|
46
42
|
ENV['PGPASSWORD'] = Storey.database_config[:password]
|
47
43
|
prepare_schema_dump_directories
|
48
44
|
|
49
|
-
|
45
|
+
if Storey.database_config[:host].present?
|
50
46
|
options[:host] ||= Storey.database_config[:host]
|
51
47
|
end
|
52
|
-
options[:username] ||= Storey.database_config[:username]
|
53
|
-
options[:file] ||= self.source_file
|
54
|
-
options[:schema] ||= self.source_schema
|
55
48
|
|
56
|
-
|
57
|
-
|
58
|
-
|
49
|
+
arg_options = options.dup
|
50
|
+
arg_options[:username] ||= Storey.database_config[:username]
|
51
|
+
arg_options[:file] ||= @source_file
|
52
|
+
arg_options[:schema] ||= @source_schema
|
53
|
+
arg_options['schema-only'] = nil if @structure_only
|
54
|
+
|
55
|
+
switches = Utils.command_line_switches_from(arg_options)
|
59
56
|
|
60
57
|
success = system("pg_dump #{switches} #{Storey.database_config[:database]}")
|
61
58
|
unless success
|
62
|
-
raise StoreyError, "There seems to have been a problem dumping `#{
|
59
|
+
raise StoreyError, "There seems to have been a problem dumping `#{@source_schema}` to make a copy of it into `#{@target_schema}`"
|
63
60
|
end
|
64
61
|
end
|
65
62
|
|
66
63
|
def prepare_schema_dump_directories
|
67
|
-
[
|
64
|
+
[@source_dump_dir, @target_dump_dir].each do |d|
|
68
65
|
FileUtils.mkdir_p(d)
|
69
66
|
end
|
70
67
|
end
|
71
68
|
|
72
69
|
def load_schema(options={})
|
73
|
-
options[:file] ||=
|
74
|
-
|
70
|
+
options[:file] ||= @target_file
|
71
|
+
psql_options = Storey.database_config.merge(options)
|
75
72
|
|
76
73
|
if duplicating_from_default?
|
77
74
|
# Since we are copying the source schema and we're after structure only,
|
78
75
|
# the dump_schema ended up creating a SQL file without the "CREATE SCHEMA" command
|
79
76
|
# thus we have to create it manually
|
80
|
-
::Storey.create_plain_schema
|
77
|
+
::Storey.create_plain_schema @target_schema
|
81
78
|
end
|
82
79
|
|
83
|
-
|
80
|
+
psql_load_command = BuildsLoadCommand.execute(psql_options)
|
81
|
+
system psql_load_command
|
84
82
|
|
85
83
|
copy_source_schema_migrations
|
86
84
|
|
@@ -88,7 +86,7 @@ module Storey
|
|
88
86
|
end
|
89
87
|
|
90
88
|
def copy_source_schema_migrations
|
91
|
-
::Storey.switch
|
89
|
+
::Storey.switch @target_schema do
|
92
90
|
source_schema_migrations.each do |version|
|
93
91
|
unless target_schema_migrations.include?(version)
|
94
92
|
command = "INSERT INTO schema_migrations (version) VALUES ('#{version}');"
|
@@ -99,28 +97,28 @@ module Storey
|
|
99
97
|
end
|
100
98
|
|
101
99
|
def source_schema_migrations
|
102
|
-
::Storey.switch(
|
100
|
+
::Storey.switch(@source_schema) do
|
103
101
|
::ActiveRecord::Migrator.get_all_versions
|
104
102
|
end
|
105
103
|
end
|
106
104
|
|
107
105
|
def target_schema_migrations
|
108
|
-
::Storey.switch(
|
106
|
+
::Storey.switch(@target_schema) do
|
109
107
|
::ActiveRecord::Migrator.get_all_versions
|
110
108
|
end
|
111
109
|
end
|
112
110
|
|
113
111
|
def replace_occurrences
|
114
|
-
File.open(
|
112
|
+
File.open(@source_file, 'r') do |file|
|
115
113
|
file.each_line do |line|
|
116
|
-
new_line = line.gsub(/#{
|
117
|
-
File.open(
|
114
|
+
new_line = line.gsub(/#{@source_schema}/, @target_schema)
|
115
|
+
File.open(@target_file, 'a') {|tf| tf.puts new_line}
|
118
116
|
end
|
119
117
|
end
|
120
118
|
end
|
121
119
|
|
122
120
|
def duplicating_from_default?
|
123
|
-
::Storey.matches_default_search_path?(
|
121
|
+
::Storey.matches_default_search_path?(@source_schema) && @structure_only
|
124
122
|
end
|
125
123
|
|
126
124
|
def suffixify(schema_name)
|
data/lib/storey/exceptions.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
module Storey
|
2
2
|
class StoreyError < StandardError; end
|
3
3
|
class SchemaExists < StoreyError; end
|
4
|
+
class SchemaReserved < StoreyError; end
|
5
|
+
class SchemaInvalid < StoreyError; end
|
4
6
|
class SchemaNotFound < StoreyError; end
|
5
7
|
class TableNotFOund < StoreyError; end
|
6
8
|
class WithinTransaction < StoreyError; end
|
data/lib/storey/ruby_dumper.rb
CHANGED
@@ -0,0 +1,38 @@
|
|
1
|
+
module Storey
|
2
|
+
class SchemaName < String
|
3
|
+
RESERVED_SCHEMAS = %w(hstore)
|
4
|
+
|
5
|
+
easy_class_to_instance
|
6
|
+
|
7
|
+
def initialize(name)
|
8
|
+
@name = if @name.respond_to?(:to_s)
|
9
|
+
name.to_s
|
10
|
+
else
|
11
|
+
name
|
12
|
+
end
|
13
|
+
super @name
|
14
|
+
end
|
15
|
+
|
16
|
+
def valid?
|
17
|
+
(@name =~ /^[^0-9][\w]*$/ || @name == '"$user"') &&
|
18
|
+
@name !~ /^pg_/
|
19
|
+
end
|
20
|
+
|
21
|
+
def reserved?
|
22
|
+
RESERVED_SCHEMAS.include?(@name)
|
23
|
+
end
|
24
|
+
|
25
|
+
def validate_format!
|
26
|
+
unless self.valid?
|
27
|
+
raise SchemaInvalid, "`#{@name}` is not a valid schema name"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def validate_reserved!
|
32
|
+
if self.reserved?
|
33
|
+
raise SchemaReserved, "`#{@name}` is a reserved schema name"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
data/lib/storey/sql_dumper.rb
CHANGED
@@ -1,24 +1,36 @@
|
|
1
1
|
module Storey
|
2
2
|
class SqlDumper
|
3
3
|
|
4
|
-
|
5
|
-
self.new(*args).dump
|
6
|
-
end
|
4
|
+
easy_class_to_instance
|
7
5
|
|
8
6
|
def initialize(options={})
|
9
7
|
@file = options[:file] || File.join(Rails.root, "db", "structure.sql")
|
10
8
|
end
|
11
9
|
|
12
10
|
def dump
|
13
|
-
|
14
|
-
set_psql_env(abcs[Rails.env])
|
15
|
-
search_path = abcs[Rails.env]['schema_search_path']
|
16
|
-
unless search_path.blank?
|
17
|
-
search_path = search_path.split(",").map{|search_path_part| "--schema=#{Shellwords.escape(search_path_part.strip)}" }.join(" ")
|
18
|
-
end
|
19
|
-
`pg_dump -i -s -x -O -f #{Shellwords.escape(@file)} #{search_path} #{Shellwords.escape(abcs[Rails.env]['database'])}`
|
11
|
+
`#{command}`
|
20
12
|
raise 'Error dumping database' if $?.exitstatus == 1
|
21
|
-
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def abcs
|
18
|
+
@abcs ||= ::ActiveRecord::Base.configurations.with_indifferent_access[Rails.env]
|
19
|
+
end
|
20
|
+
|
21
|
+
def search_path
|
22
|
+
@search_path ||= abcs[:schema_search_path]
|
23
|
+
end
|
24
|
+
|
25
|
+
def database_name
|
26
|
+
@database_name ||= Shellwords.escape(abcs[:database])
|
27
|
+
end
|
28
|
+
|
29
|
+
def command
|
30
|
+
@command ||= BuildsDumpCommand.execute(structure_only: true,
|
31
|
+
file: @file,
|
32
|
+
schemas: search_path,
|
33
|
+
database: database_name)
|
22
34
|
end
|
23
35
|
|
24
36
|
end
|
data/lib/storey/utils.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
module Storey
|
2
|
+
class Utils
|
3
|
+
|
4
|
+
def self.db_command_line_switches_from(db_config={}, extra_config={})
|
5
|
+
switches = {}
|
6
|
+
if db_config.has_key?(:host)
|
7
|
+
switches[:host] = db_config[:host]
|
8
|
+
end
|
9
|
+
switches[:dbname] = db_config[:database]
|
10
|
+
switches[:username] = db_config[:username]
|
11
|
+
command_line_switches_from switches.merge(extra_config)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.command_line_switches_from(hash={})
|
15
|
+
hash.map do |k, v|
|
16
|
+
arg = "--#{k}"
|
17
|
+
arg << "=#{v}" if v
|
18
|
+
arg
|
19
|
+
end.join(' ')
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
data/lib/storey/version.rb
CHANGED
data/lib/storey.rb
CHANGED
@@ -1,22 +1,26 @@
|
|
1
|
+
require 'easy_class_to_instance_method'
|
2
|
+
require "active_support/core_ext/module" # so we can use mattr_accessor
|
3
|
+
|
1
4
|
require "storey/version"
|
2
5
|
require "rails/all"
|
3
|
-
require "active_support/core_ext/module" # so we can use mattr_accessor
|
4
|
-
require 'easy_class_to_instance_method'
|
5
6
|
require 'storey/railtie' if defined?(Rails)
|
6
7
|
require 'storey/exceptions'
|
7
8
|
require 'storey/migrator'
|
8
9
|
require 'storey/duplicator'
|
9
10
|
require 'storey/hstore'
|
10
|
-
require 'storey/dumper'
|
11
11
|
require 'storey/ruby_dumper'
|
12
12
|
require 'storey/sql_dumper'
|
13
|
+
require 'storey/dumper'
|
13
14
|
require 'storey/native_schema_matcher'
|
14
15
|
require 'storey/suffixifier'
|
15
16
|
require 'storey/unsuffixifier'
|
16
17
|
require 'storey/resets_column_info'
|
18
|
+
require 'storey/utils'
|
19
|
+
require 'storey/builds_dump_command'
|
20
|
+
require 'storey/builds_load_command'
|
21
|
+
require 'storey/schema_name'
|
17
22
|
|
18
23
|
module Storey
|
19
|
-
RESERVED_SCHEMAS = %w(hstore)
|
20
24
|
|
21
25
|
mattr_accessor :suffix, :persistent_schemas
|
22
26
|
mattr_writer :default_search_path
|
@@ -60,17 +64,12 @@ module Storey
|
|
60
64
|
end
|
61
65
|
|
62
66
|
def create(name, options={}, &block)
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
if RESERVED_SCHEMAS.include?(name) && !options[:force]
|
68
|
-
fail ArgumentError, "'#{name}' is a reserved schema name"
|
69
|
-
end
|
67
|
+
name = SchemaName.new(name)
|
68
|
+
name.validate_format!
|
69
|
+
name.validate_reserved! unless options[:force]
|
70
70
|
|
71
71
|
if self.schemas.include?(name)
|
72
|
-
fail(Storey::SchemaExists,
|
73
|
-
%{The schema "#{name}" already exists.})
|
72
|
+
fail(Storey::SchemaExists, %{The schema "#{name}" already exists.})
|
74
73
|
end
|
75
74
|
|
76
75
|
if options[:load_database_structure].nil?
|
@@ -93,20 +92,8 @@ module Storey
|
|
93
92
|
|
94
93
|
def create_plain_schema(schema_name)
|
95
94
|
name = suffixify schema_name
|
96
|
-
command =
|
97
|
-
|
98
|
-
`psql #{switches}`
|
99
|
-
end
|
100
|
-
|
101
|
-
def command_line_switches(options={})
|
102
|
-
switches = {}
|
103
|
-
if self.database_config.has_key?(:host)
|
104
|
-
switches[:host] = self.database_config[:host]
|
105
|
-
end
|
106
|
-
switches[:dbname] = self.database_config[:database]
|
107
|
-
switches[:username] = self.database_config[:username]
|
108
|
-
switches = switches.merge(options)
|
109
|
-
switches.map {|k, v| "--#{k}=#{v}"}.join(' ')
|
95
|
+
command = "CREATE SCHEMA #{name}"
|
96
|
+
system psql_load_command(command: command)
|
110
97
|
end
|
111
98
|
|
112
99
|
def schemas(options={})
|
@@ -208,6 +195,10 @@ module Storey
|
|
208
195
|
self.default_search_path == schema_name
|
209
196
|
end
|
210
197
|
|
198
|
+
def db_command_line_switches_from(extra_config={})
|
199
|
+
Utils.db_command_line_switches_from(self.database_config, extra_config)
|
200
|
+
end
|
201
|
+
|
211
202
|
protected
|
212
203
|
|
213
204
|
def schema_migrations
|
@@ -248,4 +239,8 @@ module Storey
|
|
248
239
|
end
|
249
240
|
end
|
250
241
|
|
242
|
+
def psql_load_command(options={})
|
243
|
+
BuildsLoadCommand.execute(self.database_config.merge(options))
|
244
|
+
end
|
245
|
+
|
251
246
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -18,6 +18,8 @@ RSpec.configure do |config|
|
|
18
18
|
# We don't want configuration to leak into other tests
|
19
19
|
Storey.reload_config!
|
20
20
|
|
21
|
+
FileUtils.rm_rf File.join(Rails.root, 'tmp', 'schema_dumps')
|
22
|
+
|
21
23
|
# Clean the public schema
|
22
24
|
Storey.switch do
|
23
25
|
tables = ::ActiveRecord::Base.connection.tables
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Storey::BuildsDumpCommand do
|
4
|
+
|
5
|
+
describe '.execute' do
|
6
|
+
subject do
|
7
|
+
described_class.execute(options)
|
8
|
+
end
|
9
|
+
|
10
|
+
let(:options) do
|
11
|
+
{
|
12
|
+
structure_only: true,
|
13
|
+
file: 'myfile.sql',
|
14
|
+
schemas: 'public',
|
15
|
+
database: 'mydb'
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
context 'when structure_only: true' do
|
20
|
+
let(:options) do
|
21
|
+
{
|
22
|
+
database: 'mydb',
|
23
|
+
structure_only: true
|
24
|
+
}
|
25
|
+
end
|
26
|
+
it { should include('--schema-only') }
|
27
|
+
end
|
28
|
+
|
29
|
+
context 'when structure_only: false' do
|
30
|
+
let(:options) do
|
31
|
+
{
|
32
|
+
database: 'mydb',
|
33
|
+
structure_only: false
|
34
|
+
}
|
35
|
+
end
|
36
|
+
it { should_not include('--schema-only') }
|
37
|
+
end
|
38
|
+
|
39
|
+
it { should include('--no-privileges') }
|
40
|
+
it { should include('--no-owner') }
|
41
|
+
|
42
|
+
context 'file: is "/path/to/file name.sql"' do
|
43
|
+
let(:options) do
|
44
|
+
{
|
45
|
+
database: 'mydb',
|
46
|
+
file: '/path/to/file name.sql'
|
47
|
+
}
|
48
|
+
end
|
49
|
+
it { should include('/path/to/file\ name.sql') }
|
50
|
+
end
|
51
|
+
|
52
|
+
context "schemas: 'public'" do
|
53
|
+
let(:options) do
|
54
|
+
{
|
55
|
+
database: 'mydb',
|
56
|
+
schemas: %q(public)
|
57
|
+
}
|
58
|
+
end
|
59
|
+
it { should include('--schema=public') }
|
60
|
+
end
|
61
|
+
|
62
|
+
context "schemas: '$user','public'" do
|
63
|
+
let(:options) do
|
64
|
+
{
|
65
|
+
database: 'mydb',
|
66
|
+
schemas: %q($user,public)
|
67
|
+
}
|
68
|
+
end
|
69
|
+
it { should include('--schema=\$user --schema=public') }
|
70
|
+
end
|
71
|
+
|
72
|
+
it { should match(/mydb$/)}
|
73
|
+
|
74
|
+
context 'no database is given' do
|
75
|
+
let(:options) { {} }
|
76
|
+
it 'should fail' do
|
77
|
+
expect{subject}.to raise_error(ArgumentError, 'database must be supplied')
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'should build a valid full executable command' do
|
82
|
+
expect(subject).
|
83
|
+
to eq("pg_dump --schema-only --no-privileges --no-owner --file=myfile.sql --schema=public mydb")
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Storey::BuildsLoadCommand do
|
4
|
+
|
5
|
+
describe '.execute' do
|
6
|
+
let(:options) do
|
7
|
+
{
|
8
|
+
file: '/path/file dump.sql',
|
9
|
+
database: 'mydb',
|
10
|
+
username: 'myuser',
|
11
|
+
host: 'localhost',
|
12
|
+
port: '5467',
|
13
|
+
password: '12345'
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
subject { described_class.execute(options) }
|
18
|
+
|
19
|
+
it { should include('--file=/path/file\ dump.sql') }
|
20
|
+
it { should include('--dbname=mydb') }
|
21
|
+
it { should include('--username=myuser') }
|
22
|
+
it { should include('--host=localhost') }
|
23
|
+
it { should include('--password=12345')}
|
24
|
+
|
25
|
+
context 'when host is not set' do
|
26
|
+
before { options[:host] = nil }
|
27
|
+
it { should_not include('--host=') }
|
28
|
+
end
|
29
|
+
|
30
|
+
context 'when username is not set' do
|
31
|
+
before { options[:username] = nil }
|
32
|
+
it { should_not include('--username') }
|
33
|
+
end
|
34
|
+
|
35
|
+
context 'when no password is given' do
|
36
|
+
before { options[:password] = nil }
|
37
|
+
it { should_not include('--password') }
|
38
|
+
it { should include('--no-password') }
|
39
|
+
end
|
40
|
+
|
41
|
+
context 'when no file is given' do
|
42
|
+
before { options[:file] = nil }
|
43
|
+
it { should_not include('--file=')}
|
44
|
+
end
|
45
|
+
|
46
|
+
context 'command is given' do
|
47
|
+
before { options[:command] = "EXECUTE THIS!" }
|
48
|
+
it { should include('--command="EXECUTE THIS!"')}
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'should generate a valid command' do
|
52
|
+
expect(subject).to eq('psql --file=/path/file\ dump.sql --dbname=mydb --username=myuser --host=localhost --port=5467 --password=12345')
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
data/spec/storey/create_spec.rb
CHANGED
@@ -1,6 +1,32 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Storey, "#create" do
|
4
|
+
|
5
|
+
context 'given an invalid schema' do
|
6
|
+
it 'should fail' do
|
7
|
+
expect { Storey.create('a a') }.to raise_error(Storey::SchemaInvalid)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
context 'given a reserved schema' do
|
12
|
+
context 'force is true' do
|
13
|
+
it 'should create the schema' do
|
14
|
+
reserved_schema = Storey::SchemaName::RESERVED_SCHEMAS.sample
|
15
|
+
expect { Storey.create(reserved_schema, force: true) }.
|
16
|
+
to_not raise_error
|
17
|
+
expect(Storey.schemas).to include(reserved_schema)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
context 'force is not true' do
|
22
|
+
it 'should fail' do
|
23
|
+
reserved_schema = Storey::SchemaName::RESERVED_SCHEMAS.sample
|
24
|
+
expect { Storey.create(reserved_schema) }.
|
25
|
+
to raise_error(Storey::SchemaReserved)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
4
30
|
it "should load the database structure into the new schema" do
|
5
31
|
public_tables = Storey.switch { ::ActiveRecord::Base.connection.tables }.sort
|
6
32
|
Storey.create "foobar" do
|
@@ -44,7 +70,7 @@ describe Storey, "#create" do
|
|
44
70
|
|
45
71
|
context "when a blank string is passed" do
|
46
72
|
it "should raise an argument error about an invalid schema name" do
|
47
|
-
expect {Storey.create ""}.to raise_error
|
73
|
+
expect {Storey.create ""}.to raise_error
|
48
74
|
end
|
49
75
|
end
|
50
76
|
|
@@ -97,19 +123,4 @@ describe Storey, "#create" do
|
|
97
123
|
end
|
98
124
|
end
|
99
125
|
|
100
|
-
context 'when creating a reserved schema' do
|
101
|
-
it 'should fail' do
|
102
|
-
expect {Storey.create('hstore')}.to raise_error(ArgumentError, "'hstore' is a reserved schema name")
|
103
|
-
end
|
104
|
-
|
105
|
-
context 'when force: true is passed in' do
|
106
|
-
it 'should create the schema' do
|
107
|
-
expect {
|
108
|
-
Storey.create 'hstore', force: true
|
109
|
-
}.to_not raise_error(ArgumentError)
|
110
|
-
Storey.schemas.should include('hstore')
|
111
|
-
end
|
112
|
-
end
|
113
|
-
end
|
114
|
-
|
115
126
|
end
|
@@ -14,6 +14,18 @@ describe Storey::Duplicator do
|
|
14
14
|
)
|
15
15
|
end
|
16
16
|
end
|
17
|
+
|
18
|
+
it 'should remove the target and source sql files after work' do
|
19
|
+
Storey.create 'boo'
|
20
|
+
duplicator = described_class.new('boo', 'ya')
|
21
|
+
duplicator.perform!
|
22
|
+
source_dump_dir =
|
23
|
+
File.join(Rails.root, 'tmp', 'schema_dumps', 'source', '*.*')
|
24
|
+
target_dump_dir =
|
25
|
+
File.join(Rails.root, 'tmp', 'schema_dumps', 'target', '*.*')
|
26
|
+
expect(Dir[source_dump_dir]).to be_empty
|
27
|
+
expect(Dir[target_dump_dir]).to be_empty
|
28
|
+
end
|
17
29
|
end
|
18
30
|
|
19
31
|
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Storey::SchemaName do
|
4
|
+
|
5
|
+
it 'should behave just like a string' do
|
6
|
+
expect(described_class.new('hi')).to eq('hi')
|
7
|
+
end
|
8
|
+
|
9
|
+
describe '#valid?' do
|
10
|
+
subject { described_class.new(schema_name) }
|
11
|
+
|
12
|
+
context 'when the name starts with `pg_`' do
|
13
|
+
let(:schema_name) { 'pg_shouldbereserved' }
|
14
|
+
it { should_not be_valid }
|
15
|
+
end
|
16
|
+
|
17
|
+
context 'when the name starts with a number' do
|
18
|
+
let(:schema_name) { '8isthemagicnumber' }
|
19
|
+
it { should_not be_valid }
|
20
|
+
end
|
21
|
+
|
22
|
+
context 'when the name has a space' do
|
23
|
+
let(:schema_name) { 'almost valid' }
|
24
|
+
it { should_not be_valid }
|
25
|
+
end
|
26
|
+
|
27
|
+
context 'when the name is valid' do
|
28
|
+
let(:schema_name) { 'a_5' }
|
29
|
+
it { should be_valid }
|
30
|
+
end
|
31
|
+
|
32
|
+
context 'when the name is valid with $ and "' do
|
33
|
+
let(:schema_name) { '"$user"' }
|
34
|
+
it { should be_valid }
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'when the string is blank' do
|
38
|
+
let(:schema_name) { '' }
|
39
|
+
it { should_not be_valid }
|
40
|
+
end
|
41
|
+
|
42
|
+
context 'when the string is nil' do
|
43
|
+
let(:schema_name) { nil }
|
44
|
+
it { should_not be_valid }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe '#reserved?' do
|
49
|
+
subject { described_class.new(schema_name) }
|
50
|
+
|
51
|
+
context 'when the schema name is reserved' do
|
52
|
+
let(:schema_name) { described_class::RESERVED_SCHEMAS.sample }
|
53
|
+
it { should be_reserved }
|
54
|
+
end
|
55
|
+
|
56
|
+
context 'when the schema name is not reserved' do
|
57
|
+
let(:schema_name) { 'available' }
|
58
|
+
it { should_not be_reserved }
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe '#validate_format!' do
|
63
|
+
subject { described_class.validate_format!(schema_name) }
|
64
|
+
|
65
|
+
context 'an invalid name is given' do
|
66
|
+
let(:schema_name) { 'a a' }
|
67
|
+
it 'should fail' do
|
68
|
+
expect { subject }.
|
69
|
+
to raise_error(Storey::SchemaInvalid, '`a a` is not a valid schema name')
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
context 'a valid name is given' do
|
74
|
+
let(:schema_name) { 'a_5' }
|
75
|
+
it 'should initialize without exceptions' do
|
76
|
+
expect { subject }.to_not raise_error
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
describe '#validate_reserved!' do
|
82
|
+
context 'when the name is a reserved schema name' do
|
83
|
+
let(:schema_name) { described_class::RESERVED_SCHEMAS.sample }
|
84
|
+
|
85
|
+
it 'should fail with reserved schema argument error' do
|
86
|
+
expect { described_class.validate_reserved!(schema_name) }.
|
87
|
+
to raise_error(Storey::SchemaReserved, "`#{schema_name}` is a reserved schema name")
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Storey::Utils do
|
4
|
+
|
5
|
+
describe '.command_line_switches_from(hash)' do
|
6
|
+
it 'should build command line switches from the hash' do
|
7
|
+
hash = {some: 'key',
|
8
|
+
pretty: 'cool'}
|
9
|
+
expect(described_class.command_line_switches_from(hash)).
|
10
|
+
to eq("--some=key --pretty=cool")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe '.db_command_line_switches_from(db_config)' do
|
15
|
+
subject do
|
16
|
+
described_class.db_command_line_switches_from(db_config, extra_config)
|
17
|
+
end
|
18
|
+
|
19
|
+
context 'db_config does not have :host' do
|
20
|
+
let(:db_config) { {} }
|
21
|
+
it { should_not include('--host=') }
|
22
|
+
end
|
23
|
+
|
24
|
+
context 'db_config has :host' do
|
25
|
+
let(:db_config) { {host: 'localhost'} }
|
26
|
+
it { should include('--host=localhost') }
|
27
|
+
end
|
28
|
+
|
29
|
+
let(:db_config) do
|
30
|
+
{
|
31
|
+
database: 'mydb',
|
32
|
+
username: 'uname'
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
let(:extra_config) do
|
37
|
+
{
|
38
|
+
extra: 'config',
|
39
|
+
'without-arg' => nil
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'should set the database' do
|
44
|
+
expect(subject).to include('--dbname=mydb')
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'should set the username' do
|
48
|
+
expect(subject).to include('--username=uname')
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'should include extra config' do
|
52
|
+
expect(subject).to include('--extra=config')
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'should set flag arguments' do
|
56
|
+
expect(subject).to match(/--without-arg$/)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
data/storey.gemspec
CHANGED
@@ -23,6 +23,6 @@ Gem::Specification.new do |s|
|
|
23
23
|
s.add_development_dependency "pg", "~> 0.12.2"
|
24
24
|
s.add_development_dependency "database_cleaner"
|
25
25
|
s.add_development_dependency "pry"
|
26
|
-
s.add_runtime_dependency 'easy_class_to_instance_method', '~> 0.0.
|
26
|
+
s.add_runtime_dependency 'easy_class_to_instance_method', '~> 0.0.2'
|
27
27
|
s.add_runtime_dependency "rails", "~> 3.2.2"
|
28
28
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: storey
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ramon Tayag
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-
|
11
|
+
date: 2013-07-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rspec-rails
|
@@ -72,14 +72,14 @@ dependencies:
|
|
72
72
|
requirements:
|
73
73
|
- - ~>
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version: 0.0.
|
75
|
+
version: 0.0.2
|
76
76
|
type: :runtime
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
80
|
- - ~>
|
81
81
|
- !ruby/object:Gem::Version
|
82
|
-
version: 0.0.
|
82
|
+
version: 0.0.2
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
84
|
name: rails
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -109,6 +109,8 @@ files:
|
|
109
109
|
- README.md
|
110
110
|
- Rakefile
|
111
111
|
- lib/storey.rb
|
112
|
+
- lib/storey/builds_dump_command.rb
|
113
|
+
- lib/storey/builds_load_command.rb
|
112
114
|
- lib/storey/dumper.rb
|
113
115
|
- lib/storey/duplicator.rb
|
114
116
|
- lib/storey/exceptions.rb
|
@@ -118,9 +120,11 @@ files:
|
|
118
120
|
- lib/storey/railtie.rb
|
119
121
|
- lib/storey/resets_column_info.rb
|
120
122
|
- lib/storey/ruby_dumper.rb
|
123
|
+
- lib/storey/schema_name.rb
|
121
124
|
- lib/storey/sql_dumper.rb
|
122
125
|
- lib/storey/suffixifier.rb
|
123
126
|
- lib/storey/unsuffixifier.rb
|
127
|
+
- lib/storey/utils.rb
|
124
128
|
- lib/storey/version.rb
|
125
129
|
- lib/tasks/storey.rake
|
126
130
|
- rvmrc.sample
|
@@ -166,7 +170,8 @@ files:
|
|
166
170
|
- spec/fixtures/.gitkeep
|
167
171
|
- spec/migrator_spec.rb
|
168
172
|
- spec/spec_helper.rb
|
169
|
-
- spec/storey/
|
173
|
+
- spec/storey/builds_dump_command_spec.rb
|
174
|
+
- spec/storey/builds_load_command_spec.rb
|
170
175
|
- spec/storey/configuration_spec.rb
|
171
176
|
- spec/storey/create_plain_schema_spec.rb
|
172
177
|
- spec/storey/create_spec.rb
|
@@ -182,6 +187,7 @@ files:
|
|
182
187
|
- spec/storey/persistent_schemas_spec.rb
|
183
188
|
- spec/storey/ruby_dumper_spec.rb
|
184
189
|
- spec/storey/schema_exists_spec.rb
|
190
|
+
- spec/storey/schema_name_spec.rb
|
185
191
|
- spec/storey/schema_search_path_for_spec.rb
|
186
192
|
- spec/storey/schema_spec.rb
|
187
193
|
- spec/storey/schemas_spec.rb
|
@@ -189,6 +195,7 @@ files:
|
|
189
195
|
- spec/storey/suffixifier_spec.rb
|
190
196
|
- spec/storey/switch_spec.rb
|
191
197
|
- spec/storey/unsuffixifier_spec.rb
|
198
|
+
- spec/storey/utils_spec.rb
|
192
199
|
- spec/tasks/storey_rake_spec.rb
|
193
200
|
- storey.gemspec
|
194
201
|
homepage: https://github.com/ramontayag/storey
|
@@ -257,7 +264,8 @@ test_files:
|
|
257
264
|
- spec/fixtures/.gitkeep
|
258
265
|
- spec/migrator_spec.rb
|
259
266
|
- spec/spec_helper.rb
|
260
|
-
- spec/storey/
|
267
|
+
- spec/storey/builds_dump_command_spec.rb
|
268
|
+
- spec/storey/builds_load_command_spec.rb
|
261
269
|
- spec/storey/configuration_spec.rb
|
262
270
|
- spec/storey/create_plain_schema_spec.rb
|
263
271
|
- spec/storey/create_spec.rb
|
@@ -273,6 +281,7 @@ test_files:
|
|
273
281
|
- spec/storey/persistent_schemas_spec.rb
|
274
282
|
- spec/storey/ruby_dumper_spec.rb
|
275
283
|
- spec/storey/schema_exists_spec.rb
|
284
|
+
- spec/storey/schema_name_spec.rb
|
276
285
|
- spec/storey/schema_search_path_for_spec.rb
|
277
286
|
- spec/storey/schema_spec.rb
|
278
287
|
- spec/storey/schemas_spec.rb
|
@@ -280,4 +289,5 @@ test_files:
|
|
280
289
|
- spec/storey/suffixifier_spec.rb
|
281
290
|
- spec/storey/switch_spec.rb
|
282
291
|
- spec/storey/unsuffixifier_spec.rb
|
292
|
+
- spec/storey/utils_spec.rb
|
283
293
|
- spec/tasks/storey_rake_spec.rb
|
@@ -1,22 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe Storey, '.command_line_switches' do
|
4
|
-
it 'should build default command line switches for calling the psql command' do
|
5
|
-
described_class.stub(:database_config).and_return(:host => 'hoop',
|
6
|
-
:database => 'db',
|
7
|
-
:username => 'jj')
|
8
|
-
described_class.command_line_switches.
|
9
|
-
should == "--host=hoop --dbname=db --username=jj"
|
10
|
-
end
|
11
|
-
|
12
|
-
context 'given options' do
|
13
|
-
it 'should add the options to the switches, overriding any default' do
|
14
|
-
described_class.stub(:database_config).and_return(:host => 'hoop',
|
15
|
-
:database => 'db',
|
16
|
-
:username => 'jj')
|
17
|
-
|
18
|
-
described_class.command_line_switches(:a => 'boo', :host => 'loop').
|
19
|
-
should == "--host=loop --dbname=db --username=jj --a=boo"
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|