breathing 0.0.3 → 0.0.8

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 815ec172eb186702177838e5213fb84c471b15dc7a4bdd7c951a97c78d9666c2
4
- data.tar.gz: d05a272b946c324ca9f0031e291840ca58d21069607ffc2e8b0579e9a99ecb76
3
+ metadata.gz: 61e525b923beadee13acfa8b5dc2708b88a3d4e4add63ea910d4ee3fd67d42e4
4
+ data.tar.gz: 4ab65c2f8a026145dc121641922204bcd373f3ef1f379ced10b6575f1229e0a9
5
5
  SHA512:
6
- metadata.gz: f5130a4714437d96c658266626989a86feb1c670d62308168dc64b009a0ea660aa86d8ede410acba4fdd6bb0a33504248f0c6ecca1e9b95ecc74293783ea03c5
7
- data.tar.gz: 1c624939e64701ab5dc42bcbac6ac46cfae7ece8dd967ee1d960c6f1a209b63c42bdaf0d454e50a8bd06d1fe29982967c85098af683da1b0e93c7c3a85a197e0
6
+ metadata.gz: c41a580a29c80ffeae3318a71153a6c6004a6a5f17bd02db91ecf3dbb8687dba7dd36318f5d3abbe4c86912a4103b4592aa9f6f4da446187deee072eaf84bccb
7
+ data.tar.gz: 5ffc9e9e41a1fa92ed44503b2df61dd6306fb2acff8ca390fc024eaa9468a1f65953d1de3c982e7d5a6e1b18f17a950f2c76075156a7af960ff8d84323d5c124
@@ -6,8 +6,7 @@ executors:
6
6
  docker:
7
7
  - image: circleci/ruby:2.6
8
8
  environment:
9
- DB_USER: 'root'
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 'breathing'
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
- ### export
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.
@@ -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.3'
5
+ s.version = '0.0.8'
6
6
  s.platform = Gem::Platform::RUBY
7
7
  s.authors = ['Akira Kusumoto']
8
8
  s.email = ['akirakusumo10@gmail.com']
@@ -20,9 +20,12 @@ 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']
24
- s.add_dependency 'rubyXL', ['>= 3.4.0']
23
+ s.add_dependency 'activerecord', ['>= 6.0.0']
24
+ s.add_dependency 'hairtrigger'
25
25
  s.add_dependency 'mysql2'
26
+ s.add_dependency 'pg'
27
+ s.add_dependency 'terminal-table'
28
+ s.add_dependency 'rubyXL', ['>= 3.4.0']
26
29
  s.add_development_dependency 'pry-byebug'
27
30
  s.add_development_dependency 'rspec', '~> 3.9'
28
31
  end
@@ -1,8 +1,10 @@
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
@@ -27,5 +29,24 @@ module Breathing
27
29
  ActiveRecord::Base.establish_connection
28
30
  Breathing::Excel.new.create
29
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
30
51
  end
31
52
  end
@@ -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
@@ -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 :install
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
@@ -18,12 +18,16 @@ module Breathing
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)
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,27 @@ module Breathing
32
36
 
33
37
  private
34
38
 
35
- def add_header_row(sheet, row)
36
- sheet.add_cell(0, 0, 'change_logs.id').change_font_bold(true)
37
- sheet.add_cell(0, 1, 'action').change_font_bold(true)
38
- sheet.add_cell(0, 2, 'id').change_font_bold(true)
39
- row.data_column_names.each.with_index(3) do |column_name, i|
40
- sheet.add_cell(0, i, column_name).change_font_bold(true)
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_fill(header_color)
44
+
45
+ column_widths << header_column.size
41
46
  end
42
47
  end
43
48
 
44
- def add_body_rows(sheet, rows)
45
- rows.each.with_index(1) do |row, i|
46
- sheet.add_cell(i, 0, row.id)
47
- sheet.add_cell(i, 1, row.action)
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])
49
+ def add_body_rows(sheet, rows, column_widths)
50
+ rows.each.with_index(1) do |row, row_number|
51
+ row.data_attributes.each.with_index do |(column_name, value), column_index|
52
+ cell = sheet.add_cell(row_number, column_index, value)
52
53
  if row.action == 'UPDATE' && column_name != 'updated_at' && row.changed_attribute_columns.include?(column_name)
53
- cell_object.change_fill('ffff00') # color: yellow
54
+ cell.change_fill('ffff00') # color: yellow
54
55
  elsif row.action == 'DELETE'
55
- cell_object.change_fill('d9d9d9') # color: grey
56
+ cell.change_fill('d9d9d9') # color: grey
56
57
  end
58
+
59
+ column_widths[column_index] = value.to_s.size if column_widths[column_index] < value.to_s.size
57
60
  end
58
61
  end
59
62
  end
@@ -65,7 +68,7 @@ module Breathing
65
68
  cell.change_border(direction, 'thin')
66
69
  end
67
70
 
68
- cell.change_border(:bottom, 'medium') if i.zero?
71
+ cell.change_border(:bottom, 'double') if i.zero?
69
72
  end
70
73
  end
71
74
  sheet.change_row_horizontal_alignment(0, 'center')
@@ -4,9 +4,13 @@ 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
9
- create_log_table unless log_table_exists?
11
+ raise Breathing::UnsupportedError, "Version MySQL 5.6 is not supported." unless database_supported_version?
12
+
13
+ create_log_table
10
14
 
11
15
  models.each do |model|
12
16
  column_names = model.columns.map(&:name)
@@ -17,39 +21,39 @@ module Breathing
17
21
  end
18
22
 
19
23
  def uninstall
20
- drop_log_table if log_table_exists?
21
-
24
+ drop_log_table
22
25
  models.each { |model| Breathing::Trigger.new(model, log_table_name).drop }
23
26
  end
24
27
 
25
28
  private
26
29
 
30
+ def database_supported_version?
31
+ connection = ActiveRecord::Base.connection
32
+ connection.adapter_name == "PostgreSQL" || (connection.adapter_name == 'Mysql2' && connection.raw_connection.info[:version].to_f >= 5.7)
33
+ end
34
+
27
35
  def log_table_name
28
36
  Breathing::ChangeLog.table_name
29
37
  end
30
38
 
31
39
  def create_log_table(table_name: log_table_name)
32
40
  ActiveRecord::Schema.define version: 0 do
33
- create_table table_name, force: false do |t|
34
- t.string :action, null: false
35
- t.string :table_name, null: false
36
- t.string :transaction_id, null: false
37
- t.json :before_data, null: false
38
- t.json :after_data, null: false
39
- 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 :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
+
40
49
  t.index %w[table_name transaction_id]
41
50
  end
42
51
  end
43
52
  end
44
53
 
45
54
  def drop_log_table
46
- sql = "DROP TABLE #{log_table_name}"
47
- puts sql
48
- ActiveRecord::Base.connection.execute(sql)
49
- end
50
-
51
- def log_table_exists?
52
- 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)
53
57
  end
54
58
 
55
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,70 +11,28 @@ module Breathing
11
11
  end
12
12
 
13
13
  def create
14
+ exists_trigger_names = ActiveRecord::Base.connection.triggers.keys
15
+
14
16
  trigger_name = "#{log_table_name}_insert_#{model.table_name}"
15
- unless exists?(trigger_name)
16
- puts "CREATE TRIGGER #{trigger_name}"
17
- ActiveRecord::Base.connection.execute <<-SQL
18
- CREATE TRIGGER #{trigger_name}
19
- AFTER INSERT
20
- ON #{model.table_name}
21
- FOR EACH ROW
22
- BEGIN
23
- INSERT INTO #{log_table_name} (`action`, `table_name`, `transaction_id`, `before_data`, `after_data`, `created_at`)
24
- VALUES ('INSERT', '#{model.table_name}', NEW.id,
25
- JSON_OBJECT(),
26
- JSON_OBJECT(#{json_object_values(model.columns, 'NEW')}),
27
- CURRENT_TIMESTAMP);
28
- END;
29
- SQL
30
- end
17
+ create_insert_trigger(trigger_name, model) if exists_trigger_names.exclude?(trigger_name)
31
18
 
32
19
  trigger_name = "#{log_table_name}_update_#{model.table_name}"
33
- unless exists?(trigger_name)
34
- puts "CREATE TRIGGER #{trigger_name}"
35
- ActiveRecord::Base.connection.execute <<-SQL
36
- CREATE TRIGGER #{trigger_name}
37
- BEFORE UPDATE
38
- ON #{model.table_name}
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`)
43
- VALUES ('UPDATE', '#{model.table_name}', NEW.id,
44
- JSON_OBJECT(#{json_object_values(model.columns, 'OLD')}),
45
- JSON_OBJECT(#{json_object_values(model.columns, 'NEW')}),
46
- CURRENT_TIMESTAMP);
47
- end if;
48
- END;
49
- SQL
50
- end
20
+ create_update_trigger(trigger_name, model) if exists_trigger_names.exclude?(trigger_name)
51
21
 
52
22
  trigger_name = "#{log_table_name}_delete_#{model.table_name}"
53
- unless exists?(trigger_name)
54
- puts "CREATE TRIGGER #{trigger_name}"
55
- ActiveRecord::Base.connection.execute <<-SQL
56
- CREATE TRIGGER #{trigger_name}
57
- AFTER DELETE
58
- ON #{model.table_name}
59
- FOR EACH ROW
60
- BEGIN
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(),
65
- CURRENT_TIMESTAMP);
66
- END;
67
- SQL
68
- end
23
+ create_delete_trigger(trigger_name, model) if exists_trigger_names.exclude?(trigger_name)
69
24
  end
70
25
 
71
26
  def drop
72
- %w[insert update delete].each do |action|
73
- trigger_name = "#{log_table_name}_#{action}_#{model.table_name}"
74
- next unless exists?(trigger_name)
27
+ trigger_names = %w[insert update delete].map { |action| "#{log_table_name}_#{action}_#{model.table_name}" }
75
28
 
29
+ trigger_names.each do |trigger_name|
76
30
  begin
77
- sql = "DROP TRIGGER #{trigger_name}"
31
+ sql = "DROP TRIGGER IF EXISTS #{trigger_name}"
32
+ if postgresql?
33
+ sql << " ON #{model.table_name} CASCADE;"
34
+ sql << " DROP FUNCTION IF EXISTS #{trigger_name} CASCADE;"
35
+ end
78
36
  puts sql
79
37
  ActiveRecord::Base.connection.execute(sql)
80
38
  rescue StandardError => e
@@ -85,16 +43,58 @@ module Breathing
85
43
 
86
44
  private
87
45
 
88
- def exists?(trigger_name)
89
- @trigger_names ||= ActiveRecord::Base.connection.select_rows('SHOW TRIGGERS').map { |row| row.first }
90
- @trigger_names.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
91
53
  end
92
54
 
93
- def json_object_values(columns, state)
94
- columns.each.with_object([]) do |column, array|
95
- array << "'#{column.name}'"
96
- array << "#{state}.#{column.name}"
97
- end.join(',')
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
86
+ end
87
+
88
+ def row_to_json(columns, state)
89
+ if postgresql?
90
+ "row_to_json(#{state}.*)"
91
+ else
92
+ json_object_values = columns.each.with_object([]) do |column, array|
93
+ array << "'#{column.name}'"
94
+ array << "#{state}.#{column.name}"
95
+ end
96
+ "JSON_OBJECT(#{json_object_values.join(',')})"
97
+ end
98
98
  end
99
99
  end
100
100
  end
@@ -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)['test']
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 { Breathing::Installer.new.uninstall }
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
@@ -5,36 +5,41 @@ describe Breathing do
5
5
  expect(Breathing::VERSION).not_to be nil
6
6
  end
7
7
 
8
- before { Breathing::Installer.new.install }
9
- after { Breathing::Installer.new.uninstall }
10
-
11
- it do
12
- expect(Breathing::ChangeLog.count).to eq(0)
13
-
14
- # INSERT
15
- user = User.create!(name: 'a', age: 20)
16
- expect(Breathing::ChangeLog.count).to eq(1)
17
-
18
- log = Breathing::ChangeLog.where(table_name: user.class.table_name, transaction_id: user.id).last
19
- expect(log.before_data).to eq({})
20
- expect(log.after_data['name']).to eq('a')
21
- expect(log.after_data['age']).to eq(20)
22
-
23
- # UPDATE
24
- user.update!(age: 21)
25
- expect(Breathing::ChangeLog.count).to eq(2)
26
-
27
- log = Breathing::ChangeLog.where(table_name: user.class.table_name, transaction_id: user.id).last
28
- expect(log.before_data['age']).to eq(20)
29
- expect(log.after_data['age']).to eq(21)
30
- expect(log.before_data['name']).to eq(log.after_data['name'])
31
- expect(log.changed_attribute_columns).to eq(%w[age updated_at])
32
-
33
- # DELETE
34
- user.destroy!
35
- expect(Breathing::ChangeLog.count).to eq(3)
36
- log = Breathing::ChangeLog.where(table_name: user.class.table_name, transaction_id: user.id).last
37
- expect(log.before_data['name']).to eq('a')
38
- expect(log.after_data).to eq({})
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
@@ -1,11 +1,18 @@
1
- test:
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: breathing_test
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
@@ -4,6 +4,8 @@ require 'active_record'
4
4
  require 'breathing'
5
5
  require_relative 'app'
6
6
 
7
+ ActiveRecord::Base.logger = Logger.new(STDOUT)
8
+
7
9
  RSpec.configure do |config|
8
10
  config.around do |example|
9
11
  ActiveRecord::Base.transaction do
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.3
4
+ version: 0.0.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Akira Kusumoto
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-12-11 00:00:00.000000000 Z
11
+ date: 2020-12-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -30,28 +30,28 @@ 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
- name: rubyXL
42
+ name: hairtrigger
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - ">="
46
46
  - !ruby/object:Gem::Version
47
- version: 3.4.0
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: 3.4.0
54
+ version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: mysql2
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -66,6 +66,48 @@ dependencies:
66
66
  - - ">="
67
67
  - !ruby/object:Gem::Version
68
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
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
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
@@ -125,7 +169,7 @@ homepage: https://github.com/bluerabbit/breathing
125
169
  licenses:
126
170
  - MIT
127
171
  metadata: {}
128
- post_install_message:
172
+ post_install_message:
129
173
  rdoc_options: []
130
174
  require_paths:
131
175
  - lib
@@ -141,12 +185,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
141
185
  version: '0'
142
186
  requirements: []
143
187
  rubygems_version: 3.0.3
144
- signing_key:
188
+ signing_key:
145
189
  specification_version: 4
146
190
  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