aspgems-redhillonrails_core 2.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. data/CHANGELOG +194 -0
  2. data/Gemfile +3 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README +161 -0
  5. data/Rakefile +59 -0
  6. data/init.rb +1 -0
  7. data/lib/redhillonrails_core.rb +46 -0
  8. data/lib/redhillonrails_core/active_record/base.rb +45 -0
  9. data/lib/redhillonrails_core/active_record/connection_adapters/abstract/column.rb +14 -0
  10. data/lib/redhillonrails_core/active_record/connection_adapters/abstract/foreign_key_definition.rb +63 -0
  11. data/lib/redhillonrails_core/active_record/connection_adapters/abstract/index_definition.rb +11 -0
  12. data/lib/redhillonrails_core/active_record/connection_adapters/abstract/schema_statements.rb +23 -0
  13. data/lib/redhillonrails_core/active_record/connection_adapters/abstract/table_definition.rb +27 -0
  14. data/lib/redhillonrails_core/active_record/connection_adapters/abstract_adapter.rb +84 -0
  15. data/lib/redhillonrails_core/active_record/connection_adapters/mysql_adapter.rb +131 -0
  16. data/lib/redhillonrails_core/active_record/connection_adapters/mysql_column.rb +8 -0
  17. data/lib/redhillonrails_core/active_record/connection_adapters/postgresql_adapter.rb +131 -0
  18. data/lib/redhillonrails_core/active_record/connection_adapters/sqlite3_adapter.rb +115 -0
  19. data/lib/redhillonrails_core/active_record/schema.rb +25 -0
  20. data/lib/redhillonrails_core/active_record/schema_dumper.rb +75 -0
  21. data/lib/redhillonrails_core/version.rb +3 -0
  22. data/lib/tasks/db/comments.rake +9 -0
  23. data/redhillonrails_core.gemspec +27 -0
  24. data/spec/connections/mysql/connection.rb +18 -0
  25. data/spec/connections/mysql2/connection.rb +18 -0
  26. data/spec/connections/postgresql/connection.rb +15 -0
  27. data/spec/connections/sqlite3/connection.rb +14 -0
  28. data/spec/foreign_key_definition_spec.rb +21 -0
  29. data/spec/foreign_key_spec.rb +100 -0
  30. data/spec/index_definition_spec.rb +145 -0
  31. data/spec/index_spec.rb +67 -0
  32. data/spec/models/comment.rb +5 -0
  33. data/spec/models/post.rb +6 -0
  34. data/spec/models/user.rb +5 -0
  35. data/spec/schema/schema.rb +21 -0
  36. data/spec/schema_dumper_spec.rb +138 -0
  37. data/spec/spec_helper.rb +16 -0
  38. data/spec/support/reference.rb +66 -0
  39. metadata +159 -0
@@ -0,0 +1,145 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ require 'models/user'
4
+
5
+ describe "Index definition" do
6
+
7
+ let(:migration) { ::ActiveRecord::Migration }
8
+
9
+ context "when index is multicolumn" do
10
+ before(:each) do
11
+ migration.suppress_messages do
12
+ migration.execute "CREATE INDEX users_login_index ON users (login, deleted_at)"
13
+ end
14
+ User.reset_column_information
15
+ @index = index_definition(%w[login deleted_at])
16
+ end
17
+
18
+ after(:each) do
19
+ migration.suppress_messages do
20
+ migration.remove_index :users, :name => 'users_login_index'
21
+ end
22
+ end
23
+
24
+ it "is included in User.indexes" do
25
+ User.indexes.select { |index| index.columns == %w[login deleted_at] }.should have(1).item
26
+ end
27
+
28
+ end
29
+
30
+ if ::ActiveRecord::Base.connection.class.include?(RedhillonrailsCore::ActiveRecord::ConnectionAdapters::PostgresqlAdapter)
31
+
32
+ context "when case insensitive is added" do
33
+
34
+ before(:each) do
35
+ migration.suppress_messages do
36
+ migration.execute "CREATE INDEX users_login_index ON users(LOWER(login))"
37
+ end
38
+ User.reset_column_information
39
+ @index = User.indexes.detect { |i| i.expression =~ /lower\(\(login\)::text\)/i }
40
+ end
41
+
42
+ after(:each) do
43
+ migration.suppress_messages do
44
+ migration.remove_index :users, :name => 'users_login_index'
45
+ end
46
+ end
47
+
48
+ it "is included in User.indexes" do
49
+ @index.should_not be_nil
50
+ end
51
+
52
+ it "is not case_sensitive" do
53
+ @index.should_not be_case_sensitive
54
+ end
55
+
56
+ it "defines expression" do
57
+ @index.expression.should == "lower((login)::text)"
58
+ end
59
+
60
+ it "doesn't define conditions" do
61
+ @index.conditions.should be_nil
62
+ end
63
+
64
+ end
65
+
66
+
67
+ context "when index is partial and column is not downcased" do
68
+ before(:each) do
69
+ migration.suppress_messages do
70
+ migration.execute "CREATE INDEX users_login_index ON users(login) WHERE deleted_at IS NULL"
71
+ end
72
+ User.reset_column_information
73
+ @index = index_definition("login")
74
+ end
75
+
76
+ after(:each) do
77
+ migration.suppress_messages do
78
+ migration.remove_index :users, :name => 'users_login_index'
79
+ end
80
+ end
81
+
82
+ it "is included in User.indexes" do
83
+ User.indexes.select { |index| index.columns == ["login"] }.should have(1).item
84
+ end
85
+
86
+ it "is case_sensitive" do
87
+ @index.should be_case_sensitive
88
+ end
89
+
90
+ it "doesn't define expression" do
91
+ @index.expression.should be_nil
92
+ end
93
+
94
+ it "defines conditions" do
95
+ @index.conditions.should == "(deleted_at IS NULL)"
96
+ end
97
+
98
+ end
99
+
100
+ context "when index contains expression" do
101
+ before(:each) do
102
+ migration.suppress_messages do
103
+ migration.execute "CREATE INDEX users_login_index ON users (extract(EPOCH from deleted_at)) WHERE deleted_at IS NULL"
104
+ end
105
+ User.reset_column_information
106
+ @index = User.indexes.detect { |i| i.expression.present? }
107
+ end
108
+
109
+ after(:each) do
110
+ migration.suppress_messages do
111
+ migration.remove_index :users, :name => 'users_login_index'
112
+ end
113
+ end
114
+
115
+ it "exists" do
116
+ @index.should_not be_nil
117
+ end
118
+
119
+ it "doesnt have columns defined" do
120
+ @index.columns.should be_empty
121
+ end
122
+
123
+ it "is case_sensitive" do
124
+ @index.should be_case_sensitive
125
+ end
126
+
127
+ it "defines expression" do
128
+ @index.expression.should == "date_part('epoch'::text, deleted_at)"
129
+ end
130
+
131
+ it "defines conditions" do
132
+ @index.conditions.should == "(deleted_at IS NULL)"
133
+ end
134
+
135
+ end
136
+
137
+ end # of postgresql specific examples
138
+
139
+ protected
140
+ def index_definition(column_names)
141
+ User.indexes.detect { |index| index.columns == Array(column_names) }
142
+ end
143
+
144
+
145
+ end
@@ -0,0 +1,67 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ require 'models/user'
4
+
5
+ describe "add_index" do
6
+
7
+ let(:migration) { ::ActiveRecord::Migration }
8
+
9
+ after(:each) do
10
+ migration.suppress_messages do
11
+ migration.remove_index(:users, :name => @index.name) if @index
12
+ end
13
+ end
14
+
15
+ it "should create index when called without additional options" do
16
+ add_index(:users, :login)
17
+ index_for(:login).should_not be_nil
18
+ end
19
+
20
+ it "should create unique index" do
21
+ add_index(:users, :login, :unique => true)
22
+ index_for(:login).unique.should == true
23
+ end
24
+
25
+ it "should assign given name" do
26
+ add_index(:users, :login, :name => 'users_login_index')
27
+ index_for(:login).name.should == 'users_login_index'
28
+ end
29
+
30
+ if ::ActiveRecord::Base.connection.class.include?(RedhillonrailsCore::ActiveRecord::ConnectionAdapters::PostgresqlAdapter)
31
+
32
+ it "should assign conditions" do
33
+ add_index(:users, :login, :conditions => 'deleted_at IS NULL')
34
+ index_for(:login).conditions.should == '(deleted_at IS NULL)'
35
+ end
36
+
37
+ it "should assign expression" do
38
+ add_index(:users, :expression => "USING hash (upper(login)) WHERE deleted_at IS NULL", :name => 'users_login_index')
39
+ @index = User.indexes.detect { |i| i.expression.present? }
40
+ @index.expression.should == "upper((login)::text)"
41
+ @index.conditions.should == "(deleted_at IS NULL)"
42
+ end
43
+
44
+ it "should raise if no column given and expression is missing" do
45
+ expect { add_index(:users, :name => 'users_login_index') }.should raise_error(ArgumentError)
46
+ end
47
+
48
+ it "should raise if expression without name is given" do
49
+ expect { add_index(:users, :expression => "USING btree (login)") }.should raise_error(ArgumentError)
50
+ end
51
+
52
+ end # of postgresql specific examples
53
+
54
+ protected
55
+ def add_index(*args)
56
+ migration.suppress_messages do
57
+ migration.add_index(*args)
58
+ end
59
+ User.reset_column_information
60
+ end
61
+
62
+ def index_for(column_names)
63
+ @index = User.indexes.detect { |i| i.columns == Array(column_names).collect(&:to_s) }
64
+ end
65
+
66
+
67
+ end
@@ -0,0 +1,5 @@
1
+ class Comment < ActiveRecord::Base
2
+
3
+ belongs_to :post
4
+
5
+ end
@@ -0,0 +1,6 @@
1
+ class Post < ActiveRecord::Base
2
+
3
+ belongs_to :user
4
+ has_many :comments
5
+
6
+ end
@@ -0,0 +1,5 @@
1
+ class User < ActiveRecord::Base
2
+
3
+ has_many :posts
4
+
5
+ end
@@ -0,0 +1,21 @@
1
+ ActiveRecord::Schema.define do
2
+
3
+ create_table :users, :force => true do |t|
4
+ t.string :login
5
+ t.datetime :deleted_at
6
+ end
7
+
8
+ create_table :posts, :force => true do |t|
9
+ t.text :body
10
+ t.integer :user_id
11
+ t.integer :author_id
12
+ end
13
+
14
+ create_table :comments, :force => true do |t|
15
+ t.text :body
16
+ t.integer :post_id
17
+ end
18
+
19
+ add_foreign_key :comments, :post_id, :posts, :id
20
+
21
+ end
@@ -0,0 +1,138 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+ require 'stringio'
3
+
4
+ require 'models/post'
5
+
6
+ describe "Schema dump" do
7
+
8
+ let(:dump) do
9
+ stream = StringIO.new
10
+ ActiveRecord::SchemaDumper.ignore_tables = %w[users comments]
11
+ ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
12
+ stream.string
13
+ end
14
+
15
+ it "should include foreign_key definition" do
16
+ with_foreign_key Post, :user_id, :users, :id do
17
+ dump.should match(to_regexp(%q{add_foreign_key "posts", ["user_id"], "users", ["id"]}))
18
+ end
19
+ end
20
+
21
+ unless ::ActiveRecord::Base.connection.class.include?(RedhillonrailsCore::ActiveRecord::ConnectionAdapters::Sqlite3Adapter)
22
+
23
+ it "should include foreign_key options" do
24
+ with_foreign_key Post, :user_id, :users, :id, :on_update => :cascade, :on_delete => :set_null do
25
+ dump.should match(to_regexp(%q{add_foreign_key "posts", ["user_id"], "users", ["id"], :on_update => :cascade, :on_delete => :set_null}))
26
+ end
27
+ end
28
+
29
+ end
30
+
31
+ it "should include index definition" do
32
+ with_index Post, :user_id do
33
+ dump.should match(to_regexp(%q{add_index "posts", ["user_id"]}))
34
+ end
35
+ end
36
+
37
+ it "should include index name" do
38
+ with_index Post, :user_id, :name => "posts_user_id_index" do
39
+ dump.should match(to_regexp(%q{add_index "posts", ["user_id"], :name => "posts_user_id_index"}))
40
+ end
41
+ end
42
+
43
+ it "should define unique index" do
44
+ with_index Post, :user_id, :name => "posts_user_id_index", :unique => true do
45
+ dump.should match(to_regexp(%q{add_index "posts", ["user_id"], :name => "posts_user_id_index", :unique => true}))
46
+ end
47
+ end
48
+
49
+ it "should order foreign keys" do
50
+ with_foreign_key Post, :user_id, :users, :id, :name => 'posts_user_id_fk' do
51
+ with_foreign_key Post, :author_id, :users, :id, :name => 'posts_author_id_fk' do
52
+ foreign_key_defs = dump.split("\n").select { |x| x.match(/add_foreign_key/) }
53
+ foreign_key_defs.size.should be_equal(2)
54
+ foreign_key_defs[0].should match(to_regexp(%q{add_foreign_key "posts", ["author_id"], "users", ["id"]}))
55
+ foreign_key_defs[1].should match(to_regexp(%q{add_foreign_key "posts", ["user_id"], "users", ["id"]}))
56
+ end
57
+ end
58
+ end
59
+
60
+ it "should order indexes" do
61
+ with_index Post, :user_id do
62
+ with_index Post, :author_id do
63
+ index_defs = dump.split("\n").select { |x| x.match(/add_index/) }
64
+ index_defs.size.should be_equal(2)
65
+ index_defs[0].should match(to_regexp(%q{add_index "posts", ["author_id"]}))
66
+ index_defs[1].should match(to_regexp(%q{add_index "posts", ["user_id"]}))
67
+ end
68
+ end
69
+ end
70
+
71
+ if ::ActiveRecord::Base.connection.class.include?(RedhillonrailsCore::ActiveRecord::ConnectionAdapters::PostgresqlAdapter)
72
+
73
+ it "should define case insensitive index" do
74
+ with_index Post, :name => "posts_user_body_index", :expression => "USING btree (LOWER(body))" do
75
+ dump.should match(to_regexp(%q{add_index "posts", ["body"], :name => "posts_user_body_index", :case_sensitive => false}))
76
+ end
77
+ end
78
+
79
+ it "should define conditions" do
80
+ with_index Post, :user_id, :name => "posts_user_id_index", :conditions => "user_id IS NOT NULL" do
81
+ dump.should match(to_regexp(%q{add_index "posts", ["user_id"], :name => "posts_user_id_index", :conditions => "(user_id IS NOT NULL)"}))
82
+ end
83
+ end
84
+
85
+ it "should define expression" do
86
+ with_index Post, :name => "posts_freaky_index", :expression => "USING hash (least(id, user_id))" do
87
+ dump.should match(to_regexp(%q{add_index "posts", :name => "posts_freaky_index", :kind => "hash", :expression => "LEAST(id, user_id)"}))
88
+ end
89
+ end
90
+
91
+ it "should define kind" do
92
+ with_index Post, :name => "posts_body_index", :expression => "USING hash (body)" do
93
+ dump.should match(to_regexp(%q{add_index "posts", ["body"], :name => "posts_body_index", :kind => "hash"}))
94
+ end
95
+ end
96
+
97
+ end # of postgresql specific examples
98
+
99
+ protected
100
+ def to_regexp(string)
101
+ Regexp.new(Regexp.escape(string))
102
+ end
103
+
104
+ def with_foreign_key(model, columns, referenced_table_name, referenced_columns, options = {})
105
+ ActiveRecord::Migration.suppress_messages do
106
+ ActiveRecord::Migration.add_foreign_key(model.table_name, columns, referenced_table_name, referenced_columns, options)
107
+ end
108
+ model.reset_column_information
109
+ yield
110
+ ActiveRecord::Migration.suppress_messages do
111
+ ActiveRecord::Migration.remove_foreign_key(model.table_name, determine_foreign_key_name(model, columns, options))
112
+ end
113
+ end
114
+
115
+ def with_index(model, columns, options = {})
116
+ ActiveRecord::Migration.suppress_messages do
117
+ ActiveRecord::Migration.add_index(model.table_name, columns, options)
118
+ end
119
+ model.reset_column_information
120
+ yield
121
+ ActiveRecord::Migration.suppress_messages do
122
+ ActiveRecord::Migration.remove_index(model.table_name, :name => determine_index_name(model, columns, options))
123
+ end
124
+ end
125
+
126
+ def determine_index_name(model, columns, options)
127
+ name = columns[:name] if columns.is_a?(Hash)
128
+ name ||= options[:name]
129
+ name ||= model.indexes.detect { |index| index.table == model.table_name.to_s && index.columns == Array(columns).collect(&:to_s) }.name
130
+ name
131
+ end
132
+
133
+ def determine_foreign_key_name(model, columns, options)
134
+ name = options[:name]
135
+ name ||= model.foreign_keys.detect { |fk| fk.table_name == model.table_name.to_s && fk.column_names == Array(columns).collect(&:to_s) }.name
136
+ end
137
+
138
+ end
@@ -0,0 +1,16 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ require 'rubygems'
4
+ require 'active_record'
5
+ require 'redhillonrails_core'
6
+ require 'connection'
7
+ Dir[File.dirname(__FILE__) + "/support/**/*.rb"].each {|f| require f}
8
+
9
+ RSpec.configure do |config|
10
+ config.include(RedhillonrailsCoreMatchers)
11
+ # load schema
12
+ ActiveRecord::Migration.suppress_messages do
13
+ eval(File.read(File.join(File.dirname(__FILE__), 'schema', 'schema.rb')))
14
+ end
15
+ end
16
+
@@ -0,0 +1,66 @@
1
+ module RedhillonrailsCoreMatchers
2
+
3
+ class Reference
4
+ def initialize(expected)
5
+ @column_names = nil
6
+ unless expected.empty?
7
+ @references_column_names = Array(expected).collect(&:to_s)
8
+ @references_table_name = @references_column_names.shift
9
+ end
10
+ end
11
+
12
+ def matches?(model)
13
+ @model = model
14
+ if @references_table_name
15
+ @result = @model.foreign_keys.select do |fk|
16
+ fk.references_table_name == @references_table_name &&
17
+ @references_column_names.empty? ? true : fk.references_column_names == @references_column_names
18
+ end
19
+ else
20
+ @result = @model.foreign_keys
21
+ end
22
+ if @column_names
23
+ @result.any? do |fk|
24
+ fk.column_names == @column_names &&
25
+ (@on_update ? fk.on_update == @on_update : true) &&
26
+ (@on_delete ? fk.on_delete == @on_delete : true)
27
+ end
28
+ else
29
+ !@result.empty?
30
+ end
31
+ end
32
+
33
+ def failure_message_for_should(should_not = false)
34
+ target_column_names = @column_names.present? ? "(#{@column_names.join(', ')})" : ""
35
+ destinantion_column_names = @references_table_name ? "#{@references_table_name}(#{@references_column_names.join(', ')})" : "anything"
36
+ invert = should_not ? 'not' : ''
37
+ "Expected #{@model.table_name}#{target_column_names} to #{invert} reference #{destinantion_column_names}"
38
+ end
39
+
40
+ def failure_message_for_should_not
41
+ failure_message_for_should(true)
42
+ end
43
+
44
+ def on(*column_names)
45
+ @column_names = column_names.collect(&:to_s)
46
+ self
47
+ end
48
+
49
+ def on_update(action)
50
+ @on_update = action
51
+ self
52
+ end
53
+
54
+ def on_delete(action)
55
+ @on_delete = action
56
+ self
57
+ end
58
+
59
+ end
60
+
61
+ def reference(*expect)
62
+ Reference.new(expect)
63
+ end
64
+
65
+ end
66
+