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.
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