rare_map 2.1.1 → 2.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Rakefile +3 -19
- data/lib/rare_map.rb +13 -3
- data/lib/rare_map/column.rb +42 -0
- data/lib/rare_map/config_loader.rb +18 -69
- data/lib/rare_map/database_profile.rb +32 -0
- data/lib/rare_map/errors.rb +7 -1
- data/lib/rare_map/model.rb +48 -0
- data/lib/rare_map/model_builder.rb +12 -29
- data/lib/rare_map/model_generator.rb +12 -8
- data/lib/rare_map/options.rb +78 -0
- data/lib/rare_map/rails_locator.rb +14 -6
- data/lib/rare_map/relation.rb +41 -0
- data/lib/rare_map/schema_parser.rb +7 -71
- data/lib/rare_map/schema_reader.rb +7 -0
- data/lib/rare_map/table.rb +89 -0
- data/lib/rare_map/version.rb +7 -3
- data/test/column_test.rb +29 -0
- data/test/config_loader_test.rb +34 -0
- data/test/database_profile_test.rb +25 -0
- data/test/model_builder_test.rb +22 -0
- data/test/model_test.rb +40 -0
- data/test/options_test.rb +54 -0
- data/test/rails_locator_test.rb +12 -0
- data/test/rare_map.yml +16 -0
- data/test/rare_map_simple.yml +14 -0
- data/test/rare_map_test.rb +1 -3
- data/test/relation_test.rb +29 -0
- data/test/schema_parser_test.rb +28 -0
- data/test/table_test.rb +51 -0
- data/test/{helper.rb → test_helper.rb} +7 -1
- metadata +50 -19
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'active_support/core_ext/hash'
|
2
|
+
|
3
|
+
module RareMap
|
4
|
+
# RareMap::Options defines all available options of RareMap.
|
5
|
+
# @author Wei-Ming Wu
|
6
|
+
# @!attribute [r] opts
|
7
|
+
# @return [Hash] the details of options
|
8
|
+
class Options
|
9
|
+
# A default group name
|
10
|
+
DEFAULT_GROUP = 'default'
|
11
|
+
attr_reader :opts
|
12
|
+
|
13
|
+
# Creates a Options.
|
14
|
+
#
|
15
|
+
# @param raw_opts [Hash] the details of options
|
16
|
+
# @return [Options] a Options object
|
17
|
+
def initialize(raw_opts = {})
|
18
|
+
raw_opts ||= {}
|
19
|
+
raw_opts = raw_opts.with_indifferent_access
|
20
|
+
@opts = { group: DEFAULT_GROUP,
|
21
|
+
primary_key: {},
|
22
|
+
foreign_key: { suffix: nil, alias: {} } }.with_indifferent_access
|
23
|
+
|
24
|
+
if raw_opts.kind_of? Hash
|
25
|
+
if raw_opts[:group]
|
26
|
+
@opts[:group] = raw_opts[:group]
|
27
|
+
end
|
28
|
+
if raw_opts[:primary_key].kind_of? Hash
|
29
|
+
@opts[:primary_key] = raw_opts[:primary_key].select { |k, v| k.kind_of? String and v.kind_of? String }
|
30
|
+
end
|
31
|
+
if raw_opts[:foreign_key].kind_of? Hash and raw_opts[:foreign_key][:suffix].kind_of? String
|
32
|
+
@opts[:foreign_key][:suffix] = raw_opts[:foreign_key][:suffix]
|
33
|
+
end
|
34
|
+
if raw_opts[:foreign_key].kind_of? Hash and raw_opts[:foreign_key][:alias].kind_of? Hash
|
35
|
+
@opts[:foreign_key][:alias] = raw_opts[:foreign_key][:alias].select { |k, v| k.kind_of? String and v.kind_of? String }
|
36
|
+
end
|
37
|
+
if raw_opts[:group].kind_of? String
|
38
|
+
@opts[:group] = raw_opts[:group]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Checks if this Options belongs to a group.
|
44
|
+
#
|
45
|
+
# @return [true, false] true if this Options contains a group, false otherwise
|
46
|
+
def group?
|
47
|
+
@opts[:group] != DEFAULT_GROUP
|
48
|
+
end
|
49
|
+
|
50
|
+
# Returns the name of this Options' group
|
51
|
+
#
|
52
|
+
# @return [String] the name of this Options' group
|
53
|
+
def group
|
54
|
+
@opts[:group] || DEFAULT_GROUP
|
55
|
+
end
|
56
|
+
|
57
|
+
# Returns the primary key of a table specified by this Options
|
58
|
+
#
|
59
|
+
# @return [String, nil] the primary key of a table specified by this Options
|
60
|
+
def find_primary_key_by_table(table_name)
|
61
|
+
@opts[:primary_key].values_at(table_name).first
|
62
|
+
end
|
63
|
+
|
64
|
+
# Returns the table of a foreign key specified by this Options
|
65
|
+
#
|
66
|
+
# @return [String, nil] the table of a foreign key specified by this Options
|
67
|
+
def find_table_by_foreign_key(column_name)
|
68
|
+
@opts[:foreign_key][:alias].values_at(column_name).first
|
69
|
+
end
|
70
|
+
|
71
|
+
# Returns the suffix of a foreign key should have
|
72
|
+
#
|
73
|
+
# @return [String, nil] the suffix of a foreign key should have
|
74
|
+
def fk_suffix
|
75
|
+
@opts[:foreign_key][:suffix]
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -1,19 +1,27 @@
|
|
1
1
|
module RareMap
|
2
|
+
# RareMap::RailsLocator locates the root of a rails application from any
|
3
|
+
# where inside it.
|
4
|
+
# @author Wei-Ming Wu
|
2
5
|
module RailsLocator
|
3
|
-
|
4
|
-
|
6
|
+
# Finds the root of a rails application.
|
7
|
+
#
|
8
|
+
# @param [String] path the location where start to search
|
9
|
+
# @param [Integer] depth the max levels of folders to search
|
10
|
+
# @return [String, nil] the root of a rails application
|
11
|
+
def locate_rails_root(path = '.', depth = 5)
|
12
|
+
rails_dirs = %w(app config db lib log public)
|
5
13
|
|
6
14
|
depth.times do |level|
|
7
15
|
found = true
|
8
|
-
|
16
|
+
paths = [path]
|
9
17
|
|
10
|
-
level.times {
|
18
|
+
level.times { paths << '..' }
|
11
19
|
|
12
20
|
rails_dirs.each do |dir|
|
13
|
-
found = false unless Dir.exist?(
|
21
|
+
found = false unless Dir.exist? File.join(*(paths + [dir]))
|
14
22
|
end
|
15
23
|
|
16
|
-
return
|
24
|
+
return File.absolute_path(File.join(*paths)) if found
|
17
25
|
end
|
18
26
|
|
19
27
|
nil
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'rare_map/errors'
|
2
|
+
|
3
|
+
module RareMap
|
4
|
+
# RareMap::Relation defines one of has_one, has_many and has_many_through
|
5
|
+
# relations of a database table.
|
6
|
+
# @author Wei-Ming Wu
|
7
|
+
# @!attribute [r] type
|
8
|
+
# @return [Symbol] the type of relation, `:has_one` or `:has_many` or `:has_many_through`
|
9
|
+
# @!attribute [r] foreign_key
|
10
|
+
# @return [String] the foreign key of this Relation based on
|
11
|
+
# @!attribute [r] table
|
12
|
+
# @return [String] the table of this Relation refers to
|
13
|
+
# @!attribute [r] through
|
14
|
+
# @return [String, nil] the table of this Relation goes through
|
15
|
+
class Relation
|
16
|
+
# The :has_one association.
|
17
|
+
HAS_ONE = :has_one
|
18
|
+
# The :belongs_to association.
|
19
|
+
BELONGS_TO = :belongs_to
|
20
|
+
# The :has_many association.
|
21
|
+
HAS_MANY = :has_many
|
22
|
+
# The :has_many_through association.
|
23
|
+
HAS_MANY_THROUGH = :has_many_through
|
24
|
+
# All three kinds of relations
|
25
|
+
RELATIONS = [HAS_ONE, BELONGS_TO, HAS_MANY, HAS_MANY_THROUGH]
|
26
|
+
include Errors
|
27
|
+
attr_reader :type, :foreign_key, :table, :through
|
28
|
+
|
29
|
+
# Creates a Relation.
|
30
|
+
#
|
31
|
+
# @param type [Symbol] the type, `:has_one` or `:has_many` or `:has_many_through`
|
32
|
+
# @param foreign_key [String] the foreign key of this Relation based on
|
33
|
+
# @param table [String] the table of this Relation refers to
|
34
|
+
# @param through [String, nil] the table of this Relation goes through
|
35
|
+
# @return [Relation] the Relation object
|
36
|
+
def initialize(type, foreign_key, table, through = nil)
|
37
|
+
raise RelationNotDefinedError, 'Relation type not defined.' unless RELATIONS.include? type
|
38
|
+
@type, @foreign_key, @table, @through = type, foreign_key, table, through
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -1,7 +1,13 @@
|
|
1
|
-
require '
|
1
|
+
require 'rare_map/table'
|
2
|
+
require 'rare_map/column'
|
2
3
|
|
3
4
|
module RareMap
|
5
|
+
# RareMap::SchemaParser parses schema.rb into Table.
|
6
|
+
# @author Wei-Ming Wu
|
4
7
|
module SchemaParser
|
8
|
+
# Parses schema.rb into an Array of Table.
|
9
|
+
#
|
10
|
+
# @return [Array] an Array of Table
|
5
11
|
def parse_schema(schema)
|
6
12
|
tables = []
|
7
13
|
|
@@ -27,74 +33,4 @@ module RareMap
|
|
27
33
|
tables
|
28
34
|
end
|
29
35
|
end
|
30
|
-
|
31
|
-
class Table
|
32
|
-
attr_reader :name, :id, :columns
|
33
|
-
attr_writer :primary_key
|
34
|
-
attr_accessor :fk_suffix
|
35
|
-
|
36
|
-
def initialize(name, opts = { :id => true, :primary_key => nil })
|
37
|
-
@name = name
|
38
|
-
@id = opts[:id]
|
39
|
-
@primary_key = opts[:primary_key]
|
40
|
-
@columns = []
|
41
|
-
@fk_suffix = 'id'
|
42
|
-
end
|
43
|
-
|
44
|
-
def primary_key
|
45
|
-
return @primary_key if @primary_key
|
46
|
-
return 'id' if @id
|
47
|
-
|
48
|
-
candidates = @columns.find_all { |col| col.unique }.map { |col| col.name }
|
49
|
-
# return @primary_key if candidates.include? @primary_key
|
50
|
-
return 'id' if candidates.include? 'id'
|
51
|
-
candidates.find { |c| c =~ eval("/^#{@name}.*id$/") } ||
|
52
|
-
candidates.find { |c| c =~ eval("/^#{singularize}.*id$/") } ||
|
53
|
-
candidates.find { |c| c =~ eval("/^#{pluralize}.*id$/") } ||
|
54
|
-
candidates.first
|
55
|
-
end
|
56
|
-
|
57
|
-
def singularize
|
58
|
-
@name.pluralize.singularize
|
59
|
-
end
|
60
|
-
|
61
|
-
def pluralize
|
62
|
-
@name.pluralize
|
63
|
-
end
|
64
|
-
|
65
|
-
def match_foreign_key(column)
|
66
|
-
if column.references == @name || foreign_keys.include?(column.name)
|
67
|
-
@name if primary_key
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
def match_foreign_key_by_primary_key(pk)
|
72
|
-
@name if foreign_keys.include?(pk) && primary_key
|
73
|
-
end
|
74
|
-
|
75
|
-
private
|
76
|
-
def foreign_keys
|
77
|
-
["#{@name}_#{fk_suffix}", "#{@name}#{fk_suffix}", "#{singularize}_#{fk_suffix}",
|
78
|
-
"#{singularize}#{fk_suffix}", "#{pluralize}_#{fk_suffix}", "#{pluralize}#{fk_suffix}"]
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
class Column
|
83
|
-
attr_reader :name, :type
|
84
|
-
attr_accessor :unique, :references
|
85
|
-
|
86
|
-
def initialize(name, type)
|
87
|
-
@name = name
|
88
|
-
@type = type
|
89
|
-
@unique = false
|
90
|
-
end
|
91
|
-
|
92
|
-
def unique?
|
93
|
-
@unique
|
94
|
-
end
|
95
|
-
|
96
|
-
def foreign_key?
|
97
|
-
@references ? true : false
|
98
|
-
end
|
99
|
-
end
|
100
36
|
end
|
@@ -2,7 +2,12 @@ require 'active_record'
|
|
2
2
|
require 'activerecord-jdbc-adapter' if RUBY_PLATFORM == 'java'
|
3
3
|
|
4
4
|
module RareMap
|
5
|
+
# RareMap::SchemaReader dumps database schema by using ActiveRecord::SchemaDumper.
|
6
|
+
# @author Wei-Ming Wu
|
5
7
|
module SchemaReader
|
8
|
+
# Returns the content of schema.rb which is created by ActiveRecord::SchemaDumper.
|
9
|
+
#
|
10
|
+
# @return [String] the content of schema.rb which is created by ActiveRecord::SchemaDumper
|
6
11
|
def read_schema(db_profile)
|
7
12
|
conn = db_profile.connection.map { |k, v| v.kind_of?(Integer) ? "'#{k}'=>#{v}" : "'#{k}'=>'#{v}'" }.join(', ')
|
8
13
|
schema = if RUBY_PLATFORM == 'java'
|
@@ -21,6 +26,8 @@ module RareMap
|
|
21
26
|
=end
|
22
27
|
end
|
23
28
|
|
29
|
+
private
|
30
|
+
|
24
31
|
def detect_errors(schema)
|
25
32
|
if $? != 0
|
26
33
|
puts schema
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'active_support/inflector'
|
2
|
+
|
3
|
+
module RareMap
|
4
|
+
# RareMap::Table defines a table of a database.
|
5
|
+
# @author Wei-Ming Wu
|
6
|
+
# @!attribute [r] name
|
7
|
+
# @return [String] the name of this Table
|
8
|
+
# @!attribute [r] id
|
9
|
+
# @return [true, false] true if this Table has id, false otherwise
|
10
|
+
# @!attribute primary_key
|
11
|
+
# @return [String] the primary key of this Table
|
12
|
+
# @!attribute fk_suffix
|
13
|
+
# @return [String] the foreign key suffix of this Table
|
14
|
+
# @!attribute columns
|
15
|
+
# @return [Array] an Array of Column of this Table
|
16
|
+
class Table
|
17
|
+
attr_reader :name, :id
|
18
|
+
attr_writer :primary_key
|
19
|
+
attr_accessor :fk_suffix, :columns
|
20
|
+
|
21
|
+
# Creates a Table.
|
22
|
+
#
|
23
|
+
# @param name [String] the name of this Table
|
24
|
+
# @param opts [Hash] the options of this Table
|
25
|
+
# @option opts [true, false] :id the id existence of this Table
|
26
|
+
# @option opts [String] :primary_key the primary key of this Table
|
27
|
+
# @option opts [String] :fk_suffix the foreign key suffix of this Table
|
28
|
+
# @return [Table] a Table object
|
29
|
+
def initialize(name, opts = {})
|
30
|
+
@name = name
|
31
|
+
@id = opts[:id] != false
|
32
|
+
@primary_key = opts[:primary_key]
|
33
|
+
@columns = []
|
34
|
+
@fk_suffix = opts[:fk_suffix] || 'id'
|
35
|
+
end
|
36
|
+
|
37
|
+
# Returns the primary key of this Table.
|
38
|
+
#
|
39
|
+
# @return [String, nil] the primary key of this Table
|
40
|
+
def primary_key
|
41
|
+
return @primary_key if @primary_key
|
42
|
+
return 'id' if @id
|
43
|
+
|
44
|
+
candidates = @columns.find_all { |col| col.unique }.map { |col| col.name }
|
45
|
+
return 'id' if candidates.include? 'id'
|
46
|
+
candidates.find { |c| c =~ eval("/^#{@name}.*id$/") } ||
|
47
|
+
candidates.find { |c| c =~ eval("/^#{singularize}.*id$/") } ||
|
48
|
+
candidates.find { |c| c =~ eval("/^#{pluralize}.*id$/") } ||
|
49
|
+
candidates.first
|
50
|
+
end
|
51
|
+
|
52
|
+
# Returns the singular name of this Table.
|
53
|
+
#
|
54
|
+
# @return [String] the singular name of this Table
|
55
|
+
def singularize
|
56
|
+
@name.pluralize.singularize
|
57
|
+
end
|
58
|
+
|
59
|
+
# Returns the plural name of this Table.
|
60
|
+
#
|
61
|
+
# @return [String] the plural name of this Table
|
62
|
+
def pluralize
|
63
|
+
@name.pluralize
|
64
|
+
end
|
65
|
+
|
66
|
+
# Returns the name of this Table if given Column matched.
|
67
|
+
#
|
68
|
+
# @return [String, nil] the name of this Table if given Column matched, nil otherwise
|
69
|
+
def match_foreign_key(column)
|
70
|
+
if column.ref_table == @name || foreign_keys.include?(column.name)
|
71
|
+
@name if primary_key
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Returns the name of this Table if given primary key matched.
|
76
|
+
#
|
77
|
+
# @return [String, nil] the name of this Table if given primary key matched, nil otherwise
|
78
|
+
def match_foreign_key_by_primary_key(pk)
|
79
|
+
@name if foreign_keys.include?(pk) && primary_key
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def foreign_keys
|
85
|
+
["#{@name}_#{fk_suffix}", "#{@name}#{fk_suffix}", "#{singularize}_#{fk_suffix}",
|
86
|
+
"#{singularize}#{fk_suffix}", "#{pluralize}_#{fk_suffix}", "#{pluralize}#{fk_suffix}"]
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
data/lib/rare_map/version.rb
CHANGED
@@ -1,7 +1,11 @@
|
|
1
1
|
module RareMap
|
2
|
+
# The major version of RareMap
|
2
3
|
MAJOR = 2
|
3
|
-
|
4
|
-
|
5
|
-
|
4
|
+
# The minor version of RareMap
|
5
|
+
MINOR = 2
|
6
|
+
# The patch version of RareMap
|
7
|
+
PATCH = 0
|
8
|
+
|
9
|
+
# The version of RareMap
|
6
10
|
VERSION = [MAJOR, MINOR, PATCH].compact.join('.')
|
7
11
|
end
|
data/test/column_test.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'rare_map/column'
|
3
|
+
|
4
|
+
class ColumnTest < Test::Unit::TestCase
|
5
|
+
def setup
|
6
|
+
@column = RareMap::Column.new 'col1', 'integer'
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_constructor
|
10
|
+
assert @column.kind_of? RareMap::Column
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_properties
|
14
|
+
assert_equal 'col1', @column.name
|
15
|
+
assert_equal 'integer', @column.type
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_unique?
|
19
|
+
assert_equal false, @column.unique?
|
20
|
+
@column.unique = true
|
21
|
+
assert @column.unique?
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_foreign_key?
|
25
|
+
assert_equal false, @column.foreign_key?
|
26
|
+
@column.ref_table = 'table1'
|
27
|
+
assert @column.foreign_key?
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'rare_map/config_loader'
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
class ConfigLoaderTest < Test::Unit::TestCase
|
6
|
+
include RareMap::ConfigLoader
|
7
|
+
include RareMap::Errors
|
8
|
+
|
9
|
+
def test_load_config
|
10
|
+
db_profiles = load_config File.dirname(__FILE__)
|
11
|
+
assert_equal 1, db_profiles.size
|
12
|
+
db_profile = db_profiles.first
|
13
|
+
assert db_profile.kind_of? RareMap::DatabaseProfile
|
14
|
+
assert_equal 'main', db_profile.name
|
15
|
+
assert_equal YAML.load_file(File.join(File.dirname(__FILE__), 'rare_map.yml'))['sample'][1]['main'], db_profile.connection
|
16
|
+
assert_equal 'sample', db_profile.options.group
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_load_config_simple
|
20
|
+
db_profiles = load_config File.dirname(__FILE__), 'rare_map_simple.yml'
|
21
|
+
assert_equal 1, db_profiles.size
|
22
|
+
db_profile = db_profiles.first
|
23
|
+
assert db_profile.kind_of? RareMap::DatabaseProfile
|
24
|
+
assert_equal 'main', db_profile.name
|
25
|
+
assert_equal YAML.load_file(File.join(File.dirname(__FILE__), 'rare_map_simple.yml'))['main'], db_profile.connection
|
26
|
+
assert_equal 'default', db_profile.options.group
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_load_config_error
|
30
|
+
assert_raise ConfigNotFoundError do
|
31
|
+
load_config 'no_file'
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'rare_map/database_profile'
|
3
|
+
require 'rare_map/options'
|
4
|
+
|
5
|
+
class DatabaseProfileTest < Test::Unit::TestCase
|
6
|
+
def setup
|
7
|
+
@connection = { adapter: 'sqlite3', database: 'db/test.sqlite3' }
|
8
|
+
@options = RareMap::Options.new
|
9
|
+
@db_profile = RareMap::DatabaseProfile.new 'profile1', @connection, @options
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_constructor
|
13
|
+
assert @db_profile.kind_of? RareMap::DatabaseProfile
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_properties
|
17
|
+
assert_equal 'profile1', @db_profile.name
|
18
|
+
assert_equal @connection, @db_profile.connection
|
19
|
+
assert_equal @options, @db_profile.options
|
20
|
+
assert_equal nil, @db_profile.schema
|
21
|
+
@db_profile.schema = ''
|
22
|
+
assert_equal '', @db_profile.schema
|
23
|
+
assert_equal [], @db_profile.tables
|
24
|
+
end
|
25
|
+
end
|