rare_map 2.1.1 → 2.2.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.
- 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
|