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 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.
@@ -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[:load_database_schema] = true unless options.has_key?(:load_database_schema)
46
+ options[:load_database_structure] = true if options[:load_database_structure].nil?
44
47
 
45
- name = suffixify(name)
46
- ActiveRecord::Base.connection.execute "CREATE SCHEMA #{name}"
47
- switch name do
48
- load_database_schema if options[:load_database_schema] == true
49
- block.call if block_given?
50
- end
51
- rescue ActiveRecord::StatementInvalid => e
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
- raise e
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(name)
128
- if Storey.suffix && !name.include?(Storey.suffix) && name != self.default_search_path
129
- "#{name}#{Storey.suffix}"
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
- name
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|
@@ -1,15 +1,17 @@
1
1
  class Storey::Duplicator
2
- DUMP_PATH = File.join Rails.root, 'tmp', 'schema_dumps'
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
- attr_accessor :source_schema, :target_schema, :source_file, :target_file
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.source_file = File.join SOURCE_DUMP_PATH, "#{Time.now.to_i}_#{self.source_schema}.sql"
12
- self.target_file = File.join TARGET_DUMP_PATH, "#{Time.now.to_i}_#{self.target_schema}.sql"
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}" }.join(" ")
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
- [SOURCE_DUMP_PATH, TARGET_DUMP_PATH].each { |d| FileUtils.mkdir_p(d) }
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
@@ -1,3 +1,3 @@
1
1
  module Storey
2
- VERSION = "0.2.0"
2
+ VERSION = "0.3.0"
3
3
  end
@@ -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|
@@ -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
@@ -1,7 +1,7 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Storey, "#create" do
4
- it "should load the schema.rb into the new schema" do
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 ":load_database_schema => false" do
21
- it "should not load the schema.rb" do
22
- Storey.should_not_receive(:load_database_schema)
23
- Storey.create "foobar", :load_database_schema => false
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
- FileUtils.rm_rf(Storey::Duplicator::DUMP_PATH)
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.2.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: &82245390 !ruby/object:Gem::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: *82245390
24
+ version_requirements: *76025600
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: pg
27
- requirement: &82244830 !ruby/object:Gem::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: *82244830
35
+ version_requirements: *76025050
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: database_cleaner
38
- requirement: &82244090 !ruby/object:Gem::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: *82244090
46
+ version_requirements: *76024290
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: pry
49
- requirement: &82243450 !ruby/object:Gem::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: *82243450
57
+ version_requirements: *76023620
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: rails
60
- requirement: &82242810 !ruby/object:Gem::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: *82242810
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