schema_plus 0.1.0.pre1
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/.gitignore +25 -0
- data/Gemfile +3 -0
- data/MIT-LICENSE +25 -0
- data/README.rdoc +147 -0
- data/Rakefile +70 -0
- data/init.rb +1 -0
- data/lib/schema_plus/active_record/associations.rb +211 -0
- data/lib/schema_plus/active_record/base.rb +81 -0
- data/lib/schema_plus/active_record/connection_adapters/abstract_adapter.rb +96 -0
- data/lib/schema_plus/active_record/connection_adapters/column.rb +55 -0
- data/lib/schema_plus/active_record/connection_adapters/foreign_key_definition.rb +115 -0
- data/lib/schema_plus/active_record/connection_adapters/index_definition.rb +51 -0
- data/lib/schema_plus/active_record/connection_adapters/mysql_adapter.rb +111 -0
- data/lib/schema_plus/active_record/connection_adapters/postgresql_adapter.rb +163 -0
- data/lib/schema_plus/active_record/connection_adapters/schema_statements.rb +39 -0
- data/lib/schema_plus/active_record/connection_adapters/sqlite3_adapter.rb +78 -0
- data/lib/schema_plus/active_record/connection_adapters/table_definition.rb +130 -0
- data/lib/schema_plus/active_record/migration.rb +220 -0
- data/lib/schema_plus/active_record/schema.rb +27 -0
- data/lib/schema_plus/active_record/schema_dumper.rb +122 -0
- data/lib/schema_plus/active_record/validations.rb +139 -0
- data/lib/schema_plus/railtie.rb +12 -0
- data/lib/schema_plus/version.rb +3 -0
- data/lib/schema_plus.rb +248 -0
- data/schema_plus.gemspec +37 -0
- data/schema_plus.gemspec.rails3.0 +36 -0
- data/schema_plus.gemspec.rails3.1 +36 -0
- data/spec/association_spec.rb +529 -0
- data/spec/connections/mysql/connection.rb +18 -0
- data/spec/connections/mysql2/connection.rb +18 -0
- data/spec/connections/postgresql/connection.rb +15 -0
- data/spec/connections/sqlite3/connection.rb +14 -0
- data/spec/foreign_key_definition_spec.rb +23 -0
- data/spec/foreign_key_spec.rb +142 -0
- data/spec/index_definition_spec.rb +139 -0
- data/spec/index_spec.rb +71 -0
- data/spec/migration_spec.rb +405 -0
- data/spec/models/comment.rb +2 -0
- data/spec/models/post.rb +2 -0
- data/spec/models/user.rb +2 -0
- data/spec/references_spec.rb +78 -0
- data/spec/schema/auto_schema.rb +23 -0
- data/spec/schema/core_schema.rb +21 -0
- data/spec/schema_dumper_spec.rb +167 -0
- data/spec/schema_spec.rb +71 -0
- data/spec/spec_helper.rb +59 -0
- data/spec/support/extensions/active_model.rb +13 -0
- data/spec/support/helpers.rb +16 -0
- data/spec/support/matchers/automatic_foreign_key_matchers.rb +2 -0
- data/spec/support/matchers/have_index.rb +52 -0
- data/spec/support/matchers/reference.rb +66 -0
- data/spec/support/reference.rb +66 -0
- data/spec/validations_spec.rb +294 -0
- data/spec/views_spec.rb +140 -0
- metadata +269 -0
@@ -0,0 +1,18 @@
|
|
1
|
+
print "Using MySQL\n"
|
2
|
+
require 'logger'
|
3
|
+
|
4
|
+
ActiveRecord::Base.logger = Logger.new(File.open("mysql.log", "w"))
|
5
|
+
|
6
|
+
ActiveRecord::Base.configurations = {
|
7
|
+
'schema_plus' => {
|
8
|
+
:adapter => 'mysql',
|
9
|
+
:database => 'schema_plus_unittest',
|
10
|
+
:username => 'schema_plus',
|
11
|
+
:encoding => 'utf8',
|
12
|
+
:socket => '/tmp/mysql.sock',
|
13
|
+
:min_messages => 'warning'
|
14
|
+
}
|
15
|
+
|
16
|
+
}
|
17
|
+
|
18
|
+
ActiveRecord::Base.establish_connection 'schema_plus'
|
@@ -0,0 +1,18 @@
|
|
1
|
+
print "Using MySQL2\n"
|
2
|
+
require 'logger'
|
3
|
+
|
4
|
+
ActiveRecord::Base.logger = Logger.new(File.open("mysql2.log", "w"))
|
5
|
+
|
6
|
+
ActiveRecord::Base.configurations = {
|
7
|
+
'schema_plus' => {
|
8
|
+
:adapter => 'mysql2',
|
9
|
+
:database => 'schema_plus_unittest',
|
10
|
+
:username => 'schema_plus',
|
11
|
+
:encoding => 'utf8',
|
12
|
+
:socket => '/tmp/mysql.sock',
|
13
|
+
:min_messages => 'warning'
|
14
|
+
}
|
15
|
+
|
16
|
+
}
|
17
|
+
|
18
|
+
ActiveRecord::Base.establish_connection 'schema_plus'
|
@@ -0,0 +1,15 @@
|
|
1
|
+
print "Using PostgreSQL\n"
|
2
|
+
require 'logger'
|
3
|
+
|
4
|
+
ActiveRecord::Base.logger = Logger.new(File.open("postgresql.log", "w"))
|
5
|
+
|
6
|
+
ActiveRecord::Base.configurations = {
|
7
|
+
'schema_plus' => {
|
8
|
+
:adapter => 'postgresql',
|
9
|
+
:database => 'schema_plus_unittest',
|
10
|
+
:min_messages => 'warning'
|
11
|
+
}
|
12
|
+
|
13
|
+
}
|
14
|
+
|
15
|
+
ActiveRecord::Base.establish_connection 'schema_plus'
|
@@ -0,0 +1,14 @@
|
|
1
|
+
print "Using SQLite3\n"
|
2
|
+
require 'logger'
|
3
|
+
|
4
|
+
ActiveRecord::Base.logger = Logger.new(File.open("sqlite3.log", "w"))
|
5
|
+
|
6
|
+
ActiveRecord::Base.configurations = {
|
7
|
+
'schema_plus' => {
|
8
|
+
:adapter => 'sqlite3',
|
9
|
+
:database => File.expand_path('schema_plus.sqlite3', File.dirname(__FILE__)),
|
10
|
+
}
|
11
|
+
|
12
|
+
}
|
13
|
+
|
14
|
+
ActiveRecord::Base.establish_connection 'schema_plus'
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe "Foreign Key definition" do
|
4
|
+
|
5
|
+
before(:all) do
|
6
|
+
load_core_schema
|
7
|
+
end
|
8
|
+
|
9
|
+
let(:definition) { SchemaPlus::ActiveRecord::ConnectionAdapters::ForeignKeyDefinition.new("posts_user_fkey", :posts, :user, :users, :id) }
|
10
|
+
|
11
|
+
it "it is dumped to sql with quoted values" do
|
12
|
+
definition.to_sql.should == %Q{CONSTRAINT posts_user_fkey FOREIGN KEY (#{quote_column_name('user')}) REFERENCES #{quote_table_name('users')} (#{quote_column_name('id')})}
|
13
|
+
end
|
14
|
+
|
15
|
+
def quote_table_name(table)
|
16
|
+
ActiveRecord::Base.connection.quote_table_name(table)
|
17
|
+
end
|
18
|
+
|
19
|
+
def quote_column_name(column)
|
20
|
+
ActiveRecord::Base.connection.quote_column_name(column)
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
require 'models/user'
|
4
|
+
require 'models/post'
|
5
|
+
require 'models/comment'
|
6
|
+
|
7
|
+
describe "Foreign Key" do
|
8
|
+
|
9
|
+
before(:all) do
|
10
|
+
load_core_schema
|
11
|
+
end
|
12
|
+
|
13
|
+
let(:migration) { ::ActiveRecord::Migration }
|
14
|
+
|
15
|
+
if SchemaPlusHelpers.sqlite3?
|
16
|
+
|
17
|
+
it "raises an exception when attempting to add" do
|
18
|
+
expect {
|
19
|
+
add_foreign_key(:posts, :author_id, :users, :id, :on_update => :cascade, :on_delete => :restrict)
|
20
|
+
}.should raise_error(NotImplementedError)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "raises an exception when attempting to remove" do
|
24
|
+
expect {
|
25
|
+
remove_foreign_key(:posts, "dummy")
|
26
|
+
}.should raise_error(NotImplementedError)
|
27
|
+
end
|
28
|
+
|
29
|
+
else
|
30
|
+
|
31
|
+
context "when is added", "posts(author_id)" do
|
32
|
+
|
33
|
+
before(:each) do
|
34
|
+
add_foreign_key(:posts, :author_id, :users, :id, :on_update => :cascade, :on_delete => :restrict)
|
35
|
+
end
|
36
|
+
|
37
|
+
after(:each) do
|
38
|
+
fk = Post.foreign_keys.detect { |fk| fk.column_names == %w[author_id] }
|
39
|
+
remove_foreign_key(:posts, fk.name)
|
40
|
+
end
|
41
|
+
|
42
|
+
it "references users(id)" do
|
43
|
+
Post.should reference(:users, :id).on(:author_id)
|
44
|
+
end
|
45
|
+
|
46
|
+
it "cascades on update" do
|
47
|
+
Post.should reference(:users).on_update(:cascade)
|
48
|
+
end
|
49
|
+
|
50
|
+
it "restricts on delete" do
|
51
|
+
Post.should reference(:users).on_delete(:restrict)
|
52
|
+
end
|
53
|
+
|
54
|
+
it "is available in Post.foreign_keys" do
|
55
|
+
Post.foreign_keys.collect(&:column_names).should include(%w[author_id])
|
56
|
+
end
|
57
|
+
|
58
|
+
it "is available in User.reverse_foreign_keys" do
|
59
|
+
User.reverse_foreign_keys.collect(&:column_names).should include(%w[author_id])
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
context "when is dropped", "comments(post_id)" do
|
65
|
+
|
66
|
+
let(:foreign_key_name) { fk = Comment.foreign_keys.detect { |definition| definition.column_names == %w[post_id] } and fk.name }
|
67
|
+
|
68
|
+
before(:each) do
|
69
|
+
remove_foreign_key(:comments, foreign_key_name)
|
70
|
+
end
|
71
|
+
|
72
|
+
after(:each) do
|
73
|
+
add_foreign_key(:comments, :post_id, :posts, :id)
|
74
|
+
end
|
75
|
+
|
76
|
+
it "doesn't reference posts(id)" do
|
77
|
+
Comment.should_not reference(:posts).on(:post_id)
|
78
|
+
end
|
79
|
+
|
80
|
+
it "is no longer available in Post.foreign_keys" do
|
81
|
+
Comment.foreign_keys.collect(&:column_names).should_not include(%w[post_id])
|
82
|
+
end
|
83
|
+
|
84
|
+
it "is no longer available in User.reverse_foreign_keys" do
|
85
|
+
Post.reverse_foreign_keys.collect(&:column_names).should_not include(%w[post_id])
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
context "when referencing column and column is removed" do
|
91
|
+
|
92
|
+
let(:foreign_key_name) { Comment.foreign_keys.detect { |definition| definition.column_names == %w[post_id] }.name }
|
93
|
+
|
94
|
+
it "should remove foreign keys" do
|
95
|
+
remove_foreign_key(:comments, foreign_key_name)
|
96
|
+
Post.reverse_foreign_keys.collect { |fk| fk.column_names == %w[post_id] && fk.table_name == "comments" }.should be_empty
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
context "when table name is a rerved word" do
|
102
|
+
before(:each) do
|
103
|
+
migration.suppress_messages do
|
104
|
+
migration.create_table :references, :force => true do |t|
|
105
|
+
t.integer :post_id
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
it "can add, detect, and remove a foreign key without error" do
|
111
|
+
migration.suppress_messages do
|
112
|
+
expect {
|
113
|
+
migration.add_foreign_key(:references, :post_id, :posts, :id)
|
114
|
+
foreign_key = migration.foreign_keys(:references).detect{|definition| definition.column_names == ["post_id"]}
|
115
|
+
migration.remove_foreign_key(:references, foreign_key.name)
|
116
|
+
}.should_not raise_error
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
122
|
+
|
123
|
+
protected
|
124
|
+
def add_foreign_key(*args)
|
125
|
+
migration.suppress_messages do
|
126
|
+
migration.add_foreign_key(*args)
|
127
|
+
end
|
128
|
+
User.reset_column_information
|
129
|
+
Post.reset_column_information
|
130
|
+
Comment.reset_column_information
|
131
|
+
end
|
132
|
+
|
133
|
+
def remove_foreign_key(*args)
|
134
|
+
migration.suppress_messages do
|
135
|
+
migration.remove_foreign_key(*args)
|
136
|
+
end
|
137
|
+
User.reset_column_information
|
138
|
+
Post.reset_column_information
|
139
|
+
Comment.reset_column_information
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
|
4
|
+
describe "Index definition" do
|
5
|
+
|
6
|
+
let(:migration) { ::ActiveRecord::Migration }
|
7
|
+
|
8
|
+
before(:all) do
|
9
|
+
load_core_schema
|
10
|
+
end
|
11
|
+
|
12
|
+
around(:each) do |example|
|
13
|
+
migration.suppress_messages do
|
14
|
+
example.run
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
after(:each) do
|
19
|
+
migration.remove_index :users, :name => 'users_login_index' if migration.index_name_exists? :users, 'users_login_index', true
|
20
|
+
end
|
21
|
+
|
22
|
+
context "when index is multicolumn" do
|
23
|
+
before(:each) do
|
24
|
+
migration.execute "CREATE INDEX users_login_index ON users (login, deleted_at)"
|
25
|
+
User.reset_column_information
|
26
|
+
@index = index_definition(%w[login deleted_at])
|
27
|
+
end
|
28
|
+
|
29
|
+
it "is included in User.indexes" do
|
30
|
+
User.indexes.select { |index| index.columns == %w[login deleted_at] }.should have(1).item
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should correctly report supports_partial_indexes?" do
|
36
|
+
query = lambda { migration.execute "CREATE INDEX users_login_index ON users(login) WHERE deleted_at IS NULL" }
|
37
|
+
if migration.supports_partial_indexes?
|
38
|
+
query.should_not raise_error
|
39
|
+
else
|
40
|
+
query.should raise_error
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
if SchemaPlusHelpers.postgresql?
|
45
|
+
|
46
|
+
context "when case insensitive is added" do
|
47
|
+
|
48
|
+
before(:each) do
|
49
|
+
migration.execute "CREATE INDEX users_login_index ON users(LOWER(login))"
|
50
|
+
User.reset_column_information
|
51
|
+
@index = User.indexes.detect { |i| i.expression =~ /lower\(\(login\)::text\)/i }
|
52
|
+
end
|
53
|
+
|
54
|
+
it "is included in User.indexes" do
|
55
|
+
@index.should_not be_nil
|
56
|
+
end
|
57
|
+
|
58
|
+
it "is not case_sensitive" do
|
59
|
+
@index.should_not be_case_sensitive
|
60
|
+
end
|
61
|
+
|
62
|
+
it "its column should not be case sensitive" do
|
63
|
+
User.columns.find{|column| column.name == "login"}.should_not be_case_sensitive
|
64
|
+
end
|
65
|
+
|
66
|
+
it "defines expression" do
|
67
|
+
@index.expression.should == "lower((login)::text)"
|
68
|
+
end
|
69
|
+
|
70
|
+
it "doesn't define conditions" do
|
71
|
+
@index.conditions.should be_nil
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
|
77
|
+
context "when index is partial and column is not downcased" do
|
78
|
+
before(:each) do
|
79
|
+
migration.execute "CREATE INDEX users_login_index ON users(login) WHERE deleted_at IS NULL"
|
80
|
+
User.reset_column_information
|
81
|
+
@index = index_definition("login")
|
82
|
+
end
|
83
|
+
|
84
|
+
it "is included in User.indexes" do
|
85
|
+
User.indexes.select { |index| index.columns == ["login"] }.should have(1).item
|
86
|
+
end
|
87
|
+
|
88
|
+
it "is case_sensitive" do
|
89
|
+
@index.should be_case_sensitive
|
90
|
+
end
|
91
|
+
|
92
|
+
it "doesn't define expression" do
|
93
|
+
@index.expression.should be_nil
|
94
|
+
end
|
95
|
+
|
96
|
+
it "defines conditions" do
|
97
|
+
@index.conditions.should == "(deleted_at IS NULL)"
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
context "when index contains expression" do
|
103
|
+
before(:each) do
|
104
|
+
migration.execute "CREATE INDEX users_login_index ON users (extract(EPOCH from deleted_at)) WHERE deleted_at IS NULL"
|
105
|
+
User.reset_column_information
|
106
|
+
@index = User.indexes.detect { |i| i.expression.present? }
|
107
|
+
end
|
108
|
+
|
109
|
+
it "exists" do
|
110
|
+
@index.should_not be_nil
|
111
|
+
end
|
112
|
+
|
113
|
+
it "doesnt have columns defined" do
|
114
|
+
@index.columns.should be_empty
|
115
|
+
end
|
116
|
+
|
117
|
+
it "is case_sensitive" do
|
118
|
+
@index.should be_case_sensitive
|
119
|
+
end
|
120
|
+
|
121
|
+
it "defines expression" do
|
122
|
+
@index.expression.should == "date_part('epoch'::text, deleted_at)"
|
123
|
+
end
|
124
|
+
|
125
|
+
it "defines conditions" do
|
126
|
+
@index.conditions.should == "(deleted_at IS NULL)"
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
130
|
+
|
131
|
+
end # of postgresql specific examples
|
132
|
+
|
133
|
+
protected
|
134
|
+
def index_definition(column_names)
|
135
|
+
User.indexes.detect { |index| index.columns == Array(column_names) }
|
136
|
+
end
|
137
|
+
|
138
|
+
|
139
|
+
end
|
data/spec/index_spec.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
require 'models/user'
|
4
|
+
|
5
|
+
describe "add_index" do
|
6
|
+
|
7
|
+
before(:all) do
|
8
|
+
load_core_schema
|
9
|
+
end
|
10
|
+
|
11
|
+
let(:migration) { ::ActiveRecord::Migration }
|
12
|
+
|
13
|
+
after(:each) do
|
14
|
+
migration.suppress_messages do
|
15
|
+
migration.remove_index(:users, :name => @index.name) if @index
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should create index when called without additional options" do
|
20
|
+
add_index(:users, :login)
|
21
|
+
index_for(:login).should_not be_nil
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should create unique index" do
|
25
|
+
add_index(:users, :login, :unique => true)
|
26
|
+
index_for(:login).unique.should == true
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should assign given name" do
|
30
|
+
add_index(:users, :login, :name => 'users_login_index')
|
31
|
+
index_for(:login).name.should == 'users_login_index'
|
32
|
+
end
|
33
|
+
|
34
|
+
if SchemaPlusHelpers.postgresql?
|
35
|
+
|
36
|
+
it "should assign conditions" do
|
37
|
+
add_index(:users, :login, :conditions => 'deleted_at IS NULL')
|
38
|
+
index_for(:login).conditions.should == '(deleted_at IS NULL)'
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should assign expression" do
|
42
|
+
add_index(:users, :expression => "USING hash (upper(login)) WHERE deleted_at IS NULL", :name => 'users_login_index')
|
43
|
+
@index = User.indexes.detect { |i| i.expression.present? }
|
44
|
+
@index.expression.should == "upper((login)::text)"
|
45
|
+
@index.conditions.should == "(deleted_at IS NULL)"
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should raise if no column given and expression is missing" do
|
49
|
+
expect { add_index(:users, :name => 'users_login_index') }.should raise_error(ArgumentError)
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should raise if expression without name is given" do
|
53
|
+
expect { add_index(:users, :expression => "USING btree (login)") }.should raise_error(ArgumentError)
|
54
|
+
end
|
55
|
+
|
56
|
+
end # of postgresql specific examples
|
57
|
+
|
58
|
+
protected
|
59
|
+
def add_index(*args)
|
60
|
+
migration.suppress_messages do
|
61
|
+
migration.add_index(*args)
|
62
|
+
end
|
63
|
+
User.reset_column_information
|
64
|
+
end
|
65
|
+
|
66
|
+
def index_for(column_names)
|
67
|
+
@index = User.indexes.detect { |i| i.columns == Array(column_names).collect(&:to_s) }
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
end
|