rare_map 0.9.7 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -1,6 +1,187 @@
1
- = rare_map
1
+ = rare_map >= 1.0.0
2
+
3
+ Relational db to ActiveREcord models MAPper
4
+
5
+ RareMap can be used for BOTH standalone application & Rails
6
+
7
+ WARN: rare_map >= 1.0.0 is totally not compatible with 0.9.x
8
+
9
+ * Installation:
10
+
11
+ gem install rare_map
12
+
13
+ == Basic RareMap use
14
+
15
+ ==== Standalone:
16
+ Create a new database.yml with following lines in the root of your application
17
+
18
+ ==== Rails:
19
+ Add following lines to config/database.yml of rails
20
+
21
+ * database.yml
22
+
23
+ rare_map:
24
+ legacy_db:
25
+ adapter: sqlite3
26
+ database: db/db1.sqlite3
27
+
28
+ your_db:
29
+ adapter: mysql2
30
+ host: localhost
31
+ database: db_name
32
+ port: 3306
33
+ username: user
34
+ password: pw
35
+
36
+ * Run following command in the root of your application or rails
37
+
38
+ $ raremap
39
+
40
+ * Standalone: A demo.rb example is generated for you in the root of your applicaiton
41
+
42
+ * Rails: Add following line to your config/application.rb
43
+ config.autoload_paths += Dir[Rails.root.join('app', 'models', '{**}')]
44
+
45
+ == Advanced RareMap use
46
+
47
+ ==== Seperate databases to groups(highly recommended)
48
+
49
+ rare_map:
50
+ her_group:
51
+ -
52
+ db1:
53
+ adapter: sqlite3
54
+ database: db/db1.sqlite3
55
+ -
56
+ db2:
57
+ adapter: sqlite3
58
+ database: db/db1.sqlite3
59
+
60
+ his_group:
61
+ -
62
+ db1:
63
+ adapter: sqlite3
64
+ database: db/db3.sqlite3
65
+ -
66
+ db2:
67
+ adapter: sqlite3
68
+ database: db/db4.sqlite3
69
+
70
+ There are benefits by separating databases into groups
71
+ 1. Associations are built between databases within a group
72
+ 2. Model name is prepended with group name
73
+ 3. Models of a group are organized within a folder
74
+
75
+ If all your data reside in several legacy databases, it is important to build back those associations across databases
76
+
77
+ If there are 2 or more tables with the same name, giving them group names could avoid naming collision
78
+
79
+ If there are tons of tables, it is better to organize them well
80
+
81
+
82
+ ==== Set up RareMap Options
83
+ rare_map_opts:
84
+ foreign_key:
85
+ suffix: fk
86
+ alias:
87
+ abnormal_fk1: table1
88
+ abnormal_fk2: table2
89
+ primary_key:
90
+ table1: abnormal_pk
91
+
92
+ * rare_map_opts[foreign_key][suffix]: If your foreign keys are not ended with 'id', you can specify the suffix you want here
93
+ * rare_map_opts[foreign_key][alias]: If naming convention is not followed by some foreign keys, you can list them here
94
+ * rare_map_opts[primary_key]: Usually rare_map can identify the primary key of a table, if it fails, please list primary keys here
95
+
96
+ ==== RareMap Options Precedence
97
+
98
+ You can place rare_map options in 3 ways
99
+
100
+ rare_map:
101
+ rare_map_opts: # Global options
102
+ ...
103
+ group1:
104
+ -
105
+ rare_map_opts: # Group options
106
+ ...
107
+ -
108
+ db1:
109
+ ...
110
+ legacy_db:
111
+ adapter: sqlite3
112
+ database: db/db.sqlite3
113
+ rare_map_opts: # DB options
114
+ ...
115
+
116
+ * Precedence: DB > Group > Global
117
+
118
+
119
+ = rare_map ~> 0.9.x(deprecated)
120
+
121
+ Relational DB to ActiveRecord models Mapper
122
+
123
+ * Installation:
124
+
125
+ gem install rare_map
126
+
127
+ == Basic RareMap use
128
+
129
+ Set up your legacay db under your Rails applicaiton
130
+
131
+ * database.yml
132
+
133
+ mylegacy_db1:
134
+ adapter: sqlite3
135
+ database: db/db1.sqlite3
136
+
137
+ mylegacy_db2:
138
+ adapter: sqlite3
139
+ database: db/db2/sqlite3
140
+
141
+ hislegacy_db1:
142
+ adapter: sqlite3
143
+ database: db/db1.sqlite3
144
+
145
+ "mylegacy" is treated as a group name.
146
+
147
+ You MUST give all your legacy databases a group name.
148
+
149
+ The associations of databases with the same group name will be built together.
150
+
151
+
152
+ * Create a {RAILS_ROOT}/demo.rb file with following content under your Rails application
153
+
154
+ RareMap.mapping
155
+
156
+ * Under terminal
157
+
158
+ ruby demo.rb
159
+
160
+ * Add following line to your config/application.rb
161
+
162
+ config.autoload_paths += Dir[Rails.root.join('app', 'models', '{**}')]
163
+
164
+ == Advanced RareMap use
165
+
166
+ If the foreign key of your legacy db is not end with '_id', you can change it manually.
167
+
168
+ foreign_keys = { :mylegacy => 'id', :hislegacy => 'id' }
169
+
170
+ If the foreign key is not started with regular table names, you can create aliases for them.
171
+
172
+ aliases = { :mylegacy => { :pclid => :protocol, personid => people }, :hislegacy => { :measid => measurement } }
173
+
174
+ Some of the databases like Oracle won't show the primary key on the dump of schema. If your primary key of a table is not 'id' and it didn't show the schema either. You can specify your special primary key beside 'id' if you want.
175
+
176
+ primary_keys = { :mylegacy => { :table1 => 'table1id' }, :hislegacy => { :table2 => 'table2id' } }
177
+
178
+ When you using the RareMap
179
+
180
+ info = RareMap.mapping(:foreign_key => foreign_keys, :alias => aliases, :primary_key => primary_keys)
181
+
182
+ [info]
183
+ It contains all details of your tables.
2
184
 
3
- Description goes here.
4
185
 
5
186
  == Contributing to rare_map
6
187
 
data/bin/raremap ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rare_map'
4
+
5
+ RareMap.mapping
@@ -0,0 +1,108 @@
1
+ require 'yaml'
2
+
3
+ module RareMap
4
+ module ConfigLoader
5
+ OPTS_KEY = 'rare_map_opts'
6
+
7
+ def load_config(path, file_name = 'database.yml')
8
+ config = YAML.load_file "#{path}#{file_name}"
9
+ organize_config_properties config['rare_map'] || {}
10
+ end
11
+
12
+ private
13
+ def organize_config_properties(raw_config)
14
+ db_profiles = []
15
+ global_opts = Options.new(raw_config.delete OPTS_KEY) if raw_config[OPTS_KEY]
16
+
17
+ raw_config.each do |k, v|
18
+ case v.class.name
19
+ when 'Hash'
20
+ if v[OPTS_KEY]
21
+ db_profiles << DatabaseProfile.new(remove_opts(v), Options.new(v[OPTS_KEY]))
22
+ else
23
+ db_profiles << DatabaseProfile.new(v, global_opts)
24
+ end
25
+ when 'Array'
26
+ v = v.reduce(:merge)
27
+ group_opts = Options.new(v.delete(OPTS_KEY), k)
28
+ v.each do |db, config|
29
+ if config[OPTS_KEY]
30
+ db_profiles << DatabaseProfile.new(remove_opts(config), Options.new(config[OPTS_KEY], k))
31
+ else
32
+ db_profiles << DatabaseProfile.new(config, group_opts || global_opts)
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ db_profiles
39
+ end
40
+
41
+ def remove_opts(db)
42
+ db.select { |k, _| k != OPTS_KEY }
43
+ end
44
+ end
45
+
46
+ class DatabaseProfile
47
+ attr_reader :connection, :options
48
+ attr_accessor :schema, :tables
49
+
50
+ def initialize(connection, options)
51
+ @connection = connection
52
+ @options = options || Options.new
53
+ @tables = []
54
+ end
55
+ end
56
+
57
+ class Options
58
+ attr_reader :opts
59
+
60
+ def initialize(raw_opts = nil, group = nil)
61
+ @opts = { 'group' => 'default',
62
+ 'primary_key' => {},
63
+ 'foreign_key' => { 'suffix' => nil, 'alias' => {} } }
64
+
65
+ if raw_opts and raw_opts.kind_of? Hash
66
+ if raw_opts['group']
67
+ @opts['group'] = raw_opts['group']
68
+ end
69
+ if raw_opts['primary_key'].kind_of? Hash
70
+ @opts['primary_key'] = raw_opts['primary_key'].select { |k, v| k.kind_of? String and v.kind_of? String }
71
+ end
72
+ if raw_opts['foreign_key'] and raw_opts['foreign_key']['suffix'].kind_of? String
73
+ @opts['foreign_key']['suffix'] = raw_opts['foreign_key']['suffix']
74
+ end
75
+ if raw_opts['foreign_key'] and raw_opts['foreign_key']['alias'].kind_of? Hash
76
+ @opts['foreign_key']['alias'] = raw_opts['foreign_key']['alias'].select { |k, v| k.kind_of? String and v.kind_of? String }
77
+ end
78
+ end
79
+ @opts['group'] = group if group.kind_of? String
80
+ end
81
+
82
+ def group?
83
+ @opts['group'] != 'default'
84
+ end
85
+
86
+ def group
87
+ @opts['group'] || 'default'
88
+ end
89
+
90
+ def find_primary_key_by_table(table_name)
91
+ @opts['primary_key'].each { |k, v| return v if k == table_name }
92
+ nil
93
+ end
94
+
95
+ def find_table_by_foreign_key(column_name)
96
+ @opts['foreign_key']['alias'].each { |k, v| return v if k == column_name }
97
+ nil
98
+ end
99
+
100
+ def fk_suffix
101
+ @opts['foreign_key']['suffix']
102
+ end
103
+
104
+ def to_s
105
+ @opts.to_s
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,112 @@
1
+ require 'active_support/inflector'
2
+
3
+ module RareMap
4
+ module ModelBuilder
5
+ def build_models(db_profiles)
6
+ models = []
7
+
8
+ default_id = 1
9
+ db_profiles.each do |db_prof|
10
+ db_prof.tables.each do |table|
11
+ opts = db_prof.options
12
+ set_primary_key_by_options(table, opts)
13
+ set_foreign_keys_by_options(table, opts)
14
+ set_fk_suffix_by_options(table, opts)
15
+ if opts.group?
16
+ models << Model.new(db_prof.connection, table, opts.group)
17
+ else
18
+ models << Model.new(db_prof.connection, table, 'default', default_id)
19
+ end
20
+ end
21
+ default_id += 1 unless db_prof.options.group?
22
+ end
23
+
24
+ build_relations models
25
+
26
+ models
27
+ end
28
+
29
+ private
30
+ def build_relations(models)
31
+ models.each do |model|
32
+ group_models = models.select { |m| m.group == model.group && m.default_id == model.default_id }
33
+
34
+ model.table.columns.each do |col|
35
+ group_models.each do |gm|
36
+ if gm.table.match_foreign_key(col) && model != gm
37
+ model.relations << Relation.new(:belongs_to, col.name, gm.table.name)
38
+ gm.relations << Relation.new(col.unique? ? :has_one : :has_many, col.name, model.table.name)
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ models.each do |model|
45
+ group_models = models.select { |m| m.group == model.group && m.default_id == model.default_id }
46
+
47
+ model.relations.each do |rel_from|
48
+ model.relations.each do |rel_to|
49
+ if rel_from != rel_to &&
50
+ rel_from.type == :belongs_to &&
51
+ rel_to.type == :belongs_to &&
52
+ rel_from.table != rel_to.table
53
+ model_from = models.find { |m| m.table.name == rel_from.table }
54
+ model_to = models.find { |m| m.table.name == rel_to.table }
55
+ model_from.relations << Relation.new(:has_many_through, rel_to.foreign_key, model_to.table.name, model.table.name)
56
+ model_to.relations << Relation.new(:has_many_through, rel_from.foreign_key, model_from.table.name, model.table.name)
57
+ end
58
+ end
59
+ end
60
+ end
61
+
62
+ models.each do |model|
63
+ model.relations.uniq! { |rel| "#{rel.type} #{rel.table} #{rel.through}" }
64
+ end
65
+ end
66
+
67
+ def set_fk_suffix_by_options(table, options)
68
+ table.fk_suffix = options.fk_suffix if options.fk_suffix
69
+ end
70
+
71
+ def set_foreign_keys_by_options(table, options)
72
+ table.columns.each do |col|
73
+ ref = options.find_table_by_foreign_key col.name
74
+ col.references = ref if ref
75
+ end
76
+ end
77
+
78
+ def set_primary_key_by_options(table, options)
79
+ pk = options.find_primary_key_by_table table.name
80
+ table.primary_key = pk if pk
81
+ end
82
+ end
83
+
84
+ class Model
85
+ attr_reader :connection, :table, :group, :relations, :default_id
86
+
87
+ def initialize(connection, table, group = 'default', default_id = nil)
88
+ @connection, @table, @group, @default_id = connection, table, group, default_id
89
+ @relations = []
90
+ end
91
+
92
+ def group?
93
+ group != 'default'
94
+ end
95
+
96
+ def classify
97
+ if group?
98
+ "#{group}_#{table.name}".pluralize.classify
99
+ else
100
+ "#{table.name}".pluralize.classify
101
+ end
102
+ end
103
+ end
104
+
105
+ class Relation
106
+ attr_reader :type, :foreign_key, :table, :through
107
+
108
+ def initialize(type, foreign_key, table, through = nil)
109
+ @type, @foreign_key, @table, @through = type, foreign_key, table, through
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,66 @@
1
+ require 'active_support/inflector'
2
+
3
+ module RareMap
4
+ module ModelGenerator
5
+ def generate_models(models, root = './')
6
+ root ||= './'
7
+ path = root + 'app/models/'
8
+
9
+ models.each do |model|
10
+ output = ''
11
+ output <<
12
+ "class #{model.classify} < ActiveRecord::Base\n" <<
13
+ " establish_connection #{model.connection.to_s[1..-2]}\n" <<
14
+ " self.table_name = '#{model.table.name}'\n" <<
15
+ " self.inheritance_column = 'ruby_type'\n" <<
16
+ " #{ "self.primary_key = '#{model.table.primary_key}'\n" if model.table.primary_key }\n" <<
17
+ " attr_accessible #{model.table.columns.map { |col| ":#{col.name}" }.join(', ')}\n\n"
18
+
19
+ belongs_to = model.relations.select { |rel| rel.type == :belongs_to }
20
+ unless belongs_to.empty?
21
+ output << belongs_to.
22
+ map { |rel| " belongs_to :#{rel.table.pluralize.singularize}, :foreign_key => '#{rel.foreign_key}', :class_name => '#{classify_by_table(rel.table, models)}'" }.
23
+ join("\n") << "\n"
24
+ end
25
+
26
+ has_one = model.relations.select { |rel| rel.type == :has_one }
27
+ unless has_one.empty?
28
+ output << has_one.
29
+ map { |rel| " has_one :#{rel.table.pluralize.singularize}, :foreign_key => '#{rel.foreign_key}', :class_name => '#{classify_by_table(rel.table, models)}'" }.
30
+ join("\n") << "\n"
31
+ end
32
+
33
+ has_many = model.relations.select { |rel| rel.type == :has_many }
34
+ unless has_many.empty?
35
+ output << has_many.
36
+ map { |rel| " has_many :#{rel.table.pluralize}, :foreign_key => '#{rel.foreign_key}', :class_name => '#{classify_by_table(rel.table, models)}'" }.
37
+ join("\n") << "\n"
38
+ end
39
+
40
+ has_many_through = model.relations.select { |rel| rel.type == :has_many_through }
41
+ unless has_many_through.empty?
42
+ output << has_many_through.
43
+ map { |rel| " has_many :#{rel.table.pluralize}#{"_by_#{rel.through}, :source => :#{rel.table.pluralize.singularize}" if has_many_through.count { |hmt| hmt.table == rel.table } > 1}, :through => :#{rel.through.pluralize}, :foreign_key => '#{rel.foreign_key}', :class_name => '#{classify_by_table(rel.table, models)}'" }.
44
+ join("\n") << "\n"
45
+ end
46
+
47
+ output << 'end'
48
+
49
+ Dir.mkdir root + 'app' unless Dir.exist? root + 'app'
50
+ Dir.mkdir path unless Dir.exist? path
51
+ Dir.mkdir path + "#{model.group}" unless Dir.exist? path + "#{model.group}"
52
+ f = File.new(path + "#{model.group}/#{model.classify.underscore}.rb", 'w')
53
+ f.write output
54
+ f.close
55
+ end
56
+
57
+ models
58
+ end
59
+
60
+ private
61
+ def classify_by_table(table, models)
62
+ model = models.find { |m| m.table.name == table }
63
+ model.classify
64
+ end
65
+ end
66
+ end
@@ -1,22 +1,22 @@
1
- module RailsLocator
2
-
3
- def self.locate(level = 5)
4
- rails_dirs = ['app', 'config', 'db', 'doc', 'lib']
5
-
6
- level.times do |i|
7
- found = true
8
- path = ''
1
+ module RareMap
2
+ module RailsLocator
3
+ def locate_rails_root(depth = 5)
4
+ rails_dirs = ['app', 'config', 'db', 'lib', 'log', 'public', 'script']
9
5
 
10
- i.times { path += '../' }
11
-
12
- rails_dirs.each do |dir|
13
- found = false unless Dir.exist?(path + dir)
6
+ depth.times do |level|
7
+ found = true
8
+ path = ''
9
+
10
+ level.times { path << '../' }
11
+
12
+ rails_dirs.each do |dir|
13
+ found = false unless Dir.exist?(path + dir)
14
+ end
15
+
16
+ return path if found
14
17
  end
15
18
 
16
- return path if found
19
+ nil
17
20
  end
18
-
19
- nil
20
21
  end
21
-
22
22
  end
@@ -0,0 +1,93 @@
1
+ require 'active_support/inflector'
2
+
3
+ module RareMap
4
+ module SchemaParser
5
+ def parse_schema(schema)
6
+ tables = []
7
+
8
+ schema.split(/\n/).each do |line|
9
+ case line.strip!
10
+ when /^create_table/
11
+ name = line.match(/create_table\s+['"]([^'"]+)['"]/)[1]
12
+ id = line.match(/:id\s*=>\s*false/) ? false : true
13
+ primary_key = pk[1] if (pk = line.match(/:primary_key\s*=>\s*['"](.+)['"]/))
14
+ tables << Table.new(name, :id => id, :primary_key => primary_key)
15
+ when /^t\./
16
+ name = line.match(/t\.\w+\s+['"]([^'"]+)['"]/)[1]
17
+ type = line.match(/t\.(\w+)\s+/)[1]
18
+ tables.last.columns << Column.new(name, type)
19
+ when /^add_index\s+.*\[\s*['"]([^'"]+)['"]\s*\].*:unique\s*=>\s*true/
20
+ unique_column = line.match(/add_index\s+.*\[\s*['"]([^'"]+)['"]\s*\].*:unique\s*=>\s*true/)[1]
21
+ column = tables.last.columns.find { |col| col.name == unique_column }
22
+ column.unique = true
23
+ end
24
+ end
25
+
26
+ tables
27
+ end
28
+ end
29
+
30
+ class Table
31
+ attr_reader :name, :id, :columns
32
+ attr_writer :primary_key
33
+ attr_accessor :fk_suffix
34
+
35
+ def initialize(name, opts = { :id => true, :primary_key => nil })
36
+ @name = name
37
+ @id = opts[:id]
38
+ @primary_key = opts[:primary_key]
39
+ @columns = []
40
+ @fk_suffix = 'id'
41
+ end
42
+
43
+ def primary_key
44
+ return 'id' if @id
45
+
46
+ candidates = @columns.find_all { |col| col.unique }.map { |col| col.name }
47
+ return @primary_key if candidates.include? @primary_key
48
+ return 'id' if candidates.include? 'id'
49
+ candidates.find { |c| c =~ eval("/^#{@name}.*id$/") } ||
50
+ candidates.find { |c| c =~ eval("/^#{singularize}.*id$/") } ||
51
+ candidates.find { |c| c =~ eval("/^#{pluralize}.*id$/") } ||
52
+ candidates.first
53
+ end
54
+
55
+ def singularize
56
+ @name.pluralize.singularize
57
+ end
58
+
59
+ def pluralize
60
+ @name.pluralize
61
+ end
62
+
63
+ def match_foreign_key(column)
64
+ if column.references == @name || ["#{@name}_#{fk_suffix}",
65
+ "#{@name}#{fk_suffix}",
66
+ "#{singularize}_#{fk_suffix}",
67
+ "#{singularize}#{fk_suffix}",
68
+ "#{pluralize}_#{fk_suffix}",
69
+ "#{pluralize}#{fk_suffix}"].include?(column.name)
70
+ @name if primary_key
71
+ end
72
+ end
73
+ end
74
+
75
+ class Column
76
+ attr_reader :name, :type
77
+ attr_accessor :unique, :references
78
+
79
+ def initialize(name, type)
80
+ @name = name
81
+ @type = type
82
+ @unique = false
83
+ end
84
+
85
+ def unique?
86
+ @unique
87
+ end
88
+
89
+ def foreign_key?
90
+ @references ? true : false
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,19 @@
1
+ require 'active_record'
2
+
3
+ module RareMap
4
+ module SchemaReader
5
+ def read_schema(db_profile)
6
+ conn = db_profile.connection.map { |k, v| v.kind_of?(Integer) ? "'#{k}'=>#{v}" : "'#{k}'=>'#{v}'" }.join(', ')
7
+ if RUBY_PLATFORM == "java"
8
+ %x[jruby -e "require 'active_record'; ActiveRecord::Base.establish_connection(#{conn}); ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection);"]
9
+ else
10
+ %x[ruby -e "require 'active_record'; ActiveRecord::Base.establish_connection(#{conn}); ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection);"]
11
+ end
12
+ # schema = StringIO.new
13
+ # ActiveRecord::Base.establish_connection db_profile.connection
14
+ # ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, schema)
15
+ # schema.close
16
+ # schema.string
17
+ end
18
+ end
19
+ end
@@ -1,8 +1,8 @@
1
1
  class RareMap
2
2
  module Version
3
- MAJOR = 0
4
- MINOR = 9
5
- PATCH = 7
3
+ MAJOR = 1
4
+ MINOR = 0
5
+ PATCH = 0
6
6
 
7
7
  STRING = [MAJOR, MINOR, PATCH].compact.join('.')
8
8
  end
data/lib/rare_map.rb CHANGED
@@ -1,42 +1,56 @@
1
- require 'rare_map/database_to_schema_builder'
2
- require 'rare_map/schema_to_relation_builder'
3
- require 'rare_map/schema_to_model_builder'
1
+ # $:.unshift File.dirname(__FILE__)
2
+
4
3
  require 'rare_map/rails_locator'
4
+ require 'rare_map/config_loader'
5
+ require 'rare_map/schema_reader'
6
+ require 'rare_map/schema_parser'
7
+ require 'rare_map/model_builder'
8
+ require 'rare_map/model_generator'
5
9
 
6
- class RareMap
10
+ module RareMap
11
+ def self.mapping
12
+ Mapper.new.mapping
13
+ end
7
14
 
8
- def self.mapping(opt = {})
9
- unless RailsLocator.locate
10
- $stderr.puts 'Run RareMap under Rails root directory'
11
- return
12
- end
15
+ class Mapper
16
+ include RailsLocator, ConfigLoader, SchemaReader, SchemaParser, ModelBuilder, ModelGenerator
13
17
 
14
- info = {}
18
+ def initialize
19
+ @rails_root = locate_rails_root
20
+ end
15
21
 
16
- databases = DatabaseToSchemaBuilder.build
17
- databases.each do |group_name, tables|
18
- [:foreign_key, :alias, :primary_key].each { |sym| opt[sym] ||= {} }
19
- foreign_keys = opt[:foreign_key][group_name] || nil
20
- aliases = opt[:alias][group_name] || {}
21
- primary_keys = opt[:primary_key][group_name] || {}
22
-
23
- schemata = tables.inject({}) { |o, i| o = o.merge(i[:schema]) }
24
- primary_keys.each do |table, primary_key|
25
- schemata[table][:primary_key] = primary_key if schemata[table]
22
+ def mapping
23
+ @db_profiles = load_config @rails_root ? @rails_root + 'config/' : './'
24
+ @db_profiles.each do |profile|
25
+ profile.schema = read_schema profile
26
+ profile.tables = parse_schema profile.schema
27
+ end
28
+ @models = build_models @db_profiles
29
+ generate_models @models, @rails_root
30
+ if @rails_root
31
+ puts '*****************************************************************************'
32
+ puts ' Add following line to your config/application.rb'
33
+ puts " config.autoload_paths += Dir[Rails.root.join('app', 'models', '{**}')]"
34
+ puts '*****************************************************************************'
35
+ else
36
+ puts '*****************************************************************************'
37
+ puts ' A demo.rb is generated'
38
+ puts '*****************************************************************************'
39
+ generate_demo unless File.exist?('demo.rb')
26
40
  end
27
- relations = SchemaToRelationBuilder.build(schemata, foreign_keys, aliases)
28
- SchemaToModelBuilder.build(schemata, relations, group_name, databases)
29
-
30
- info[group_name] ||= {}
31
- info[group_name][:schemata] = schemata
32
- info[group_name][:relations] = relations
41
+ @models
33
42
  end
34
43
 
35
- $stdout.puts %{Add following line to your config/application.rb}
36
- $stdout.puts %{config.autoload_paths += Dir[Rails.root.join('app', 'models', '{**}')]}
37
-
38
- info[:databases] = databases
39
- info
44
+ private
45
+ def generate_demo
46
+ f = File.new('demo.rb', 'w')
47
+ f.write "require 'active_record'\n"
48
+ f.write "Dir[File.dirname(__FILE__) + '/app/models/**/*.rb'].each { |file| require file }\n"
49
+ f.close
50
+ end
40
51
  end
41
-
42
52
  end
53
+
54
+ if __FILE__ == $0
55
+ RareMap.mapping
56
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rare_map
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.7
4
+ version: 1.0.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-10-08 00:00:00.000000000 Z
12
+ date: 2012-11-17 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
@@ -18,7 +18,7 @@ dependencies:
18
18
  requirements:
19
19
  - - ! '>='
20
20
  - !ruby/object:Gem::Version
21
- version: '0'
21
+ version: 3.2.0
22
22
  type: :runtime
23
23
  prerelease: false
24
24
  version_requirements: !ruby/object:Gem::Requirement
@@ -26,7 +26,7 @@ dependencies:
26
26
  requirements:
27
27
  - - ! '>='
28
28
  - !ruby/object:Gem::Version
29
- version: '0'
29
+ version: 3.2.0
30
30
  - !ruby/object:Gem::Dependency
31
31
  name: shoulda
32
32
  requirement: !ruby/object:Gem::Requirement
@@ -107,24 +107,26 @@ dependencies:
107
107
  - - ~>
108
108
  - !ruby/object:Gem::Version
109
109
  version: 0.9.11
110
- description: Translate legacy db to ActiveRecord models
110
+ description: Relational db to ActiveREcord models MAPper
111
111
  email: wnameless@gmail.com
112
- executables: []
112
+ executables:
113
+ - raremap
113
114
  extensions: []
114
115
  extra_rdoc_files:
115
116
  - LICENSE.txt
116
117
  - README.rdoc
117
118
  files:
118
119
  - lib/rare_map.rb
119
- - lib/rare_map/database_to_schema_builder.rb
120
- - lib/rare_map/inheritor_util.rb
120
+ - lib/rare_map/config_loader.rb
121
+ - lib/rare_map/model_builder.rb
122
+ - lib/rare_map/model_generator.rb
121
123
  - lib/rare_map/rails_locator.rb
122
- - lib/rare_map/schema_to_hash_mapping.rb
123
- - lib/rare_map/schema_to_model_builder.rb
124
- - lib/rare_map/schema_to_relation_builder.rb
124
+ - lib/rare_map/schema_parser.rb
125
+ - lib/rare_map/schema_reader.rb
125
126
  - lib/rare_map/version.rb
126
127
  - LICENSE.txt
127
128
  - README.rdoc
129
+ - bin/raremap
128
130
  homepage: http://github.com/wnameless/rare_map
129
131
  licenses:
130
132
  - Apache License, Version 2.0
@@ -140,7 +142,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
140
142
  version: '0'
141
143
  segments:
142
144
  - 0
143
- hash: -4273128051063044158
145
+ hash: 2391284993457456649
144
146
  required_rubygems_version: !ruby/object:Gem::Requirement
145
147
  none: false
146
148
  requirements:
@@ -152,5 +154,5 @@ rubyforge_project:
152
154
  rubygems_version: 1.8.24
153
155
  signing_key:
154
156
  specification_version: 3
155
- summary: RelationDB-ActiveRecord Mapper
157
+ summary: rare_map-
156
158
  test_files: []
@@ -1,27 +0,0 @@
1
- require 'rare_map/rails_locator'
2
- require 'rare_map/schema_to_hash_mapping'
3
-
4
- module DatabaseToSchemaBuilder
5
-
6
- def self.build
7
- file = File.open(RailsLocator.locate + 'config/database.yml') { |f| f.read }
8
- database = file.split("\n").select do |line|
9
- line.match(/^[a-z]+/) && line != 'development:' && line != 'test:' && line != 'production:'
10
- end
11
- database.map! { |db| db.delete(':') }
12
-
13
- database_hash = Hash.new { |hash, key| hash[key] = [] }
14
- database.each { |db| database_hash[db.split('_').first.to_sym] << { :dbname => db } }
15
-
16
- database_hash.each do |group, dbs|
17
- dbs.each do |db|
18
- puts "db:schema:dump RAILS_ENV=#{db[:dbname]}"
19
- system "rake db:schema:dump RAILS_ENV=#{db[:dbname]}"
20
- schema = File.open(RailsLocator.locate + 'db/schema.rb') { |f| f.read }
21
- db[:schema] = SchemaToHashMapping.convert(schema.split("\n"))
22
- end
23
- end
24
- database_hash
25
- end
26
-
27
- end
@@ -1,50 +0,0 @@
1
- require 'rare_map/rails_locator'
2
-
3
- module InheritorUtil
4
-
5
- def self.to_class_name(name)
6
- name.to_s.split('_').map { |word| word.capitalize }.join
7
- end
8
-
9
- def self.remove_underline_between_numbers(line)
10
- line = line.to_s
11
- md = line.match(/(\d+)(_)(\d+)/)
12
- md.size.times { line.gsub!(/(\d+)_(\d+)/, '\1\2') } unless md.nil?
13
- line.gsub!(/([a-zA-Z])(_)(\d)/, '\1\3')
14
- line.to_sym
15
- end
16
-
17
- def self.write_model_file(file_name, content, group_name)
18
- write_file(file_name, content, group_name, 'models')
19
- end
20
-
21
- def self.write_controller_file(file_name, content, group_name)
22
- write_file(file_name, content, group_name, 'controllers')
23
- end
24
-
25
- def self.complex_flatten(node, flatAry = [])
26
- if node.kind_of?(Array)
27
- node.map { |leaf| complex_flatten(leaf, flatAry) }
28
- elsif node.kind_of?(Hash)
29
- node.each do |key, val|
30
- flatAry << key
31
- complex_flatten(val, flatAry)
32
- end
33
- else
34
- flatAry << node
35
- end
36
- flatAry
37
- end
38
-
39
-
40
- private
41
-
42
-
43
- def self.write_file(file_name, content, group_name, mvc)
44
- Dir.mkdir(RailsLocator.locate + "app/#{mvc}/#{group_name}") unless Dir.exist?(RailsLocator.locate + "app/#{mvc}/#{group_name}")
45
- f = File.new(RailsLocator.locate + "app/#{mvc}/#{group_name}/#{group_name}_#{remove_underline_between_numbers(file_name)}.rb", 'w')
46
- f.write(content)
47
- f.close
48
- end
49
-
50
- end
@@ -1,53 +0,0 @@
1
- module SchemaToHashMapping
2
-
3
- def self.convert(schema)
4
- schema_hash = {}
5
- table_name_sym = :Blank
6
-
7
- schema.each do |line|
8
- case line.strip!
9
- when /^create_table/
10
- table_name_sym = filter_table_name_sym(line)
11
- schema_hash[table_name_sym] = {}
12
- if line.match(/:primary_key\s*=>\s*"[^"]+",/)
13
- schema_hash[table_name_sym][:primary_key] = line.match(/:primary_key\s*=>\s*"[^"]+",/).to_s.delete('",').split(/\s+/)[2].to_sym
14
- end
15
- when /^t\./
16
- column_name_sym = filter_column_name_sym(line)
17
- column_type_sym = filter_column_type_sym(line)
18
- schema_hash[table_name_sym][column_name_sym] ||= {}
19
- schema_hash[table_name_sym][column_name_sym][:type] = table_name_sym
20
- schema_hash[table_name_sym][column_name_sym][:unique] = false
21
- when /^add_index.*:unique\s*=>\s*true/
22
- uniqueColumns = filter_unique_columns(line)
23
- uniqueColumns.each do |column|
24
- schema_hash[table_name_sym][column][:unique] = true
25
- end
26
- end
27
- end
28
-
29
- return schema_hash
30
- end
31
-
32
-
33
- private
34
-
35
-
36
- def self.filter_unique_columns(line)
37
- cols = line.match(/\[[^\]]*\]/).to_s.delete('"[],').split(/\s+/).map { |col| col.to_sym }
38
- cols.size == 1 ? cols : []
39
- end
40
-
41
- def self.filter_table_name_sym(line)
42
- line.delete('",').split(/\s+/)[1].to_sym
43
- end
44
-
45
- def self.filter_column_name_sym(line)
46
- line.delete('",').split(/\s+/)[1].to_sym
47
- end
48
-
49
- def self.filter_column_type_sym(line)
50
- line.delete('",').split(/\s+/)[0][2 .. -1].to_sym
51
- end
52
-
53
- end
@@ -1,74 +0,0 @@
1
- require 'rare_map/inheritor_util'
2
-
3
- module SchemaToModelBuilder
4
-
5
- def self.build(schema_hash, rel_hash, group_name, database_hash)
6
- schema_hash.each do |table_name, table_content|
7
- model_content = generate_header(table_name, schema_hash, group_name, database_hash)
8
- model_content << generate_attributes_permission(table_content, 'attr_accessible') unless table_content.size == 0
9
- model_content << generate_relationships(table_name, rel_hash, group_name)
10
- model_content << generate_footer
11
- InheritorUtil.write_model_file(table_name, model_content, group_name)
12
- end
13
- end
14
-
15
-
16
- private
17
-
18
-
19
- def self.generate_relationships(table_name, rel_hash, group_name)
20
- return '' if rel_hash.count { |_, hash| hash[table_name] != nil } == 0
21
-
22
- has_one_ary = []
23
- rel_hash[:has_one][table_name].each do |table, foreign_key|
24
- has_one_ary << " has_one :#{table}, :foreign_key => '#{foreign_key}', :class_name => '#{group_name.to_s.capitalize}#{InheritorUtil.to_class_name(table)}'"
25
- end if rel_hash[:has_one][table_name] != nil
26
-
27
- has_many_ary = []
28
- rel_hash[:has_many][table_name].each do |table, foreign_key|
29
- has_many_ary << " has_many :#{table}, :foreign_key => '#{foreign_key}', :class_name => '#{group_name.to_s.capitalize}#{InheritorUtil.to_class_name(table)}'"
30
- end if rel_hash[:has_many][table_name] != nil
31
-
32
- belongs_to_ary = []
33
- rel_hash[:belongs_to][table_name].each do |table, foreign_key|
34
- belongs_to_ary << " belongs_to :#{table}, :foreign_key => '#{foreign_key}', :class_name => '#{group_name.to_s.capitalize}#{InheritorUtil.to_class_name(table)}'"
35
- end if rel_hash[:belongs_to][table_name] != nil
36
-
37
- has_many_through_ary = []
38
- rel_hash[:has_many_through][table_name].each do |table, foreign_key, through|
39
- has_many_through_ary << " has_many :#{table}, :foreign_key => '#{foreign_key}', :class_name => '#{group_name.to_s.capitalize}#{InheritorUtil.to_class_name(table)}', :through => :#{through}"
40
- end if rel_hash[:has_many_through][table_name] != nil
41
-
42
- has_one_ary.join("\n") << "\n" <<
43
- has_many_ary.join("\n") << "\n" <<
44
- belongs_to_ary.join("\n") + "\n" <<
45
- has_many_through_ary.join("\n") << "\n"
46
- end
47
-
48
- def self.generate_attributes_permission(table_content, permission)
49
- table_content.keys.inject(' ' + permission) { |o, i| o = o + " :#{i}," }[0 .. -2] + "\n"
50
- end
51
-
52
- def self.generate_header(table_name, schema_hash, group_name, database_hash)
53
- "class #{group_name.to_s.capitalize}#{InheritorUtil.to_class_name(table_name)} < ActiveRecord::Base\n" <<
54
- " establish_connection :#{get_connection_name(table_name, group_name, database_hash)}\n" <<
55
- " self.table_name = '#{table_name}'\n" <<
56
- " self.inheritance_column = 'ruby_type'\n" <<
57
- " self.primary_key = '#{check_primary_key(table_name, schema_hash)}'\n"
58
- end
59
-
60
- def self.get_connection_name(table_name, group_name, database_hash)
61
- database_hash[group_name].each do |db|
62
- return db[:dbname] unless db[:schema][table_name].nil?
63
- end
64
- end
65
-
66
- def self.check_primary_key(table_name, schema_hash)
67
- schema_hash[table_name][:primary_key].nil? ? 'id' : schema_hash[table_name][:primary_key]
68
- end
69
-
70
- def self.generate_footer
71
- "end\n"
72
- end
73
-
74
- end
@@ -1,104 +0,0 @@
1
- module BelongsTo2HasManyThroughtRelationBuilder
2
-
3
- def self.build(belongs_to_hash, has_one_hash, has_many_hash)
4
- belongs_to_hash = belongs_to_hash.clone
5
- has_many_through_hash = {}
6
-
7
- belongs_to_hash = belongs_to_hash.delete_if { |table, rels| rels.size < 2 }
8
- belongs_to_hash.each do |table, rels|
9
- rels.each do |from_table, from_table_key|
10
- rels.each do |to_table, to_table_key|
11
- if from_table != to_table
12
- has_many_through_hash[from_table] ||= []
13
- rel = [to_table, to_table_key, table]
14
- has_many_through_hash[from_table] << rel
15
- end
16
- end
17
- end
18
- end
19
-
20
- final_has_many_through_hash = {}
21
- has_many_through_hash.each do |table, rels|
22
- rels.each do |rel|
23
- if has_many_hash[table] != nil && has_many_hash[table].count { |i| i.first == rel.first } == 0
24
- final_has_many_through_hash[table] ||= []
25
- final_has_many_through_hash[table] << rel
26
- end
27
- end
28
- end
29
- has_many_through_hash = final_has_many_through_hash
30
-
31
- final_has_many_through_hash = {}
32
- has_many_through_hash.each do |table, rels|
33
- rels.each do |rel|
34
- if has_one_hash[table] != nil && has_one_hash[table].count { |i| i.first == rel.first } == 0
35
- final_has_many_through_hash[table] ||= []
36
- final_has_many_through_hash[table] << rel
37
- end
38
- end
39
- end
40
- has_many_through_hash = final_has_many_through_hash
41
-
42
- final_has_many_through_hash = {}
43
- has_many_through_hash.each do |table, rels|
44
- rels.each do |rel|
45
- if belongs_to_hash[table] != nil && belongs_to_hash[table].count { |i| i.first == rel.first } == 0
46
- final_has_many_through_hash[table] ||= []
47
- final_has_many_through_hash[table] << rel
48
- end
49
- end
50
- end
51
- has_many_through_hash = final_has_many_through_hash
52
-
53
- has_many_through_hash
54
- end
55
-
56
- end
57
-
58
- module SchemaToRelationBuilder
59
-
60
- def self.build(schema_hash, relation_mark, alias_hash = {})
61
- relation_mark ||= '_id'
62
- has_one_hash = {}; has_many_hash = {}; belongs_to_hash = {}
63
-
64
- schema_hash.each do |table_name, table_content|
65
- table_content.each do |column, type|
66
- if alias_hash[column] != nil
67
- belongs_to = alias_hash[column]
68
- if schema_hash[belongs_to] != nil
69
- if schema_hash[table_name][column][:unique] == true
70
- has_one_hash[belongs_to] ||= []
71
- has_one_hash[belongs_to] << [table_name, column]
72
- else
73
- has_many_hash[belongs_to] ||= []
74
- has_many_hash[belongs_to] << [table_name, column]
75
- end
76
- belongs_to_hash[table_name] ||= []
77
- belongs_to_hash[table_name] << [belongs_to, column]
78
- end
79
- elsif column.to_s.match(eval("/#{relation_mark}$/")) && column.to_s[0 .. -(relation_mark.length + 1)].to_sym != table_name
80
- belongs_to = column.to_s[0 .. -(relation_mark.length + 1)].to_sym
81
- if schema_hash[belongs_to] != nil
82
- if schema_hash[table_name][column][:unique] == true
83
- has_one_hash[belongs_to] ||= []
84
- has_one_hash[belongs_to] << [table_name, column]
85
- else
86
- has_many_hash[belongs_to] ||= []
87
- has_many_hash[belongs_to] << [table_name, column]
88
- end
89
- belongs_to_hash[table_name] ||= []
90
- belongs_to_hash[table_name] << [belongs_to, column]
91
- end
92
- end
93
- end
94
- end
95
-
96
- has_many_through_hash = BelongsTo2HasManyThroughtRelationBuilder.build(belongs_to_hash, has_one_hash, has_many_hash)
97
-
98
- return { :has_one => has_one_hash,
99
- :has_many => has_many_hash,
100
- :belongs_to => belongs_to_hash,
101
- :has_many_through => has_many_through_hash }
102
- end
103
-
104
- end