breathing 0.0.3 → 0.0.8

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: 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