storey 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +6 -0
- data/lib/storey.rb +37 -33
- data/lib/storey/duplicator.rb +35 -9
- data/lib/storey/hstore.rb +8 -0
- data/lib/storey/version.rb +1 -1
- data/lib/tasks/storey.rake +7 -0
- data/spec/spec_helper.rb +1 -0
- data/spec/storey/create_plain_schema_spec.rb +21 -0
- data/spec/storey/create_spec.rb +24 -5
- data/spec/storey/duplicate_spec.rb +23 -1
- data/spec/storey/hstore_spec.rb +22 -0
- data/spec/storey/suffixify_spec.rb +11 -0
- metadata +15 -11
data/README.md
CHANGED
@@ -86,3 +86,9 @@ Copies a schema with all data under a new name. Best used in conjunction with `S
|
|
86
86
|
Usage:
|
87
87
|
|
88
88
|
Storey.duplicate!("original_schema", "new_schema")
|
89
|
+
|
90
|
+
# Rake tasks
|
91
|
+
|
92
|
+
## storey:hstore:install
|
93
|
+
|
94
|
+
Run `rake storey:hstore:install` to install hstore extension into the hstore schema. Ensure that 'hstore' is one of the persistent schemas.
|
data/lib/storey.rb
CHANGED
@@ -4,11 +4,12 @@ require "active_support/core_ext/module" # so we can use mattr_accessor
|
|
4
4
|
require 'storey/railtie' if defined?(Rails)
|
5
5
|
require 'storey/exceptions'
|
6
6
|
require 'storey/migrator'
|
7
|
+
require 'storey/duplicator'
|
8
|
+
require 'storey/hstore'
|
7
9
|
|
8
10
|
module Storey
|
9
11
|
extend self
|
10
|
-
|
11
|
-
autoload :Duplicator, 'storey/duplicator'
|
12
|
+
RESERVED_SCHEMAS = %w(hstore)
|
12
13
|
|
13
14
|
mattr_accessor :suffix, :default_search_path, :persistent_schemas
|
14
15
|
mattr_reader :excluded_models
|
@@ -39,23 +40,27 @@ module Storey
|
|
39
40
|
|
40
41
|
def create(name, options={}, &block)
|
41
42
|
fail ArgumentError, "Must pass in a valid schema name" if name.blank?
|
43
|
+
fail ArgumentError, "'#{name}' is a reserved schema name" if RESERVED_SCHEMAS.include?(name) && !options[:force]
|
44
|
+
fail Storey::SchemaExists, %{The schema "#{name}" already exists.} if self.schemas.include?(name)
|
42
45
|
|
43
|
-
options[:
|
46
|
+
options[:load_database_structure] = true if options[:load_database_structure].nil?
|
44
47
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
if e.to_s =~ /schema ".+" already exists/
|
53
|
-
fail Storey::SchemaExists, %{The schema "#{name}" already exists.}
|
48
|
+
if options[:load_database_structure]
|
49
|
+
duplicator = Storey::Duplicator.new 'public', name, structure_only: true
|
50
|
+
duplicator.perform!
|
51
|
+
name = suffixify name
|
52
|
+
switch name do
|
53
|
+
block.call if block_given?
|
54
|
+
end
|
54
55
|
else
|
55
|
-
|
56
|
+
self.create_plain_schema name
|
56
57
|
end
|
57
58
|
end
|
58
59
|
|
60
|
+
def create_plain_schema(schema_name)
|
61
|
+
ActiveRecord::Base.connection.execute "CREATE SCHEMA #{self.suffixify schema_name}"
|
62
|
+
end
|
63
|
+
|
59
64
|
def schemas(options={})
|
60
65
|
options[:suffix] ||= false
|
61
66
|
options[:public] = true unless options.has_key?(:public)
|
@@ -119,16 +124,20 @@ module Storey
|
|
119
124
|
Rails.configuration.database_configuration[Rails.env].with_indifferent_access
|
120
125
|
end
|
121
126
|
|
122
|
-
def duplicate!(from_schema, to_schema)
|
123
|
-
duplicator = Duplicator.new from_schema, to_schema
|
127
|
+
def duplicate!(from_schema, to_schema, options={})
|
128
|
+
duplicator = Duplicator.new from_schema, to_schema, options
|
124
129
|
duplicator.perform!
|
125
130
|
end
|
126
131
|
|
127
|
-
def suffixify(
|
128
|
-
|
129
|
-
|
132
|
+
def suffixify(schema_name)
|
133
|
+
|
134
|
+
if Storey.suffix &&
|
135
|
+
!schema_name.include?(Storey.suffix) &&
|
136
|
+
!matches_default_search_path?(schema_name)
|
137
|
+
|
138
|
+
"#{schema_name}#{Storey.suffix}"
|
130
139
|
else
|
131
|
-
|
140
|
+
schema_name
|
132
141
|
end
|
133
142
|
end
|
134
143
|
|
@@ -149,6 +158,15 @@ module Storey
|
|
149
158
|
search_path
|
150
159
|
end
|
151
160
|
|
161
|
+
def matches_default_search_path?(schema_name)
|
162
|
+
paths = self.default_search_path.split(',')
|
163
|
+
paths.each do |path|
|
164
|
+
return true if path == schema_name
|
165
|
+
end
|
166
|
+
return true if self.default_search_path == schema_name
|
167
|
+
return false
|
168
|
+
end
|
169
|
+
|
152
170
|
protected
|
153
171
|
|
154
172
|
def schema_migrations
|
@@ -160,20 +178,6 @@ module Storey
|
|
160
178
|
ActiveRecord::Base.connection.schema_search_path = path
|
161
179
|
end
|
162
180
|
|
163
|
-
# Loads the Rails schema.rb into the current schema
|
164
|
-
def load_database_schema
|
165
|
-
ActiveRecord::Schema.verbose = false # do not log schema load output.
|
166
|
-
load_or_abort("#{Rails.root}/db/schema.rb")
|
167
|
-
end
|
168
|
-
|
169
|
-
def load_or_abort(file)
|
170
|
-
if File.exists?(file)
|
171
|
-
load(file)
|
172
|
-
else
|
173
|
-
abort %{#{file} doesn't exist yet}
|
174
|
-
end
|
175
|
-
end
|
176
|
-
|
177
181
|
def process_excluded_models
|
178
182
|
self.excluded_models.each do |model_name|
|
179
183
|
model_name.constantize.tap do |klass|
|
data/lib/storey/duplicator.rb
CHANGED
@@ -1,15 +1,17 @@
|
|
1
1
|
class Storey::Duplicator
|
2
|
-
|
3
|
-
SOURCE_DUMP_PATH = File.join DUMP_PATH, 'source'
|
4
|
-
TARGET_DUMP_PATH = File.join DUMP_PATH, 'target'
|
2
|
+
attr_accessor :source_schema, :target_schema, :source_file, :target_file, :structure_only, :file_prefix, :dump_path, :source_dump_path, :target_dump_path
|
5
3
|
|
6
|
-
|
4
|
+
def initialize(from_schema, to_schema, options={})
|
5
|
+
self.dump_path = File.join Rails.root, 'tmp', 'schema_dumps'
|
6
|
+
self.source_dump_path = File.join self.dump_path, 'source'
|
7
|
+
self.target_dump_path = File.join self.dump_path, 'target'
|
8
|
+
self.structure_only = options[:structure_only] || false
|
7
9
|
|
8
|
-
def initialize(from_schema, to_schema)
|
9
10
|
self.source_schema = Storey.suffixify from_schema
|
10
11
|
self.target_schema = Storey.suffixify to_schema
|
11
|
-
self.
|
12
|
-
self.
|
12
|
+
self.file_prefix = "#{Time.now.to_i}_#{rand(100000)}"
|
13
|
+
self.source_file = File.join self.source_dump_path, "#{self.file_prefix}_#{self.source_schema}.sql"
|
14
|
+
self.target_file = File.join self.target_dump_path, "#{self.file_prefix}_#{self.target_schema}.sql"
|
13
15
|
end
|
14
16
|
|
15
17
|
def perform!
|
@@ -29,13 +31,15 @@ class Storey::Duplicator
|
|
29
31
|
options[:file] ||= self.source_file
|
30
32
|
options[:schema] ||= self.source_schema
|
31
33
|
|
32
|
-
switches = options.map { |k, v| "--#{k}=#{v}" }
|
34
|
+
switches = options.map { |k, v| "--#{k}=#{v}" }
|
35
|
+
switches << '--schema-only' if self.structure_only
|
36
|
+
switches = switches.join(" ")
|
33
37
|
|
34
38
|
`pg_dump #{switches} #{Storey.database_config[:database]}`
|
35
39
|
end
|
36
40
|
|
37
41
|
def prepare_schema_dump_directories
|
38
|
-
[
|
42
|
+
[self.source_dump_path, self.target_dump_path].each { |d| FileUtils.mkdir_p(d) }
|
39
43
|
end
|
40
44
|
|
41
45
|
def load_schema(options={})
|
@@ -46,7 +50,25 @@ class Storey::Duplicator
|
|
46
50
|
|
47
51
|
switches = options.map { |k, v| "--#{k}=#{v}" }.join(" ")
|
48
52
|
|
53
|
+
if duplicating_from_default?
|
54
|
+
# Since we are copying the source schema and we're after structure only,
|
55
|
+
# the dump_schema ended up creating a SQL file without the "CREATE SCHEMA" command
|
56
|
+
# thus we have to create it manually
|
57
|
+
::Storey.create_plain_schema self.target_schema
|
58
|
+
end
|
59
|
+
|
60
|
+
source_schema_migrations = ::Storey.switch(self.source_schema) do
|
61
|
+
ActiveRecord::Migrator.get_all_versions
|
62
|
+
end
|
63
|
+
|
49
64
|
`psql #{switches}`
|
65
|
+
|
66
|
+
::Storey.switch self.target_schema do
|
67
|
+
source_schema_migrations.each do |version|
|
68
|
+
ActiveRecord::Base.connection.execute "INSERT INTO schema_migrations (version) VALUES ('#{version}');"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
50
72
|
ENV['PGPASSWORD'] = nil
|
51
73
|
end
|
52
74
|
|
@@ -58,4 +80,8 @@ class Storey::Duplicator
|
|
58
80
|
end
|
59
81
|
end
|
60
82
|
end
|
83
|
+
|
84
|
+
def duplicating_from_default?
|
85
|
+
::Storey.matches_default_search_path?(self.source_schema) && self.structure_only
|
86
|
+
end
|
61
87
|
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
class Storey::Hstore
|
2
|
+
def self.install
|
3
|
+
fail Storey::StoreyError, 'You are attempting to install hstore data type, but the hstore schema (where the data type will be installed) is not one of the persistent schemas. Please add hstore to the list of persistent schemas.' unless Storey.persistent_schemas.include?('hstore')
|
4
|
+
|
5
|
+
Storey.create 'hstore', force: true
|
6
|
+
ActiveRecord::Base.connection.execute "CREATE EXTENSION IF NOT EXISTS hstore SCHEMA #{Storey.suffixify('hstore')}"
|
7
|
+
end
|
8
|
+
end
|
data/lib/storey/version.rb
CHANGED
data/lib/tasks/storey.rake
CHANGED
@@ -1,5 +1,12 @@
|
|
1
1
|
namespace :storey do
|
2
2
|
|
3
|
+
namespace :hstore do
|
4
|
+
desc "Install hstore into the hstore#{Storey.suffix} schema"
|
5
|
+
task :install => :environment do
|
6
|
+
Storey::Hstore.install
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
3
10
|
desc "Migrate all schemas including public"
|
4
11
|
task :migrate => "db:migrate" do
|
5
12
|
Storey.schemas.each do |schema|
|
data/spec/spec_helper.rb
CHANGED
@@ -51,6 +51,7 @@ RSpec.configure do |config|
|
|
51
51
|
# we don't want any test that has set this to keep it hanging around
|
52
52
|
# screwing with our migration
|
53
53
|
ENV['STEP'] = ENV['VERSION'] = nil
|
54
|
+
Rails.application.config.active_record.schema_format = :ruby
|
54
55
|
@rake["db:migrate"].invoke
|
55
56
|
end
|
56
57
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Storey, '.create_plain_schema' do
|
4
|
+
context 'when there is no suffix set' do
|
5
|
+
it 'should create a schema without a suffix' do
|
6
|
+
Storey.create_plain_schema 'dun'
|
7
|
+
Storey.schemas(suffix: true).should include('dun')
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
context 'when there is a suffix set' do
|
12
|
+
before do
|
13
|
+
Storey.suffix = '_lop'
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'should create a schema with the suffix' do
|
17
|
+
Storey.create_plain_schema 'dun'
|
18
|
+
Storey.schemas(suffix: true).should include('dun_lop')
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/spec/storey/create_spec.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Storey, "#create" do
|
4
|
-
it "should load the
|
4
|
+
it "should load the database structure into the new schema" do
|
5
5
|
public_tables = Storey.switch { ActiveRecord::Base.connection.tables }.sort
|
6
6
|
Storey.create "foobar" do
|
7
7
|
foobar_tables = ActiveRecord::Base.connection.tables.sort
|
@@ -17,10 +17,13 @@ describe Storey, "#create" do
|
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
20
|
-
context ":
|
21
|
-
it "should not load the
|
22
|
-
Storey.
|
23
|
-
|
20
|
+
context "when load_database_schema: false" do
|
21
|
+
it "should not load the structure" do
|
22
|
+
Storey.create "foobar", load_database_structure: false do
|
23
|
+
tables = ActiveRecord::Base.connection.tables
|
24
|
+
tables.should_not include('companies')
|
25
|
+
tables.should_not include('posts')
|
26
|
+
end
|
24
27
|
end
|
25
28
|
end
|
26
29
|
|
@@ -84,4 +87,20 @@ describe Storey, "#create" do
|
|
84
87
|
end
|
85
88
|
end
|
86
89
|
end
|
90
|
+
|
91
|
+
context 'when creating a reserved schema' do
|
92
|
+
it 'should fail' do
|
93
|
+
expect {Storey.create('hstore')}.to raise_error(ArgumentError, "'hstore' is a reserved schema name")
|
94
|
+
end
|
95
|
+
|
96
|
+
context 'when force: true is passed in' do
|
97
|
+
it 'should create the schema' do
|
98
|
+
expect {
|
99
|
+
Storey.create 'hstore', force: true
|
100
|
+
}.to_not raise_error(ArgumentError)
|
101
|
+
Storey.schemas.should include('hstore')
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
87
106
|
end
|
@@ -3,7 +3,8 @@ require 'spec_helper'
|
|
3
3
|
describe Storey, "#duplicate!" do
|
4
4
|
before do
|
5
5
|
# Always clear the tmp file of the Rails app
|
6
|
-
|
6
|
+
tmp_dir = File.join Rails.root, 'tmp'
|
7
|
+
FileUtils.rm_rf(tmp_dir)
|
7
8
|
end
|
8
9
|
|
9
10
|
context "when there's no suffix set" do
|
@@ -21,6 +22,27 @@ describe Storey, "#duplicate!" do
|
|
21
22
|
Post.find_by_name("Hi").should_not be_nil
|
22
23
|
end
|
23
24
|
end
|
25
|
+
|
26
|
+
context 'when setting structure_only: true' do
|
27
|
+
before do
|
28
|
+
Storey.duplicate! 'ricky', 'bobby', structure_only: true
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'should create a duplicate schema but copy the structure only' do
|
32
|
+
Storey.schemas.should include('bobby')
|
33
|
+
Storey.switch 'bobby' do
|
34
|
+
Post.count.should == 0
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'should copy all the schema_migrations over' do
|
39
|
+
public_schema_migrations = Storey.switch { ActiveRecord::Migrator.get_all_versions }
|
40
|
+
Storey.switch 'bobby' do
|
41
|
+
ActiveRecord::Migrator.get_all_versions.should == public_schema_migrations
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
24
46
|
end
|
25
47
|
|
26
48
|
it "should clear the PGPASSWORD environment variable" do
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Storey::Hstore do
|
4
|
+
describe '.install' do
|
5
|
+
it 'should install the extension into the hstore schema' do
|
6
|
+
Storey.persistent_schemas = %w(hstore)
|
7
|
+
described_class.install
|
8
|
+
expect {
|
9
|
+
ActiveRecord::Base.connection.execute "DROP EXTENSION hstore"
|
10
|
+
}.to_not raise_error(ActiveRecord::StatementInvalid)
|
11
|
+
end
|
12
|
+
|
13
|
+
context 'when hstore is not one of the persistent schemas' do
|
14
|
+
it 'should fail with an StoreyError' do
|
15
|
+
Storey.persistent_schemas = []
|
16
|
+
expect {
|
17
|
+
described_class.install
|
18
|
+
}.to raise_error(Storey::StoreyError, 'You are attempting to install hstore data type, but the hstore schema (where the data type will be installed) is not one of the persistent schemas. Please add hstore to the list of persistent schemas.')
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Storey, '.suffixify' do
|
4
|
+
context 'given a schema that is not the default schema' do
|
5
|
+
it 'should not add a suffix' do
|
6
|
+
Storey.suffix = '_doo'
|
7
|
+
Storey.suffixify('public').should == 'public'
|
8
|
+
Storey.suffixify(%{"$user",public}).should == %{"$user",public}
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: storey
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -13,7 +13,7 @@ date: 2012-06-17 00:00:00.000000000Z
|
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec-rails
|
16
|
-
requirement: &
|
16
|
+
requirement: &76025600 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :development
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *76025600
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: pg
|
27
|
-
requirement: &
|
27
|
+
requirement: &76025050 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ~>
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: 0.12.2
|
33
33
|
type: :development
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *76025050
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: database_cleaner
|
38
|
-
requirement: &
|
38
|
+
requirement: &76024290 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ! '>='
|
@@ -43,10 +43,10 @@ dependencies:
|
|
43
43
|
version: '0'
|
44
44
|
type: :development
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *76024290
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: pry
|
49
|
-
requirement: &
|
49
|
+
requirement: &76023620 !ruby/object:Gem::Requirement
|
50
50
|
none: false
|
51
51
|
requirements:
|
52
52
|
- - ! '>='
|
@@ -54,10 +54,10 @@ dependencies:
|
|
54
54
|
version: '0'
|
55
55
|
type: :development
|
56
56
|
prerelease: false
|
57
|
-
version_requirements: *
|
57
|
+
version_requirements: *76023620
|
58
58
|
- !ruby/object:Gem::Dependency
|
59
59
|
name: rails
|
60
|
-
requirement: &
|
60
|
+
requirement: &76023040 !ruby/object:Gem::Requirement
|
61
61
|
none: false
|
62
62
|
requirements:
|
63
63
|
- - ~>
|
@@ -65,7 +65,7 @@ dependencies:
|
|
65
65
|
version: 3.2.2
|
66
66
|
type: :runtime
|
67
67
|
prerelease: false
|
68
|
-
version_requirements: *
|
68
|
+
version_requirements: *76023040
|
69
69
|
description: Storey aims to simplify the implementation of managing a multi-tenant
|
70
70
|
application.
|
71
71
|
email:
|
@@ -83,6 +83,7 @@ files:
|
|
83
83
|
- lib/storey.rb
|
84
84
|
- lib/storey/duplicator.rb
|
85
85
|
- lib/storey/exceptions.rb
|
86
|
+
- lib/storey/hstore.rb
|
86
87
|
- lib/storey/migrator.rb
|
87
88
|
- lib/storey/railtie.rb
|
88
89
|
- lib/storey/version.rb
|
@@ -130,13 +131,16 @@ files:
|
|
130
131
|
- spec/migrator_spec.rb
|
131
132
|
- spec/spec_helper.rb
|
132
133
|
- spec/storey/configuration_spec.rb
|
134
|
+
- spec/storey/create_plain_schema_spec.rb
|
133
135
|
- spec/storey/create_spec.rb
|
134
136
|
- spec/storey/drop_spec.rb
|
135
137
|
- spec/storey/duplicate_spec.rb
|
136
138
|
- spec/storey/excluded_models_spec.rb
|
139
|
+
- spec/storey/hstore_spec.rb
|
137
140
|
- spec/storey/persistent_schemas_spec.rb
|
138
141
|
- spec/storey/schema_spec.rb
|
139
142
|
- spec/storey/schemas_spec.rb
|
143
|
+
- spec/storey/suffixify_spec.rb
|
140
144
|
- spec/storey/switch_spec.rb
|
141
145
|
- spec/tasks/storey_rake_spec.rb
|
142
146
|
- storey.gemspec
|