dbtodoc 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 6f435503e31f05aaa1598c8d7fee82519cc38ecc36b5ce0e4d3f783948579543
4
+ data.tar.gz: 10177a6e91718a7c8e4da15bc5db59e0351f6e6ba35719e272e431a0e8e119fd
5
+ SHA512:
6
+ metadata.gz: c7408cbe931b98cc51d0850fce98a8150094f141a04838afb5f2f877c59e033d11ad2a39cc65d8c61f157bd38a8837b06ae4e976d71acedd47b99db9c749eea1
7
+ data.tar.gz: 804cf971c9051fcdb8602f87001140659ea04524af9aea2e039315cc8cc48a1ebdd92f3fa5ea444b07543b025c42fe60d13d215bf3b1ed53098791a2f610bced
data/.DS_Store ADDED
Binary file
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/README.md ADDED
@@ -0,0 +1,31 @@
1
+ # Dbtodoc
2
+
3
+ TODO: Delete this and the text below, and describe your gem
4
+
5
+ Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/dbtodoc`. To experiment with that code, run `bin/console` for an interactive prompt.
6
+
7
+ ## Installation
8
+
9
+ TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.
10
+
11
+ Install the gem and add to the application's Gemfile by executing:
12
+
13
+ $ bundle add UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
14
+
15
+ If bundler is not being used to manage dependencies, install the gem by executing:
16
+
17
+ $ gem install UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Development
24
+
25
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
26
+
27
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
28
+
29
+ ## Contributing
30
+
31
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/dbtodoc.
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
data/bin/.DS_Store ADDED
Binary file
data/bin/dbtodoc ADDED
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+ require 'bundler/setup'
4
+
5
+ require 'optparse'
6
+ class ArgsParser
7
+ def self.parse!(args)
8
+ options = {
9
+ type: 'excel'
10
+ }
11
+ opt_parser = OptionParser.new do |opts|
12
+ opts.banner = "Usage: dbtodoc [options]"
13
+
14
+ opts.on('-t', '--type TYPE', "指定输出类型,默认值:excel,可选值:schema, csv, excel") do |o|
15
+ options[:type] = o
16
+ end
17
+
18
+ opts.on('-h', '--help', "显示帮助\n例:dbtodoc -t csv /rails/root/path") do
19
+ puts opts
20
+ exit
21
+ end
22
+ end
23
+ # 解析命令行参数
24
+ opt_parser.parse!(args)
25
+ options
26
+ end
27
+ end
28
+
29
+ # 解析命令行参数
30
+ options = ArgsParser.parse!(ARGV)
31
+ # 获取目标目录
32
+ path = ARGV.first || '.'
33
+ # 确保目录存在
34
+ unless File.exist?(path)
35
+ puts "#{path}: 文件或目录不存在"
36
+ exit 1
37
+ end
38
+ options[:path] = path
39
+
40
+ require 'dbtodoc'
41
+
42
+ Dbtodoc.start(options)
data/dbtodoc.gemspec ADDED
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/dbtodoc/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "dbtodoc"
7
+ spec.version = Dbtodoc::VERSION
8
+ spec.authors = ["shuhjx"]
9
+ spec.email = ["shuh_jx@163.com"]
10
+
11
+ spec.summary = "将 Rails 项目的数据库 schema 导出为文档"
12
+ spec.description = "将 Rails 项目的数据库 schema 导出为文档,支持 schema, csv, excel 格式"
13
+ spec.homepage = "https://github.com/shuhjx/dbtodoc"
14
+ spec.required_ruby_version = ">= 2.6.0"
15
+
16
+ spec.metadata["allowed_push_host"] = "https://rubygems.org"
17
+
18
+ spec.metadata["homepage_uri"] = spec.homepage
19
+ spec.metadata["source_code_uri"] = spec.homepage
20
+ spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/main/CHANGELOG.md"
21
+
22
+ # Specify which files should be added to the gem when it is released.
23
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
24
+ spec.files = Dir.chdir(__dir__) do
25
+ `git ls-files -z`.split("\x0").reject do |f|
26
+ (File.expand_path(f) == __FILE__) ||
27
+ f.start_with?(*%w[test/ spec/ features/ .git .github appveyor Gemfile])
28
+ end
29
+ end
30
+ spec.executable = 'dbtodoc'
31
+ spec.require_paths = ["lib"]
32
+
33
+ # Uncomment to register a new dependency of your gem
34
+ # spec.add_dependency "example-gem", "~> 1.0"
35
+
36
+ # For more information and examples about making a new gem, check out our
37
+ # guide at: https://bundler.io/guides/creating_gem.html
38
+ end
@@ -0,0 +1,116 @@
1
+ require 'active_record'
2
+ require_relative './i18n.rb'
3
+
4
+ module Dbtodoc
5
+ module Definition
6
+ def self.included(klass)
7
+ klass.extend(ClassMethods)
8
+ end
9
+
10
+ module ClassMethods
11
+ def define(info = {}, &block)
12
+ new.define(info, &block)
13
+ end
14
+ end
15
+
16
+ def define(info, &block)
17
+ before_define()
18
+ instance_eval(&block)
19
+ ensure
20
+ after_define()
21
+ end
22
+
23
+ def index(column_names, **options)
24
+ @table_columns.each do |row|
25
+ next if !column_names.include?(row[:column_name])
26
+ row[:index_name] = options[:name]
27
+ break if column_names.size == 1
28
+ end
29
+ end
30
+
31
+ def i18n
32
+ @_i18n ||= Dbtodoc::I18n.new(File.join(Dir.pwd, 'config/locales/*.yml'))
33
+ end
34
+
35
+ def database
36
+ @_database ||= ActiveRecord::Base.connection.current_database
37
+ end
38
+ private
39
+ # 写入文档
40
+ def write_to_doc(table_name, table_columns)
41
+ raise 'Please override write_to_doc method!'
42
+ end
43
+ def before_define
44
+ #TODO 执行scheam.rb前
45
+ end
46
+ def after_define
47
+ #TODO 执行scheam.rb后
48
+ end
49
+
50
+ def add_row(column_name, type, **options)
51
+ # puts "table: #{@table_name}, column: #{column_name}, sql_type: #{type}"
52
+ default = options[:default]
53
+ default = default.call if default.respond_to?(:call)
54
+ comment = options[:comment] || i18n.column_name(@table_name, column_name)
55
+ @table_columns << {
56
+ column_name: column_name,
57
+ comment: comment,
58
+ type: type,
59
+ null: options[:null],
60
+ primary_key: options[:primary_key],
61
+ default: default,
62
+ description: nil,
63
+ sample_data: nil,
64
+ index_name: nil
65
+ }
66
+ end
67
+
68
+ def add_primary_key_row(options)
69
+ id_options = options[:id]
70
+ return if id_options == false
71
+ id_options ||= {type: :bigint}
72
+ primary_key = options[:primary_key] || :id
73
+ id_options = {type: id_options} unless id_options.is_a?(Hash)
74
+ type = id_options[:type] || :bigint
75
+ id_options[:limit] = type == :bigint ? 8 : nil # 默认bigint
76
+ id_options[:null] = false
77
+ id_options[:comment] ||= 'ID'
78
+ id_options[:primary_key] = true
79
+ send(type, primary_key, **id_options)
80
+ end
81
+
82
+ def create_table(table_name, **options, &block)
83
+ @table_name = table_name
84
+ @table_columns = [] #||= Hash.new { |hash, key| hash[key] = [] }
85
+ add_primary_key_row(options)
86
+ block.call(self)
87
+ # 写入文档
88
+ write_to_doc(table_name, @table_columns)
89
+ end
90
+
91
+ def add_foreign_key(from_table, to_table, **options)
92
+ #TODO 外键
93
+ end
94
+
95
+ def method_missing(name, *args, &block)
96
+ type = case name
97
+ when :bigint
98
+ ActiveRecord::Type.registry.lookup(:big_integer)
99
+ else
100
+ ActiveRecord::Type.registry.lookup(name.to_sym) rescue nil
101
+ end
102
+ if type
103
+ column = args[0]
104
+ options = args[1] || {}
105
+ # 调用type_to_sql方法
106
+ sql_type = ActiveRecord::Base.connection.schema_creation.send(:type_to_sql, type.type, **options)
107
+ add_row(column, sql_type, **options)
108
+ else
109
+ puts '------------'
110
+ puts "name: #{name}, args: #{args}"
111
+ puts '------------'
112
+ super
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,29 @@
1
+ require 'csv'
2
+ module Dbtodoc
3
+ module Doc
4
+ module Csv
5
+ DOC_HEADER = %w(表名 字段名 类型 NULL許可 默认値 日本語名 説明 样本数据 索引名)
6
+
7
+ private
8
+ def before_define
9
+ file_path = File.join(Dir.pwd, "tmp/#{database}.csv")
10
+ @csv = File.open(file_path, 'w:utf-8') # 以写入模式打开文件,覆盖原有内容
11
+ end
12
+
13
+ def after_define
14
+ @csv.close if @csv # 确保文件关闭
15
+ end
16
+
17
+ def write_to_doc(table_name, table_columns)
18
+ #以追加模式打开文件
19
+ row = CSV.generate_line(DOC_HEADER)
20
+ @csv.write(row)
21
+ table_columns.each do |rows|
22
+ row = CSV.generate_line([table_name, rows[:column_name], rows[:type], rows[:null], rows[:default], rows[:comment], rows[:description], rows[:sample_data], rows[:index_name]])
23
+ @csv.write(row)
24
+ end
25
+ @csv.write("\n")
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,156 @@
1
+ require 'rubyXL'
2
+ require 'rubyXL/convenience_methods'
3
+ require 'fileutils'
4
+ module Dbtodoc
5
+ module Doc
6
+ module Excel
7
+ DOC_HEADER = %w(フィールド名 型 NULL許可 デフォルト値 日本語名 説明 サンプルデータ 索引名)
8
+
9
+ private
10
+ def before_define
11
+ @file_path = File.join(Dir.pwd, "tmp/#{database}.xlsx")
12
+ File.delete(@file_path) if File.exist?(@file_path) # 如果文件存在,则删除
13
+ @workbook = RubyXL::Workbook.new # 创建新的 Excel 工作簿
14
+ @worksheet = @workbook.worksheets.first # 获取第一个工作表
15
+ @max_row = 0 # 最大行索引
16
+ @col_max_widths = Hash.new { |h, k| h[k] = 0 } # 列索引 => 最大宽度
17
+ end
18
+
19
+ def after_define
20
+ # 设置列宽(Excel 列宽单位 ≈ 字符数 + 一些边距)
21
+ @col_max_widths[0] = 1 # 第一列(索引列)宽度设为 1 * 1.5 + 2 = 3.5
22
+ @col_max_widths.each do |col_index, width|
23
+ # 限制最大宽度(避免过宽),例如不超过 50
24
+ adjusted_width = [width * 1.5 + 2, 50].min # +2 为边距
25
+ @worksheet.change_column_width(col_index, adjusted_width)
26
+ end
27
+ # 保存Excel文件
28
+ @workbook.write @file_path
29
+ end
30
+
31
+ # 实现将数据库 schema 导出到 Excel 文件的方法
32
+ def write_to_doc(table_name, table_columns)
33
+ # 写入2行空行
34
+ add_blank_row(2)
35
+ # 写入表名
36
+ add_table_name_row(table_name)
37
+ # 写入表头
38
+ add_header_row
39
+ # 写入数据
40
+ table_columns.each_with_index do |rows, index|
41
+ a_row = [rows[:column_name].to_s, rows[:type], rows[:null], rows[:default], rows[:comment], rows[:description], rows[:sample_data], rows[:index_name]]
42
+ add_column_row(a_row, rows[:primary_key] ? :id_column : :column)
43
+ end
44
+ end
45
+
46
+ def add_table_name_row(table_name)
47
+ [table_name, i18n.table_name(table_name), *Array.new(DOC_HEADER.size - 2, nil)].each_with_index do |value, col|
48
+ @col_max_widths[col+1] = [@col_max_widths[col+1], value.to_s.length].max if value
49
+ cell = @worksheet.add_cell(@max_row, col + 1, value)
50
+ # 设置背景颜色(蓝色)
51
+ set_cell_style(cell, :table_name)
52
+ end
53
+ @max_row += 1
54
+ end
55
+
56
+ def add_blank_row(count = 1)
57
+ count.times do
58
+ @worksheet.add_cell(@max_row, 0, nil)
59
+ @max_row += 1
60
+ end
61
+ end
62
+
63
+ def add_header_row
64
+ DOC_HEADER.each_with_index do |value, col|
65
+ @col_max_widths[col+1] = [@col_max_widths[col+1], value.to_s.length].max if value
66
+ cell = @worksheet.add_cell(@max_row, col + 1, value)
67
+ # 设置背景颜色(浅绿色)
68
+ set_cell_style(cell, :header)
69
+ end
70
+ @max_row += 1
71
+ end
72
+
73
+ def add_column_row(cols, cell_type = :column)
74
+ cols.each_with_index do |value, col|
75
+ @col_max_widths[col+1] = [@col_max_widths[col+1], value.to_s.length].max if value
76
+ cell = @worksheet.add_cell(@max_row, col + 1, value)
77
+ # 设置背景颜色(白色)
78
+ set_cell_style(cell, cell_type)
79
+ end
80
+ @max_row += 1
81
+ end
82
+
83
+ # 辅助方法:设置单元格背景颜色
84
+ def set_cell_style(cell, cell_type)
85
+ @h_style_index ||= Hash.new { |h, k| h[k] = create_style_index(k) }
86
+ style_index = @h_style_index[cell_type]
87
+
88
+ # 应用样式到单元格
89
+ cell.style_index = style_index
90
+ end
91
+
92
+ def create_style_index(cell_type)
93
+ rgb_color = case cell_type
94
+ when :header then '4EE257'
95
+ when :table_name then '000090'
96
+ when :column then 'FFFFFF'
97
+ when :id_column then 'CCFFCC'
98
+ else raise "Unknown cell type: #{cell_type}"
99
+ end
100
+ fill = RubyXL::Fill.new
101
+ pattern_fill = RubyXL::PatternFill.new
102
+ pattern_fill.pattern_type = 'solid'
103
+ pattern_fill.fg_color = RubyXL::Color.new(rgb: rgb_color)
104
+ pattern_fill.bg_color = RubyXL::Color.new(rgb: rgb_color)
105
+ fill.pattern_fill = pattern_fill
106
+ # 添加到工作簿的样式表
107
+ @workbook.stylesheet.fills << fill
108
+ # 获取新的 fill_id
109
+ fill_id = @workbook.stylesheet.fills.size - 1
110
+
111
+ # 创建新的 XF 样式
112
+ xf = RubyXL::XF.new
113
+ xf.fill_id = fill_id
114
+ xf.apply_fill = 1 # 使用1而不是true
115
+ # 添加到工作簿的单元格样式
116
+ @workbook.stylesheet.cell_xfs << xf
117
+
118
+ # 设置字体「MS Pゴシック」
119
+ font = RubyXL::Font.new
120
+ font.name = RubyXL::StringValue.new(val: 'MS Pゴシック')
121
+ font.sz = RubyXL::FloatValue.new(val: 12)
122
+ if cell_type == :table_name
123
+ font.color = RubyXL::Color.new(rgb: 'FFFFFF')
124
+ font.b = RubyXL::BooleanValue.new(val: true)
125
+ else
126
+ font.color = RubyXL::Color.new(rgb: '000000')
127
+ font.b = RubyXL::BooleanValue.new(val: false)
128
+ end
129
+ @workbook.stylesheet.fonts << font
130
+ font_id = @workbook.stylesheet.fonts.size - 1
131
+ xf.font_id = font_id
132
+
133
+ if @cell_border_index.nil?
134
+ border = RubyXL::Border.new
135
+ # 边框样式,可选值:hairline, thin, medium, thick
136
+ border.left = RubyXL::BorderEdge.new(style: 'medium')
137
+ border.right = RubyXL::BorderEdge.new(style: 'medium')
138
+ border.top = RubyXL::BorderEdge.new(style: 'medium')
139
+ border.bottom = RubyXL::BorderEdge.new(style: 'medium')
140
+ edge_color = '000000' # 黑色边框
141
+ border.set_edge_color(:left, edge_color)
142
+ border.set_edge_color(:right, edge_color)
143
+ border.set_edge_color(:top, edge_color)
144
+ border.set_edge_color(:bottom, edge_color)
145
+ @workbook.stylesheet.borders << border
146
+ @cell_border_index = @workbook.stylesheet.borders.size - 1
147
+ end
148
+ xf.border_id = @cell_border_index
149
+
150
+ # 获取新的 style_index
151
+ style_index = @workbook.stylesheet.cell_xfs.size - 1
152
+ return style_index
153
+ end
154
+ end
155
+ end
156
+ end
@@ -0,0 +1,38 @@
1
+ require 'yaml'
2
+ require 'active_support/core_ext/string/inflections'
3
+ module Dbtodoc
4
+ class I18n
5
+ def initialize(path)
6
+ @models = Hash.new { |h, k| h[k] = {} }
7
+ @attributes = Hash.new { |h, k| h[k] = {} }
8
+ Dir.glob(path).each do |f|
9
+ yaml = YAML.load_file(f)
10
+ lang = yaml.keys[0]
11
+ next if yaml[lang]['activerecord'].blank?
12
+ if data = yaml[lang]['activerecord']['models']
13
+ @models[lang].merge!(data)
14
+ end
15
+ if data = yaml[lang]['activerecord']['attributes']
16
+ @attributes[lang].merge!(data)
17
+ end
18
+ end
19
+ end
20
+
21
+ def table_name(name)
22
+ # 单词复数转单数
23
+ name = name.singularize if name.end_with?('s')
24
+ # 尝试从i18n配置中获取表名
25
+ @models.map do |lang, models|
26
+ models.key?(name.downcase) ? models[name.downcase] : nil
27
+ end.compact.join("\n").presence || name.titleize
28
+ end
29
+
30
+ def column_name(tablename, name)
31
+ tablename = tablename.singularize if tablename.end_with?('s')
32
+ # 尝试从i18n配置中获取列名
33
+ @attributes.map do |lang, attrs|
34
+ attrs.key?(tablename.downcase) && attrs[tablename.downcase].key?(name.downcase) ? attrs[tablename.downcase][name.downcase] : nil
35
+ end.compact.join("\n").presence || name.titleize
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,59 @@
1
+ =begin
2
+ require 'active_record'
3
+ # 方法重写
4
+ module ActiveRecord
5
+ class Schema
6
+ class << self
7
+ alias_method :original_get, :'[]'
8
+ def [](version)
9
+ @class_for_version ||= {}
10
+ @class_for_version[version] ||= Class.new do
11
+ # include Definition
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ #TODO
18
+ # 方法恢复
19
+ module ActiveRecord
20
+ class Schema
21
+ class << self
22
+ ::ActiveRecord::Schema.instance_variable_get('@class_for_version').clear rescue nil
23
+ alias_method :'[]', :original_get
24
+ undef_method :original_get
25
+ end
26
+ end
27
+ end
28
+ =end
29
+ require 'active_record'
30
+ require_relative './definition.rb'
31
+ module ActiveRecord
32
+ class Schema
33
+ class << self
34
+ alias_method :original_get, :'[]'
35
+ def [](version)
36
+ @class_for_version ||= {}
37
+ @class_for_version[version] ||= Class.new do
38
+ include ::Dbtodoc::Definition
39
+ if @@doc_type_module
40
+ include @@doc_type_module
41
+ end
42
+ end
43
+ end
44
+
45
+ def set_doc_type(type)
46
+ @@doc_type_module = case type
47
+ when 'csv'
48
+ require_relative('./doc/csv.rb')
49
+ ::Dbtodoc::Doc::Csv
50
+ when 'excel'
51
+ require_relative './doc/excel.rb'
52
+ ::Dbtodoc::Doc::Excel
53
+ else
54
+ raise ArgumentError, "Invalid doc type: #{type}"
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dbtodoc
4
+ VERSION = "0.1.0"
5
+ end
data/lib/dbtodoc.rb ADDED
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "dbtodoc/version"
4
+ require 'active_record'
5
+ require 'yaml'
6
+ require 'fileutils'
7
+ require 'erb'
8
+
9
+ if Psych::VERSION >= '4.0'
10
+ module Psych
11
+ class << self
12
+ alias_method :original_safe_load, :safe_load
13
+
14
+ def safe_load(*args, **kwargs)
15
+ kwargs[:aliases] = true # 允许使用别名
16
+ original_safe_load(*args, **kwargs)
17
+ end
18
+ end
19
+ end
20
+ end
21
+
22
+ module Dbtodoc
23
+ class Error < StandardError; end
24
+
25
+ def self.start(options)
26
+ path = options[:path] || '.'
27
+ Dir.chdir(path) if path != '.' # 切换到目标目录
28
+
29
+ db_config_file = File.join(path, 'config/database.yml')
30
+ unless File.exist?(db_config_file)
31
+ puts "#{db_config_file}: 文件或目录不存在"
32
+ exit 1
33
+ end
34
+ # 读取数据库配置文件
35
+ all_db_configs = YAML.load_file(db_config_file).each_with_object({}) do |(_, config), h|
36
+ next if config['database'].blank?
37
+ config.each do |k, v|
38
+ config[k] = ERB.new(v).result if v.is_a?(String)
39
+ end
40
+ h[config['database']] = config
41
+ end
42
+
43
+ db_name = if all_db_configs.keys.size == 1
44
+ # 如果只有一个数据库,直接使用它
45
+ all_db_configs.keys.first
46
+ else
47
+ require 'cli/ui' #https://github.com/shopify/cli-ui
48
+ # 如果有多个数据库,选择一个
49
+ CLI::UI.ask('Select database:', options: all_db_configs.keys)
50
+ end
51
+ exit if db_name.blank?
52
+
53
+ # 读取数据库配置的adapter动态加载对应库
54
+ adapter = all_db_configs[db_name]['adapter']
55
+ case adapter
56
+ when 'mysql2'
57
+ require 'mysql2'
58
+ when 'pg'
59
+ require 'pg'
60
+ when 'sqlite3'
61
+ require 'sqlite3'
62
+ else
63
+ puts "Unknown adapter: #{adapter}"
64
+ exit 1
65
+ end
66
+
67
+ # 连接数据库
68
+ ActiveRecord::Base.establish_connection(all_db_configs[db_name])
69
+
70
+ # 确保tmp目录存在
71
+ FileUtils.mkdir_p File.join(path, 'tmp')
72
+
73
+ # 生成数据库 schema 文件
74
+ schema_file = File.join(path, "tmp/#{db_name}_schema.rb")
75
+ File.open(schema_file, 'w:utf-8') do |file|
76
+ if ActiveRecord.version >= '7.1'
77
+ ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection_pool, file)
78
+ else
79
+ ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, file)
80
+ end
81
+ end
82
+
83
+ case options[:type].downcase
84
+ when 'schema'
85
+ puts "Schema file: #{schema_file}"
86
+ when 'csv', 'excel'
87
+ require_relative File.join(__dir__, 'dbtodoc/schema.rb')
88
+ ActiveRecord::Schema.set_doc_type(options[:type])
89
+ #执行schema.rb文件,生成csv|excel文件
90
+ eval File.read(schema_file), binding
91
+ puts "CSV file: #{File.join(path, "tmp/#{db_name}.csv")}" if options[:type] == 'csv'
92
+ puts "Excel file: #{File.join(path, "tmp/#{db_name}.xlsx")}" if options[:type] == 'excel'
93
+ else
94
+ puts "Unknown type: #{options[:type]}"
95
+ end
96
+ end
97
+ end
data/sig/dbtodoc.rbs ADDED
@@ -0,0 +1,4 @@
1
+ module Dbtodoc
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,62 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dbtodoc
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - shuhjx
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-01-05 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: 将 Rails 项目的数据库 schema 导出为文档,支持 schema, csv, excel 格式
14
+ email:
15
+ - shuh_jx@163.com
16
+ executables:
17
+ - dbtodoc
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - ".DS_Store"
22
+ - ".rspec"
23
+ - README.md
24
+ - Rakefile
25
+ - bin/.DS_Store
26
+ - bin/dbtodoc
27
+ - dbtodoc.gemspec
28
+ - lib/dbtodoc.rb
29
+ - lib/dbtodoc/definition.rb
30
+ - lib/dbtodoc/doc/csv.rb
31
+ - lib/dbtodoc/doc/excel.rb
32
+ - lib/dbtodoc/i18n.rb
33
+ - lib/dbtodoc/schema.rb
34
+ - lib/dbtodoc/version.rb
35
+ - sig/dbtodoc.rbs
36
+ homepage: https://github.com/shuhjx/dbtodoc
37
+ licenses: []
38
+ metadata:
39
+ allowed_push_host: https://rubygems.org
40
+ homepage_uri: https://github.com/shuhjx/dbtodoc
41
+ source_code_uri: https://github.com/shuhjx/dbtodoc
42
+ changelog_uri: https://github.com/shuhjx/dbtodoc/blob/main/CHANGELOG.md
43
+ post_install_message:
44
+ rdoc_options: []
45
+ require_paths:
46
+ - lib
47
+ required_ruby_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: 2.6.0
52
+ required_rubygems_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: '0'
57
+ requirements: []
58
+ rubygems_version: 3.3.26
59
+ signing_key:
60
+ specification_version: 4
61
+ summary: 将 Rails 项目的数据库 schema 导出为文档
62
+ test_files: []