breathing 0.0.5 → 0.0.10

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3a60d7dd7673c1ef147e8bc39818dab52fb12c8a55b30b5cb914f12d929b7cf5
4
- data.tar.gz: 621d83b711bfa0856fe18787999839787b76cc75bfb1be6b84658b6a0e50ee2c
3
+ metadata.gz: 9424309ec8f7ac77b44503b31bbd1a62fa54881cdaa7f75a507ba90d596d1f6a
4
+ data.tar.gz: d217036b86439348557fe9d0538be415260b970b43fa6fc0f29997c4772292d6
5
5
  SHA512:
6
- metadata.gz: 181c52cfa7084f5513de2a1313f031035f817d347e8b389577c327942e41368d0fd67252dd15dec5dbd51cd595fccb87f897d032d4023c5c3186e98a75ffa024
7
- data.tar.gz: 66cb585fbd41230d352d1c9f4e8a17bf44e5b96564e0c9eb868adcd1bb243795ba7edcf033b450acdb79a637a02b291a80b7887ec8b2aacb00d95efdcea4138b
6
+ metadata.gz: dfd1f75df70cb2380f9ed51000dcfacfdef64ffc185c9965cdc324979f676380c508ee7ed1e4d326bd134732c35aa430f8e10a211edfdf1e2bd30285a909ae84
7
+ data.tar.gz: 931f3a899695f04b044cf83f936db122291b28b85e7eb3924469745cd3b9cb44dd1c5366179c57f5e15720d78ac09dac4b1743062b21e34d6783b07bdb53d6cb
data/README.md CHANGED
@@ -49,6 +49,30 @@ Cleanup command.
49
49
 
50
50
  - Output file `breathing.xlsx`
51
51
 
52
+ ### out
53
+
54
+ ```
55
+ % DATABASE_URL="mysql2://user:pass@host:port/database" breathing out --table users --id 1
56
+ ```
57
+
58
+ ```
59
+ +----------------+------------------------+--------+----+-----+------+----------------------------+----------------------------+
60
+ | users |
61
+ +----------------+------------------------+--------+----+-----+------+----------------------------+----------------------------+
62
+ | change_logs.id | change_logs.created_at | action | id | age | name | created_at | updated_at |
63
+ +----------------+------------------------+--------+----+-----+------+----------------------------+----------------------------+
64
+ | 1 | 2020-12-18 22:43:32 | INSERT | 10 | 20 | a | 2020-12-18 13:43:32.316923 | 2020-12-18 13:43:32.316923 |
65
+ | 2 | 2020-12-18 22:43:32 | UPDATE | 10 | 21 | a | 2020-12-18 13:43:32.316923 | 2020-12-18 13:43:32.319706 |
66
+ | 3 | 2020-12-18 22:43:32 | DELETE | 10 | 21 | a | 2020-12-18 13:43:32.316923 | 2020-12-18 13:43:32.319706 |
67
+ +----------------+------------------------+--------+----+-----+------+----------------------------+----------------------------+
68
+ ```
69
+
70
+ ### tail
71
+
72
+ ```
73
+ % DATABASE_URL="mysql2://user:pass@host:port/database" breathing tail --table users --id 1
74
+ ```
75
+
52
76
  ## Compatibility
53
77
 
54
78
  - Ruby 2.3.0+
@@ -2,7 +2,7 @@ $:.push File.expand_path('lib', __dir__)
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = 'breathing'
5
- s.version = '0.0.5'
5
+ s.version = '0.0.10'
6
6
  s.platform = Gem::Platform::RUBY
7
7
  s.authors = ['Akira Kusumoto']
8
8
  s.email = ['akirakusumo10@gmail.com']
@@ -20,10 +20,11 @@ Gem::Specification.new do |s|
20
20
  s.executables = s.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
21
  s.add_runtime_dependency 'thor'
22
22
 
23
- s.add_dependency 'activerecord', ['>= 5.0.0']
23
+ s.add_dependency 'activerecord', ['>= 6.0.0']
24
24
  s.add_dependency 'hairtrigger'
25
25
  s.add_dependency 'mysql2'
26
26
  s.add_dependency 'pg'
27
+ s.add_dependency 'terminal-table'
27
28
  s.add_dependency 'rubyXL', ['>= 3.4.0']
28
29
  s.add_development_dependency 'pry-byebug'
29
30
  s.add_development_dependency 'rspec', '~> 3.9'
@@ -4,6 +4,7 @@ require 'breathing/installer'
4
4
  require 'breathing/trigger'
5
5
  require 'breathing/change_log'
6
6
  require 'breathing/excel'
7
+ require 'breathing/terminal_table'
7
8
 
8
9
  module Breathing
9
10
  VERSION = Gem.loaded_specs['breathing'].version.to_s
@@ -28,5 +29,24 @@ module Breathing
28
29
  ActiveRecord::Base.establish_connection
29
30
  Breathing::Excel.new.create
30
31
  end
32
+
33
+ def render_terminal_table(table_name:, id: 1)
34
+ ActiveRecord::Base.establish_connection
35
+ puts Breathing::TerminalTable.new(table_name).render(id: id)
36
+ end
37
+
38
+ def tail_f(table_name:, id: 1)
39
+ ActiveRecord::Base.establish_connection
40
+ table = Breathing::TerminalTable.new(table_name)
41
+
42
+ loop do
43
+ text = table.render(id: id)
44
+ if text.present?
45
+ puts text
46
+ id = table.last_id + 1
47
+ end
48
+ sleep 5
49
+ end
50
+ end
31
51
  end
32
52
  end
@@ -12,5 +12,37 @@ module Breathing
12
12
  names = before_data.keys.present? ? before_data.keys : after_data.keys
13
13
  names.reject { |name| name == 'id' }
14
14
  end
15
+
16
+ def data
17
+ action == 'DELETE' ? before_data : after_data
18
+ end
19
+
20
+ def data_attributes
21
+ data_column_names.each.with_object('change_logs.id' => id,
22
+ 'change_logs.created_at' => created_at.to_s(:db),
23
+ 'action' => action,
24
+ 'id' => transaction_id) do |name, hash|
25
+ hash[name] = data[name]
26
+ end
27
+ end
28
+
29
+ def diff
30
+ return nil if action != 'UPDATE'
31
+
32
+ changed_attribute_columns.map do |column_name|
33
+ "#{column_name}: #{before_data[column_name]} -> #{after_data[column_name]}"
34
+ end.join(" \n")
35
+ end
36
+
37
+ def attributes_for_excel
38
+ {
39
+ 'change_logs.id' => id,
40
+ 'created_at' => created_at.to_s(:db),
41
+ 'table_name' => table_name,
42
+ 'action' => action,
43
+ 'id' => transaction_id,
44
+ 'diff' => diff
45
+ }
46
+ end
15
47
  end
16
48
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'thor'
2
3
  require 'breathing'
3
4
 
@@ -6,26 +7,51 @@ module Breathing
6
7
  default_command :export
7
8
 
8
9
  desc 'install', 'Create table change_logs and create triggers'
10
+
9
11
  def install
10
12
  Breathing.install
11
13
  end
12
14
 
13
15
  desc 'uninstall', 'Drop table change_logs and drop triggers'
16
+
14
17
  def uninstall
15
18
  Breathing.uninstall
16
19
  end
17
20
 
18
21
  desc 'clear', 'Delete all records in change_logs table'
22
+
19
23
  def clear
20
24
  Breathing.clear
21
25
  end
22
26
 
23
27
  desc 'export', 'output xlsx'
28
+
24
29
  def export
25
30
  Breathing.export
26
31
  end
27
32
 
33
+ desc 'out', 'output stdout'
34
+ method_option :type, aliases: '-t', default: 'terminal_table', type: :string
35
+ method_option :table, type: :string, required: true
36
+ method_option :id, default: 1, type: :numeric
37
+ def out
38
+ if options[:table] == 'terminal_table'
39
+ Breathing.render_terminal_table(table_name: options[:table], id: options[:id].to_i)
40
+ else
41
+ # TODO
42
+ # Breathing.export(table_name: options[:table], id: options[:id].to_i)
43
+ end
44
+ end
45
+
46
+ desc 'tail', 'tail terminal_table'
47
+ method_option :table, type: :string, required: true
48
+ method_option :id, default: 1, type: :numeric
49
+ def tail
50
+ Breathing.tail_f(table_name: options[:table], id: options[:id].to_i)
51
+ end
52
+
28
53
  desc 'version', 'Show Version'
54
+
29
55
  def version
30
56
  say "Version: #{Breathing::VERSION}"
31
57
  end
@@ -11,98 +11,103 @@ module Breathing
11
11
  def create(id: 1, file_name: 'breathing.xlsx')
12
12
  sheet = @workbook[0]
13
13
  table_names = Breathing::ChangeLog.where('id >= ?', id).group(:table_name).pluck(:table_name)
14
- table_names.each do |table_name|
14
+ table_names.sort.each do |table_name|
15
15
  if sheet.sheet_name == 'Sheet1'
16
16
  sheet.sheet_name = table_name
17
17
  else
18
18
  sheet = @workbook.add_worksheet(table_name)
19
19
  end
20
20
 
21
- rows = Breathing::ChangeLog.where(table_name: table_name).where('id >= ?', id).order(:id)
22
- column_widths = []
21
+ rows = Breathing::ChangeLog.where(table_name: table_name).where('id >= ?', id).order(:id).to_a
23
22
 
24
- if first_row = rows.first
25
- add_header_row(sheet, first_row, column_widths)
26
- end
27
- add_body_rows(sheet, rows, column_widths)
23
+ next if rows.size.zero?
28
24
 
29
- column_widths.each.with_index(0) do |size, i|
30
- sheet.change_column_width(i, size + 2)
31
- end
25
+ add_header_row(sheet, rows.first)
26
+ add_body_rows(sheet, rows)
32
27
  add_style(sheet)
33
28
  end
34
29
 
30
+ if table_names.size.positive?
31
+ add_change_logs_sheet(id)
32
+ @workbook.worksheets.rotate!(-1)
33
+ end
35
34
  @workbook.write(file_name)
36
35
  end
37
36
 
38
37
  private
39
38
 
40
- def add_header_row(sheet, row, column_widths)
41
- sheet.add_cell(0, 0, 'change_logs.id').tap do |cell|
42
- cell.change_font_bold(true)
43
- cell.change_fill('ddedf3') # color: blue
44
- end
45
- sheet.add_cell(0, 1, 'change_logs.created_at').tap do |cell|
46
- cell.change_font_bold(true)
47
- cell.change_fill('ddedf3') # color: blue
48
- end
49
- sheet.add_cell(0, 2, 'action').tap do |cell|
50
- cell.change_font_bold(true)
51
- cell.change_fill('ddedf3') # color: blue
52
- end
53
- sheet.add_cell(0, 3, 'id').tap do |cell|
54
- cell.change_font_bold(true)
55
- cell.change_fill('ddedf3') # color: blue
56
- end
57
-
58
- column_widths << 'change_logs.id'.size
59
- column_widths << 'change_logs.created_at'.size
60
- column_widths << 'action'.size
61
- column_widths << 'id'.size
39
+ def add_header_row(sheet, row)
40
+ header_color = 'ddedf3' # blue
41
+ foreign_keys = ActiveRecord::Base.connection.foreign_keys(sheet.sheet_name)
42
+ row.data_attributes.keys.each.with_index do |header_column, column_index|
43
+ cell = sheet.add_cell(0, column_index, header_column)
44
+ cell.change_fill(header_color)
45
+ if column_index == 0
46
+ add_hyperlink(sheet: sheet, cell: cell, to_sheet: 'change_logs', to_cell: 'A1')
47
+ end
62
48
 
63
- row.data_column_names.each.with_index(3) do |column_name, i|
64
- cell = sheet.add_cell(0, i, column_name)
65
- cell.change_font_bold(true)
66
- cell.change_fill('ddedf3') # color: blue
67
- column_widths << column_name.size
49
+ if key = foreign_keys.detect { |k| k.options[:column] == header_column }
50
+ add_hyperlink(sheet: sheet, cell: cell, to_sheet: key.to_table, to_cell: 'A1')
51
+ end
68
52
  end
69
53
  end
70
54
 
71
- def add_body_rows(sheet, rows, column_widths)
72
- rows.each.with_index(1) do |row, i|
73
- column_widths[0] = row.id.to_s.size if column_widths[0] < row.id.to_s.size
74
- column_widths[2] = row.transaction_id.to_s.size if column_widths[2] < row.transaction_id.to_s.size
75
- sheet.add_cell(i, 0, row.id)
76
- sheet.add_cell(i, 1, row.created_at.to_s(:db))
77
- sheet.add_cell(i, 2, row.action)
78
- sheet.add_cell(i, 3, row.transaction_id)
79
-
80
- data = row.action == 'DELETE' ? row.before_data : row.after_data
81
-
82
- row.data_column_names.each.with_index(3) do |column_name, j|
83
- value = data[column_name].to_s
84
- column_widths[j] = value.size if column_widths[j] < value.size
85
- cell_object = sheet.add_cell(i, j, value)
55
+ def add_body_rows(sheet, rows)
56
+ rows.each.with_index(1) do |row, row_number|
57
+ row.data_attributes.each.with_index do |(column_name, value), column_index|
58
+ cell = sheet.add_cell(row_number, column_index, value)
86
59
  if row.action == 'UPDATE' && column_name != 'updated_at' && row.changed_attribute_columns.include?(column_name)
87
- cell_object.change_fill('ffff00') # color: yellow
60
+ cell.change_fill('ffff00') # color: yellow
88
61
  elsif row.action == 'DELETE'
89
- cell_object.change_fill('d9d9d9') # color: grey
62
+ cell.change_fill('d9d9d9') # color: grey
90
63
  end
91
64
  end
92
65
  end
93
66
  end
94
67
 
95
68
  def add_style(sheet)
96
- sheet.sheet_data.rows.each.with_index do |row, i|
97
- row.cells.each do |cell|
69
+ sheet.sheet_data.rows.each.with_index do |row, row_index|
70
+ row.cells.each.with_index do |cell, column_index|
98
71
  %i[top bottom left right].each do |direction|
99
72
  cell.change_border(direction, 'thin')
100
73
  end
74
+ cell.change_border(:bottom, 'double') if row_index.zero?
101
75
 
102
- cell.change_border(:bottom, 'double') if i.zero?
76
+ cell_width = cell.value.to_s.size + 2
77
+ sheet.change_column_width(column_index, cell_width) if cell_width > sheet.get_column_width(column_index)
103
78
  end
104
79
  end
80
+
105
81
  sheet.change_row_horizontal_alignment(0, 'center')
106
82
  end
83
+
84
+ def add_change_logs_sheet(id)
85
+ sheet = @workbook.add_worksheet(Breathing::ChangeLog.table_name)
86
+
87
+ change_logs = Breathing::ChangeLog.where('id >= ?', id).order(:id)
88
+ change_logs.first.attributes_for_excel.keys.each.with_index do |header_column, column_index|
89
+ cell = sheet.add_cell(0, column_index, header_column)
90
+ cell.change_fill('ddedf3') # blue
91
+ end
92
+
93
+ change_logs.each.with_index(1) do |change_log, row_number|
94
+ change_log.attributes_for_excel.each.with_index do |(column_name, value), column_index|
95
+ cell = sheet.add_cell(row_number, column_index, value)
96
+
97
+ if column_name == 'table_name'
98
+ add_hyperlink(sheet: sheet, cell: cell, to_sheet: value, to_cell: 'A1')
99
+ end
100
+ end
101
+ end
102
+
103
+ add_style(sheet)
104
+ end
105
+
106
+ def add_hyperlink(sheet:, cell:, to_sheet:, to_cell:)
107
+ sheet.hyperlinks ||= RubyXL::Hyperlinks.new
108
+ sheet.hyperlinks << RubyXL::Hyperlink.new(ref: RubyXL::Reference.ind2ref(cell.row, cell.column), location: "#{to_sheet}!#{to_cell}")
109
+ cell.change_font_underline(true)
110
+ cell.change_font_color('1F1FFF') # blue
111
+ end
107
112
  end
108
113
  end
@@ -8,9 +8,9 @@ module Breathing
8
8
 
9
9
  class Installer
10
10
  def install
11
- raise Breathing::UnsupportedError, "Version MySQL 5.6 is not supported." unless database_version_valid?
11
+ raise Breathing::UnsupportedError, "Version MySQL 5.6 is not supported." unless database_supported_version?
12
12
 
13
- create_log_table unless log_table_exists?
13
+ create_log_table
14
14
 
15
15
  models.each do |model|
16
16
  column_names = model.columns.map(&:name)
@@ -21,14 +21,13 @@ module Breathing
21
21
  end
22
22
 
23
23
  def uninstall
24
- drop_log_table if log_table_exists?
25
-
24
+ drop_log_table
26
25
  models.each { |model| Breathing::Trigger.new(model, log_table_name).drop }
27
26
  end
28
27
 
29
28
  private
30
29
 
31
- def database_version_valid?
30
+ def database_supported_version?
32
31
  connection = ActiveRecord::Base.connection
33
32
  connection.adapter_name == "PostgreSQL" || (connection.adapter_name == 'Mysql2' && connection.raw_connection.info[:version].to_f >= 5.7)
34
33
  end
@@ -39,26 +38,22 @@ module Breathing
39
38
 
40
39
  def create_log_table(table_name: log_table_name)
41
40
  ActiveRecord::Schema.define version: 0 do
42
- create_table table_name, force: false do |t|
43
- t.string :action, null: false
44
- t.string :table_name, null: false
45
- t.string :transaction_id, null: false
46
- t.json :before_data, null: false
47
- t.json :after_data, null: false
48
- t.datetime :created_at, null: false, index: true
41
+ create_table table_name, if_not_exists: true do |t|
42
+ t.datetime :created_at, null: false, index: true
43
+ t.string :table_name, null: false
44
+ t.string :action, null: false
45
+ t.string :transaction_id, null: false
46
+ t.json :before_data, null: false
47
+ t.json :after_data, null: false
48
+
49
49
  t.index %w[table_name transaction_id]
50
50
  end
51
51
  end
52
52
  end
53
53
 
54
54
  def drop_log_table
55
- sql = "DROP TABLE #{log_table_name}"
56
- puts sql
57
- ActiveRecord::Base.connection.execute(sql)
58
- end
59
-
60
- def log_table_exists?
61
- ActiveRecord::Base.connection.table_exists?(log_table_name)
55
+ puts "DROP TABLE #{log_table_name}"
56
+ ActiveRecord::Base.connection.drop_table(log_table_name, if_exists: true)
62
57
  end
63
58
 
64
59
  def models
@@ -0,0 +1,30 @@
1
+ require 'breathing'
2
+ require 'terminal-table'
3
+
4
+ module Breathing
5
+ class TerminalTable
6
+ attr_reader :last_id
7
+
8
+ def initialize(table_name)
9
+ @last_id = 1
10
+ @table_name = table_name
11
+ end
12
+
13
+ def render(id: 1)
14
+ rows = Breathing::ChangeLog.where(table_name: @table_name).where("id >= ? ", id).order(:id)
15
+
16
+ return if rows.size.zero?
17
+
18
+ @table = Terminal::Table.new(title: rows.first.table_name,
19
+ headings: rows.first.data_attributes.keys,
20
+ rows: rows.map { |row| row.data_attributes.values })
21
+
22
+ @last_id = rows.last.id
23
+ @table.to_s
24
+ end
25
+
26
+ def rows
27
+ @table.rows
28
+ end
29
+ end
30
+ end
@@ -11,59 +11,25 @@ module Breathing
11
11
  end
12
12
 
13
13
  def create
14
- trigger_name = "#{log_table_name}_insert_#{model.table_name}"
15
-
16
- unless exists?(trigger_name)
17
- puts "CREATE TRIGGER #{trigger_name}"
14
+ exists_trigger_names = ActiveRecord::Base.connection.triggers.keys
18
15
 
19
- ActiveRecord::Base.connection.create_trigger(trigger_name).on(model.table_name).after(:insert) do
20
- <<-SQL
21
- INSERT INTO #{log_table_name} (action, table_name, transaction_id, before_data, after_data, created_at)
22
- VALUES ('INSERT', '#{model.table_name}', NEW.id,
23
- '{}',
24
- #{row_to_json(model.columns, 'NEW')},
25
- CURRENT_TIMESTAMP);
26
- SQL
27
- end
28
- end
16
+ trigger_name = "#{log_table_name}_insert_#{model.table_name}"
17
+ create_insert_trigger(trigger_name, model) if exists_trigger_names.exclude?(trigger_name)
29
18
 
30
19
  trigger_name = "#{log_table_name}_update_#{model.table_name}"
31
- unless exists?(trigger_name)
32
- puts "CREATE TRIGGER #{trigger_name}"
33
-
34
- ActiveRecord::Base.connection.create_trigger(trigger_name).on(model.table_name).before(:update).of(:updated_at) do
35
- <<-SQL
36
- INSERT INTO #{log_table_name} (action, table_name, transaction_id, before_data, after_data, created_at)
37
- VALUES ('UPDATE', '#{model.table_name}', NEW.id,
38
- #{row_to_json(model.columns, 'OLD')},
39
- #{row_to_json(model.columns, 'NEW')},
40
- CURRENT_TIMESTAMP);
41
- SQL
42
- end
43
- end
20
+ create_update_trigger(trigger_name, model) if exists_trigger_names.exclude?(trigger_name)
44
21
 
45
22
  trigger_name = "#{log_table_name}_delete_#{model.table_name}"
46
- unless exists?(trigger_name)
47
- puts "CREATE TRIGGER #{trigger_name}"
48
- ActiveRecord::Base.connection.create_trigger(trigger_name).on(model.table_name).after(:delete) do
49
- <<-SQL
50
- INSERT INTO #{log_table_name} (action, table_name, transaction_id, before_data, after_data, created_at)
51
- VALUES ('DELETE', '#{model.table_name}', OLD.id,
52
- #{row_to_json(model.columns, 'OLD')},
53
- '{}',
54
- CURRENT_TIMESTAMP);
55
- SQL
56
- end
57
- end
23
+ create_delete_trigger(trigger_name, model) if exists_trigger_names.exclude?(trigger_name)
58
24
  end
59
25
 
60
26
  def drop
61
- %w[insert update delete].each do |action|
62
- trigger_name = "#{log_table_name}_#{action}_#{model.table_name}"
27
+ trigger_names = %w[insert update delete].map { |action| "#{log_table_name}_#{action}_#{model.table_name}" }
63
28
 
29
+ trigger_names.each do |trigger_name|
64
30
  begin
65
31
  sql = "DROP TRIGGER IF EXISTS #{trigger_name}"
66
- if ActiveRecord::Base.connection.adapter_name == "PostgreSQL"
32
+ if postgresql?
67
33
  sql << " ON #{model.table_name} CASCADE;"
68
34
  sql << " DROP FUNCTION IF EXISTS #{trigger_name} CASCADE;"
69
35
  end
@@ -77,12 +43,50 @@ module Breathing
77
43
 
78
44
  private
79
45
 
80
- def exists?(trigger_name)
81
- ActiveRecord::Base.connection.triggers.keys.include?(trigger_name)
46
+ def postgresql?
47
+ ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
48
+ end
49
+
50
+ def create_trigger(name)
51
+ puts "CREATE TRIGGER #{name}"
52
+ ActiveRecord::Base.connection.create_trigger(name) # hairtrigger gem
53
+ end
54
+
55
+ def create_insert_trigger(trigger_name, model)
56
+ create_trigger(trigger_name).on(model.table_name).after(:insert) do
57
+ <<-SQL
58
+ INSERT INTO #{log_table_name} (created_at, action, table_name, transaction_id, before_data, after_data)
59
+ VALUES (CURRENT_TIMESTAMP, 'INSERT', '#{model.table_name}', NEW.id,
60
+ '{}',
61
+ #{row_to_json(model.columns, 'NEW')});
62
+ SQL
63
+ end
64
+ end
65
+
66
+ def create_update_trigger(trigger_name, model)
67
+ create_trigger(trigger_name).on(model.table_name).before(:update).of(:updated_at) do
68
+ <<-SQL
69
+ INSERT INTO #{log_table_name} (created_at, action, table_name, transaction_id, before_data, after_data)
70
+ VALUES (CURRENT_TIMESTAMP, 'UPDATE', '#{model.table_name}', NEW.id,
71
+ #{row_to_json(model.columns, 'OLD')},
72
+ #{row_to_json(model.columns, 'NEW')});
73
+ SQL
74
+ end
75
+ end
76
+
77
+ def create_delete_trigger(trigger_name, model)
78
+ create_trigger(trigger_name).on(model.table_name).after(:delete) do
79
+ <<-SQL
80
+ INSERT INTO #{log_table_name} (created_at, action, table_name, transaction_id, before_data, after_data)
81
+ VALUES (CURRENT_TIMESTAMP, 'DELETE', '#{model.table_name}', OLD.id,
82
+ #{row_to_json(model.columns, 'OLD')},
83
+ '{}');
84
+ SQL
85
+ end
82
86
  end
83
87
 
84
88
  def row_to_json(columns, state)
85
- if ActiveRecord::Base.connection.adapter_name == "PostgreSQL"
89
+ if postgresql?
86
90
  "row_to_json(#{state}.*)"
87
91
  else
88
92
  json_object_values = columns.each.with_object([]) do |column, array|
@@ -7,6 +7,7 @@ ActiveRecord::Base.establish_connection(
7
7
 
8
8
  ActiveRecord::Schema.define version: 0 do
9
9
  create_table :users, force: true do |t|
10
+ t.references :department, foreign_key: false
10
11
  t.string :name, null: false
11
12
  t.integer :age, null: false
12
13
  t.timestamps null: false
@@ -16,6 +17,8 @@ ActiveRecord::Schema.define version: 0 do
16
17
  t.string :name, null: false
17
18
  t.timestamps null: false
18
19
  end
20
+
21
+ add_foreign_key :users, :departments
19
22
  end
20
23
 
21
24
  class User < ActiveRecord::Base
@@ -16,20 +16,26 @@ describe Breathing::Excel do
16
16
  Tempfile.open(['tmp', '.xlsx']) do |file|
17
17
  Breathing::Excel.new.create(file_name: file.path)
18
18
  workbook = RubyXL::Parser.parse(file.path)
19
- expect(workbook.sheets[0].name).to eq('users')
20
- user_sheet = workbook.worksheets[0]
19
+ expect(workbook.sheets.map(&:name)).to eq(%w[change_logs users])
20
+ user_sheet = workbook.worksheets.last
21
21
  expect(user_sheet.sheet_data.size).to eq(Breathing::ChangeLog.where(table_name: :users).count + 1)
22
22
  end
23
23
  end
24
24
 
25
25
  it 'multi sheets' do
26
- User.create!(name: 'a', age: 20)
27
- Department.create!(name: 'a')
26
+ department = Department.create!(name: 'a')
27
+
28
+ user = User.create!(name: 'a', age: 20, department_id: department.id)
29
+ user.update!(age: 21)
30
+ user.destroy!
28
31
 
29
32
  Tempfile.open(['tmp', '.xlsx']) do |file|
30
33
  Breathing::Excel.new.create(file_name: file.path)
31
34
  workbook = RubyXL::Parser.parse(file.path)
32
- expect(workbook.sheets.size).to eq(2)
35
+ expect(workbook.sheets.map(&:name)).to eq(%w[change_logs departments users])
36
+ change_logs_sheet = workbook.worksheets.first
37
+
38
+ expect(change_logs_sheet.sheet_data.size).to eq(Breathing::ChangeLog.count + 1)
33
39
  end
34
40
  end
35
41
  end
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+
3
+ describe Breathing::TerminalTable do
4
+ describe '#render' do
5
+ before { Breathing::Installer.new.install }
6
+ after do
7
+ Breathing::Installer.new.uninstall if ActiveRecord::Base.connection.adapter_name == "Mysql2"
8
+ end
9
+
10
+ it do
11
+ user = User.create!(name: 'a', age: 20)
12
+ user.update!(age: 21)
13
+ user.destroy!
14
+ expect(Breathing::ChangeLog.count).to eq(3)
15
+
16
+ table = Breathing::TerminalTable.new(:users)
17
+ puts table.render(id: 1)
18
+
19
+ expect(table.rows.size).to eq(3)
20
+ end
21
+ end
22
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: breathing
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ version: 0.0.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Akira Kusumoto
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-12-18 00:00:00.000000000 Z
11
+ date: 2021-01-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: 5.0.0
33
+ version: 6.0.0
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: 5.0.0
40
+ version: 6.0.0
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: hairtrigger
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -80,6 +80,20 @@ dependencies:
80
80
  - - ">="
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: terminal-table
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
83
97
  - !ruby/object:Gem::Dependency
84
98
  name: rubyXL
85
99
  requirement: !ruby/object:Gem::Requirement
@@ -143,9 +157,11 @@ files:
143
157
  - lib/breathing/cli.rb
144
158
  - lib/breathing/excel.rb
145
159
  - lib/breathing/installer.rb
160
+ - lib/breathing/terminal_table.rb
146
161
  - lib/breathing/trigger.rb
147
162
  - spec/app.rb
148
163
  - spec/breathing/excel_spec.rb
164
+ - spec/breathing/terminal_table_spec.rb
149
165
  - spec/breathing_spec.rb
150
166
  - spec/database.yml
151
167
  - spec/spec_helper.rb
@@ -175,6 +191,7 @@ summary: Audit logging for database
175
191
  test_files:
176
192
  - spec/app.rb
177
193
  - spec/breathing/excel_spec.rb
194
+ - spec/breathing/terminal_table_spec.rb
178
195
  - spec/breathing_spec.rb
179
196
  - spec/database.yml
180
197
  - spec/spec_helper.rb