alexrothenberg-legacy_data 0.0.6 → 0.0.7
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.
- data/VERSION +1 -1
- data/generators/models_from_tables/models_from_tables_generator.rb +13 -12
- data/generators/models_from_tables/templates/model.rb +4 -4
- data/legacy_data.gemspec +7 -4
- data/lib/legacy_data.rb +1 -0
- data/lib/legacy_data/schema.rb +34 -16
- data/lib/legacy_data/table_class_name_mapper.rb +57 -0
- data/spec/{legacy_data_schema_spec.rb → legacy_data/schema_spec.rb} +33 -32
- data/spec/legacy_data/table_class_name_mapper_spec.rb +21 -0
- metadata +7 -4
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.0.
|
|
1
|
+
0.0.7
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
require File.dirname(__FILE__) + '/../../lib/legacy_data/schema'
|
|
2
|
+
require File.dirname(__FILE__) + '/../../lib/legacy_data/table_class_name_mapper'
|
|
2
3
|
require File.dirname(__FILE__) + '/../../lib/active_record/connection_adapters/oracle_enhanced_adapter'
|
|
3
4
|
|
|
4
5
|
class ModelsFromTablesGenerator < Rails::Generator::Base
|
|
@@ -6,20 +7,20 @@ class ModelsFromTablesGenerator < Rails::Generator::Base
|
|
|
6
7
|
record do |m|
|
|
7
8
|
m.directory File.join('app/models')
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
puts "analyzing #{table_name}"
|
|
17
|
-
analysis = LegacyData::Schema.new(table_name, options[:table_naming_convention]).analyze_table
|
|
10
|
+
LegacyData::TableClassNameMapper.naming_convention = options[:table_naming_convention]
|
|
11
|
+
|
|
12
|
+
analyzed_tables = LegacyData::Schema.analyze(:table_name=>options[:table_name])
|
|
13
|
+
LegacyData::TableClassNameMapper.save_dictionary
|
|
14
|
+
puts "Please look at #{LegacyData::TableClassNameMapper.dictionary_file_name} [hit <enter> to continue]"
|
|
15
|
+
gets
|
|
16
|
+
LegacyData::TableClassNameMapper.load_dictionary
|
|
18
17
|
|
|
19
|
-
|
|
18
|
+
analyzed_tables.each do |analyzed_table|
|
|
19
|
+
analyzed_table[:class_name] = LegacyData::TableClassNameMapper.class_name_for(analyzed_table[:table_name])
|
|
20
|
+
m.class_collisions :class_path, analyzed_table[:class_name]
|
|
20
21
|
m.template 'model.rb',
|
|
21
|
-
File.join('app/models', "#{
|
|
22
|
-
:assigns =>
|
|
22
|
+
File.join('app/models', "#{analyzed_table[:class_name].underscore}.rb"),
|
|
23
|
+
:assigns => analyzed_table
|
|
23
24
|
end
|
|
24
25
|
end
|
|
25
26
|
end
|
|
@@ -3,11 +3,11 @@ class <%= class_name -%> < ActiveRecord::Base
|
|
|
3
3
|
<%= "set_primary_key #{primary_key.inspect}" if primary_key %>
|
|
4
4
|
|
|
5
5
|
# Relationships
|
|
6
|
-
<%- relations[:has_some].each do |
|
|
7
|
-
-%> has_many <%=
|
|
6
|
+
<%- relations[:has_some].each do |table_name, foreign_key|
|
|
7
|
+
-%> has_many <%= LegacyData::TableClassNameMapper.class_name_for(table_name).underscore.pluralize.to_sym.inspect %>, :foreign_key => <%= foreign_key.inspect %>
|
|
8
8
|
<%- end -%>
|
|
9
|
-
<%- relations[:belongs_to].each do |
|
|
10
|
-
-%> belongs_to <%=
|
|
9
|
+
<%- relations[:belongs_to].each do |table_name, foreign_key|
|
|
10
|
+
-%> belongs_to <%= LegacyData::TableClassNameMapper.class_name_for(table_name).underscore.to_sym.inspect %>, :foreign_key => <%= foreign_key.inspect %>
|
|
11
11
|
<%- end -%>
|
|
12
12
|
|
|
13
13
|
# Constraints
|
data/legacy_data.gemspec
CHANGED
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
|
|
6
6
|
Gem::Specification.new do |s|
|
|
7
7
|
s.name = %q{legacy_data}
|
|
8
|
-
s.version = "0.0.
|
|
8
|
+
s.version = "0.0.7"
|
|
9
9
|
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
|
11
11
|
s.authors = ["Alex Rothenberg"]
|
|
12
|
-
s.date = %q{2009-09-
|
|
12
|
+
s.date = %q{2009-09-20}
|
|
13
13
|
s.description = %q{Create ActiveRecord models from an existing database}
|
|
14
14
|
s.email = %q{alex@alexrothenberg.com}
|
|
15
15
|
s.extra_rdoc_files = [
|
|
@@ -30,7 +30,9 @@ Gem::Specification.new do |s|
|
|
|
30
30
|
"lib/active_record/connection_adapters/oracle_enhanced_adapter.rb",
|
|
31
31
|
"lib/legacy_data.rb",
|
|
32
32
|
"lib/legacy_data/schema.rb",
|
|
33
|
-
"
|
|
33
|
+
"lib/legacy_data/table_class_name_mapper.rb",
|
|
34
|
+
"spec/legacy_data/schema_spec.rb",
|
|
35
|
+
"spec/legacy_data/table_class_name_mapper_spec.rb",
|
|
34
36
|
"spec/spec_helper.rb"
|
|
35
37
|
]
|
|
36
38
|
s.has_rdoc = true
|
|
@@ -40,7 +42,8 @@ Gem::Specification.new do |s|
|
|
|
40
42
|
s.rubygems_version = %q{1.3.2}
|
|
41
43
|
s.summary = %q{Create ActiveRecord models from an existing database}
|
|
42
44
|
s.test_files = [
|
|
43
|
-
"spec/
|
|
45
|
+
"spec/legacy_data/schema_spec.rb",
|
|
46
|
+
"spec/legacy_data/table_class_name_mapper_spec.rb",
|
|
44
47
|
"spec/spec_helper.rb"
|
|
45
48
|
]
|
|
46
49
|
|
data/lib/legacy_data.rb
CHANGED
data/lib/legacy_data/schema.rb
CHANGED
|
@@ -2,13 +2,41 @@ module LegacyData
|
|
|
2
2
|
class Schema
|
|
3
3
|
attr_reader :table_name
|
|
4
4
|
|
|
5
|
-
def
|
|
5
|
+
def self.analyze(options={})
|
|
6
|
+
analyzed_schema = []
|
|
7
|
+
|
|
8
|
+
@tables = {}
|
|
9
|
+
if options[:table_name]
|
|
10
|
+
@tables[options[:table_name]] = :pending
|
|
11
|
+
else
|
|
12
|
+
LegacyData::Schema.tables.each {|table| @tables[table] = :pending }
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
while table_name = next_table_to_process
|
|
16
|
+
# puts " Tables: #{@tables.inspect}"
|
|
17
|
+
@tables[table_name] = analyze_table(table_name)
|
|
18
|
+
|
|
19
|
+
[:has_some, :belongs_to].each do |relation_type|
|
|
20
|
+
associated_tables = @tables[table_name][:relations][relation_type].keys.map(&:to_s)
|
|
21
|
+
associated_tables.each {|associated_table| @tables[associated_table] = :pending if @tables[associated_table].nil? }
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
@tables.values
|
|
25
|
+
end
|
|
26
|
+
def self.analyze_table table_name
|
|
27
|
+
new(table_name).analyze_table
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.next_table_to_process
|
|
31
|
+
@tables.keys.detect {|table_name| @tables[table_name] == :pending }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def initialize(table_name)
|
|
6
35
|
@table_name = table_name
|
|
7
|
-
@naming_convention = naming_convention
|
|
8
|
-
@naming_convention = /^#{naming_convention.gsub('*', '(.*)')}$/i if @naming_convention.is_a? String
|
|
9
36
|
end
|
|
10
37
|
|
|
11
38
|
def analyze_table
|
|
39
|
+
puts "analyzing #{table_name} => #{class_name}"
|
|
12
40
|
{ :table_name => table_name,
|
|
13
41
|
:class_name => class_name,
|
|
14
42
|
:primary_key => primary_key,
|
|
@@ -21,16 +49,8 @@ module LegacyData
|
|
|
21
49
|
connection.tables.select {|table_name| table_name =~ name_pattern }.sort
|
|
22
50
|
end
|
|
23
51
|
|
|
24
|
-
|
|
25
|
-
|
|
26
52
|
def class_name
|
|
27
|
-
class_name_for(self.table_name)
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def class_name_for table
|
|
31
|
-
table =~ @naming_convention
|
|
32
|
-
stripped_table_name = $1 || table
|
|
33
|
-
ActiveRecord::Base.class_name(stripped_table_name.downcase.pluralize)
|
|
53
|
+
TableClassNameMapper.class_name_for(self.table_name)
|
|
34
54
|
end
|
|
35
55
|
|
|
36
56
|
def primary_key
|
|
@@ -49,8 +69,7 @@ module LegacyData
|
|
|
49
69
|
|
|
50
70
|
belongs_to = {}
|
|
51
71
|
connection.foreign_keys_for(table_name).each do |relation|
|
|
52
|
-
|
|
53
|
-
belongs_to[class_name] = relation.second.downcase.to_sym
|
|
72
|
+
belongs_to[relation.first.downcase] = relation.second.downcase.to_sym
|
|
54
73
|
end
|
|
55
74
|
belongs_to
|
|
56
75
|
end
|
|
@@ -59,8 +78,7 @@ module LegacyData
|
|
|
59
78
|
|
|
60
79
|
has_some = {}
|
|
61
80
|
connection.foreign_keys_of(table_name).each do |relation|
|
|
62
|
-
|
|
63
|
-
has_some[class_name] = relation.second.downcase.to_sym
|
|
81
|
+
has_some[relation.first.downcase] = relation.second.downcase.to_sym
|
|
64
82
|
end
|
|
65
83
|
has_some
|
|
66
84
|
end
|
|
@@ -0,0 +1,57 @@
|
|
|
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.naming_convention= value
|
|
15
|
+
instance.naming_convention = value
|
|
16
|
+
end
|
|
17
|
+
def self.load_dictionary
|
|
18
|
+
instance.load_dictionary
|
|
19
|
+
end
|
|
20
|
+
def self.save_dictionary
|
|
21
|
+
instance.save_dictionary
|
|
22
|
+
end
|
|
23
|
+
def self.dictionary_file_name
|
|
24
|
+
instance.dictionary_file_name
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def load_dictionary
|
|
28
|
+
@dictionary = nil
|
|
29
|
+
YAML.load_file(dictionary_file_name) || {}
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def save_dictionary
|
|
33
|
+
File.open(dictionary_file_name, 'w') do |out|
|
|
34
|
+
YAML.dump(dictionary, out)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def dictionary_file_name
|
|
39
|
+
File.join(RAILS_ROOT, 'app', 'models', 'table_mappings.yaml')
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def self.class_name_for table_name
|
|
43
|
+
instance.lookup_class_name(table_name) || instance.compute_class_name(table_name)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def lookup_class_name table_name
|
|
47
|
+
dictionary[table_name]
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def compute_class_name table_name
|
|
51
|
+
table_name =~ /#{naming_convention}/i
|
|
52
|
+
stripped_table_name = $1 || table_name
|
|
53
|
+
dictionary[table_name] = ActiveRecord::Base.class_name(stripped_table_name.downcase.pluralize)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
end
|
|
@@ -1,6 +1,29 @@
|
|
|
1
|
-
require File.expand_path(File.dirname(__FILE__) + '
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
|
2
2
|
|
|
3
3
|
describe LegacyData::Schema do
|
|
4
|
+
describe 'following associations' do
|
|
5
|
+
it 'should analyze all tables when not given a table to start with' do
|
|
6
|
+
LegacyData::Schema.should_receive(:tables).and_return(['posts', 'comments'])
|
|
7
|
+
LegacyData::Schema.should_receive(:analyze_table).with('posts' ).and_return(posts_analysis =mock)
|
|
8
|
+
LegacyData::Schema.should_receive(:analyze_table).with('comments').and_return(comments_analysis=mock)
|
|
9
|
+
|
|
10
|
+
posts_analysis.stub!( :[]).with(:relations).and_return({:belongs_to=>{}, :has_some=>{}})
|
|
11
|
+
comments_analysis.stub!(:[]).with(:relations).and_return({:belongs_to=>{}, :has_some=>{}})
|
|
12
|
+
|
|
13
|
+
LegacyData::Schema.analyze.should include(posts_analysis, comments_analysis)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it 'should find the associated comments when starting with posts' do
|
|
17
|
+
LegacyData::Schema.should_receive(:analyze_table).with('posts' ).and_return(posts_analysis =mock)
|
|
18
|
+
LegacyData::Schema.should_receive(:analyze_table).with('comments').and_return(comments_analysis=mock)
|
|
19
|
+
|
|
20
|
+
posts_analysis.stub!( :[]).with(:relations).and_return({:belongs_to=>{ }, :has_some=>{:comments=>:posts_id}})
|
|
21
|
+
comments_analysis.stub!(:[]).with(:relations).and_return({:belongs_to=>{:posts=>:posts_id}, :has_some=>{ }})
|
|
22
|
+
|
|
23
|
+
LegacyData::Schema.analyze(:table_name=>'posts').should include(posts_analysis, comments_analysis)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
4
27
|
before :each do
|
|
5
28
|
LegacyData::Schema.instance_variable_set('@conn', nil)
|
|
6
29
|
ActiveRecord::Base.stub(:connection).and_return(@connection=stub(:connection))
|
|
@@ -19,14 +42,14 @@ describe LegacyData::Schema do
|
|
|
19
42
|
describe 'analyzing a table' do
|
|
20
43
|
before :each do
|
|
21
44
|
@schema = LegacyData::Schema.new('some_table')
|
|
45
|
+
@schema.stub!(:puts)
|
|
22
46
|
end
|
|
23
47
|
|
|
24
|
-
it 'should have all the information about the table' do
|
|
25
|
-
@schema.
|
|
26
|
-
@schema.
|
|
27
|
-
@schema.
|
|
28
|
-
@schema.
|
|
29
|
-
|
|
48
|
+
it 'should have all the information about the table' do
|
|
49
|
+
@schema.stub!(:class_name)
|
|
50
|
+
@schema.stub!(:primary_key)
|
|
51
|
+
@schema.stub!(:relations)
|
|
52
|
+
@schema.stub!(:constraints)
|
|
30
53
|
@schema.analyze_table.keys.should include(:table_name, :class_name, :primary_key, :relations, :constraints)
|
|
31
54
|
end
|
|
32
55
|
|
|
@@ -35,32 +58,10 @@ describe LegacyData::Schema do
|
|
|
35
58
|
end
|
|
36
59
|
|
|
37
60
|
it 'should have the correct class name' do
|
|
38
|
-
|
|
61
|
+
LegacyData::TableClassNameMapper.should_receive(:class_name_for).with('some_table').and_return('SomeClass')
|
|
39
62
|
@schema.class_name.should == 'SomeClass'
|
|
40
63
|
end
|
|
41
64
|
|
|
42
|
-
it 'should handle tables with a singular name that ends with s' do
|
|
43
|
-
@schema.class_name_for('ADDRESS').should == 'Address'
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
it 'should handle tables with an irregular pluralization name' do
|
|
47
|
-
@schema.class_name_for('TBPERSON').should == 'Tbperson'
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
describe 'ignore the table prefix naming convention when figuring out the model name' do
|
|
51
|
-
before :each do
|
|
52
|
-
@schema = LegacyData::Schema.new('some_table', /^TB(.*)$/)
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
it 'should strip off the prefix' do
|
|
56
|
-
@schema.class_name_for('TBPERSON').should == 'Person'
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
it 'should work on tables that do not have the prefix' do
|
|
60
|
-
@schema.class_name_for('PERSON').should == 'Person'
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
|
-
|
|
64
65
|
it 'should have the correct primary_key' do
|
|
65
66
|
@connection.should_receive(:pk_and_sequence_for).with('some_table').and_return(['PK', 'SEQ'])
|
|
66
67
|
@schema.primary_key.should == 'PK'
|
|
@@ -74,7 +75,7 @@ describe LegacyData::Schema do
|
|
|
74
75
|
it 'should get all "has_some" (has_many and has_one) relationships when my primary key is the foreign key in another table ' do
|
|
75
76
|
@connection.should_receive(:respond_to?).with(:foreign_keys_of).and_return(true)
|
|
76
77
|
@connection.should_receive(:foreign_keys_of).and_return([['OTHER_TABLE', 'PK'], ['THE_TABLE', 'PK']])
|
|
77
|
-
@schema.has_some_relations.should == {
|
|
78
|
+
@schema.has_some_relations.should == {'other_table' => :pk, 'the_table' => :pk}
|
|
78
79
|
end
|
|
79
80
|
|
|
80
81
|
it 'should give no "belongs_to" when the adapter does not support foreign keys' do
|
|
@@ -85,7 +86,7 @@ describe LegacyData::Schema do
|
|
|
85
86
|
it 'should get all "belongs_to" relationships when a foreign key is in my table' do
|
|
86
87
|
@connection.should_receive(:respond_to?).with(:foreign_keys_for).and_return(true)
|
|
87
88
|
@connection.should_receive(:foreign_keys_for).and_return([['OTHER_TABLE', 'FK_1'], ['THE_TABLE', 'the_table_id']])
|
|
88
|
-
@schema.belongs_to_relations.should == {
|
|
89
|
+
@schema.belongs_to_relations.should == {'other_table' => :fk_1, 'the_table' => :the_table_id}
|
|
89
90
|
end
|
|
90
91
|
|
|
91
92
|
it 'should have the correct constraints'
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
|
2
|
+
|
|
3
|
+
describe LegacyData::TableClassNameMapper do
|
|
4
|
+
describe 'computing class names without an input dictionary' do
|
|
5
|
+
before :each do
|
|
6
|
+
LegacyData::TableClassNameMapper.instance.stub!(:load_dictionary).and_return({})
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
it 'should handle tables with a singular name that ends with s' do
|
|
10
|
+
LegacyData::TableClassNameMapper.class_name_for('ADDRESS').should == 'Address'
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
it 'should handle tables with an irregular pluralization name' do
|
|
14
|
+
LegacyData::TableClassNameMapper.class_name_for('TBPERSON').should == 'Tbperson'
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it 'should work on tables that do not have the prefix' do
|
|
18
|
+
LegacyData::TableClassNameMapper.class_name_for('PERSON').should == 'Person'
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: alexrothenberg-legacy_data
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.7
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Alex Rothenberg
|
|
@@ -9,7 +9,7 @@ autorequire:
|
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
11
|
|
|
12
|
-
date: 2009-09-
|
|
12
|
+
date: 2009-09-20 00:00:00 -07:00
|
|
13
13
|
default_executable:
|
|
14
14
|
dependencies:
|
|
15
15
|
- !ruby/object:Gem::Dependency
|
|
@@ -55,7 +55,9 @@ files:
|
|
|
55
55
|
- lib/active_record/connection_adapters/oracle_enhanced_adapter.rb
|
|
56
56
|
- lib/legacy_data.rb
|
|
57
57
|
- lib/legacy_data/schema.rb
|
|
58
|
-
-
|
|
58
|
+
- lib/legacy_data/table_class_name_mapper.rb
|
|
59
|
+
- spec/legacy_data/schema_spec.rb
|
|
60
|
+
- spec/legacy_data/table_class_name_mapper_spec.rb
|
|
59
61
|
- spec/spec_helper.rb
|
|
60
62
|
has_rdoc: true
|
|
61
63
|
homepage: http://github.com/alexrothenberg/legacy_data
|
|
@@ -85,5 +87,6 @@ signing_key:
|
|
|
85
87
|
specification_version: 3
|
|
86
88
|
summary: Create ActiveRecord models from an existing database
|
|
87
89
|
test_files:
|
|
88
|
-
- spec/
|
|
90
|
+
- spec/legacy_data/schema_spec.rb
|
|
91
|
+
- spec/legacy_data/table_class_name_mapper_spec.rb
|
|
89
92
|
- spec/spec_helper.rb
|