aspgems-redhillonrails_core 2.0.0.beta1

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 (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
+