arql 0.3.31 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -3,84 +3,129 @@ require 'terminal-table'
3
3
  module Arql::Commands
4
4
  module Models
5
5
  class << self
6
- def models
7
- t = []
8
- t << ['Table Name', 'Model Class', 'Abbr', 'Comment']
9
- t << nil
10
- Arql::Definition.models.each do |definition|
11
- t << [definition[:table], definition[:model].name, definition[:abbr] || '', definition[:comment] || '']
12
- end
13
- t
14
- end
15
-
16
- def models_table(table_regexp=nil, column_regexp=nil)
17
- if column_regexp.nil?
18
- Terminal::Table.new do |t|
19
- models.each_with_index { |row, idx| t << (row || :separator) if row.nil? ||
20
- table_regexp.nil? ||
21
- idx.zero? ||
22
- row.any? { |e| e =~ table_regexp }
23
- }
6
+ def filter_tables(env_name, definition, format, table_regexp=nil)
7
+ result = ''
8
+ result << '-- ' if format == 'sql'
9
+ result << "Environment: #{env_name}\n"
10
+ result << "------------------------------\n\n"
11
+ if format == 'sql'
12
+ definition.models.each do |model|
13
+ if table_regexp? || ( model[:table] =~ table_regexp || model[:comment] =~ table_regexp )
14
+ result << "-- Table: #{table_name}\n\n"
15
+ result << definition.connection.exec_query("show create table `#{table_name}`").rows.last.last + ';'
16
+ end
24
17
  end
25
18
  else
26
- connection = ::ActiveRecord::Base.connection
27
- table = Terminal::Table.new do |t|
28
- t << ['PK', 'Table', 'Model', 'Name', 'SQL Type', 'Ruby Type', 'Limit', 'Precision', 'Scale', 'Default', 'Nullable', 'Comment']
19
+ Terminal::Table.new do |t|
20
+ t.style = table_style_for_format(format)
21
+ t << ['Table Name', 'Model Class', 'Abbr', 'Comment']
29
22
  t << :separator
30
- Arql::Definition.models.each do |definition|
31
- model_class = definition[:model]
32
- matched_columns = model_class.columns.select { |column| column.name =~ column_regexp || column.comment =~ column_regexp }
33
- next if matched_columns.empty?
34
- matched_columns.each do |column|
35
- pk = if [connection.primary_key(definition[:table])].flatten.include?(column.name)
36
- 'Y'
37
- else
38
- ''
39
- end
40
- t << [pk, definition[:table], model_class.name, column.name, column.sql_type,
41
- column.sql_type_metadata.type, column.sql_type_metadata.limit || '',
42
- column.sql_type_metadata.precision || '', column.sql_type_metadata.scale || '', column.default || '',
43
- column.null, "#{definition[:comment]} - #{column.comment}"]
23
+ definition.models.each do |model|
24
+ if table_regexp.nil? || ( model[:table] =~ table_regexp || model[:comment] =~ table_regexp )
25
+ t << [model[:table], model[:model].name, model[:abbr] || '', model[:comment] || '']
44
26
  end
45
27
  end
28
+ end.try { |e|
29
+ case format
30
+ when 'md'
31
+ result << e.to_s.lines.map { |l| ' ' + l }.join
32
+ when 'org'
33
+ result << e.to_s.lines.map { |l| ' ' + l.gsub(/^\+|\+$/, '|') }.join
34
+ else
35
+ result << e.to_s
36
+ end
37
+ }
38
+ end
39
+ result
40
+ end
41
+
42
+ def filter_columns(env_name, definition, format, column_regexp=nil)
43
+ result = ''
44
+ result << '-- ' if format == 'sql'
45
+ result << "Environment: #{env_name}\n"
46
+ result << "------------------------------\n\n"
47
+ Terminal::Table.new do |t|
48
+ t.style = table_style_for_format(format)
49
+ t << ['Table', 'Model', 'Name', 'SQL Type', 'Ruby Type', 'Limit', 'Precision', 'Scale', 'Default', 'Nullable', 'Comment']
50
+ t << :separator
51
+ definition.models.each do |model_def|
52
+ model_class = model_def[:model]
53
+ matched_columns = model_class.columns.select { |column| column.name =~ column_regexp || column.comment =~ column_regexp }
54
+ next if matched_columns.empty?
55
+ matched_columns.each do |column|
56
+ t << [model_def[:table], model_class.name, column.name, column.sql_type,
57
+ column.sql_type_metadata.type, column.sql_type_metadata.limit || '',
58
+ column.sql_type_metadata.precision || '', column.sql_type_metadata.scale || '', column.default || '',
59
+ column.null, "#{model_def[:comment]} - #{column.comment}"]
60
+ end
61
+ end
62
+ end.try { |e|
63
+ case format
64
+ when 'md'
65
+ result << e.to_s.lines.map { |l| ' ' + l }.join
66
+ when 'org'
67
+ result << e.to_s.lines.map { |l| ' ' + l.gsub(/^\+|\+$/, '|') }.join
68
+ else
69
+ result << e.to_s
46
70
  end
47
- puts table
71
+ }
72
+ result
73
+ end
74
+
75
+ def table_style_for_format(format)
76
+ case format
77
+ when 'md'
78
+ {
79
+ border_top: false,
80
+ border_bottom: false,
81
+ border_i: '|'
82
+ }
83
+ when 'org'
84
+ {
85
+ border_top: false,
86
+ border_bottom: false,
87
+ }
88
+ else
89
+ {}
48
90
  end
49
91
  end
50
92
  end
51
93
  end
52
94
 
53
- Pry.commands.block_command 'm' do |arg|
54
- puts
55
- if arg.start_with?('c=') or arg.start_with?('column=')
56
- column_regexp = arg.sub(/^c(olumn)?=/, '')
57
- Models::models_table(nil, column_regexp.try { |e| e.start_with?('/') ? eval(e) : Regexp.new(e) })
58
- else
59
- puts Models::models_table(arg.try { |e| e.start_with?('/') ? eval(e) : Regexp.new(e) }, nil)
95
+ Pry.commands.create_command 'm' do
96
+ description 'List models or columns (specified by `-c`): m [-e env_name_regexp] -c [column_regexp] [table_regexp]'
97
+
98
+ def options(opt)
99
+ opt.on '-e', '--env', 'Environment name regexp', argument: true, as: String, required: false, default: nil
100
+ opt.on '-f', '--format', 'Table format, available: terminal(default), md, org, sql', argument: true, as: String, required: false, default: 'terminal'
101
+ opt.on '-c', '--column', 'Column name regexp', argument: true, as: String, required: false, default: nil
60
102
  end
61
- end
62
103
 
63
- Pry.commands.alias_command 'l', 'm'
64
- end
104
+ def process
65
105
 
66
- module Kernel
67
- def models
68
- Arql::Commands::Models::models
69
- end
106
+ if opts[:format] == 'sql' && opts[:column]
107
+ output.puts 'SQL format is not supported for column listing'
108
+ return
109
+ end
70
110
 
71
- def tables
72
- models
73
- end
111
+ env_names = opts[:env].try {|e| [e]}.presence || Arql::App.environments
112
+ env_names = env_names.map { |e| e.start_with?('/') ? eval(e) : Regexp.new(e) }
74
113
 
75
- def model_classes
76
- ::ArqlModel.subclasses
77
- end
114
+ Arql::App.instance.definitions.each do |env_name, definition|
115
+ next unless env_names.any? { |e| env_name =~ e }
78
116
 
79
- def table_names
80
- models[2..-1].map(&:first)
81
- end
117
+ output.puts
118
+ if opts[:column]
119
+ column_regexp = opts[:column]
120
+ output.puts Models::filter_columns(env_name, definition, opts[:format], column_regexp.try { |e| e.start_with?('/') ? eval(e) : Regexp.new(e) })
121
+ else
122
+ table_regexp = args&.first
123
+ output.puts Models::filter_tables(env_name, definition, opts[:format], table_regexp.try { |e| e.start_with?('/') ? eval(e) : Regexp.new(e) })
124
+ end
125
+ end
126
+ end
82
127
 
83
- def model_names
84
- models[2..-1].map(&:second)
85
128
  end
129
+
130
+ Pry.commands.alias_command 'l', 'm'
86
131
  end
@@ -2,13 +2,17 @@ module Arql::Commands
2
2
  module Reconnect
3
3
  class << self
4
4
  def reconnect
5
- Arql::SSHProxy.reconnect if Arql::App.config[:ssh].present?
6
- ActiveRecord::Base.connection.reconnect! unless ActiveRecord::Base.connection.active?
5
+ Arql::App.instance.definitions.each do |_, definition|
6
+ definition.ssh_proxy.reconnect if definition.options[:ssh].present?
7
+ definition.connection.reconnect! unless definition.connection.active?
8
+ end
7
9
  end
8
10
 
9
11
  def reconnect!
10
- Arql::SSHProxy.reconnect! if Arql::App.config[:ssh].present?
11
- ActiveRecord::Base.connection.reconnect!
12
+ Arql::App.instance.definitions.each do |_, definition|
13
+ definition.ssh_proxy.reconnect if definition.options[:ssh].present?
14
+ definition.connection.reconnect!
15
+ end
12
16
  end
13
17
  end
14
18
 
@@ -2,7 +2,9 @@ module Arql::Commands
2
2
  module Redefine
3
3
  class << self
4
4
  def redefine
5
- Arql::Definition.redefine
5
+ Arql::App.instance.definitions.each do |_, definition|
6
+ definition.redefine
7
+ end
6
8
  end
7
9
  end
8
10
 
@@ -9,16 +9,18 @@ module Arql::Commands
9
9
 
10
10
  def enter
11
11
  ActiveRecord::ConnectionAdapters::AbstractAdapter.set_callback(:checkout, :after, &@sandbox_callback)
12
- ActiveRecord::Base.connection.begin_transaction(joinable: false)
12
+ Arql::App.instance.definitions.each do |_, definition|
13
+ definition.connection.begin_transaction(joinable: false)
14
+ end
13
15
  @enabled = true
14
16
  end
15
17
 
16
18
  def quit
17
19
  ActiveRecord::ConnectionAdapters::AbstractAdapter.skip_callback(:checkout, :after, &@sandbox_callback)
20
+ Arql::App.instance.definitions.each do |_, definition|
21
+ definition.connection.rollback_transaction
22
+ end
18
23
  @enabled = false
19
-
20
- puts "begin_transaction callbacks removed."
21
- puts "You still have open %d transactions open, don't forget commit or rollback them." % ActiveRecord::Base.connection.open_transactions if ActiveRecord::Base.connection.open_transactions > 0
22
24
  end
23
25
  end
24
26
 
data/lib/arql/commands.rb CHANGED
@@ -1,11 +1,9 @@
1
1
  require 'arql/commands/info'
2
2
  require 'arql/commands/models'
3
- require 'arql/commands/table'
4
3
  require 'arql/commands/reconnect'
5
4
  require 'arql/commands/redefine'
6
5
  require 'arql/commands/show_sql'
7
6
  require 'arql/commands/sandbox'
8
- require 'arql/commands/vd'
9
7
 
10
8
  module Arql::Commands
11
9
  end
@@ -159,7 +159,14 @@ module Arql
159
159
  #
160
160
  # See also TableDefinition#column for details on how to create columns.
161
161
  def create_table(table_name, **options, &blk)
162
- ActiveRecord::Base.connection.create_table(table_name, **options, &blk)
162
+ env_name = options[:env]
163
+ unless env_name
164
+ raise ArgumentError, ':env option is required' if Arql::App.instance.environments.size > 1
165
+
166
+ env_name = Arql::App.instance.environments.first
167
+ end
168
+ options = options.except(:env)
169
+ Arql::App.instance.definitions[env_name].connection.create_table(table_name, **options, &blk)
163
170
  end
164
171
 
165
172
  # Creates a new join table with the name created using the lexical order of the first two
@@ -201,7 +208,14 @@ module Arql
201
208
  # ) ENGINE=InnoDB DEFAULT CHARSET=utf8
202
209
  #
203
210
  def create_join_table(table_1, table_2, column_options: {}, **options)
204
- ActiveRecord::Base.connection.create_join_table(table_1, table_2, column_options, **options)
211
+ env_name = options[:env]
212
+ unless env_name
213
+ raise ArgumentError, ':env option is required' if Arql::App.instance.environments.size > 1
214
+
215
+ env_name = Arql::App.instance.environments.first
216
+ end
217
+ options = options.except(:env)
218
+ Arql::App.instance.definitions[env_name].connection.create_join_table(table_1, table_2, column_options, **options)
205
219
  end
206
220
 
207
221
  # Drops a table from the database.
@@ -217,7 +231,14 @@ module Arql
217
231
  # it can be helpful to provide these in a migration's +change+ method so it can be reverted.
218
232
  # In that case, +options+ and the block will be used by #create_table.
219
233
  def drop_table(table_name, **options)
220
- ActiveRecord::Base.connection.drop_table(table_name, **options)
234
+ env_name = options[:env]
235
+ unless env_name
236
+ raise ArgumentError, ':env option is required' if Arql::App.instance.environments.size > 1
237
+
238
+ env_name = Arql::App.instance.environments.first
239
+ end
240
+ options = options.except(:env)
241
+ Arql::App.instance.definitions[env_name].connection.drop_table(table_name, **options)
221
242
  end
222
243
 
223
244
  # Drops the join table specified by the given arguments.
@@ -227,7 +248,14 @@ module Arql
227
248
  # to provide one in a migration's +change+ method so it can be reverted.
228
249
  # In that case, the block will be used by #create_join_table.
229
250
  def drop_join_table(table_1, table_2, **options)
230
- ActiveRecord::Base.connection.drop_join_table(table_1, table_2, **options)
251
+ env_name = options[:env]
252
+ unless env_name
253
+ raise ArgumentError, ':env option is required' if Arql::App.instance.environments.size > 1
254
+
255
+ env_name = Arql::App.instance.environments.first
256
+ end
257
+ options = options.except(:env)
258
+ Arql::App.instance.definitions[env_name].connection.drop_join_table(table_1, table_2, **options)
231
259
  end
232
260
 
233
261
  # Renames a table.
@@ -235,9 +263,15 @@ module Arql
235
263
  # rename_table('octopuses', 'octopi')
236
264
  #
237
265
  def rename_table(table_name, new_name)
238
- ActiveRecord::Base.connection.rename_table(table_name, new_name)
239
- end
266
+ env_name = options[:env]
267
+ unless env_name
268
+ raise ArgumentError, ':env option is required' if Arql::App.instance.environments.size > 1
240
269
 
270
+ env_name = Arql::App.instance.environments.first
271
+ end
272
+ options = options.except(:env)
273
+ Arql::App.instance.definitions[env_name].connection.rename_table(table_name, new_name)
274
+ end
241
275
  end
242
276
  end
243
277
  end
@@ -0,0 +1,168 @@
1
+ module Arql
2
+ module Extension
3
+ extend ActiveSupport::Concern
4
+
5
+ def v(compact: false)
6
+ t = []
7
+ t << ['Attribute Name', 'Attribute Value', 'SQL Type', 'Comment']
8
+ t << nil
9
+ self.class.connection.columns(self.class.table_name).each do |column|
10
+ value = read_attribute(column.name)
11
+ if compact && value.blank?
12
+ next
13
+ end
14
+ t << [column.name, value, column.sql_type, column.comment || '']
15
+ end
16
+ t
17
+ end
18
+
19
+ def t(compact: false, format: :terminal)
20
+ puts Terminal::Table.new { |t|
21
+ t.style = self.class.table_style_for_format(format)
22
+ v(compact: compact).each { |row| t << (row || :separator) }
23
+ }.try { |e|
24
+ case format
25
+ when :md
26
+ e.to_s.lines.map { |l| ' ' + l }.join
27
+ when :org
28
+ e.to_s.lines.map { |l| ' ' + l.gsub(/^\+|\+$/, '|') }.join
29
+ else
30
+ e.to_s
31
+ end
32
+ }
33
+ end
34
+
35
+ def vd(compact: false)
36
+ VD.new do |vd|
37
+ vd << ['Attribute Name', 'Attribute Value', 'SQL Type', 'Comment']
38
+ self.class.connection.columns(self.class.table_name).each do |column|
39
+ value = read_attribute(column.name)
40
+ next if compact && value.blank?
41
+
42
+ vd << [column.name, read_attribute(column.name), column.sql_type, column.comment || '']
43
+ end
44
+ end
45
+ end
46
+
47
+ def to_insert_sql
48
+ self.class.to_insert_sql([self])
49
+ end
50
+
51
+ def to_upsert_sql
52
+ self.class.to_upsert_sql([self])
53
+ end
54
+
55
+ def write_csv(filename, *fields, **options)
56
+ [self].write_csv(filename, *fields, **options)
57
+ end
58
+
59
+ def write_excel(filename, *fields, **options)
60
+ [self].write_excel(filename, *fields, **options)
61
+ end
62
+
63
+ def dump(filename, batch_size=500)
64
+ [self].dump(filename, batch_size)
65
+ end
66
+
67
+ included do
68
+ end
69
+
70
+ class_methods do
71
+
72
+ def v
73
+ t = [['PK', 'Name', 'SQL Type', 'Ruby Type', 'Limit', 'Precision', 'Scale', 'Default', 'Nullable', 'Comment']]
74
+ t << nil
75
+ columns.each do |column|
76
+ pk = if [connection.primary_key(table_name)].flatten.include?(column.name)
77
+ 'Y'
78
+ else
79
+ ''
80
+ end
81
+ t << [pk, column.name, column.sql_type,
82
+ column.sql_type_metadata.type, column.sql_type_metadata.limit || '',
83
+ column.sql_type_metadata.precision || '', column.sql_type_metadata.scale || '', column.default || '',
84
+ column.null, column.comment || '']
85
+ end
86
+ t
87
+ end
88
+
89
+ def t(format: :terminal)
90
+ heading_prefix = case format
91
+ when :md
92
+ '# '
93
+ when :org
94
+ '* '
95
+ when :sql
96
+ '-- '
97
+ end
98
+ puts "\n#{heading_prefix}Table: #{table_name}\n\n"
99
+ if format == :sql
100
+ puts to_create_sql + ';'
101
+ else
102
+ puts(Terminal::Table.new { |t|
103
+ t.style = self.table_style_for_format(format)
104
+ v.each { |row| t << (row || :separator) }
105
+ }.try { |e|
106
+ case format
107
+ when :md
108
+ e.to_s.lines.map { |l| ' ' + l }.join
109
+ when :org
110
+ e.to_s.lines.map { |l| ' ' + l.gsub(/^\+|\+$/, '|') }.join
111
+ else
112
+ e.to_s
113
+ end
114
+ })
115
+ end
116
+ end
117
+
118
+ def vd
119
+ VD.new do |vd|
120
+ v.each do |row|
121
+ vd << row if row
122
+ end
123
+ end
124
+ nil
125
+ end
126
+
127
+ def to_insert_sql(records, batch_size=1)
128
+ to_sql(records, :skip, batch_size)
129
+ end
130
+
131
+ def to_upsert_sql(records, batch_size=1)
132
+ to_sql(records, :update, batch_size)
133
+ end
134
+
135
+ def to_sql(records, on_duplicate, batch_size)
136
+ records.in_groups_of(batch_size, false).map do |group|
137
+ ActiveRecord::InsertAll.new(self, group.map(&:attributes), on_duplicate: on_duplicate).send(:to_sql) + ';'
138
+ end.join("\n")
139
+ end
140
+
141
+ def to_create_sql
142
+ superclass.definition.connection.exec_query("show create table `#{table_name}`").rows.last.last
143
+ end
144
+
145
+ def dump(filename, no_create_table=false)
146
+ Arql::Mysqldump.new(superclass.definition.options).dump_table(filename, table_name, no_create_table)
147
+ end
148
+
149
+ def table_style_for_format(format)
150
+ case format.to_s
151
+ when 'md'
152
+ {
153
+ border_top: false,
154
+ border_bottom: false,
155
+ border_i: '|'
156
+ }
157
+ when 'org'
158
+ {
159
+ border_top: false,
160
+ border_bottom: false,
161
+ }
162
+ else
163
+ {}
164
+ end
165
+ end
166
+ end
167
+ end
168
+ end