rare_map 0.9.7 → 1.0.0

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/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