schema_plus 0.1.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. data/.gitignore +25 -0
  2. data/Gemfile +3 -0
  3. data/MIT-LICENSE +25 -0
  4. data/README.rdoc +147 -0
  5. data/Rakefile +70 -0
  6. data/init.rb +1 -0
  7. data/lib/schema_plus/active_record/associations.rb +211 -0
  8. data/lib/schema_plus/active_record/base.rb +81 -0
  9. data/lib/schema_plus/active_record/connection_adapters/abstract_adapter.rb +96 -0
  10. data/lib/schema_plus/active_record/connection_adapters/column.rb +55 -0
  11. data/lib/schema_plus/active_record/connection_adapters/foreign_key_definition.rb +115 -0
  12. data/lib/schema_plus/active_record/connection_adapters/index_definition.rb +51 -0
  13. data/lib/schema_plus/active_record/connection_adapters/mysql_adapter.rb +111 -0
  14. data/lib/schema_plus/active_record/connection_adapters/postgresql_adapter.rb +163 -0
  15. data/lib/schema_plus/active_record/connection_adapters/schema_statements.rb +39 -0
  16. data/lib/schema_plus/active_record/connection_adapters/sqlite3_adapter.rb +78 -0
  17. data/lib/schema_plus/active_record/connection_adapters/table_definition.rb +130 -0
  18. data/lib/schema_plus/active_record/migration.rb +220 -0
  19. data/lib/schema_plus/active_record/schema.rb +27 -0
  20. data/lib/schema_plus/active_record/schema_dumper.rb +122 -0
  21. data/lib/schema_plus/active_record/validations.rb +139 -0
  22. data/lib/schema_plus/railtie.rb +12 -0
  23. data/lib/schema_plus/version.rb +3 -0
  24. data/lib/schema_plus.rb +248 -0
  25. data/schema_plus.gemspec +37 -0
  26. data/schema_plus.gemspec.rails3.0 +36 -0
  27. data/schema_plus.gemspec.rails3.1 +36 -0
  28. data/spec/association_spec.rb +529 -0
  29. data/spec/connections/mysql/connection.rb +18 -0
  30. data/spec/connections/mysql2/connection.rb +18 -0
  31. data/spec/connections/postgresql/connection.rb +15 -0
  32. data/spec/connections/sqlite3/connection.rb +14 -0
  33. data/spec/foreign_key_definition_spec.rb +23 -0
  34. data/spec/foreign_key_spec.rb +142 -0
  35. data/spec/index_definition_spec.rb +139 -0
  36. data/spec/index_spec.rb +71 -0
  37. data/spec/migration_spec.rb +405 -0
  38. data/spec/models/comment.rb +2 -0
  39. data/spec/models/post.rb +2 -0
  40. data/spec/models/user.rb +2 -0
  41. data/spec/references_spec.rb +78 -0
  42. data/spec/schema/auto_schema.rb +23 -0
  43. data/spec/schema/core_schema.rb +21 -0
  44. data/spec/schema_dumper_spec.rb +167 -0
  45. data/spec/schema_spec.rb +71 -0
  46. data/spec/spec_helper.rb +59 -0
  47. data/spec/support/extensions/active_model.rb +13 -0
  48. data/spec/support/helpers.rb +16 -0
  49. data/spec/support/matchers/automatic_foreign_key_matchers.rb +2 -0
  50. data/spec/support/matchers/have_index.rb +52 -0
  51. data/spec/support/matchers/reference.rb +66 -0
  52. data/spec/support/reference.rb +66 -0
  53. data/spec/validations_spec.rb +294 -0
  54. data/spec/views_spec.rb +140 -0
  55. 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
@@ -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