arql 0.3.31 → 0.4.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.
@@ -2,369 +2,221 @@ require 'arql/concerns'
2
2
  require 'arql/vd'
3
3
 
4
4
  module Arql
5
- module Extension
6
- extend ActiveSupport::Concern
7
5
 
8
- def t(**options)
9
- puts Terminal::Table.new { |t|
10
- v(**options).each { |row| t << (row || :separator) }
11
- }
6
+ class BaseModel < ::ActiveRecord::Base
7
+ self.abstract_class = true
8
+
9
+ define_singleton_method(:indexes) do
10
+ connection.indexes(table_name).map do |idx|
11
+ {
12
+ Table: idx.table,
13
+ Name: idx.name,
14
+ Columns: idx.columns.join(', '),
15
+ Unique: idx.unique,
16
+ Comment: idx.comment
17
+ }
18
+ end.t
12
19
  end
20
+ end
13
21
 
14
- def vd
15
- VD.new do |vd|
16
- vd << ['Attribute Name', 'Attribute Value', 'SQL Type', 'Comment']
17
- self.class.connection.columns(self.class.table_name).each do |column|
18
- vd << [column.name, read_attribute(column.name), column.sql_type, column.comment || '']
19
- end
20
- end
21
- end
22
+ class Definition
23
+ attr :connection, :ssh_proxy, :options, :models, :namespace_module, :namespace
22
24
 
23
- def v(**options)
24
- t = []
25
- t << ['Attribute Name', 'Attribute Value', 'SQL Type', 'Comment']
26
- t << nil
27
- compact_mode = options[:compact] || false
28
- self.class.connection.columns(self.class.table_name).each do |column|
29
- value = read_attribute(column.name)
30
- if compact_mode && value.blank?
31
- next
32
- end
33
- t << [column.name, value, column.sql_type, column.comment || '']
25
+ def redefine
26
+ @models.each do |model|
27
+ @namespace_module.send :remove_const, model[:model].class_name.to_sym if model[:model]
28
+ @namespace_module.send :remove_const, model[:abbr].sub(/^#{@namespace}::/, '').to_sym if model[:abbr]
34
29
  end
35
- t
36
- end
30
+ @models = []
31
+ @connection.tables.each do |table_name|
32
+ model = define_model_from_table(table_name, @primary_keys[table_name])
33
+ next unless model
37
34
 
38
- def to_insert_sql
39
- self.class.to_insert_sql([self])
35
+ model[:comment] = @comments[table_name]
36
+ @models << model
37
+ end
38
+ App.instance&.load_initializer!
40
39
  end
41
40
 
42
- def to_upsert_sql
43
- self.class.to_upsert_sql([self])
44
- end
41
+ def initialize(options)
42
+ @models = []
43
+ @options = options
44
+ @classify_method = @options[:singularized_table_names] ? :camelize : :classify
45
+ @ssh_proxy = start_ssh_proxy if options[:ssh].present?
46
+ create_connection
45
47
 
46
- def write_csv(filename, *fields, **options)
47
- [self].write_csv(filename, *fields, **options)
48
- end
48
+ tables = @connection.tables
49
+ if @connection.adapter_name == 'Mysql2'
50
+ require 'arql/ext/active_record/connection_adapters/abstract_mysql_adapter'
51
+ @comments = @connection.table_comment_of_tables(tables)
52
+ @primary_keys = @connection.primary_keys_of_tables(tables)
53
+ else
54
+ @comments = tables.map { |t| [t, @connection.table_comment(t)] }.to_h
55
+ @primary_keys = tables.map { |t| [t, @connection.primary_keys(t)] }.to_h
56
+ end
49
57
 
50
- def write_excel(filename, *fields, **options)
51
- [self].write_excel(filename, *fields, **options)
52
- end
58
+ tables.each do |table_name|
59
+ model = define_model_from_table(table_name, @primary_keys[table_name])
60
+ next unless model
53
61
 
54
- def dump(filename, batch_size=500)
55
- [self].dump(filename, batch_size)
62
+ model[:comment] = @comments[table_name]
63
+ @models << model
64
+ end
56
65
  end
57
66
 
58
- included do
67
+ def model_names_mapping
68
+ @model_names_mapping ||= @options[:model_names] || {}
59
69
  end
60
70
 
61
- class_methods do
62
- def t
63
- table_name = Commands::Table::get_table_name(name)
64
- puts "\nTable: #{table_name}"
65
- puts Commands::Table::table_info_table(table_name)
66
- end
67
-
68
- def vd
69
- table_name = Commands::Table::get_table_name(name)
70
- Commands::VD::table_info_vd(table_name)
71
- nil
72
- end
73
-
74
- def v
75
- table_name = Commands::Table::get_table_name(name)
76
- Commands::Table::table_info(table_name)
77
- end
78
- def to_insert_sql(records, batch_size=1)
79
- to_sql(records, :skip, batch_size)
80
- end
81
-
82
- def to_upsert_sql(records, batch_size=1)
83
- to_sql(records, :update, batch_size)
84
- end
71
+ def define_model_from_table(table_name, primary_keys)
72
+ model_name = make_model_name(table_name)
73
+ return unless model_name
85
74
 
86
- def to_sql(records, on_duplicate, batch_size)
87
- records.in_groups_of(batch_size, false).map do |group|
88
- ActiveRecord::InsertAll.new(self, group.map(&:attributes), on_duplicate: on_duplicate).send(:to_sql) + ';'
89
- end.join("\n")
90
- end
75
+ model_class = make_model_class(table_name, primary_keys)
76
+ @namespace_module.const_set(model_name, model_class)
77
+ abbr_name = make_model_abbr_name(model_name, table_name)
78
+ @namespace_module.const_set(abbr_name, model_class)
91
79
 
92
- def to_create_sql
93
- ActiveRecord::Base.connection.exec_query("show create table #{table_name}").rows.last.last
94
- end
80
+ # if Arql::App.instance.environments&.size == 1
81
+ # Object.const_set(model_name, model_class)
82
+ # Object.const_set(abbr_name, model_class)
83
+ # end
95
84
 
96
- def dump(filename, no_create_table=false)
97
- Arql::Mysqldump.new.dump_table(filename, table_name, no_create_table)
98
- end
85
+ { model: model_class, abbr: "#@namespace::#{abbr_name}", table: table_name }
99
86
  end
100
- end
101
-
102
- class Definition
103
- class << self
104
- def models
105
- @@models ||= []
106
- end
107
87
 
108
- def redefine
109
- options = @@options
110
- @@models.each do |model|
111
- Object.send :remove_const, model[:model].name.to_sym if model[:model]
112
- Object.send :remove_const, model[:abbr].to_sym if model[:abbr]
88
+ def make_model_abbr_name(model_name, table_name)
89
+ mapping = model_names_mapping[table_name]
90
+ return mapping[1] if mapping.present? && mapping.is_a?(Array) && mapping.size > 1
91
+
92
+ bare_abbr = model_name.gsub(/[a-z]*/, '')
93
+ model_abbr_name = bare_abbr
94
+ 1000.times do |idx|
95
+ abbr = idx.zero? ? bare_abbr : "#{bare_abbr}#{idx + 1}"
96
+ unless @namespace_module.const_defined?(abbr)
97
+ model_abbr_name = abbr
98
+ break
113
99
  end
114
- @@models = []
115
- ActiveRecord::Base.connection.tap do |conn|
116
- tables = conn.tables
117
- comments = conn.table_comment_of_tables(tables)
118
- primary_keys = conn.primary_keys_of_tables(tables)
119
- tables.each do |table_name|
120
- table_comment = comments[table_name]
121
- primary_keys[table_name].tap do |pkey|
122
- table_name_prefixes = options[:table_name_prefixes] || []
123
- normalized_table_name = table_name_prefixes.each_with_object(table_name.dup) do |prefix, name|
124
- name.sub!(/^#{prefix}/, '')
125
- end
126
- normalized_table_name.send(@@classify_method).tap do |const_name|
127
- const_name = 'Modul' if const_name == 'Module'
128
- const_name = 'Clazz' if const_name == 'Class'
129
- Class.new(::ArqlModel) do
130
- include Arql::Extension
131
- if pkey.is_a?(Array) && pkey.size > 1
132
- self.primary_keys = pkey
133
- else
134
- self.primary_key = pkey&.first
135
- end
136
- self.table_name = table_name
137
- self.inheritance_column = nil
138
- ActiveRecord.default_timezone = :local
139
- if options[:created_at].present?
140
- define_singleton_method :timestamp_attributes_for_create do
141
- options[:created_at]
142
- end
143
- end
144
-
145
- if options[:updated_at].present?
146
- define_singleton_method :timestamp_attributes_for_update do
147
- options[:updated_at]
148
- end
149
- end
150
- end.tap do |clazz|
151
- Object.const_set(const_name, clazz).tap do |const|
152
- const_name.gsub(/[a-z]*/, '').tap do |bare_abbr|
153
- abbr_const = nil
154
- 9.times do |idx|
155
- abbr = idx.zero? ? bare_abbr : "#{bare_abbr}#{idx+1}"
156
- unless Object.const_defined?(abbr)
157
- Object.const_set abbr, const
158
- abbr_const = abbr
159
- break
160
- end
161
- end
162
-
163
- @@models << {
164
- model: const,
165
- abbr: abbr_const,
166
- table: table_name,
167
- comment: table_comment
168
- }
169
- end
170
- end
171
- end
172
- end
173
- end
174
- end
175
- end
176
-
177
- App.instance&.load_initializer!
178
100
  end
101
+ model_abbr_name
179
102
  end
180
103
 
181
- def initialize(options)
182
- @@options = options
183
- @@models = []
184
- @@classify_method = if @@options[:singularized_table_names]
185
- :camelize
186
- else
187
- :classify
188
- end
189
- ActiveRecord::Base.connection.tap do |conn|
190
- Object.const_set('ArqlModel', Class.new(ActiveRecord::Base) do
191
- include ::Arql::Concerns::TableDataDefinition
192
- self.abstract_class = true
193
-
194
- define_singleton_method(:indexes) do
195
- conn.indexes(table_name).map do |idx|
196
- {
197
- Table: idx.table,
198
- Name: idx.name,
199
- Columns: idx.columns.join(', '),
200
- Unique: idx.unique,
201
- Comment: idx.comment
202
- }
203
- end.t
204
- end
205
- end)
206
-
207
- tables = conn.tables
208
- if conn.adapter_name == 'Mysql2'
209
- require 'arql/ext/active_record/connection_adapters/abstract_mysql_adapter'
210
- comments = conn.table_comment_of_tables(tables)
211
- primary_keys = conn.primary_keys_of_tables(tables)
104
+ def make_model_name(table_name)
105
+ mapping = model_names_mapping[table_name]
106
+ if mapping.present?
107
+ return mapping if mapping.is_a?(String)
108
+ return mapping.first if mapping.is_a?(Array) && mapping.size >= 1
109
+ end
110
+ table_name_prefixes = @options[:table_name_prefixes] || []
111
+ model_name = table_name_prefixes.each_with_object(table_name.dup) do |prefix, name|
112
+ name.sub!(/^#{prefix}/, '')
113
+ end.send(@classify_method)
114
+ model_name.gsub!(/^Module|Class|BaseModel$/, '$&0')
115
+ if model_name !~ /^[A-Z][A-Za-z0-9_]*$/
116
+ warn "Could not make model name from table name: #{table_name}"
117
+ return
118
+ end
119
+ model_name
120
+ end
121
+
122
+ def make_model_class(table_name, primary_keys)
123
+ options = @options
124
+ Class.new(@base_model) do
125
+ include Arql::Extension
126
+ if primary_keys.is_a?(Array) && primary_keys.size > 1
127
+ self.primary_keys = primary_keys
212
128
  else
213
- comments = tables.map { |t| [t, conn.table_comment(t)] }.to_h
214
- primary_keys = tables.map { |t| [t, conn.primary_keys(t)] }.to_h
129
+ self.primary_key = primary_keys&.first
130
+ end
131
+ self.table_name = table_name
132
+ self.inheritance_column = nil
133
+ ActiveRecord.default_timezone = :local
134
+ if options[:created_at].present?
135
+ define_singleton_method :timestamp_attributes_for_create do
136
+ options[:created_at]
137
+ end
215
138
  end
216
139
 
217
- tables.each do |table_name|
218
- table_comment = comments[table_name]
219
- primary_keys[table_name].tap do |pkey|
220
- table_name_prefixes = options[:table_name_prefixes] || []
221
- normalized_table_name = table_name_prefixes.each_with_object(table_name.dup) do |prefix, name|
222
- name.sub!(/^#{prefix}/, '')
223
- end
224
- normalized_table_name.send(@@classify_method).tap do |const_name|
225
- const_name = 'Modul' if const_name == 'Module'
226
- const_name = 'Clazz' if const_name == 'Class'
227
- if const_name !~ /^[A-Z][A-Za-z0-9_]*$/
228
- puts "Invalid class name: #{const_name}, skipped."
229
- next
230
- end
231
- Class.new(::ArqlModel) do
232
- include Arql::Extension
233
- if pkey.is_a?(Array) && pkey.size > 1
234
- self.primary_keys = pkey
235
- else
236
- self.primary_key = pkey&.first
237
- end
238
- self.table_name = table_name
239
- self.inheritance_column = nil
240
- ActiveRecord.default_timezone = :local
241
- if options[:created_at].present?
242
- define_singleton_method :timestamp_attributes_for_create do
243
- options[:created_at]
244
- end
245
- end
246
-
247
- if options[:updated_at].present?
248
- define_singleton_method :timestamp_attributes_for_update do
249
- options[:updated_at]
250
- end
251
- end
252
- end.tap do |clazz|
253
- Object.const_set(const_name, clazz).tap do |const|
254
- const_name.gsub(/[a-z]*/, '').tap do |bare_abbr|
255
- abbr_const = nil
256
- 9.times do |idx|
257
- abbr = idx.zero? ? bare_abbr : "#{bare_abbr}#{idx+1}"
258
- unless Object.const_defined?(abbr)
259
- Object.const_set abbr, const
260
- abbr_const = abbr
261
- break
262
- end
263
- end
264
-
265
- @@models << {
266
- model: const,
267
- abbr: abbr_const,
268
- table: table_name,
269
- comment: table_comment
270
- }
271
- end
272
- end
273
- end
274
- end
140
+ if options[:updated_at].present?
141
+ define_singleton_method :timestamp_attributes_for_update do
142
+ options[:updated_at]
275
143
  end
276
144
  end
277
145
  end
278
146
  end
279
147
 
280
- ::ActiveRecord::Relation.class_eval do
281
- def t(*attrs, **options)
282
- records.t(*attrs, **options)
283
- end
284
-
285
- def vd(*attrs, **options)
286
- records.vd(*attrs, **options)
287
- end
288
-
289
- def v
290
- records.v
291
- end
292
-
293
- def a
294
- to_a
295
- end
296
-
297
- def write_csv(filename, *fields, **options)
298
- records.write_csv(filename, *fields, **options)
299
- end
300
-
301
- def write_excel(filename, *fields, **options)
302
- records.write_excel(filename, *fields, **options)
303
- end
304
-
305
- def dump(filename, batch_size=500)
306
- records.dump(filename, batch_size)
307
- end
148
+ def start_ssh_proxy
149
+ ssh_config = @options[:ssh]
150
+ Arql::SSHProxy.new(
151
+ ssh_config.slice(:host, :user, :port, :password)
152
+ .merge(forward_host: @options[:host],
153
+ forward_port: @options[:port],
154
+ local_port: ssh_config[:local_port]))
308
155
  end
309
156
 
310
- ::ActiveRecord::Result.class_eval do
311
- def t(*attrs, **options)
312
- to_a.t(*attrs, **options)
313
- end
314
-
315
- def vd(*attrs, **options)
316
- to_a.vd(*attrs, **options)
317
- end
318
-
319
- def v
320
- to_a.v
321
- end
157
+ def get_connection_options
158
+ connect_conf = @options.slice(:adapter, :host, :username, :password,
159
+ :database, :encoding, :pool, :port, :socket)
160
+ connect_conf.merge(@ssh_proxy.database_host_port) if @ssh_proxy
161
+ connect_conf
162
+ end
322
163
 
323
- def a
324
- to_a
164
+ def create_connection
165
+ @namespace = @options[:namespace]
166
+ connection_opts = get_connection_options
167
+ print "Establishing DB connection to #{connection_opts[:host]}:#{connection_opts[:port]}"
168
+ @namespace_module = create_namespace_module(@namespace)
169
+ @base_model = @namespace_module.const_set('BaseModel', Class.new(BaseModel))
170
+ @base_model.class_eval do
171
+ include ::Arql::Concerns::TableDataDefinition
172
+ self.abstract_class = true
173
+ establish_connection(connection_opts)
174
+ class << self
175
+ attr_accessor :definition
176
+ end
325
177
  end
326
-
327
- def write_csv(filename, *fields, **options)
328
- to_a.write_csv(filename, *fields, **options)
178
+ print "\u001b[2K"
179
+ puts "\rDB connection to #{connection_opts[:host]}:#{connection_opts[:port]} established\n"
180
+ @connection = @base_model.connection
181
+ @base_model.define_singleton_method(:dump) do |filename, no_create_db = false|
182
+ Arql::Mysqldump.new(options).dump_database(filename, no_create_db)
329
183
  end
184
+ @base_model.definition = self
185
+ end
330
186
 
331
- def write_excel(filename, *fields, **options)
332
- to_a.write_excel(filename, *fields, **options)
333
- end
187
+ def create_namespace_module(namespace)
188
+ definition = self
334
189
 
335
- def dump(filename, batch_size=500)
336
- to_a.dump(filename, batch_size)
337
- end
338
- end
190
+ Object.const_set(namespace, Module.new {
339
191
 
340
- ::Ransack::Search.class_eval do
341
- def t(*attrs, **options)
342
- result.t(*attrs, **options)
343
- end
192
+ define_singleton_method(:config) do
193
+ definition.options
194
+ end
344
195
 
345
- def vd(*attrs, **options)
346
- result.vd(*attrs, **options)
347
- end
196
+ define_singleton_method(:models) do
197
+ definition.models.map { |m| m[:model] }
198
+ end
348
199
 
349
- def v
350
- result.v
351
- end
200
+ define_singleton_method(:tables) do
201
+ definition.models.map { |m| m[:table] }
202
+ end
352
203
 
353
- def a
354
- result.a
355
- end
204
+ define_singleton_method(:model_names) do
205
+ models.map(&:name)
206
+ end
356
207
 
357
- def write_csv(filename, *fields, **options)
358
- result.write_csv(filename, *fields, **options)
359
- end
208
+ define_singleton_method(:q) do |sql|
209
+ definition.connection.exec_query(sql)
210
+ end
360
211
 
361
- def write_excel(filename, *fields, **options)
362
- result.write_excel(filename, *fields, **options)
363
- end
212
+ define_singleton_method(:create_table) do |table_name, **options, &blk|
213
+ definition.connection.create_table(table_name, **options, &blk)
214
+ end
364
215
 
365
- def dump(filename, batch_size=500)
366
- result.dump(filename, batch_size)
367
- end
216
+ define_singleton_method(:dump) do |filename, no_create_db = false|
217
+ Arql::Mysqldump.new(definition.options).dump_database(filename, no_create_db)
218
+ end
219
+ })
368
220
  end
369
221
  end
370
222
  end
@@ -0,0 +1,29 @@
1
+ ActiveRecord::Relation.class_eval do
2
+ def t(*attrs, **options)
3
+ records.t(*attrs, **options)
4
+ end
5
+
6
+ def vd(*attrs, **options)
7
+ records.vd(*attrs, **options)
8
+ end
9
+
10
+ def v
11
+ records.v
12
+ end
13
+
14
+ def a
15
+ to_a
16
+ end
17
+
18
+ def write_csv(filename, *fields, **options)
19
+ records.write_csv(filename, *fields, **options)
20
+ end
21
+
22
+ def write_excel(filename, *fields, **options)
23
+ records.write_excel(filename, *fields, **options)
24
+ end
25
+
26
+ def dump(filename, batch_size=500)
27
+ records.dump(filename, batch_size)
28
+ end
29
+ end
@@ -0,0 +1,29 @@
1
+ ActiveRecord::Result.class_eval do
2
+ def t(*attrs, **options)
3
+ to_a.t(*attrs, **options)
4
+ end
5
+
6
+ def vd(*attrs, **options)
7
+ to_a.vd(*attrs, **options)
8
+ end
9
+
10
+ def v
11
+ to_a.v
12
+ end
13
+
14
+ def a
15
+ to_a
16
+ end
17
+
18
+ def write_csv(filename, *fields, **options)
19
+ to_a.write_csv(filename, *fields, **options)
20
+ end
21
+
22
+ def write_excel(filename, *fields, **options)
23
+ to_a.write_excel(filename, *fields, **options)
24
+ end
25
+
26
+ def dump(filename, batch_size=500)
27
+ to_a.dump(filename, batch_size)
28
+ end
29
+ end
@@ -17,6 +17,7 @@ class Array
17
17
  end
18
18
 
19
19
  def t(*attrs, **options)
20
+ format = options[:format] || :terminal
20
21
  if (attrs.present? || options.present? && options[:except]) && present? && first.is_a?(ActiveRecord::Base)
21
22
  column_names = first.attribute_names.map(&:to_sym)
22
23
  attrs = attrs.flat_map { |e| e.is_a?(Regexp) ? column_names.grep(e) : e }.uniq
@@ -32,16 +33,36 @@ class Array
32
33
  # attrs = attrs.select { |e| any { |r| r.attributes[e.to_s]&.present? } }
33
34
  # end
34
35
  puts Terminal::Table.new { |t|
36
+ t.style = table_style_for_format(format)
35
37
  t << attrs
36
38
  t << :separator
37
39
  each do |e|
38
40
  t << e.attributes.values_at(*attrs.map(&:to_s))
39
41
  end
42
+ }.try { |e|
43
+ case format
44
+ when 'md'
45
+ e.to_s.lines.map { |l| ' ' + l }.join
46
+ when 'org'
47
+ e.to_s.lines.map { |l| ' ' + l.gsub(/^\+|\+$/, '|') }.join
48
+ else
49
+ e.to_s
50
+ end
40
51
  }
41
52
  else
42
53
  table = Terminal::Table.new { |t|
54
+ t.style = table_style_for_format(format)
43
55
  v(**options).each { |row| t << (row || :separator)}
44
- }.to_s
56
+ }.try { |e|
57
+ case format
58
+ when 'md'
59
+ e.to_s.lines.map { |l| ' ' + l }.join
60
+ when 'org'
61
+ e.to_s.lines.map { |l| ' ' + l.gsub(/^\+|\+$/, '|') }.join
62
+ else
63
+ e.to_s
64
+ end
65
+ }
45
66
 
46
67
  terminal_width = `tput cols`.to_i
47
68
  if table.lines.first.size > terminal_width
@@ -220,4 +241,22 @@ class Array
220
241
  plot.render(STDOUT)
221
242
  end
222
243
  end
244
+
245
+ def table_style_for_format(format)
246
+ case format.to_s
247
+ when 'md'
248
+ {
249
+ border_top: false,
250
+ border_bottom: false,
251
+ border_i: '|'
252
+ }
253
+ when 'org'
254
+ {
255
+ border_top: false,
256
+ border_bottom: false,
257
+ }
258
+ else
259
+ {}
260
+ end
261
+ end
223
262
  end