breathing 0.0.2 → 0.0.7
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 +4 -4
- data/.circleci/config.yml +10 -3
- data/README.md +36 -9
- data/breathing.gemspec +5 -2
- data/lib/breathing.rb +23 -6
- data/lib/breathing/change_log.rb +13 -0
- data/lib/breathing/cli.rb +27 -1
- data/lib/breathing/excel.rb +24 -20
- data/lib/breathing/installer.rb +9 -0
- data/lib/breathing/terminal_table.rb +30 -0
- data/lib/breathing/trigger.rb +41 -45
- data/spec/app.rb +1 -1
- data/spec/breathing/excel_spec.rb +3 -1
- data/spec/breathing/terminal_table_spec.rb +22 -0
- data/spec/breathing_spec.rb +36 -31
- data/spec/database.yml +10 -3
- data/spec/spec_helper.rb +2 -0
- metadata +51 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1e0db4001a0d83c74be3f2adcf49f754ea0c39837ecb17c13e2fbf016dc73ad8
|
4
|
+
data.tar.gz: 31cc6bd7ad4391573b64c909ea713dea43260a5b1e379405396e24f8bf29da18
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 802fb8542d7b310d40c8ca066c603416a5976f6729d4d4e6bcd4942b383a517ee75500c99503093b2d70226592d7fbc83aab14bd92930d84183255e7a1319b92
|
7
|
+
data.tar.gz: 783595b5a3faa2d5934ba2cfe1b5baa95c37c6d1131ffadcbaf54cb3ba120642c5c93d9932081916710a5e48ef2e002ba2c7e1e7fff046d8a198e3bb43dbb30c
|
data/.circleci/config.yml
CHANGED
@@ -6,8 +6,7 @@ executors:
|
|
6
6
|
docker:
|
7
7
|
- image: circleci/ruby:2.6
|
8
8
|
environment:
|
9
|
-
DB_USER:
|
10
|
-
DB_PASS: 'root'
|
9
|
+
DB_USER: root
|
11
10
|
DB_HOST: '127.0.0.1'
|
12
11
|
- image: circleci/mysql:8-ram
|
13
12
|
environment:
|
@@ -15,6 +14,10 @@ executors:
|
|
15
14
|
MYSQL_ROOT_PASSWORD: root
|
16
15
|
MYSQL_DATABASE: breathing_test
|
17
16
|
command: [--default-authentication-plugin=mysql_native_password]
|
17
|
+
- image: circleci/postgres:10.6-alpine-ram
|
18
|
+
environment:
|
19
|
+
POSTGRES_USER: root
|
20
|
+
POSTGRES_DB: breathing_test
|
18
21
|
|
19
22
|
commands:
|
20
23
|
setup_bundle:
|
@@ -35,6 +38,9 @@ commands:
|
|
35
38
|
- run:
|
36
39
|
name: Wait for DB
|
37
40
|
command: dockerize -wait tcp://127.0.0.1:3306 -timeout 1m
|
41
|
+
- run:
|
42
|
+
name: Wait for DB
|
43
|
+
command: dockerize -wait tcp://127.0.0.1:5432 -timeout 1m
|
38
44
|
|
39
45
|
jobs:
|
40
46
|
test:
|
@@ -43,7 +49,8 @@ jobs:
|
|
43
49
|
- checkout
|
44
50
|
- setup_bundle
|
45
51
|
- wait_for_db
|
46
|
-
- run: bundle exec rspec ./spec
|
52
|
+
- run: DB=mysql DB_PASS=root bundle exec rspec ./spec
|
53
|
+
- run: DB=pg bundle exec rspec ./spec
|
47
54
|
|
48
55
|
workflows:
|
49
56
|
version: 2
|
data/README.md
CHANGED
@@ -5,15 +5,8 @@ Logging mechanism using database triggers to store the old and new row states in
|
|
5
5
|
|
6
6
|
## Install
|
7
7
|
|
8
|
-
Put this line in your Gemfile:
|
9
|
-
|
10
8
|
```
|
11
|
-
gem
|
12
|
-
```
|
13
|
-
|
14
|
-
Then bundle:
|
15
|
-
```
|
16
|
-
% bundle
|
9
|
+
gem install breathing
|
17
10
|
```
|
18
11
|
|
19
12
|
## Usage
|
@@ -24,6 +17,8 @@ Just run the following command.
|
|
24
17
|
|
25
18
|
```
|
26
19
|
% DATABASE_URL="mysql2://user:pass@host:port/database" breathing install
|
20
|
+
or
|
21
|
+
% DATABASE_URL="postgres://user:pass@host:port/database" breathing install
|
27
22
|
```
|
28
23
|
|
29
24
|
- Create table `change_logs`
|
@@ -46,12 +41,44 @@ Cleanup command.
|
|
46
41
|
- change_logs_update_{table_name}
|
47
42
|
- change_logs_delete_{table_name}
|
48
43
|
|
49
|
-
###
|
44
|
+
### Export
|
50
45
|
|
51
46
|
```
|
52
47
|
% DATABASE_URL="mysql2://user:pass@host:port/database" breathing export
|
53
48
|
```
|
54
49
|
|
50
|
+
- Output file `breathing.xlsx`
|
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
|
+
|
76
|
+
## Compatibility
|
77
|
+
|
78
|
+
- Ruby 2.3.0+
|
79
|
+
- MySQL 5.7.0+
|
80
|
+
- PostgreSQL 8.0+
|
81
|
+
|
55
82
|
## Copyright
|
56
83
|
|
57
84
|
Copyright (c) 2020 Akira Kusumoto. See MIT-LICENSE file for further details.
|
data/breathing.gemspec
CHANGED
@@ -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
|
+
s.version = '0.0.7'
|
6
6
|
s.platform = Gem::Platform::RUBY
|
7
7
|
s.authors = ['Akira Kusumoto']
|
8
8
|
s.email = ['akirakusumo10@gmail.com']
|
@@ -21,8 +21,11 @@ Gem::Specification.new do |s|
|
|
21
21
|
s.add_runtime_dependency 'thor'
|
22
22
|
|
23
23
|
s.add_dependency 'activerecord', ['>= 5.0.0']
|
24
|
+
s.add_dependency 'hairtrigger'
|
25
|
+
s.add_dependency 'mysql2'
|
26
|
+
s.add_dependency 'pg'
|
27
|
+
s.add_dependency 'terminal-table'
|
24
28
|
s.add_dependency 'rubyXL', ['>= 3.4.0']
|
25
|
-
s.add_development_dependency 'mysql2'
|
26
29
|
s.add_development_dependency 'pry-byebug'
|
27
30
|
s.add_development_dependency 'rspec', '~> 3.9'
|
28
31
|
end
|
data/lib/breathing.rb
CHANGED
@@ -1,35 +1,52 @@
|
|
1
1
|
require 'active_record'
|
2
|
+
require 'hairtrigger'
|
2
3
|
require 'breathing/installer'
|
3
4
|
require 'breathing/trigger'
|
4
5
|
require 'breathing/change_log'
|
5
6
|
require 'breathing/excel'
|
7
|
+
require 'breathing/terminal_table'
|
6
8
|
|
7
9
|
module Breathing
|
8
10
|
VERSION = Gem.loaded_specs['breathing'].version.to_s
|
9
11
|
|
10
12
|
class << self
|
11
13
|
def install
|
12
|
-
establish_connection
|
14
|
+
ActiveRecord::Base.establish_connection
|
13
15
|
Breathing::Installer.new.install
|
14
16
|
end
|
15
17
|
|
16
18
|
def uninstall
|
17
|
-
establish_connection
|
19
|
+
ActiveRecord::Base.establish_connection
|
18
20
|
Breathing::Installer.new.uninstall
|
19
21
|
end
|
20
22
|
|
21
23
|
def clear
|
22
|
-
establish_connection
|
24
|
+
ActiveRecord::Base.establish_connection
|
23
25
|
Breathing::ChangeLog.delete_all
|
24
26
|
end
|
25
27
|
|
26
28
|
def export
|
27
|
-
establish_connection
|
29
|
+
ActiveRecord::Base.establish_connection
|
28
30
|
Breathing::Excel.new.create
|
29
31
|
end
|
30
32
|
|
31
|
-
def
|
32
|
-
ActiveRecord::Base.establish_connection
|
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
|
33
50
|
end
|
34
51
|
end
|
35
52
|
end
|
data/lib/breathing/change_log.rb
CHANGED
@@ -12,5 +12,18 @@ 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
|
15
28
|
end
|
16
29
|
end
|
data/lib/breathing/cli.rb
CHANGED
@@ -1,31 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'thor'
|
2
3
|
require 'breathing'
|
3
4
|
|
4
5
|
module Breathing
|
5
6
|
class Cli < Thor
|
6
|
-
default_command :
|
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
|
data/lib/breathing/excel.rb
CHANGED
@@ -18,12 +18,16 @@ module Breathing
|
|
18
18
|
sheet = @workbook.add_worksheet(table_name)
|
19
19
|
end
|
20
20
|
|
21
|
-
rows
|
21
|
+
rows = Breathing::ChangeLog.where(table_name: table_name).where('id >= ?', id).order(:id)
|
22
|
+
column_widths = []
|
22
23
|
|
23
24
|
if first_row = rows.first
|
24
|
-
add_header_row(sheet, first_row)
|
25
|
+
add_header_row(sheet, first_row, column_widths)
|
25
26
|
end
|
26
|
-
add_body_rows(sheet, rows)
|
27
|
+
add_body_rows(sheet, rows, column_widths)
|
28
|
+
|
29
|
+
column_widths.each.with_index { |size, i| sheet.change_column_width(i, size + 2) }
|
30
|
+
|
27
31
|
add_style(sheet)
|
28
32
|
end
|
29
33
|
|
@@ -32,28 +36,28 @@ module Breathing
|
|
32
36
|
|
33
37
|
private
|
34
38
|
|
35
|
-
def add_header_row(sheet, row)
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
39
|
+
def add_header_row(sheet, row, column_widths)
|
40
|
+
header_color = 'ddedf3' # blue
|
41
|
+
row.data_attributes.keys.each.with_index do |header_column, column_index|
|
42
|
+
cell = sheet.add_cell(0, column_index, header_column)
|
43
|
+
cell.change_font_bold(true)
|
44
|
+
cell.change_fill(header_color)
|
45
|
+
|
46
|
+
column_widths << header_column.size
|
41
47
|
end
|
42
48
|
end
|
43
49
|
|
44
|
-
def add_body_rows(sheet, rows)
|
45
|
-
rows.each.with_index(1) do |row,
|
46
|
-
|
47
|
-
|
48
|
-
sheet.add_cell(i, 2, row.transaction_id)
|
49
|
-
row.data_column_names.each.with_index(3) do |column_name, j|
|
50
|
-
data = row.action == 'DELETE' ? row.before_data : row.after_data
|
51
|
-
cell_object = sheet.add_cell(i, j, data[column_name])
|
50
|
+
def add_body_rows(sheet, rows, column_widths)
|
51
|
+
rows.each.with_index(1) do |row, row_number|
|
52
|
+
row.data_attributes.each.with_index do |(column_name, value), column_index|
|
53
|
+
cell = sheet.add_cell(row_number, column_index, value)
|
52
54
|
if row.action == 'UPDATE' && column_name != 'updated_at' && row.changed_attribute_columns.include?(column_name)
|
53
|
-
|
55
|
+
cell.change_fill('ffff00') # color: yellow
|
54
56
|
elsif row.action == 'DELETE'
|
55
|
-
|
57
|
+
cell.change_fill('d9d9d9') # color: grey
|
56
58
|
end
|
59
|
+
|
60
|
+
column_widths[column_index] = value.to_s.size if column_widths[column_index] < value.to_s.size
|
57
61
|
end
|
58
62
|
end
|
59
63
|
end
|
@@ -65,7 +69,7 @@ module Breathing
|
|
65
69
|
cell.change_border(direction, 'thin')
|
66
70
|
end
|
67
71
|
|
68
|
-
cell.change_border(:bottom, '
|
72
|
+
cell.change_border(:bottom, 'double') if i.zero?
|
69
73
|
end
|
70
74
|
end
|
71
75
|
sheet.change_row_horizontal_alignment(0, 'center')
|
data/lib/breathing/installer.rb
CHANGED
@@ -4,8 +4,12 @@ require 'breathing/trigger'
|
|
4
4
|
require 'breathing/change_log'
|
5
5
|
|
6
6
|
module Breathing
|
7
|
+
class UnsupportedError < StandardError; end
|
8
|
+
|
7
9
|
class Installer
|
8
10
|
def install
|
11
|
+
raise Breathing::UnsupportedError, "Version MySQL 5.6 is not supported." unless database_version_valid?
|
12
|
+
|
9
13
|
create_log_table unless log_table_exists?
|
10
14
|
|
11
15
|
models.each do |model|
|
@@ -24,6 +28,11 @@ module Breathing
|
|
24
28
|
|
25
29
|
private
|
26
30
|
|
31
|
+
def database_version_valid?
|
32
|
+
connection = ActiveRecord::Base.connection
|
33
|
+
connection.adapter_name == "PostgreSQL" || (connection.adapter_name == 'Mysql2' && connection.raw_connection.info[:version].to_f >= 5.7)
|
34
|
+
end
|
35
|
+
|
27
36
|
def log_table_name
|
28
37
|
Breathing::ChangeLog.table_name
|
29
38
|
end
|
@@ -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
|
data/lib/breathing/trigger.rb
CHANGED
@@ -12,69 +12,61 @@ module Breathing
|
|
12
12
|
|
13
13
|
def create
|
14
14
|
trigger_name = "#{log_table_name}_insert_#{model.table_name}"
|
15
|
+
|
15
16
|
unless exists?(trigger_name)
|
16
17
|
puts "CREATE TRIGGER #{trigger_name}"
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
FOR EACH ROW
|
22
|
-
BEGIN
|
23
|
-
INSERT INTO #{log_table_name} (`action`, `table_name`, `transaction_id`, `before_data`, `after_data`, `created_at`)
|
18
|
+
|
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)
|
24
22
|
VALUES ('INSERT', '#{model.table_name}', NEW.id,
|
25
|
-
|
26
|
-
|
23
|
+
'{}',
|
24
|
+
#{row_to_json(model.columns, 'NEW')},
|
27
25
|
CURRENT_TIMESTAMP);
|
28
|
-
|
29
|
-
|
26
|
+
SQL
|
27
|
+
end
|
30
28
|
end
|
31
29
|
|
32
30
|
trigger_name = "#{log_table_name}_update_#{model.table_name}"
|
33
31
|
unless exists?(trigger_name)
|
34
32
|
puts "CREATE TRIGGER #{trigger_name}"
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
FOR EACH ROW
|
40
|
-
BEGIN
|
41
|
-
IF (OLD.updated_at != NEW.updated_at) THEN
|
42
|
-
INSERT INTO #{log_table_name} (`action`, `table_name`, `transaction_id`, `before_data`, `after_data`, `created_at`)
|
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)
|
43
37
|
VALUES ('UPDATE', '#{model.table_name}', NEW.id,
|
44
|
-
|
45
|
-
|
38
|
+
#{row_to_json(model.columns, 'OLD')},
|
39
|
+
#{row_to_json(model.columns, 'NEW')},
|
46
40
|
CURRENT_TIMESTAMP);
|
47
|
-
|
48
|
-
|
49
|
-
SQL
|
41
|
+
SQL
|
42
|
+
end
|
50
43
|
end
|
51
44
|
|
52
45
|
trigger_name = "#{log_table_name}_delete_#{model.table_name}"
|
53
46
|
unless exists?(trigger_name)
|
54
47
|
puts "CREATE TRIGGER #{trigger_name}"
|
55
|
-
ActiveRecord::Base.connection.
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
INSERT INTO #{log_table_name} (`action`, `table_name`, `transaction_id`, `before_data`, `after_data`, `created_at`)
|
62
|
-
VALUES ('DELETE', '#{model.table_name}', OLD.id,
|
63
|
-
JSON_OBJECT(#{json_object_values(model.columns, 'OLD')}),
|
64
|
-
JSON_OBJECT(),
|
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
|
+
'{}',
|
65
54
|
CURRENT_TIMESTAMP);
|
66
|
-
|
67
|
-
|
55
|
+
SQL
|
56
|
+
end
|
68
57
|
end
|
69
58
|
end
|
70
59
|
|
71
60
|
def drop
|
72
61
|
%w[insert update delete].each do |action|
|
73
62
|
trigger_name = "#{log_table_name}_#{action}_#{model.table_name}"
|
74
|
-
next unless exists?(trigger_name)
|
75
63
|
|
76
64
|
begin
|
77
|
-
sql = "DROP TRIGGER #{trigger_name}"
|
65
|
+
sql = "DROP TRIGGER IF EXISTS #{trigger_name}"
|
66
|
+
if ActiveRecord::Base.connection.adapter_name == "PostgreSQL"
|
67
|
+
sql << " ON #{model.table_name} CASCADE;"
|
68
|
+
sql << " DROP FUNCTION IF EXISTS #{trigger_name} CASCADE;"
|
69
|
+
end
|
78
70
|
puts sql
|
79
71
|
ActiveRecord::Base.connection.execute(sql)
|
80
72
|
rescue StandardError => e
|
@@ -86,15 +78,19 @@ module Breathing
|
|
86
78
|
private
|
87
79
|
|
88
80
|
def exists?(trigger_name)
|
89
|
-
|
90
|
-
@trigger_names.include?(trigger_name)
|
81
|
+
ActiveRecord::Base.connection.triggers.keys.include?(trigger_name)
|
91
82
|
end
|
92
83
|
|
93
|
-
def
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
84
|
+
def row_to_json(columns, state)
|
85
|
+
if ActiveRecord::Base.connection.adapter_name == "PostgreSQL"
|
86
|
+
"row_to_json(#{state}.*)"
|
87
|
+
else
|
88
|
+
json_object_values = columns.each.with_object([]) do |column, array|
|
89
|
+
array << "'#{column.name}'"
|
90
|
+
array << "#{state}.#{column.name}"
|
91
|
+
end
|
92
|
+
"JSON_OBJECT(#{json_object_values.join(',')})"
|
93
|
+
end
|
98
94
|
end
|
99
95
|
end
|
100
96
|
end
|
data/spec/app.rb
CHANGED
@@ -2,7 +2,7 @@ require 'active_record'
|
|
2
2
|
require 'breathing'
|
3
3
|
|
4
4
|
ActiveRecord::Base.establish_connection(
|
5
|
-
YAML.load(ERB.new(File.read('spec/database.yml')).result)['
|
5
|
+
YAML.load(ERB.new(File.read('spec/database.yml')).result)["test_#{ENV['DB'] || 'mysql'}"]
|
6
6
|
)
|
7
7
|
|
8
8
|
ActiveRecord::Schema.define version: 0 do
|
@@ -3,7 +3,9 @@ require 'spec_helper'
|
|
3
3
|
describe Breathing::Excel do
|
4
4
|
describe '#create' do
|
5
5
|
before { Breathing::Installer.new.install }
|
6
|
-
after
|
6
|
+
after do
|
7
|
+
Breathing::Installer.new.uninstall if ActiveRecord::Base.connection.adapter_name == "Mysql2"
|
8
|
+
end
|
7
9
|
|
8
10
|
it do
|
9
11
|
user = User.create!(name: 'a', age: 20)
|
@@ -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
|
data/spec/breathing_spec.rb
CHANGED
@@ -5,36 +5,41 @@ describe Breathing do
|
|
5
5
|
expect(Breathing::VERSION).not_to be nil
|
6
6
|
end
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
8
|
+
describe 'change_logs' do
|
9
|
+
before { Breathing::Installer.new.install }
|
10
|
+
|
11
|
+
after do
|
12
|
+
Breathing::Installer.new.uninstall if ActiveRecord::Base.connection.adapter_name == "Mysql2"
|
13
|
+
end
|
14
|
+
|
15
|
+
it do
|
16
|
+
expect(Breathing::ChangeLog.count).to eq(0)
|
17
|
+
|
18
|
+
# INSERT
|
19
|
+
user = User.create!(name: 'a', age: 20)
|
20
|
+
expect(Breathing::ChangeLog.count).to eq(1)
|
21
|
+
|
22
|
+
log = Breathing::ChangeLog.where(table_name: user.class.table_name, transaction_id: user.id).last
|
23
|
+
expect(log.before_data).to eq({})
|
24
|
+
expect(log.after_data['name']).to eq('a')
|
25
|
+
expect(log.after_data['age']).to eq(20)
|
26
|
+
|
27
|
+
# UPDATE
|
28
|
+
user.update!(age: 21)
|
29
|
+
expect(Breathing::ChangeLog.count).to eq(2)
|
30
|
+
|
31
|
+
log = Breathing::ChangeLog.where(table_name: user.class.table_name, transaction_id: user.id).last
|
32
|
+
expect(log.before_data['age']).to eq(20)
|
33
|
+
expect(log.after_data['age']).to eq(21)
|
34
|
+
expect(log.before_data['name']).to eq(log.after_data['name'])
|
35
|
+
expect(log.changed_attribute_columns).to eq(%w[age updated_at])
|
36
|
+
|
37
|
+
# DELETE
|
38
|
+
user.destroy!
|
39
|
+
expect(Breathing::ChangeLog.count).to eq(3)
|
40
|
+
log = Breathing::ChangeLog.where(table_name: user.class.table_name, transaction_id: user.id).last
|
41
|
+
expect(log.before_data['name']).to eq('a')
|
42
|
+
expect(log.after_data).to eq({})
|
43
|
+
end
|
39
44
|
end
|
40
45
|
end
|
data/spec/database.yml
CHANGED
@@ -1,11 +1,18 @@
|
|
1
|
-
|
1
|
+
test_mysql:
|
2
2
|
adapter: mysql2
|
3
3
|
encoding: utf8mb4
|
4
4
|
charset: utf8mb4
|
5
5
|
collation: utf8mb4_general_ci
|
6
|
-
pool: 5
|
7
6
|
username: <%= ENV.fetch("DB_USER") { 'root' } %>
|
8
7
|
password: <%= ENV.fetch("DB_PASS") { '' } %>
|
9
8
|
host: <%= ENV.fetch("DB_HOST") { '127.0.0.1' } %>
|
10
9
|
socket: /tmp/mysql.sock
|
11
|
-
database:
|
10
|
+
database: breathing_test
|
11
|
+
|
12
|
+
test_pg:
|
13
|
+
adapter: postgresql
|
14
|
+
encoding: unicode
|
15
|
+
username: <%= ENV.fetch("DB_USER") { 'root' } %>
|
16
|
+
password: <%= ENV.fetch("DB_PASS") { '' } %>
|
17
|
+
host: <%= ENV.fetch("DB_HOST") { '127.0.0.1' } %>
|
18
|
+
database: breathing_test
|
data/spec/spec_helper.rb
CHANGED
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.
|
4
|
+
version: 0.0.7
|
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-
|
11
|
+
date: 2020-12-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: thor
|
@@ -39,19 +39,19 @@ dependencies:
|
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: 5.0.0
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
42
|
+
name: hairtrigger
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - ">="
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version:
|
47
|
+
version: '0'
|
48
48
|
type: :runtime
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version:
|
54
|
+
version: '0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: mysql2
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -59,13 +59,55 @@ dependencies:
|
|
59
59
|
- - ">="
|
60
60
|
- !ruby/object:Gem::Version
|
61
61
|
version: '0'
|
62
|
-
type: :
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: pg
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :runtime
|
63
77
|
prerelease: false
|
64
78
|
version_requirements: !ruby/object:Gem::Requirement
|
65
79
|
requirements:
|
66
80
|
- - ">="
|
67
81
|
- !ruby/object:Gem::Version
|
68
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'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rubyXL
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 3.4.0
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 3.4.0
|
69
111
|
- !ruby/object:Gem::Dependency
|
70
112
|
name: pry-byebug
|
71
113
|
requirement: !ruby/object:Gem::Requirement
|
@@ -115,9 +157,11 @@ files:
|
|
115
157
|
- lib/breathing/cli.rb
|
116
158
|
- lib/breathing/excel.rb
|
117
159
|
- lib/breathing/installer.rb
|
160
|
+
- lib/breathing/terminal_table.rb
|
118
161
|
- lib/breathing/trigger.rb
|
119
162
|
- spec/app.rb
|
120
163
|
- spec/breathing/excel_spec.rb
|
164
|
+
- spec/breathing/terminal_table_spec.rb
|
121
165
|
- spec/breathing_spec.rb
|
122
166
|
- spec/database.yml
|
123
167
|
- spec/spec_helper.rb
|
@@ -147,6 +191,7 @@ summary: Audit logging for database
|
|
147
191
|
test_files:
|
148
192
|
- spec/app.rb
|
149
193
|
- spec/breathing/excel_spec.rb
|
194
|
+
- spec/breathing/terminal_table_spec.rb
|
150
195
|
- spec/breathing_spec.rb
|
151
196
|
- spec/database.yml
|
152
197
|
- spec/spec_helper.rb
|