aspgems-redhillonrails_core 2.0.0.beta2 → 2.0.0.beta4

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 (36) hide show
  1. data/.gitignore +8 -0
  2. data/.travis.yml +8 -0
  3. data/CHANGELOG +16 -0
  4. data/README.md +50 -10
  5. data/README_DEV.md +41 -0
  6. data/Rakefile +43 -13
  7. data/Thorfile +45 -0
  8. data/gemfiles/rails-3.0.7 +13 -0
  9. data/gemfiles/rails-3.0.7.lock +90 -0
  10. data/gemfiles/rails-3.0.8 +13 -0
  11. data/gemfiles/rails-3.0.8.lock +90 -0
  12. data/gemfiles/rails-3.0.9 +13 -0
  13. data/gemfiles/rails-3.0.9.lock +92 -0
  14. data/gemfiles/rails-3.1.0.rc5 +13 -0
  15. data/gemfiles/rails-3.1.0.rc5.lock +106 -0
  16. data/lib/redhillonrails_core.rb +12 -0
  17. data/lib/redhillonrails_core/active_record/base.rb +1 -1
  18. data/lib/redhillonrails_core/active_record/connection_adapters/abstract/foreign_key_definition.rb +13 -6
  19. data/lib/redhillonrails_core/active_record/connection_adapters/abstract/index_definition.rb +3 -0
  20. data/lib/redhillonrails_core/active_record/connection_adapters/abstract_adapter.rb +10 -6
  21. data/lib/redhillonrails_core/active_record/connection_adapters/mysql_adapter.rb +27 -53
  22. data/lib/redhillonrails_core/active_record/connection_adapters/postgresql_adapter.rb +82 -57
  23. data/lib/redhillonrails_core/active_record/migration/command_recorder.rb +30 -0
  24. data/lib/redhillonrails_core/active_record/schema_dumper.rb +18 -6
  25. data/lib/redhillonrails_core/version.rb +1 -1
  26. data/redhillonrails_core.gemspec +2 -1
  27. data/spec/command_recorder_spec.rb +39 -0
  28. data/spec/connections/mysql/connection.rb +4 -6
  29. data/spec/connections/mysql2/connection.rb +4 -6
  30. data/spec/connections/postgresql/connection.rb +4 -3
  31. data/spec/connections/sqlite3/connection.rb +4 -3
  32. data/spec/foreign_key_definition_spec.rb +31 -2
  33. data/spec/foreign_key_spec.rb +15 -2
  34. data/spec/migration_spec.rb +34 -0
  35. data/spec/table_definition_spec.rb +28 -0
  36. metadata +30 -11
@@ -3,7 +3,7 @@ module RedhillonrailsCore::ActiveRecord::ConnectionAdapters
3
3
  def self.included(base)
4
4
  base.class_eval do
5
5
  alias_method_chain :remove_column, :redhillonrails_core
6
- alias_method_chain :remove_columns, :redhillonrails_core
6
+ alias_method_chain :structure_dump, :views
7
7
  end
8
8
  end
9
9
 
@@ -16,7 +16,7 @@ module RedhillonrailsCore::ActiveRecord::ConnectionAdapters
16
16
  end
17
17
 
18
18
  def remove_foreign_key(table_name, foreign_key_name)
19
- execute "ALTER TABLE #{table_name} DROP FOREIGN KEY #{foreign_key_name}"
19
+ execute "ALTER TABLE #{quote_table_name(table_name)} DROP FOREIGN KEY #{foreign_key_name}"
20
20
  end
21
21
 
22
22
  def remove_column_with_redhillonrails_core(table_name, *column_names)
@@ -28,7 +28,6 @@ module RedhillonrailsCore::ActiveRecord::ConnectionAdapters
28
28
  remove_column_without_redhillonrails_core(table_name, column_name)
29
29
  end
30
30
  end
31
- alias :remove_columns_with_redhillonrails_core :remove_column_with_redhillonrails_core
32
31
 
33
32
  def foreign_keys(table_name, name = nil)
34
33
  results = execute("SHOW CREATE TABLE #{quote_table_name(table_name)}", name)
@@ -58,64 +57,19 @@ module RedhillonrailsCore::ActiveRecord::ConnectionAdapters
58
57
  foreign_keys
59
58
  end
60
59
 
61
- # def reverse_foreign_keys(table_name, name = nil)
62
- # results = execute(<<-SQL, name)
63
- # SELECT constraint_name, table_name, column_name, referenced_table_name, referenced_column_name
64
- # FROM information_schema.key_column_usage
65
- # WHERE table_schema = SCHEMA()
66
- # AND referenced_table_schema = table_schema
67
- # ORDER BY constraint_name, ordinal_position;
68
- # SQL
69
- # current_foreign_key = nil
70
- # foreign_keys = []
71
- #
72
- # results.each do |row|
73
- # next unless table_name.casecmp(row[3]) == 0
74
- # if current_foreign_key != row[0]
75
- # foreign_keys << ForeignKeyDefinition.new(row[0], row[1], [], row[3], [])
76
- # current_foreign_key = row[0]
77
- # end
78
- #
79
- # foreign_keys.last.column_names << row[2]
80
- # foreign_keys.last.references_column_names << row[4]
81
- # end
82
- #
83
- # foreign_keys
84
- # end
85
-
86
60
  def reverse_foreign_keys(table_name, name = nil)
87
- # @@schema ||= nil
88
- # @@schema_version ||= 0
89
- # current_version = ActiveRecord::Migrator.current_version
90
- # if @@schema.nil? || @@schema_version != current_version
91
- # @@schema_version = current_version
92
- # ans = execute(<<-SQL, name)
93
- # SELECT constraint_name, table_name, column_name, referenced_table_name, referenced_column_name
94
- # FROM information_schema.key_column_usage
95
- # WHERE table_schema = SCHEMA()
96
- # AND referenced_table_schema = table_schema
97
- # ORDER BY constraint_name, ordinal_position;
98
- # SQL
99
- # @@schema = []
100
- # ans.each do | row |
101
- # @@schema << [row[0], row[1], row[2], row[3], row[4]]
102
- # end
103
- # end
104
- # results = @@schema
105
-
106
61
  results = execute(<<-SQL, name)
107
62
  SELECT constraint_name, table_name, column_name, referenced_table_name, referenced_column_name
108
- FROM information_schema.key_column_usage
109
- WHERE table_schema = SCHEMA()
110
- AND referenced_table_schema = table_schema
111
- ORDER BY constraint_name, ordinal_position;
63
+ FROM information_schema.key_column_usage
64
+ WHERE table_schema = SCHEMA()
65
+ AND referenced_table_schema = table_schema
66
+ ORDER BY constraint_name, ordinal_position;
112
67
  SQL
113
-
114
68
  current_foreign_key = nil
115
69
  foreign_keys = []
116
70
 
117
71
  results.each do |row|
118
- next if row[3] != table_name
72
+ next unless table_name.to_s.casecmp(row[3].to_s) == 0
119
73
  if current_foreign_key != row[0]
120
74
  foreign_keys << ForeignKeyDefinition.new(row[0], row[1], [], row[3], [])
121
75
  current_foreign_key = row[0]
@@ -127,5 +81,25 @@ module RedhillonrailsCore::ActiveRecord::ConnectionAdapters
127
81
 
128
82
  foreign_keys
129
83
  end
84
+
85
+ def views(name = nil)
86
+ views = []
87
+ execute("SHOW FULL TABLES WHERE TABLE_TYPE='VIEW'", name).each { |row| views << row[0] }
88
+ views
89
+ end
90
+
91
+ def view_definition(view_name, name = nil)
92
+ select_one("SHOW CREATE VIEW #{quote_table_name(view_name)}", name)["Create View"]
93
+ end
94
+
95
+ def structure_dump_with_views
96
+ structure = structure_dump_without_views
97
+
98
+ views.each do |view|
99
+ structure += view_definition(view) + ";\n\n"
100
+ end
101
+
102
+ structure
103
+ end
130
104
  end
131
105
  end
@@ -2,61 +2,75 @@ module RedhillonrailsCore::ActiveRecord::ConnectionAdapters
2
2
  module PostgresqlAdapter
3
3
  def self.included(base)
4
4
  base.class_eval do
5
- alias_method_chain :indexes, :redhillonrails_core
5
+ remove_method :indexes
6
6
  end
7
7
  end
8
8
 
9
- def set_table_comment(table_name, comment)
10
- execute "COMMENT ON TABLE #{table_name} IS '#{quote_string(comment)}'"
11
- end
12
-
13
- def clear_table_comment(table_name)
14
- execute "COMMENT ON TABLE #{table_name} IS NULL"
15
- end
16
-
17
9
  def add_index(table_name, column_name, options = {})
10
+ column_name, options = [], column_name if column_name.is_a?(Hash)
18
11
  column_names = Array(column_name)
19
- index_name = index_name(table_name, :column => column_names)
12
+ if column_names.empty?
13
+ raise ArgumentError, "No columns and :expression missing from options - cannot create index" if options[:expression].blank?
14
+ raise ArgumentError, "Index name not given. Pass :name option" if options[:name].blank?
15
+ end
20
16
 
21
- if Hash === options # legacy support, since this param was a string
22
- index_type = options[:unique] ? "UNIQUE" : ""
23
- index_name = options[:name] || index_name
17
+ index_type = options[:unique] ? "UNIQUE" : ""
18
+ index_name = options[:name] || index_name(table_name, column_names)
19
+ conditions = options[:conditions]
20
+
21
+ if column_names.empty? then
22
+ sql = "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} #{options[:expression]}"
24
23
  else
25
- index_type = options
26
- end
24
+ quoted_column_names = column_names.map { |e| options[:case_sensitive] == false && e.to_s !~ /_id$/ ? "LOWER(#{quote_column_name(e)})" : quote_column_name(e) }
27
25
 
28
- quoted_column_names = column_names.map { |e| options[:case_sensitive] == false && e.to_s !~ /_id$/ ? "LOWER(#{quote_column_name(e)})" : quote_column_name(e) }
26
+ sql = "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{quoted_column_names.join(", ")})"
27
+ sql += " WHERE (#{ ::ActiveRecord::Base.send(:sanitize_sql, conditions, quote_table_name(table_name)) })" if conditions
28
+ end
29
+ execute sql
30
+ end
29
31
 
30
- execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{table_name} (#{quoted_column_names.join(", ")})"
32
+ def supports_partial_indexes?
33
+ true
31
34
  end
32
35
 
33
- def indexes_with_redhillonrails_core(table_name, name = nil)
34
- indexes = indexes_without_redhillonrails_core(table_name, name)
36
+ def indexes(table_name, name = nil)
37
+ schemas = schema_search_path.split(/,/).map { |p| quote(p) }.join(',')
35
38
  result = query(<<-SQL, name)
36
- SELECT c2.relname, i.indisunique, pg_catalog.pg_get_indexdef(i.indexrelid, 0, true)
37
- FROM pg_catalog.pg_class c, pg_catalog.pg_class c2, pg_catalog.pg_index i
38
- WHERE c.relname = '#{table_name}'
39
- AND c.oid = i.indrelid AND i.indexrelid = c2.oid
40
- AND i.indisprimary = 'f'
41
- AND i.indexprs IS NOT NULL
42
- ORDER BY 1
39
+ SELECT distinct i.relname, d.indisunique, d.indkey, m.amname, t.oid,
40
+ pg_get_expr(d.indpred, t.oid), pg_get_expr(d.indexprs, t.oid)
41
+ FROM pg_class t, pg_class i, pg_index d, pg_am m
42
+ WHERE i.relkind = 'i'
43
+ AND i.relam = m.oid
44
+ AND d.indexrelid = i.oid
45
+ AND d.indisprimary = 'f'
46
+ AND t.oid = d.indrelid
47
+ AND t.relname = '#{table_name}'
48
+ AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname IN (#{schemas}) )
49
+ ORDER BY i.relname
43
50
  SQL
44
51
 
45
- result.each do |row|
46
- if row[2]=~ /\((.*LOWER\([^:]+(::text)?\).*)\)$/i
47
- indexes.delete_if { |index| index.name == row[0] }
48
- column_names = $1.split(", ").map do |name|
49
- name = $1 if name =~ /^LOWER\(([^:]+)(::text)?\)$/i
50
- name = $1 if name =~ /^"(.*)"$/
51
- name
52
- end
53
- index = ActiveRecord::ConnectionAdapters::IndexDefinition.new(table_name, row[0], row[1] == "t", column_names)
54
- index.case_sensitive = false
55
- indexes << index
52
+ result.map do |(index_name, is_unique, indkey, kind, oid, conditions, expression)|
53
+ unique = (is_unique == 't')
54
+ index_keys = indkey.split(" ")
55
+
56
+ columns = Hash[query(<<-SQL, "Columns for index #{index_name} on #{table_name}")]
57
+ SELECT a.attnum, a.attname
58
+ FROM pg_attribute a
59
+ WHERE a.attrelid = #{oid}
60
+ AND a.attnum IN (#{index_keys.join(",")})
61
+ SQL
62
+
63
+ column_names = columns.values_at(*index_keys).compact
64
+ if md = expression.try(:match, /^lower\(\(?([^)]+)\)?(::text)?\)$/i)
65
+ column_names << md[1]
56
66
  end
67
+ index = ::ActiveRecord::ConnectionAdapters::IndexDefinition.new(table_name, index_name, unique, column_names)
68
+ index.conditions = conditions
69
+ index.case_sensitive = !(expression =~ /lower/i)
70
+ index.kind = kind unless kind.downcase == "btree"
71
+ index.expression = expression
72
+ index
57
73
  end
58
-
59
- indexes
60
74
  end
61
75
 
62
76
  def foreign_keys(table_name, name = nil)
@@ -88,7 +102,7 @@ module RedhillonrailsCore::ActiveRecord::ConnectionAdapters
88
102
  WHERE schemaname IN (#{schemas})
89
103
  SQL
90
104
  end
91
-
105
+
92
106
  def view_definition(view_name, name = nil)
93
107
  result = query(<<-SQL, name)
94
108
  SELECT pg_get_viewdef(oid)
@@ -106,26 +120,37 @@ module RedhillonrailsCore::ActiveRecord::ConnectionAdapters
106
120
  foreign_keys = []
107
121
 
108
122
  query(sql, name).each do |row|
109
- if row[1] =~ /^FOREIGN KEY \((.+?)\) REFERENCES (.+?)\((.+?)\)( ON UPDATE (.+?))?( ON DELETE (.+?))?( (DEFERRABLE|NOT DEFERRABLE))?$/
110
- name = row[0]
111
- from_table_name = row[2]
112
- column_names = $1
113
- references_table_name = $2
114
- references_column_names = $3
115
- on_update = $5
116
- on_delete = $7
117
- deferrable = $9 == "DEFERRABLE"
118
- on_update = on_update.downcase.gsub(' ', '_').to_sym if on_update
119
- on_delete = on_delete.downcase.gsub(' ', '_').to_sym if on_delete
120
-
121
- foreign_keys << ForeignKeyDefinition.new(name,
122
- from_table_name, column_names.split(', '),
123
- references_table_name.sub(/^"(.*)"$/, '\1'), references_column_names.split(', '),
124
- on_update, on_delete, deferrable)
125
- end
123
+ if row[1] =~ /^FOREIGN KEY \((.+?)\) REFERENCES (.+?)\((.+?)\)( ON UPDATE (.+?))?( ON DELETE (.+?))?( (DEFERRABLE|NOT DEFERRABLE))?$/
124
+ name = row[0]
125
+ from_table_name = row[2]
126
+ column_names = $1
127
+ references_table_name = $2
128
+ references_column_names = $3
129
+ on_update = $5
130
+ on_delete = $7
131
+ deferrable = $9 == "DEFERRABLE"
132
+ on_update = on_update.downcase.gsub(' ', '_').to_sym if on_update
133
+ on_delete = on_delete.downcase.gsub(' ', '_').to_sym if on_delete
134
+
135
+ foreign_keys << ForeignKeyDefinition.new(name,
136
+ from_table_name, column_names.split(', '),
137
+ references_table_name.sub(/^"(.*)"$/, '\1'), references_column_names.split(', '),
138
+ on_update, on_delete, deferrable)
139
+ end
126
140
  end
127
141
 
128
142
  foreign_keys
129
143
  end
144
+
145
+ # Converts form like: column1, LOWER(column2)
146
+ # to: column1, column2
147
+ def determine_index_column_names(column_definitions)
148
+ column_definitions.split(", ").map do |name|
149
+ name = $1 if name =~ /^LOWER\(([^:]+)(::text)?\)$/i
150
+ name = $1 if name =~ /^"(.*)"$/
151
+ name
152
+ end
153
+ end
154
+
130
155
  end
131
156
  end
@@ -0,0 +1,30 @@
1
+ module RedhillonrailsCore
2
+ module ActiveRecord
3
+ module Migration
4
+ module CommandRecorder
5
+ def add_foreign_key(*args)
6
+ record(:add_foreign_key, args)
7
+ end
8
+
9
+ def remove_foreign_key(*args)
10
+ record(:remove_foreign_key, args)
11
+ end
12
+
13
+ def invert_add_foreign_key(args)
14
+ from_table, to_table, add_options = *args
15
+ add_options ||= {}
16
+
17
+ if add_options[:name]
18
+ options = {:name => add_options[:name]}
19
+ elsif add_options[:column]
20
+ options = {:column => add_options[:column]}
21
+ else
22
+ options = to_table
23
+ end
24
+
25
+ [:remove_foreign_key, [from_table, options]]
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -25,12 +25,24 @@ module RedhillonrailsCore::ActiveRecord
25
25
  def indexes_with_redhillonrails_core(table, stream)
26
26
  if (indexes = @connection.indexes(table)).any?
27
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?
28
+
29
+ if index.columns.any?
30
+ statement_parts = [('add_index ' + index.table.inspect)]
31
+ statement_parts << index.columns.inspect
32
+ statement_parts << (':name => ' + index.name.inspect)
33
+ statement_parts << ':unique => true' if index.unique
34
+ # This only used in postgresql - :case_sensitive, :conditions, :kind, :expression
35
+ statement_parts << ':case_sensitive => false' unless index.case_sensitive?
36
+ statement_parts << ':conditions => ' + index.conditions.inspect unless index.conditions.blank?
37
+ statement_parts << ':kind => ' + index.kind.inspect unless index.kind.blank?
38
+ statement_parts << ':expression => ' + index.expression.inspect unless index.expression.blank?
39
+ else
40
+ # This only used in postgresql - :case_sensitive, :conditions, :kind, :expression
41
+ statement_parts = [('add_index ' + index.table.inspect)]
42
+ statement_parts << (':name => ' + index.name.inspect)
43
+ statement_parts << ':kind => ' + index.kind.inspect unless index.kind.blank?
44
+ statement_parts << ':expression => ' + index.expression.inspect unless index.expression.blank?
45
+ end
34
46
 
35
47
  if index.respond_to?(:lengths)
36
48
  index_lengths = index.lengths.compact if index.lengths.is_a?(Array)
@@ -1,3 +1,3 @@
1
1
  module RedhillonrailsCore
2
- VERSION = "2.0.0.beta2"
2
+ VERSION = "2.0.0.beta4"
3
3
  end
@@ -20,7 +20,8 @@ Gem::Specification.new do |s|
20
20
  s.add_dependency("activerecord", ">= 2")
21
21
  s.add_dependency("activesupport", ">= 2")
22
22
 
23
- s.add_development_dependency("rspec", "~> 2.5.0")
23
+ # Development dependencies through thorfile
24
+ s.add_development_dependency("rspec", "~> 2.6.0")
24
25
  s.add_development_dependency("pg")
25
26
  s.add_development_dependency("mysql")
26
27
  s.add_development_dependency("mysql2")
@@ -0,0 +1,39 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe 'Command Recorder' do
4
+ if defined?(ActiveRecord::Migration::CommandRecorder)
5
+ before do
6
+ @recorder = ActiveRecord::Migration::CommandRecorder.new
7
+ end
8
+
9
+ it 'invert_add_foreign_key' do
10
+ @recorder.add_foreign_key(:employees, :companies)
11
+ remove = @recorder.inverse.first
12
+ remove.should == [:remove_foreign_key, [:employees, :companies]]
13
+ end
14
+
15
+ it 'invert_add_foreign_key with column' do
16
+ @recorder.add_foreign_key(:employees, :companies, :column => :place_id)
17
+ remove = @recorder.inverse.first
18
+ remove.should == [:remove_foreign_key, [:employees, {:column => :place_id}]]
19
+ end
20
+
21
+ it 'invert_add_foreign_key with name' do
22
+ @recorder.add_foreign_key(:employees, :companies, :name => 'the_best_fk', :column => :place_id)
23
+ remove = @recorder.inverse.first
24
+ remove.should == [:remove_foreign_key, [:employees, {:name => 'the_best_fk'}]]
25
+
26
+ @recorder.record :rename_table, [:old, :new]
27
+ rename = @recorder.inverse.first
28
+ rename.should == [:rename_table, [:new, :old]]
29
+ end
30
+
31
+ it 'remove_foreign_key is irreversible' do
32
+ @recorder.remove_foreign_key(:employees, :companies)
33
+ expect {
34
+ @recorder.inverse
35
+ }.to raise_error(ActiveRecord::IrreversibleMigration)
36
+ end
37
+ end
38
+
39
+ end
@@ -4,15 +4,13 @@ require 'logger'
4
4
  ActiveRecord::Base.logger = Logger.new("debug.log")
5
5
 
6
6
  ActiveRecord::Base.configurations = {
7
- 'redhillonrails' => {
7
+ 'mysql' => {
8
8
  :adapter => 'mysql',
9
- :database => 'rh_core_unittest',
10
- :username => 'rh_core',
9
+ :database => 'redhillonrails_core',
10
+ :username => (ENV["TRAVIS"] ? '' : 'redhillonrails'),
11
11
  :encoding => 'utf8',
12
- :socket => '/var/run/mysqld/mysqld.sock',
13
- :min_messages => 'warning'
14
12
  }
15
13
 
16
14
  }
17
15
 
18
- ActiveRecord::Base.establish_connection 'redhillonrails'
16
+ ActiveRecord::Base.establish_connection 'mysql'
@@ -4,15 +4,13 @@ require 'logger'
4
4
  ActiveRecord::Base.logger = Logger.new("debug.log")
5
5
 
6
6
  ActiveRecord::Base.configurations = {
7
- 'redhillonrails' => {
7
+ 'mysql2' => {
8
8
  :adapter => 'mysql2',
9
- :database => 'rh_core_unittest',
10
- :username => 'rh_core',
9
+ :database => 'redhillonrails_core',
10
+ :username => (ENV["TRAVIS"] ? '' : 'redhillonrails'),
11
11
  :encoding => 'utf8',
12
- :socket => '/var/run/mysqld/mysqld.sock',
13
- :min_messages => 'warning'
14
12
  }
15
13
 
16
14
  }
17
15
 
18
- ActiveRecord::Base.establish_connection 'redhillonrails'
16
+ ActiveRecord::Base.establish_connection 'mysql2'