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,115 @@
1
+ module RedhillonrailsCore
2
+ module ActiveRecord
3
+ module ConnectionAdapters
4
+ module Sqlite3Adapter
5
+ def self.included(base)
6
+ base.class_eval do
7
+ alias_method_chain :tables, :redhillonrails_core
8
+ end
9
+ end
10
+
11
+ def move_table(from, to, options = {}, &block) #:nodoc:
12
+ copy_table(from, to, options, &block)
13
+ drop_table(from, options)
14
+ end
15
+
16
+ def add_foreign_key(from_table_name, from_column_names, to_table_name, to_column_names, options = {})
17
+ initialize_sqlite3_foreign_key_table
18
+ from_column_names = Array(from_column_names)
19
+ to_column_names = Array(to_column_names)
20
+ fk_name = options[:name] || ["fk", from_table_name, *to_column_names].join("_")
21
+
22
+ columns = %w(name from_table_name from_column_names to_table_name to_column_names)
23
+ values = [fk_name, from_table_name, from_column_names.join(","), to_table_name, to_column_names.join(",")]
24
+
25
+ quoted_values = values.map { |x| quote(x.to_s) }.join(",")
26
+
27
+ # TODO: support options
28
+
29
+ insert <<-SQL
30
+ INSERT INTO #{sqlite3_foreign_key_table}(#{quoted_columns(columns)})
31
+ VALUES (#{quoted_values})
32
+ SQL
33
+ end
34
+
35
+ def remove_foreign_key(table_name, foreign_key_name, options = {})
36
+ return if options[:temporary] == true
37
+ initialize_sqlite3_foreign_key_table
38
+
39
+ rows_deleted = delete <<-SQL
40
+ DELETE FROM #{sqlite3_foreign_key_table}
41
+ WHERE #{quote_column_name("name")} = #{quote(foreign_key_name.to_s)}
42
+ AND #{quote_column_name("from_table_name")} = #{quote(table_name.to_s)}
43
+ SQL
44
+
45
+ if rows_deleted != 1
46
+ raise ActiveRecord::ActiveRecordError, "Foreign-key '#{foreign_key_name}' on table '#{table_name}' not found"
47
+ end
48
+ end
49
+
50
+ def tables_with_redhillonrails_core(name = nil)
51
+ tables_without_redhillonrails_core.reject{ |name| name == sqlite3_foreign_key_table }
52
+ end
53
+
54
+ def foreign_keys(table_name, name = nil)
55
+ load_foreign_keys("from_table_name", table_name, name)
56
+ end
57
+
58
+ def reverse_foreign_keys(table_name, name = nil)
59
+ load_foreign_keys("to_table_name", table_name, name)
60
+ end
61
+
62
+ private
63
+
64
+ def quoted_columns(columns)
65
+ columns.map { |x| quote_column_name(x) }.join(",")
66
+ end
67
+
68
+ def sqlite3_foreign_key_table
69
+ "sqlite3_foreign_keys"
70
+ end
71
+
72
+ def initialize_sqlite3_foreign_key_table
73
+ unless sqlite3_foreign_key_table_exists?
74
+ create_table(sqlite3_foreign_key_table, :id => false) do |t|
75
+ t.string "name", :null => false
76
+ t.string "from_table_name", :null => false
77
+ t.string "from_column_names", :null => false
78
+ t.string "to_table_name", :null => false
79
+ t.string "to_column_names", :null => false
80
+ end
81
+ add_index(sqlite3_foreign_key_table, "name", :unique => true)
82
+ add_index(sqlite3_foreign_key_table, "from_table_name", :unique => false)
83
+ add_index(sqlite3_foreign_key_table, "to_table_name", :unique => false)
84
+ end
85
+ end
86
+
87
+ def sqlite3_foreign_key_table_exists?
88
+ tables_without_redhillonrails_core.detect { |name| name == sqlite3_foreign_key_table }
89
+ end
90
+
91
+ def load_foreign_keys(discriminating_column, table_name, name = nil)
92
+ if sqlite3_foreign_key_table_exists?
93
+ rows = select_all(<<-SQL, name)
94
+ SELECT *
95
+ FROM #{sqlite3_foreign_key_table}
96
+ WHERE #{quote_column_name(discriminating_column)} = #{quote(table_name.to_s)}
97
+ SQL
98
+ else
99
+ rows = []
100
+ end
101
+
102
+ rows.map do |row|
103
+ ForeignKeyDefinition.new(
104
+ row["name"],
105
+ row["from_table_name"], row["from_column_names"].split(","),
106
+ row["to_table_name"], row["to_column_names"].split(",")
107
+ )
108
+ end
109
+ end
110
+
111
+ end
112
+
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,25 @@
1
+ module RedhillonrailsCore::ActiveRecord
2
+ module Schema
3
+ def self.included(base)
4
+ base.extend(ClassMethods)
5
+ end
6
+
7
+ module ClassMethods
8
+ def self.extended(base)
9
+ class << base
10
+ attr_accessor :defining
11
+ alias :defining? :defining
12
+
13
+ alias_method_chain :define, :redhillonrails_core
14
+ end
15
+ end
16
+
17
+ def define_with_redhillonrails_core(info={}, &block)
18
+ self.defining = true
19
+ define_without_redhillonrails_core(info, &block)
20
+ ensure
21
+ self.defining = false
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,75 @@
1
+ module RedhillonrailsCore::ActiveRecord
2
+ module SchemaDumper
3
+ def self.included(base)
4
+ base.class_eval do
5
+ private
6
+ alias_method_chain :tables, :redhillonrails_core
7
+ alias_method_chain :indexes, :redhillonrails_core
8
+ end
9
+ end
10
+
11
+ private
12
+
13
+ def tables_with_redhillonrails_core(stream)
14
+ @foreign_keys = StringIO.new
15
+ begin
16
+ tables_without_redhillonrails_core(stream)
17
+ @foreign_keys.rewind
18
+ stream.print @foreign_keys.read
19
+ views(stream)
20
+ ensure
21
+ @foreign_keys = nil
22
+ end
23
+ end
24
+
25
+ def indexes_with_redhillonrails_core(table, stream)
26
+ if (indexes = @connection.indexes(table)).any?
27
+ add_index_statements = indexes.map do |index|
28
+ statement_parts = [('add_index ' + index.table.inspect)]
29
+ statement_parts << index.columns.inspect
30
+ statement_parts << (':name => ' + index.name.inspect)
31
+ statement_parts << ':unique => true' if index.unique
32
+ # This only used in postgresql
33
+ statement_parts << ':case_sensitive => false' unless index.case_sensitive?
34
+
35
+ if index.respond_to?(:lengths)
36
+ index_lengths = index.lengths.compact if index.lengths.is_a?(Array)
37
+ statement_parts << (':length => ' + Hash[*index.columns.zip(index.lengths).flatten].inspect) if index_lengths.present?
38
+ end
39
+
40
+ ' ' + statement_parts.join(', ')
41
+ end
42
+
43
+ stream.puts add_index_statements.sort.join("\n")
44
+ stream.puts
45
+ end
46
+
47
+ foreign_keys(table, @foreign_keys)
48
+ end
49
+
50
+ def foreign_keys(table, stream)
51
+ foreign_keys = @connection.foreign_keys(table)
52
+
53
+ foreign_keys.sort! do |a, b|
54
+ a.column_names.sort <=> b.column_names.sort
55
+ end
56
+
57
+ foreign_keys.each do |foreign_key|
58
+ stream.print " "
59
+ stream.print foreign_key.to_dump
60
+ stream.puts
61
+ end
62
+ stream.puts unless foreign_keys.empty?
63
+ end
64
+
65
+ def views(stream)
66
+ views = @connection.views
67
+ views.each do |view_name|
68
+ definition = @connection.view_definition(view_name)
69
+ stream.print " create_view #{view_name.inspect}, #{definition.inspect}"
70
+ stream.puts
71
+ end
72
+ stream.puts unless views.empty?
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,3 @@
1
+ module RedhillonrailsCore
2
+ VERSION = "2.0.0.beta1"
3
+ end
@@ -0,0 +1,9 @@
1
+ namespace :db do
2
+ desc "Describe all the tables in the database by reading the table comments"
3
+ task :comments => :environment do
4
+ ActiveRecord::Base.connection.tables.sort.each do |table_name|
5
+ comment = ActiveRecord::Base.connection.table_comment(table_name)
6
+ puts "#{table_name} - #{comment}"
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,27 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "redhillonrails_core/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "aspgems-redhillonrails_core"
7
+ s.version = RedhillonrailsCore::VERSION.dup
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Michał Łomnicki", "Paco Guzmán"]
10
+ s.email = ["michal.lomnicki@gmail.com", "fjguzman@aspgems.com"]
11
+ s.homepage = "https://github.com/aspgems/redhillonrails_core"
12
+ s.summary = "Adds support in ActiveRecord for foreign_keys, complex indexes and other database-related stuff"
13
+ s.description = "Adds support in ActiveRecord for foreign_keys, complex indexes and other database-related stuff. Easily create foreign_keys, complex indexes and views."
14
+
15
+ s.files = `git ls-files`.split("\n")
16
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
17
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
+ s.require_paths = ["lib"]
19
+
20
+ s.add_dependency("activerecord", ">= 2")
21
+
22
+ s.add_development_dependency("rspec", "~> 2.5.0")
23
+ s.add_development_dependency("pg")
24
+ s.add_development_dependency("mysql")
25
+ s.add_development_dependency("mysql2")
26
+ s.add_development_dependency("sqlite3-ruby", "~> 1.3.1")
27
+ end
@@ -0,0 +1,18 @@
1
+ print "Using MySQL\n"
2
+ require 'logger'
3
+
4
+ ActiveRecord::Base.logger = Logger.new("debug.log")
5
+
6
+ ActiveRecord::Base.configurations = {
7
+ 'redhillonrails' => {
8
+ :adapter => 'mysql',
9
+ :database => 'rh_core_unittest',
10
+ :username => 'rh_core',
11
+ :encoding => 'utf8',
12
+ :socket => '/var/run/mysqld/mysqld.sock',
13
+ :min_messages => 'warning'
14
+ }
15
+
16
+ }
17
+
18
+ ActiveRecord::Base.establish_connection 'redhillonrails'
@@ -0,0 +1,18 @@
1
+ print "Using MySQL2\n"
2
+ require 'logger'
3
+
4
+ ActiveRecord::Base.logger = Logger.new("debug.log")
5
+
6
+ ActiveRecord::Base.configurations = {
7
+ 'redhillonrails' => {
8
+ :adapter => 'mysql2',
9
+ :database => 'rh_core_unittest',
10
+ :username => 'rh_core',
11
+ :encoding => 'utf8',
12
+ :socket => '/var/run/mysqld/mysqld.sock',
13
+ :min_messages => 'warning'
14
+ }
15
+
16
+ }
17
+
18
+ ActiveRecord::Base.establish_connection 'redhillonrails'
@@ -0,0 +1,15 @@
1
+ print "Using PostgreSQL\n"
2
+ require 'logger'
3
+
4
+ ActiveRecord::Base.logger = Logger.new("debug.log")
5
+
6
+ ActiveRecord::Base.configurations = {
7
+ 'redhillonrails' => {
8
+ :adapter => 'postgresql',
9
+ :database => 'redhillonrails_core_test',
10
+ :min_messages => 'warning'
11
+ }
12
+
13
+ }
14
+
15
+ ActiveRecord::Base.establish_connection 'redhillonrails'
@@ -0,0 +1,14 @@
1
+ print "Using SQLite3\n"
2
+ require 'logger'
3
+
4
+ ActiveRecord::Base.logger = Logger.new("debug.log")
5
+
6
+ ActiveRecord::Base.configurations = {
7
+ 'redhillonrails' => {
8
+ :adapter => 'sqlite3',
9
+ :database => File.expand_path(File.dirname(__FILE__) + 'redhillonrails_core.db')
10
+ }
11
+
12
+ }
13
+
14
+ ActiveRecord::Base.establish_connection 'redhillonrails'
@@ -0,0 +1,21 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ require 'models/user'
4
+
5
+ describe "Foreign Key definition" do
6
+
7
+ let(:definition) { RedhillonrailsCore::ActiveRecord::ConnectionAdapters::ForeignKeyDefinition.new("posts_user_fkey", :posts, :user, :users, :id) }
8
+
9
+ it "it is dumped to sql with quoted values" do
10
+ definition.to_sql.should == %Q{CONSTRAINT posts_user_fkey FOREIGN KEY (#{quote_column_name('user')}) REFERENCES #{quote_table_name('users')} (#{quote_column_name('id')})}
11
+ end
12
+
13
+ def quote_table_name(table)
14
+ ActiveRecord::Base.connection.quote_table_name(table)
15
+ end
16
+
17
+ def quote_column_name(column)
18
+ ActiveRecord::Base.connection.quote_column_name(column)
19
+ end
20
+
21
+ end
@@ -0,0 +1,100 @@
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
+ let(:migration) { ::ActiveRecord::Migration }
10
+
11
+ context "when is added", "posts(author_id)" do
12
+
13
+ before(:each) do
14
+ add_foreign_key(:posts, :author_id, :users, :id, :on_update => :cascade, :on_delete => :restrict)
15
+ end
16
+
17
+ after(:each) do
18
+ fk = Post.foreign_keys.detect { |fk| fk.column_names == %w[author_id] }
19
+ remove_foreign_key(:posts, fk.name)
20
+ end
21
+
22
+ it "references users(id)" do
23
+ Post.should reference(:users, :id).on(:author_id)
24
+ end
25
+
26
+ it "cascades on update" do
27
+ Post.should reference(:users).on_update(:cascade)
28
+ end
29
+
30
+ it "restricts on delete" do
31
+ Post.should reference(:users).on_delete(:restrict)
32
+ end
33
+
34
+ it "is available in Post.foreign_keys" do
35
+ Post.foreign_keys.collect(&:column_names).should include(%w[author_id])
36
+ end
37
+
38
+ it "is available in User.reverse_foreign_keys" do
39
+ User.reverse_foreign_keys.collect(&:column_names).should include(%w[author_id])
40
+ end
41
+
42
+ end
43
+
44
+ context "when is dropped", "comments(post_id)" do
45
+
46
+ let(:foreign_key_name) { Comment.foreign_keys.detect { |definition| definition.column_names == %w[post_id] }.name }
47
+
48
+ before(:each) do
49
+ remove_foreign_key(:comments, foreign_key_name)
50
+ end
51
+
52
+ after(:each) do
53
+ add_foreign_key(:comments, :post_id, :posts, :id)
54
+ end
55
+
56
+ it "doesn't reference posts(id)" do
57
+ Comment.should_not reference(:posts).on(:post_id)
58
+ end
59
+
60
+ it "is no longer available in Post.foreign_keys" do
61
+ Comment.foreign_keys.collect(&:column_names).should_not include(%w[post_id])
62
+ end
63
+
64
+ it "is no longer available in User.reverse_foreign_keys" do
65
+ Post.reverse_foreign_keys.collect(&:column_names).should_not include(%w[post_id])
66
+ end
67
+
68
+ end
69
+
70
+ context "when referencing column and column is removed" do
71
+
72
+ let(:foreign_key_name) { Comment.foreign_keys.detect { |definition| definition.column_names == %w[post_id] }.name }
73
+
74
+ it "should remove foreign keys" do
75
+ remove_foreign_key(:comments, foreign_key_name)
76
+ Post.reverse_foreign_keys.collect { |fk| fk.column_names == %w[post_id] && fk.table_name == "comments" }.should be_empty
77
+ end
78
+
79
+ end
80
+
81
+ protected
82
+ def add_foreign_key(*args)
83
+ migration.suppress_messages do
84
+ migration.add_foreign_key(*args)
85
+ end
86
+ User.reset_column_information
87
+ Post.reset_column_information
88
+ Comment.reset_column_information
89
+ end
90
+
91
+ def remove_foreign_key(*args)
92
+ migration.suppress_messages do
93
+ migration.remove_foreign_key(*args)
94
+ end
95
+ User.reset_column_information
96
+ Post.reset_column_information
97
+ Comment.reset_column_information
98
+ end
99
+
100
+ end