legacy_data 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,43 @@
1
+ require File.dirname(__FILE__) + '/../../lib/legacy_data/table_definition'
2
+ require File.dirname(__FILE__) + '/../../lib/legacy_data/schema'
3
+ require File.dirname(__FILE__) + '/../../lib/legacy_data/table_class_name_mapper'
4
+ require File.dirname(__FILE__) + '/../../lib/active_record/connection_adapters/oracle_enhanced_adapter'
5
+
6
+ class ModelsFromTablesGenerator < Rails::Generator::Base
7
+ def manifest
8
+ record do |m|
9
+ m.directory File.join('app/models')
10
+
11
+ LegacyData::TableClassNameMapper.naming_convention = options[:table_naming_convention]
12
+
13
+ analyzed_tables = LegacyData::Schema.analyze(:table_name=>options[:table_name])
14
+ LegacyData::TableClassNameMapper.save_dictionary
15
+ puts <<-MSG
16
+ Done analyzing the tables.
17
+ Please review the class names shown above. If any do not look correct (for example it did not separate the words with CamelCase) please supply the correct mappings by editing the file #{LegacyData::TableClassNameMapper.dictionary_file_name}.
18
+ Once you're done hit <enter> to continue generating the models"
19
+ MSG
20
+ gets
21
+ LegacyData::TableClassNameMapper.load_dictionary
22
+
23
+ analyzed_tables.each do |analyzed_table|
24
+ analyzed_table.class_name = LegacyData::TableClassNameMapper.class_name_for(analyzed_table[:table_name])
25
+ m.class_collisions :class_path, analyzed_table[:class_name]
26
+ m.template 'model.rb',
27
+ File.join('app/models', "#{analyzed_table[:class_name].underscore}.rb"),
28
+ :assigns => analyzed_table.to_hash
29
+ end
30
+ end
31
+ rescue => e
32
+ puts e.backtrace
33
+ end
34
+
35
+ protected
36
+ def add_options!(opt)
37
+ opt.on('--table-name [ARG]',
38
+ 'Only generate models for given table') { |value| options[:table_name] = value }
39
+ opt.on('--table-naming-convention [ARG]',
40
+ 'Naming convention for tables in the database - will not be used when generating naming the models') { |value| options[:table_naming_convention] = value }
41
+ end
42
+
43
+ end
@@ -0,0 +1,31 @@
1
+ class <%= class_name -%> < ActiveRecord::Base
2
+ set_table_name <%= table_name.to_sym.inspect %>
3
+ <%= "set_primary_key #{primary_key.to_sym.inspect}" if primary_key %>
4
+
5
+ # Relationships
6
+ <%- [:has_many, :has_one, :belongs_to, :has_and_belongs_to_many].each do |relation_type|
7
+ relations[relation_type].each do |table_name, options|
8
+ class_for_table = LegacyData::TableClassNameMapper.class_name_for(table_name)
9
+ is_singular_association = [:has_one, :belongs_to].include?(relation_type)
10
+ association_name = class_for_table.underscore
11
+ association_name = association_name.pluralize unless is_singular_association
12
+ needs_class_name = (ActiveRecord::Base.class_name(association_name.pluralize) != class_for_table)
13
+ -%> <%= relation_type %> <%= association_name.to_sym.inspect %>, <%=options.keys.map {|key| "#{key.to_sym.inspect} => #{options[key].to_sym.inspect}"}.join(', ')%><%= ", :class_name=>'#{class_for_table}'" if needs_class_name %>
14
+ <%- end unless relations[relation_type].nil?
15
+ end
16
+ -%>
17
+
18
+ # Constraints
19
+ <%= "validates_uniqueness_of #{constraints[:unique ].map {|cols| cols.first.downcase.to_sym.inspect}.join(', ')}" unless constraints[:unique].blank? %>
20
+ <%- constraints[:multi_column_unique].each do |cols|
21
+ -%> #validates_uniqueness_of_multiple_column_constraint :<%= cols.inspect %>
22
+ <%- end -%>
23
+ <%= "validates_presence_of #{constraints[:non_nullable].map {|cols| cols.downcase.to_sym.inspect}.join(', ')}" unless constraints[:non_nullable].blank? %>
24
+ <%- constraints[:custom].each do |name, sql_rule|
25
+ -%> validate <%= "validate_#{name}".to_sym.inspect %>
26
+ def <%= "validate_#{name}" %>
27
+ # TODO: validate this SQL constraint
28
+ "<%= sql_rule %>"
29
+ end
30
+ <%- end %>
31
+ end
@@ -0,0 +1,69 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{legacy_data}
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Alex Rothenberg"]
12
+ s.date = %q{2009-10-01}
13
+ s.description = %q{Create ActiveRecord models from an existing database}
14
+ s.email = %q{alex@alexrothenberg.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ ".gitignore",
22
+ "LICENSE",
23
+ "README.rdoc",
24
+ "Rakefile",
25
+ "VERSION",
26
+ "examples/j2ee_petstore.sql",
27
+ "generators/models_from_tables/USAGE",
28
+ "generators/models_from_tables/models_from_tables_generator.rb",
29
+ "generators/models_from_tables/templates/model.rb",
30
+ "legacy_data.gemspec",
31
+ "lib/active_record/connection_adapters/oracle_enhanced_adapter.rb",
32
+ "lib/legacy_data.rb",
33
+ "lib/legacy_data/schema.rb",
34
+ "lib/legacy_data/table_class_name_mapper.rb",
35
+ "lib/legacy_data/table_definition.rb",
36
+ "spec/legacy_data/schema_spec.rb",
37
+ "spec/legacy_data/table_class_name_mapper_spec.rb",
38
+ "spec/legacy_data/table_definition_spec.rb",
39
+ "spec/spec_helper.rb"
40
+ ]
41
+ s.has_rdoc = true
42
+ s.homepage = %q{http://github.com/alexrothenberg/legacy_data}
43
+ s.rdoc_options = ["--charset=UTF-8"]
44
+ s.require_paths = ["lib"]
45
+ s.rubygems_version = %q{1.3.2}
46
+ s.summary = %q{Create ActiveRecord models from an existing database}
47
+ s.test_files = [
48
+ "spec/legacy_data/schema_spec.rb",
49
+ "spec/legacy_data/table_class_name_mapper_spec.rb",
50
+ "spec/legacy_data/table_definition_spec.rb",
51
+ "spec/spec_helper.rb"
52
+ ]
53
+
54
+ if s.respond_to? :specification_version then
55
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
56
+ s.specification_version = 3
57
+
58
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
59
+ s.add_development_dependency(%q<rspec>, [">= 0"])
60
+ s.add_runtime_dependency(%q<activerecord>, [">= 0"])
61
+ else
62
+ s.add_dependency(%q<rspec>, [">= 0"])
63
+ s.add_dependency(%q<activerecord>, [">= 0"])
64
+ end
65
+ else
66
+ s.add_dependency(%q<rspec>, [">= 0"])
67
+ s.add_dependency(%q<activerecord>, [">= 0"])
68
+ end
69
+ end
@@ -0,0 +1,61 @@
1
+ module OracleEnhancedAdapterConstraintsMethods
2
+
3
+ def foreign_keys_for(table_name)
4
+ (owner, table_name) = @connection.describe(table_name)
5
+
6
+ # RSI: changed select from all_constraints to user_constraints - much faster in large data dictionaries
7
+ fks = select_rows(<<-SQL, 'Foreign Keys')
8
+ select parent_c.table_name, cc.column_name
9
+ from user_constraints c, user_constraints parent_c, user_cons_columns cc
10
+ where c.owner = '#{owner}'
11
+ and c.table_name = '#{table_name}'
12
+ and c.r_constraint_name = parent_c.constraint_name
13
+ and c.constraint_type = 'R'
14
+ and cc.owner = c.owner
15
+ and cc.constraint_name = c.constraint_name
16
+ SQL
17
+ end
18
+ def foreign_keys_of(table_name)
19
+ (owner, table_name) = @connection.describe(table_name)
20
+
21
+ # RSI: changed select from all_constraints to user_constraints - much faster in large data dictionaries
22
+ fks = select_rows(<<-SQL, 'Remote Foriegn Keys')
23
+ select c.table_name, cc.column_name, c.delete_rule
24
+ from user_constraints c, user_constraints parent_c, user_cons_columns cc
25
+ where c.owner = '#{owner}'
26
+ and parent_c.table_name = '#{table_name}'
27
+ and c.r_constraint_name = parent_c.constraint_name
28
+ and c.constraint_type = 'R'
29
+ and cc.owner = c.owner
30
+ and cc.constraint_name = c.constraint_name
31
+ SQL
32
+ fks.map do |row|
33
+ dependent = case row[2]
34
+ when 'CASCADE'
35
+ :destroy
36
+ when 'SET NULL'
37
+ :nullify
38
+ end
39
+ options = {:to_table => row[0].downcase, :foreign_key =>row[1].downcase.to_sym }
40
+ options[:dependent] = dependent unless dependent.nil?
41
+ options
42
+ end
43
+ end
44
+
45
+ def constraints(table_name)
46
+ (owner, table_name) = @connection.describe(table_name)
47
+
48
+ # RSI: changed select from all_constraints to user_constraints - much faster in large data dictionaries
49
+ fks = select_rows(<<-SQL, 'User Contraints')
50
+ select c.constraint_name, c.search_condition
51
+ from user_constraints c
52
+ where c.owner = '#{owner}'
53
+ and c.table_name = '#{table_name}'
54
+ and c.constraint_type = 'C'
55
+ and c.generated = 'USER NAME'
56
+ and c.status = 'ENABLED'
57
+ SQL
58
+ end
59
+ end
60
+
61
+ ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.send(:include, OracleEnhancedAdapterConstraintsMethods) if defined? ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter
@@ -0,0 +1,155 @@
1
+ module LegacyData
2
+ class Schema
3
+ attr_reader :table_name
4
+
5
+ def self.analyze(options={})
6
+ initialize_tables(options[:table_name])
7
+
8
+ while table_name = next_table_to_process
9
+ table_definitions[table_name] = analyze_table(table_name)
10
+
11
+ [:has_many, :belongs_to].each do |relation_type|
12
+ associated_tables = table_definitions[table_name][:relations][relation_type].keys.map(&:to_s)
13
+ associated_tables.each {|associated_table| add_pending_table(associated_table) }
14
+ end
15
+ end
16
+
17
+ remove_join_tables
18
+ end
19
+ def self.analyze_table table_name
20
+ new(table_name).analyze_table
21
+ end
22
+
23
+
24
+ def self.initialize_tables(table_name)
25
+ clear_table_definitions
26
+ if table_name
27
+ add_pending_table(table_name)
28
+ else
29
+ LegacyData::Schema.tables.each {|table| add_pending_table(table) }
30
+ end
31
+ end
32
+ def self.add_pending_table table_name
33
+ table_definitions[table_name] = :pending if table_definitions[table_name].nil?
34
+ end
35
+ def self.next_table_to_process
36
+ table_definitions.keys.detect {|table_name| table_definitions[table_name] == :pending }
37
+ end
38
+ def self.clear_table_definitions
39
+ @tables = {}
40
+ end
41
+ def self.table_definitions
42
+ @tables ||= {}
43
+ end
44
+ def self.next_join_table
45
+ table_definitions.keys.detect {|table_name| table_definitions[table_name].join_table? }
46
+ end
47
+ def self.remove_join_tables
48
+ join_tables, other_tables = table_definitions.values.partition &:join_table?
49
+
50
+ join_tables.each { |join_table| convert_to_habtm(join_table) }
51
+
52
+ other_tables
53
+ end
54
+ def self.convert_to_habtm join_table
55
+ join_table.belongs_to_tables.each do |table|
56
+ table_definitions[table].convert_has_many_to_habtm(join_table)
57
+ end
58
+ end
59
+
60
+ def initialize(table_name)
61
+ @table_name = table_name
62
+ end
63
+
64
+ def analyze_table
65
+ puts "analyzing #{table_name} => #{class_name}"
66
+ TableDefinition.new(:table_name => table_name,
67
+ :columns => column_names,
68
+ :primary_key => primary_key,
69
+ :relations => relations,
70
+ :constraints => constraints
71
+ )
72
+ end
73
+
74
+ def self.tables
75
+ connection.tables.sort
76
+ end
77
+
78
+ def class_name
79
+ TableClassNameMapper.class_name_for(self.table_name)
80
+ end
81
+
82
+ def primary_key
83
+ pk_and_sequence_for = connection.pk_and_sequence_for(table_name)
84
+ pk_and_sequence_for.first if pk_and_sequence_for.respond_to? :first
85
+ end
86
+
87
+ def relations
88
+ { :belongs_to => belongs_to_relations,
89
+ :has_many => has_some_relations,
90
+ :has_and_belongs_to_many => {}
91
+ }
92
+ end
93
+
94
+ def belongs_to_relations
95
+ return [] unless connection.respond_to? :foreign_keys_for
96
+
97
+ belongs_to = {}
98
+ connection.foreign_keys_for(table_name).each do |relation|
99
+ belongs_to[relation.first.downcase] = {:foreign_key=>relation.second.downcase.to_sym}
100
+ end
101
+ belongs_to
102
+ end
103
+ def has_some_relations
104
+ return [] unless connection.respond_to? :foreign_keys_of
105
+
106
+ has_some = {}
107
+ connection.foreign_keys_of(table_name).each do |relation|
108
+ has_some[relation.delete(:to_table).downcase] = relation
109
+ end
110
+ has_some
111
+ end
112
+
113
+ def constraints
114
+ unique, multi_column_unique = unique_constraints.partition {|columns| columns.size == 1}
115
+ { :unique => unique,
116
+ :multi_column_unique => multi_column_unique,
117
+ :non_nullable => non_nullable_constraints,
118
+ :custom => custom_constraints
119
+ }
120
+ end
121
+
122
+ def columns
123
+ @columns ||= connection.columns(table_name, "#{table_name} Columns")
124
+ end
125
+ def column_names
126
+ columns.map(&:name)
127
+ end
128
+ def non_nullable_constraints
129
+ non_nullable_constraints = columns.reject(&:null).map(&:name)
130
+ non_nullable_constraints.reject {|col| col == primary_key}
131
+ end
132
+
133
+ def unique_constraints
134
+ connection.indexes(table_name).select(&:unique).map(&:columns)
135
+ end
136
+
137
+ def custom_constraints
138
+ return [] unless connection.respond_to? :constraints
139
+ user_constraints = {}
140
+ connection.constraints(table_name).each do |constraint|
141
+ user_constraints[constraint.first.underscore.to_sym] = constraint.second
142
+ end
143
+ user_constraints
144
+ end
145
+ private
146
+ def self.connection
147
+ @conn ||= ActiveRecord::Base.connection
148
+ end
149
+ def connection
150
+ self.class.connection
151
+ end
152
+
153
+
154
+ end
155
+ end
@@ -0,0 +1,52 @@
1
+ module LegacyData
2
+ class TableClassNameMapper
3
+ include Singleton
4
+
5
+ attr_accessor :naming_convention, :dictionary
6
+ def naming_convention= naming_convention
7
+ @naming_convention = (naming_convention || '*').gsub('*', '(.*)')
8
+ end
9
+
10
+ def dictionary
11
+ @dictionary ||= load_dictionary
12
+ end
13
+
14
+ def self.method_missing(method_id, *arguments, &block)
15
+ instance.send(method_id, *arguments, &block)
16
+ end
17
+
18
+ def clear_dictionary
19
+ @dictionary = nil
20
+ end
21
+
22
+ def load_dictionary
23
+ clear_dictionary
24
+ File.exists?(dictionary_file_name) ? YAML.load_file(dictionary_file_name) : {}
25
+ end
26
+
27
+ def save_dictionary
28
+ File.open(dictionary_file_name, 'w') do |out|
29
+ YAML.dump(dictionary, out)
30
+ end
31
+ end
32
+
33
+ def dictionary_file_name
34
+ File.join(RAILS_ROOT, 'app', 'models', 'table_mappings.yml')
35
+ end
36
+
37
+ def class_name_for table_name
38
+ lookup_class_name(table_name) || compute_class_name(table_name)
39
+ end
40
+
41
+ def lookup_class_name table_name
42
+ dictionary[table_name]
43
+ end
44
+
45
+ def compute_class_name table_name
46
+ table_name =~ /#{naming_convention}/i
47
+ stripped_table_name = $1 || table_name
48
+ dictionary[table_name] = ActiveRecord::Base.class_name(stripped_table_name.downcase.pluralize)
49
+ end
50
+ end
51
+
52
+ end
@@ -0,0 +1,41 @@
1
+ module LegacyData
2
+ class TableDefinition
3
+ attr_accessor :class_name, :table_name, :columns, :primary_key, :relations, :constraints
4
+
5
+ def initialize(options)
6
+ options.each {|key, value| self.send("#{key}=", value) }
7
+ end
8
+
9
+ def [] key
10
+ self.send(key)
11
+ end
12
+
13
+ def to_hash
14
+ hash = {}
15
+ [:class_name, :table_name, :columns, :primary_key, :relations, :constraints].each {|field| hash[field] = self.send(field) }
16
+ hash
17
+ end
18
+
19
+ def join_table?
20
+ (columns.size == 2) and relations[:belongs_to] and (relations[:belongs_to].values.map {|value| value[:foreign_key]}.map(&:to_s) == columns)
21
+ end
22
+
23
+ def belongs_to_relations
24
+ return {} if relations.nil? or relations[:belongs_to].nil?
25
+ relations[:belongs_to]
26
+ end
27
+
28
+ def belongs_to_tables
29
+ return [] if belongs_to_relations == {}
30
+ belongs_to_relations.keys
31
+ end
32
+
33
+ def convert_has_many_to_habtm(join_table)
34
+ other_table_name = join_table.belongs_to_tables.detect {|table_name| table_name != self.table_name}
35
+ relations[:has_and_belongs_to_many][other_table_name] = { :foreign_key =>join_table.belongs_to_relations[table_name][:foreign_key],
36
+ :association_foreign_key=>join_table.belongs_to_relations[other_table_name][:foreign_key],
37
+ :join_table =>join_table.table_name.to_sym }
38
+ relations[:has_many].delete(join_table.table_name)
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,4 @@
1
+ require 'legacy_data/table_definition'
2
+ require 'legacy_data/schema'
3
+ require 'legacy_data/table_class_name_mapper'
4
+ require 'active_record/connection_adapters/oracle_enhanced_adapter'
@@ -0,0 +1,179 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe LegacyData::Schema do
4
+ describe 'following associations' do
5
+ before :each do
6
+ LegacyData::Schema.stub!(:analyze_table).with('posts' ).and_return(@posts_analysis =mock(:posts, {:join_table? =>false}))
7
+ LegacyData::Schema.stub!(:analyze_table).with('comments').and_return(@comments_analysis=mock(:comments, {:join_table? =>false}))
8
+ @posts_analysis.stub!( :[]).with(:relations).and_return({:belongs_to=>{ }, :has_many=>{:comments=>{:foreign_key=>:posts_id}}})
9
+ @comments_analysis.stub!(:[]).with(:relations).and_return({:belongs_to=>{:posts=>{:foreign_key=>:posts_id}}, :has_many=>{ }})
10
+ end
11
+
12
+ it 'should analyze all tables when not given a table to start with' do
13
+ LegacyData::Schema.should_receive(:tables).and_return(['posts', 'comments'])
14
+
15
+ LegacyData::Schema.analyze.should include(@posts_analysis, @comments_analysis)
16
+ end
17
+
18
+ it 'should find the associated comments when starting with posts' do
19
+ LegacyData::Schema.analyze(:table_name=>'posts').should include(@posts_analysis, @comments_analysis)
20
+ end
21
+ end
22
+
23
+ it 'should analyze a single table' do
24
+ LegacyData::Schema.should_receive(:new).with('some_table').and_return(schema=mock)
25
+ schema.should_receive(:analyze_table).and_return(analysis=mock)
26
+
27
+ LegacyData::Schema.analyze_table('some_table').should == analysis
28
+ end
29
+
30
+ before :each do
31
+ LegacyData::Schema.instance_variable_set('@conn', nil)
32
+ ActiveRecord::Base.stub(:connection).and_return(@connection=stub(:connection))
33
+ end
34
+
35
+ it "should get a list of the tables from the database" do
36
+ @connection.should_receive(:tables).and_return(['comments', 'people', 'posts'])
37
+ LegacyData::Schema.tables.should == ['comments', 'people', 'posts']
38
+ end
39
+
40
+ describe 'analyzing a table' do
41
+ before :each do
42
+ @schema = LegacyData::Schema.new('some_table')
43
+ @schema.stub!(:puts)
44
+ end
45
+
46
+ it 'should have all the information about the table' do
47
+ @schema.stub!(:class_name ).and_return(class_name =mock)
48
+ @schema.stub!(:column_names).and_return(columns =mock)
49
+ @schema.stub!(:primary_key ).and_return(primary_key=mock)
50
+ @schema.stub!(:relations ).and_return(relations =mock)
51
+ @schema.stub!(:constraints ).and_return(constraints=mock)
52
+ @schema.analyze_table[:table_name ].should == 'some_table'
53
+ @schema.analyze_table[:columns ].should == columns
54
+ @schema.analyze_table[:primary_key].should == primary_key
55
+ @schema.analyze_table[:relations ].should == relations
56
+ @schema.analyze_table[:constraints].should == constraints
57
+ end
58
+
59
+ it 'should have the correct table name' do
60
+ @schema.table_name.should == 'some_table'
61
+ end
62
+
63
+ it 'should have the correct class name' do
64
+ LegacyData::TableClassNameMapper.should_receive(:class_name_for).with('some_table').and_return('SomeClass')
65
+ @schema.class_name.should == 'SomeClass'
66
+ end
67
+
68
+ it 'should have the correct primary_key' do
69
+ @connection.should_receive(:pk_and_sequence_for).with('some_table').and_return(['PK', 'SEQ'])
70
+ @schema.primary_key.should == 'PK'
71
+ end
72
+
73
+ it 'should have the names of all columns' do
74
+ @schema.should_receive(:columns).and_return([col1=mock(:col1, :name=>'Col1'), col2=mock(:col2, :name=>'Col2')])
75
+ @schema.column_names.sort.should == ['Col1', 'Col2']
76
+ end
77
+
78
+ describe 'relations' do
79
+ it 'should have the different types of relations' do
80
+ @schema.stub!(:belongs_to_relations).and_return([belongs_to=mock])
81
+ @schema.stub!(:has_some_relations ).and_return([has_some =mock])
82
+
83
+ @schema.relations.should == {:belongs_to=>[belongs_to], :has_many=>[has_some], :has_and_belongs_to_many=>{}}
84
+ end
85
+
86
+
87
+ it 'should give no "has_some" (has_many and has_one) relationships when the adapter does not support foreign keys' do
88
+ @connection.should_receive(:respond_to?).with(:foreign_keys_of).and_return(false)
89
+ @schema.has_some_relations.should == []
90
+ end
91
+
92
+ it 'should get all "has_some" (has_many and has_one) relationships when my primary key is the foreign key in another table ' do
93
+ @connection.should_receive(:respond_to?).with(:foreign_keys_of).and_return(true)
94
+ @connection.should_receive(:foreign_keys_of).and_return([ {:to_table=>'other_table', :foreign_key=>:pk, :dependent=>:destroy},
95
+ {:to_table=>'the_table', :foreign_key=>:pk, :dependent=>:destroy} ])
96
+ @schema.has_some_relations.should == {'other_table' => {:foreign_key=>:pk, :dependent=>:destroy},
97
+ 'the_table' => {:foreign_key=>:pk, :dependent=>:destroy}}
98
+ end
99
+
100
+ it 'should give no "belongs_to" when the adapter does not support foreign keys' do
101
+ @connection.should_receive(:respond_to?).with(:foreign_keys_for).and_return(false)
102
+ @schema.belongs_to_relations.should == []
103
+ end
104
+
105
+ it 'should get all "belongs_to" relationships when a foreign key is in my table' do
106
+ @connection.should_receive(:respond_to?).with(:foreign_keys_for).and_return(true)
107
+ @connection.should_receive(:foreign_keys_for).and_return([['OTHER_TABLE', 'FK_1'], ['THE_TABLE', 'the_table_id']])
108
+ @schema.belongs_to_relations.should == {'other_table' => {:foreign_key=>:fk_1 },
109
+ 'the_table' => {:foreign_key=>:the_table_id} }
110
+ end
111
+ end
112
+
113
+ describe 'constraints' do
114
+ it 'should have the different types of constraints' do
115
+ @schema.stub!(:unique_constraints ).and_return([['unique_col'], ['col1', 'col2']])
116
+ @schema.stub!(:non_nullable_constraints).and_return([non_nullable=mock ])
117
+ @schema.stub!(:custom_constraints ).and_return([custom =mock ])
118
+
119
+ @schema.constraints.should == {:unique=>[['unique_col']], :multi_column_unique=>[['col1', 'col2']], :non_nullable=>[non_nullable], :custom=>[custom]}
120
+ end
121
+
122
+ it 'should get non-nullable constraints as all columns that do not allow null except the primary key' do
123
+ @schema.stub(:primary_key).and_return('col1')
124
+ @connection.should_receive(:columns).with('some_table', 'some_table Columns').and_return([col1=stub(:null=>false, :name=>'col1'),
125
+ col2=stub(:null=>false, :name=>'col2'),
126
+ col3=stub(:null=>true, :name=>'col3'),
127
+ col3=stub(:null=>false, :name=>'col4')])
128
+ @schema.non_nullable_constraints.should == ['col2', 'col4']
129
+ end
130
+
131
+ it 'should get uniqueness constraints' do
132
+ @connection.should_receive(:indexes).with('some_table').and_return([idx1=stub(:unique=>true, :columns=>['col1']),
133
+ idx2=stub(:unique=>false, :columns=>['col2']),
134
+ idx3=stub(:unique=>true, :columns=>['col3', 'col4'])])
135
+ @schema.unique_constraints.should == [['col1'], ['col3', 'col4']]
136
+ end
137
+
138
+ it 'should give no custom constraints when the adapter does not support it' do
139
+ @connection.should_receive(:respond_to?).with(:constraints).and_return(false)
140
+ @schema.custom_constraints.should == []
141
+ end
142
+
143
+ it 'should get all "belongs_to" relationships when a foreign key is in my table' do
144
+ @connection.should_receive(:respond_to?).with(:constraints).and_return(true)
145
+ @connection.should_receive(:constraints).and_return([['SomeConstraint', 'custom sql 1'], ['anotherconstraint', 'more custom sql']])
146
+ @schema.custom_constraints.should == {:some_constraint => 'custom sql 1', :anotherconstraint => 'more custom sql'}
147
+ end
148
+ end
149
+ end
150
+
151
+ describe 'convert join tables into HABTM relations' do
152
+ before :each do
153
+ LegacyData::Schema.clear_table_definitions
154
+ LegacyData::Schema.table_definitions['posts' ] = @posts_table =mock(:posts, :join_table? => false)
155
+ LegacyData::Schema.table_definitions['tags' ] = @tags_table =mock(:tags, :join_table? => false)
156
+ LegacyData::Schema.table_definitions['tag_posts'] = @tag_posts_join_table=mock(:tag_posts, :join_table? => true )
157
+ end
158
+
159
+ it 'should remove the join table from the list of tables' do
160
+ @tag_posts_join_table.stub!(:belongs_to_tables).and_return(['posts', 'tags'])
161
+ @posts_table.should_receive(:convert_has_many_to_habtm).with(@tag_posts_join_table)
162
+ @tags_table.should_receive( :convert_has_many_to_habtm).with(@tag_posts_join_table)
163
+
164
+ analyzed_tables = LegacyData::Schema.remove_join_tables
165
+
166
+ analyzed_tables.should include(@posts_table, @tags_table)
167
+ analyzed_tables.size.should == 2
168
+ end
169
+
170
+ it 'should find the next join table' do
171
+ LegacyData::Schema.next_join_table.should == 'tag_posts'
172
+ end
173
+
174
+ it 'should know when there are no join tables' do
175
+ LegacyData::Schema.table_definitions.delete('tag_posts')
176
+ LegacyData::Schema.next_join_table.should == nil
177
+ end
178
+ end
179
+ end