crudboy 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +75 -0
- data/LICENSE.txt +21 -0
- data/README.md +12 -0
- data/Rakefile +2 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/crudboy.gemspec +39 -0
- data/exe/crudboy +7 -0
- data/exe/crudboy_setsid_wrapper +5 -0
- data/lib/crudboy.rb +16 -0
- data/lib/crudboy/app.rb +96 -0
- data/lib/crudboy/bundle.rb +79 -0
- data/lib/crudboy/cli.rb +90 -0
- data/lib/crudboy/column.rb +86 -0
- data/lib/crudboy/concerns.rb +2 -0
- data/lib/crudboy/concerns/global_data_definition.rb +244 -0
- data/lib/crudboy/concerns/table_data_definition.rb +550 -0
- data/lib/crudboy/connection.rb +10 -0
- data/lib/crudboy/definition.rb +123 -0
- data/lib/crudboy/ext.rb +5 -0
- data/lib/crudboy/ext/array.rb +139 -0
- data/lib/crudboy/ext/hash.rb +41 -0
- data/lib/crudboy/ext/kernel.rb +112 -0
- data/lib/crudboy/ext/object.rb +22 -0
- data/lib/crudboy/ext/string.rb +17 -0
- data/lib/crudboy/ext/time.rb +15 -0
- data/lib/crudboy/helper.rb +36 -0
- data/lib/crudboy/id.rb +59 -0
- data/lib/crudboy/model.rb +55 -0
- data/lib/crudboy/ssh_proxy.rb +31 -0
- data/lib/crudboy/ssh_proxy_patch.rb +88 -0
- data/lib/crudboy/template.rb +76 -0
- data/lib/crudboy/template_context.rb +35 -0
- data/lib/crudboy/version.rb +3 -0
- metadata +256 -0
@@ -0,0 +1,123 @@
|
|
1
|
+
require 'crudboy/concerns'
|
2
|
+
module Crudboy
|
3
|
+
module Extension
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
def t
|
7
|
+
puts Terminal::Table.new { |t|
|
8
|
+
v.each { |row| t << (row || :separator) }
|
9
|
+
}
|
10
|
+
end
|
11
|
+
|
12
|
+
def v
|
13
|
+
t = []
|
14
|
+
t << ['Attribute Name', 'Attribute Value', 'SQL Type', 'Comment']
|
15
|
+
t << nil
|
16
|
+
self.class.connection.columns(self.class.table_name).each do |column|
|
17
|
+
t << [column.name, read_attribute(column.name), column.sql_type, column.comment || '']
|
18
|
+
end
|
19
|
+
t
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_insert_sql
|
23
|
+
self.class.to_insert_sql([self])
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_upsert_sql
|
27
|
+
self.class.to_upsert_sql([self])
|
28
|
+
end
|
29
|
+
|
30
|
+
def write_csv(filename, *fields, **options)
|
31
|
+
[self].write_csv(filename, *fields, **options)
|
32
|
+
end
|
33
|
+
|
34
|
+
def write_excel(filename, *fields, **options)
|
35
|
+
[self].write_excel(filename, *fields, **options)
|
36
|
+
end
|
37
|
+
|
38
|
+
class_methods do
|
39
|
+
def t
|
40
|
+
table_name = Commands::Table::get_table_name(name)
|
41
|
+
puts "\nTable: #{table_name}"
|
42
|
+
puts Commands::Table::table_info_table(table_name)
|
43
|
+
end
|
44
|
+
|
45
|
+
def v
|
46
|
+
table_name = Commands::Table::get_table_name(name)
|
47
|
+
Commands::Table::table_info(table_name)
|
48
|
+
end
|
49
|
+
|
50
|
+
def to_insert_sql(records, batch_size = 1)
|
51
|
+
to_sql(records, :skip, batch_size)
|
52
|
+
end
|
53
|
+
|
54
|
+
def to_upsert_sql(records, batch_size = 1)
|
55
|
+
to_sql(records, :update, batch_size)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
class Definition
|
61
|
+
|
62
|
+
attr_accessor :table_name, :model, :model_name
|
63
|
+
|
64
|
+
def initialize(options)
|
65
|
+
@@options = options
|
66
|
+
@table_name = @@options[:table_name]
|
67
|
+
@model_name = @@options[:model_name]
|
68
|
+
ActiveRecord::Base.connection.tap do |conn|
|
69
|
+
Object.const_set('CrudboyModel', Class.new(ActiveRecord::Base) do
|
70
|
+
include ::Crudboy::Concerns::TableDataDefinition
|
71
|
+
self.abstract_class = true
|
72
|
+
end)
|
73
|
+
|
74
|
+
raise "Table not exist: #{@table_name}" unless conn.tables.include?(@table_name)
|
75
|
+
|
76
|
+
table_comment = conn.table_comment(@table_name)
|
77
|
+
conn.primary_key(@table_name).tap do |pkey|
|
78
|
+
Class.new(::CrudboyModel) do
|
79
|
+
include Crudboy::Extension
|
80
|
+
self.table_name = options[:table_name]
|
81
|
+
if pkey.is_a?(Array)
|
82
|
+
self.primary_keys = pkey
|
83
|
+
else
|
84
|
+
self.primary_key = pkey
|
85
|
+
end
|
86
|
+
self.inheritance_column = nil
|
87
|
+
self.default_timezone = :local
|
88
|
+
if options[:created_at].present?
|
89
|
+
define_singleton_method :timestamp_attributes_for_create do
|
90
|
+
options[:created_at]
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
if options[:updated_at].present?
|
95
|
+
define_singleton_method :timestamp_attributes_for_update do
|
96
|
+
options[:updated_at]
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end.tap do |clazz|
|
100
|
+
Object.const_set(@model_name, clazz).tap do |const|
|
101
|
+
@model = Model.new(const, @table_name, table_comment)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
::ActiveRecord::Relation.class_eval do
|
109
|
+
def t(*attrs, **options)
|
110
|
+
records.t(*attrs, **options)
|
111
|
+
end
|
112
|
+
|
113
|
+
def v
|
114
|
+
records.v
|
115
|
+
end
|
116
|
+
|
117
|
+
def a
|
118
|
+
to_a
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
data/lib/crudboy/ext.rb
ADDED
@@ -0,0 +1,139 @@
|
|
1
|
+
class Array
|
2
|
+
def to_insert_sql(batch_size=500)
|
3
|
+
raise 'All element should be an ActiveRecord instance object' unless all? { |e| e.is_a?(ActiveRecord::Base) }
|
4
|
+
group_by(&:class).map do |(klass, records)|
|
5
|
+
klass.to_insert_sql(records, batch_size)
|
6
|
+
end.join("\n")
|
7
|
+
end
|
8
|
+
|
9
|
+
def to_upsert_sql(batch_size=500)
|
10
|
+
raise 'All element should be an ActiveRecord instance object' unless all? { |e| e.is_a?(ActiveRecord::Base) }
|
11
|
+
group_by(&:class).map do |(klass, records)|
|
12
|
+
klass.to_upsert_sql(records, batch_size)
|
13
|
+
end.join("\n")
|
14
|
+
end
|
15
|
+
|
16
|
+
def t(*attrs, **options)
|
17
|
+
if (attrs.present? || options.present? && options[:except]) && present? && first.is_a?(ActiveRecord::Base)
|
18
|
+
column_names = first.attribute_names.map(&:to_sym)
|
19
|
+
attrs = attrs.flat_map { |e| e.is_a?(Regexp) ? column_names.grep(e) : e }.uniq
|
20
|
+
if options.present? && options[:except]
|
21
|
+
attrs = column_names if attrs.empty?
|
22
|
+
if options[:except].is_a?(Regexp)
|
23
|
+
attrs.reject! { |e| e =~ options[:except] }
|
24
|
+
else
|
25
|
+
attrs -= [options[:except]].flatten
|
26
|
+
end
|
27
|
+
end
|
28
|
+
puts Terminal::Table.new { |t|
|
29
|
+
t << attrs
|
30
|
+
t << :separator
|
31
|
+
each do |e|
|
32
|
+
t << e.attributes.values_at(*attrs.map(&:to_s))
|
33
|
+
end
|
34
|
+
}
|
35
|
+
else
|
36
|
+
table = Terminal::Table.new { |t|
|
37
|
+
v.each { |row| t << (row || :separator)}
|
38
|
+
}.to_s
|
39
|
+
|
40
|
+
terminal_width = `tput cols`.to_i
|
41
|
+
if table.lines.first.size > terminal_width
|
42
|
+
table = table.lines.map(&:chomp)
|
43
|
+
puts table[0..2].join("\n")
|
44
|
+
puts table[3..-1].join("\n#{'-' * terminal_width}\n")
|
45
|
+
else
|
46
|
+
puts table
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def v
|
52
|
+
return self unless present?
|
53
|
+
t = []
|
54
|
+
if map(&:class).uniq.size == 1
|
55
|
+
if first.is_a?(ActiveRecord::Base)
|
56
|
+
t << first.attribute_names
|
57
|
+
t << nil
|
58
|
+
each do |e|
|
59
|
+
t << e.attributes.values_at(*first.attribute_names).map(&:as_json)
|
60
|
+
end
|
61
|
+
elsif first.is_a?(Array)
|
62
|
+
t = map { |a| a.map(&:as_json) }
|
63
|
+
elsif first.is_a?(Hash) || first.is_a?(ActiveSupport::HashWithIndifferentAccess)
|
64
|
+
t << first.keys
|
65
|
+
t << nil
|
66
|
+
each do |e|
|
67
|
+
t << e.values_at(*first.keys).map(&:as_json)
|
68
|
+
end
|
69
|
+
else
|
70
|
+
return self
|
71
|
+
end
|
72
|
+
end
|
73
|
+
t
|
74
|
+
end
|
75
|
+
|
76
|
+
def write_csv(filename, *fields, **options)
|
77
|
+
generate_csv(filename, **options) do |csv|
|
78
|
+
if size > 0 && first.is_a?(ActiveRecord::Base)
|
79
|
+
if fields.empty?
|
80
|
+
fields = first.attributes.keys
|
81
|
+
else
|
82
|
+
fields = fields.map(&:to_s)
|
83
|
+
end
|
84
|
+
csv << fields
|
85
|
+
end
|
86
|
+
if size > 0 && first.is_a?(Hash)
|
87
|
+
if fields.empty?
|
88
|
+
fields = first.keys
|
89
|
+
end
|
90
|
+
csv << fields
|
91
|
+
end
|
92
|
+
each do |row|
|
93
|
+
if row.is_a?(Array)
|
94
|
+
csv << row.map(&:to_s)
|
95
|
+
else
|
96
|
+
csv << row.slice(*fields).values.map(&:to_s)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def write_excel(filename, *fields, **options)
|
103
|
+
sheet_name = options[:sheet_name] || 'Sheet1'
|
104
|
+
generate_excel(filename) do |workbook|
|
105
|
+
workbook.add_worksheet(name: sheet_name) do |sheet|
|
106
|
+
if size > 0 && first.is_a?(ActiveRecord::Base)
|
107
|
+
if fields.empty?
|
108
|
+
fields = first.attributes.keys
|
109
|
+
else
|
110
|
+
fields = fields.map(&:to_s)
|
111
|
+
end
|
112
|
+
sheet.add_row(fields, types: [:string] * fields.size)
|
113
|
+
end
|
114
|
+
if size > 0 && first.is_a?(Hash)
|
115
|
+
if fields.empty?
|
116
|
+
fields = first.keys
|
117
|
+
end
|
118
|
+
sheet.add_row(fields, types: [:string] * fields.size)
|
119
|
+
end
|
120
|
+
each do |row|
|
121
|
+
if row.is_a?(Array)
|
122
|
+
sheet.add_row(row.map(&:to_s), types: [:string] * row.size)
|
123
|
+
else
|
124
|
+
sheet.add_row(row.slice(*fields).values.map(&:to_s), types: [:string] * fields.size)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def dump(filename, batch_size=500)
|
132
|
+
File.open(File.expand_path(filename), 'w') do |file|
|
133
|
+
group_by(&:class).each do |(klass, records)|
|
134
|
+
file.puts(klass.to_upsert_sql(records, batch_size))
|
135
|
+
end
|
136
|
+
end
|
137
|
+
{size: size, file: File.expand_path(filename)}
|
138
|
+
end
|
139
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
class Hash
|
2
|
+
def write_excel(filename)
|
3
|
+
generate_excel(filename) do |workbook|
|
4
|
+
each do |sheet_name, sheet_data|
|
5
|
+
workbook.add_worksheet(name: sheet_name) do |sheet|
|
6
|
+
if sheet_data.is_a?(Hash)
|
7
|
+
fields = sheet_data[:fields].map(&:to_s)
|
8
|
+
sheet.add_row(fields, types: [:string] * fields.size)
|
9
|
+
sheet_data[:data].each do |row|
|
10
|
+
sheet.add_row(row.slice(*fields).values.map(&:to_s), types: [:string] * fields.size)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
if sheet_data.is_a?(Array)
|
15
|
+
if sheet_data.size > 0 && sheet_data.first.is_a?(ActiveModel::Base)
|
16
|
+
fields = sheet_data.first.attributes.keys
|
17
|
+
sheet.add_row(fields, types: [:string] * fields.size)
|
18
|
+
sheet_data.each do |row|
|
19
|
+
sheet.add_row(row.slice(*fields).values.map(&:to_s), types: [:string] * fields.size)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
if sheet_data.size > 0 && sheet_data.first.is_a?(Hash)
|
24
|
+
fields = sheet_data.first.keys
|
25
|
+
sheet.add_row(fields, types: [:string] * fields.size)
|
26
|
+
sheet_data.each do |row|
|
27
|
+
sheet.add_row(row.slice(*fields).values.map(&:to_s), types: [:string] * fields.size)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
if sheet_data.size > 0 && sheet_data.first.is_a?(Array)
|
32
|
+
sheet_data.each do |row|
|
33
|
+
sheet.add_row(row.map(&:to_s), types: [:string] * fields.size)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'crudboy/concerns'
|
2
|
+
module Kernel
|
3
|
+
CSV_BOM = "\xef\xbb\xbf"
|
4
|
+
|
5
|
+
include ::Crudboy::Concerns::GlobalDataDefinition
|
6
|
+
|
7
|
+
def sql(sql)
|
8
|
+
ActiveRecord::Base.connection.exec_query(sql)
|
9
|
+
end
|
10
|
+
|
11
|
+
def print_tables(format = :md)
|
12
|
+
require 'terminal-table'
|
13
|
+
|
14
|
+
tables = ActiveRecord::Base.connection.tables.map do |table_name|
|
15
|
+
{
|
16
|
+
table: table_name,
|
17
|
+
table_comment: ActiveRecord::Base.connection.table_comment(table_name) || '',
|
18
|
+
columns: ::ActiveRecord::Base.connection.columns(table_name)
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
outputs = tables.map do |table|
|
23
|
+
table_name = table[:table]
|
24
|
+
table_comment = table[:table_comment]
|
25
|
+
case format
|
26
|
+
when :md
|
27
|
+
"# #{table_name} #{table_comment}\n\n" +
|
28
|
+
Terminal::Table.new { |t|
|
29
|
+
t.headings = ['PK', 'Name', 'SQL Type', 'Limit', 'Precision', 'Scale', 'Default', 'Nullable', 'Comment']
|
30
|
+
t.rows = table[:columns].map { |column|
|
31
|
+
pk = if [::ActiveRecord::Base.connection.primary_key(table_name)].flatten.include?(column)
|
32
|
+
'Y'
|
33
|
+
else
|
34
|
+
''
|
35
|
+
end
|
36
|
+
[pk, "`#{column.name}`", column.sql_type, column.sql_type_metadata.limit || '', column.sql_type_metadata.precision || '',
|
37
|
+
column.sql_type_metadata.scale || '', column.default || '', column.null, column.comment || '']
|
38
|
+
}
|
39
|
+
t.style = {
|
40
|
+
border_top: false,
|
41
|
+
border_bottom: false,
|
42
|
+
border_i: '|'
|
43
|
+
}
|
44
|
+
}.to_s.lines.map { |l| ' ' + l }.join
|
45
|
+
when :org
|
46
|
+
"* #{table_name} #{table_comment}\n\n" +
|
47
|
+
Terminal::Table.new { |t|
|
48
|
+
t.headings = ['PK', 'Name', 'SQL Type', 'Limit', 'Precision', 'Scale', 'Default', 'Nullable', 'Comment']
|
49
|
+
t.rows = table[:columns].map { |column|
|
50
|
+
pk = if [::ActiveRecord::Base.connection.primary_key(table_name)].flatten.include?(column)
|
51
|
+
'Y'
|
52
|
+
else
|
53
|
+
''
|
54
|
+
end
|
55
|
+
[pk, "=#{column.name}=", column.sql_type, column.sql_type_metadata.limit || '', column.sql_type_metadata.precision || '',
|
56
|
+
column.sql_type_metadata.scale || '', column.default || '', column.null, column.comment || '']
|
57
|
+
}
|
58
|
+
t.style = {
|
59
|
+
border_top: false,
|
60
|
+
border_bottom: false,
|
61
|
+
}
|
62
|
+
}.to_s.lines.map { |l| ' ' + l.gsub(/^\+|\+$/, '|') }.join
|
63
|
+
when :sql
|
64
|
+
"-- Table: #{table_name}\n\n" + ActiveRecord::Base.connection.exec_query("show create table `#{table_name}`").rows.last.last + ';'
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
outputs.each { |out| puts out; puts }
|
69
|
+
end
|
70
|
+
|
71
|
+
def generate_csv(filename, **options, &block)
|
72
|
+
opts = {
|
73
|
+
col_sep: "\t",
|
74
|
+
row_sep: "\r\n"
|
75
|
+
}
|
76
|
+
opts.merge!(options.except(:encoding))
|
77
|
+
encoding = options[:encoding] || 'UTF-16LE'
|
78
|
+
File.open(File.expand_path(filename), "w:#{encoding}") do |file|
|
79
|
+
file.write(CSV_BOM)
|
80
|
+
file.write CSV.generate(**opts, &block)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def parse_csv(filename, **options)
|
85
|
+
encoding = options[:encoding] || 'UTF-16'
|
86
|
+
opts = {
|
87
|
+
headers: false,
|
88
|
+
col_sep: "\t",
|
89
|
+
row_sep: "\r\n"
|
90
|
+
}
|
91
|
+
opts.merge!(options.except(:encoding))
|
92
|
+
CSV.parse(IO.read(File.expand_path(filename), encoding: encoding, binmode: true).encode('UTF-8'), **opts).to_a
|
93
|
+
end
|
94
|
+
|
95
|
+
def generate_excel(filename)
|
96
|
+
Axlsx::Package.new do |package|
|
97
|
+
yield(package.workbook)
|
98
|
+
package.serialize(File.expand_path(filename))
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def parse_excel(filename)
|
103
|
+
xlsx = Roo::Excelx.new(File.expand_path(filename))
|
104
|
+
xlsx.sheets.each_with_object({}) do |sheet_name, result|
|
105
|
+
begin
|
106
|
+
result[sheet_name] = xlsx.sheet(sheet_name).to_a
|
107
|
+
rescue
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|