active_record_mysql_repl 0.1.4 → 0.1.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +135 -10
- data/lib/active_record_mysql_repl/cli/erd.rb +1 -1
- data/lib/active_record_mysql_repl/cli/main.rb +14 -14
- data/lib/active_record_mysql_repl/cli/zsh_completion.rb +23 -23
- data/lib/active_record_mysql_repl/cli.rb +4 -4
- data/lib/active_record_mysql_repl/config.rb +6 -6
- data/lib/active_record_mysql_repl/database/association.rb +9 -9
- data/lib/active_record_mysql_repl/database/configs.rb +2 -2
- data/lib/active_record_mysql_repl/database/connection.rb +11 -13
- data/lib/active_record_mysql_repl/database/loader.rb +9 -10
- data/lib/active_record_mysql_repl/database.rb +4 -4
- data/lib/active_record_mysql_repl/extensions/active_record.rb +8 -9
- data/lib/active_record_mysql_repl/extensions/global.rb +0 -1
- data/lib/active_record_mysql_repl/extensions/hash.rb +8 -5
- data/lib/active_record_mysql_repl/extensions/object.rb +53 -54
- data/lib/active_record_mysql_repl/extensions/tabler.rb +2 -3
- data/lib/active_record_mysql_repl/extensions.rb +5 -6
- data/lib/active_record_mysql_repl/ssh_tunnel.rb +5 -3
- data/lib/active_record_mysql_repl/version.rb +1 -1
- data/lib/active_record_mysql_repl.rb +4 -5
- data/sample_config/.army.sample/associations.sample.yml +1 -1
- metadata +16 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5c3f840dac882b20496edf1781709416bf8e81f4ed801683892b6a54431cbd4c
|
4
|
+
data.tar.gz: 5f947005433e288cc043e0375849d1ff51e335b863e81fbf890cb66fc3056658
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ae1b915b0668004ac07f597da9fae0a9b35b0103699e7e224fcb3b25ce2924700818850de6fb70fc5ecc3fe6d99ead005c1471bcb7f89744f942567b3092c66d
|
7
|
+
data.tar.gz: 37091f71892c3cf3c1fdf62aed7202ab44f72ad5cb87bf1f2e8f99bff52c0f007434e226d565229f03ed312f42e9ce23d6d7b27904e72b27d272ea2c3d29cba3
|
data/README.md
CHANGED
@@ -383,7 +383,6 @@ https://github.com/nogahighland/active_record_mysql_repl/blob/main/sample_config
|
|
383
383
|
<details><summary>output:</summary>
|
384
384
|
|
385
385
|
```rb
|
386
|
-
|
387
386
|
D, [2024-12-12T18:23:41.382606 #2816] DEBUG -- : Order Load (8.6ms) SELECT `orders`.* FROM `orders` ORDER BY `orders`.`id` DESC LIMIT 1
|
388
387
|
D, [2024-12-12T18:23:41.389954 #2816] DEBUG -- : User Load (2.4ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = '1' LIMIT 1
|
389
388
|
D, [2024-12-12T18:23:41.392060 #2816] DEBUG -- : UserProfile Load (1.8ms) SELECT `user_profiles`.* FROM `user_profiles` WHERE `user_profiles`.`id` = '1' LIMIT 1
|
@@ -397,22 +396,148 @@ D, [2024-12-12T18:23:41.392060 #2816] DEBUG -- : UserProfile Load (1.8ms) SEL
|
|
397
396
|
|
398
397
|
---
|
399
398
|
|
399
|
+
Even though `users.login_id` column exists, because the column is not an external key it should be ignored from the association.
|
400
|
+
The column is ignored because the custom association is defined here and `User#login` causes a `NoMethodError`
|
400
401
|
|
402
|
+
https://github.com/nogahighland/active_record_mysql_repl/blob/f52f04770425723f632220bb8031bf4b402020a6/sample_config/.army.sample/associations.sample.yml#L10-L13
|
401
403
|
|
402
404
|
```rb
|
403
|
-
[
|
405
|
+
[1] test(main)> User.last.login
|
404
406
|
```
|
405
407
|
|
406
408
|
<details><summary>output:</summary>
|
407
409
|
|
408
410
|
```rb
|
409
|
-
D, [2024-12-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
411
|
+
D, [2024-12-12T22:17:49.291618 #37808] DEBUG -- : User Load (2.5ms) SELECT `users`.* FROM `users` ORDER BY `users`.`id` DESC LIMIT 1
|
412
|
+
NoMethodError: undefined method `login' for an instance of User
|
413
|
+
from /Users/hiroki.kishi/develop/private/active_record_mysql_repl/vendor/bundle/ruby/3.3.0/gems/activemodel-7.2.2/lib/active_model/attribute_methods.rb:512:in `method_missing'
|
414
|
+
```
|
415
|
+
|
416
|
+
</details>
|
417
|
+
|
418
|
+
---
|
419
|
+
|
420
|
+
You can use transaction by globally defined `transaction`.
|
421
|
+
|
422
|
+
```rb
|
423
|
+
[7] test(main)> u2 = User.new(id:2, profile_id: up2, login_id: 'login2')
|
424
|
+
[9] test(main)> up2 = UserProfile.new(id:2, name: 'user2');
|
425
|
+
[10] test(main)> transaction { [u2, up2].map(&:save) }
|
426
|
+
```
|
427
|
+
|
428
|
+
<details><summary>output:</summary>
|
429
|
+
|
430
|
+
```sql
|
431
|
+
[9] test(main)> up2 = UserProfile.new(id:2, name: 'user2');
|
432
|
+
[10] test(main)> transaction { [u2, up2].map(&:save) }
|
433
|
+
D, [2024-12-12T22:25:32.266790 #37808] DEBUG -- : TRANSACTION (2.2ms) BEGIN
|
434
|
+
D, [2024-12-12T22:25:32.272857 #37808] DEBUG -- : User Create (8.3ms) INSERT INTO `users` (`id`, `login_id`) VALUES ('2', 'login2')
|
435
|
+
D, [2024-12-12T22:25:32.275213 #37808] DEBUG -- : UserProfile Create (2.1ms) INSERT INTO `user_profiles` (`id`, `name`) VALUES ('2', 'user2')
|
436
|
+
D, [2024-12-12T22:25:32.281765 #37808] DEBUG -- : TRANSACTION (6.4ms) COMMIT
|
437
|
+
[
|
438
|
+
[0] true,
|
439
|
+
[1] true
|
440
|
+
]
|
441
|
+
```
|
442
|
+
|
443
|
+
</details>
|
444
|
+
|
445
|
+
---
|
446
|
+
|
447
|
+
The following example shows the following steps:
|
448
|
+
|
449
|
+
1. Find the users with id 1 and 2
|
450
|
+
2. Change the login_id of the users
|
451
|
+
3. Show the changes
|
452
|
+
4. Save the changes in a transaction
|
453
|
+
|
454
|
+
```rb
|
455
|
+
[11] test(main)> u1, u2 = User.find([1, 2])
|
456
|
+
```
|
457
|
+
|
458
|
+
<details><summary>Output:</summary>
|
459
|
+
|
460
|
+
```rb
|
461
|
+
D, [2024-12-12T22:27:55.979311 #37808] DEBUG -- : User Load (11.7ms) SELECT `users`.* FROM `users` WHERE `users`.`id` IN ('1', '2')
|
462
|
+
[
|
463
|
+
[0] #<User:0x0000000144b325a0> {
|
464
|
+
:id => "1",
|
465
|
+
:login_id => "user1",
|
466
|
+
:profile_id => 1
|
467
|
+
},
|
468
|
+
[1] #<User:0x0000000144b32460> {
|
469
|
+
:id => "2",
|
470
|
+
:login_id => "login2",
|
471
|
+
:profile_id => nil
|
472
|
+
}
|
473
|
+
]
|
474
|
+
```
|
475
|
+
|
476
|
+
</details>
|
477
|
+
|
478
|
+
```rb
|
479
|
+
[12] test(main)> [u1, u2].each { |u| u.login_id = "login_id#{u.id}" }
|
480
|
+
```
|
481
|
+
|
482
|
+
<details><summary>Output:</summary>
|
483
|
+
|
484
|
+
```rb
|
485
|
+
[
|
486
|
+
[0] #<User:0x0000000144b325a0> {
|
487
|
+
:id => "1",
|
488
|
+
:login_id => "login_id1",
|
489
|
+
:profile_id => 1
|
490
|
+
},
|
491
|
+
[1] #<User:0x0000000144b32460> {
|
492
|
+
:id => "2",
|
493
|
+
:login_id => "login_id2",
|
494
|
+
:profile_id => nil
|
495
|
+
}
|
496
|
+
]
|
497
|
+
```
|
498
|
+
|
499
|
+
</details>
|
500
|
+
|
501
|
+
```rb
|
502
|
+
[13] test(main)> [u1, u2].map(&:changes)
|
503
|
+
```
|
504
|
+
|
505
|
+
<details><summary>Output:</summary>
|
506
|
+
|
507
|
+
```rb
|
508
|
+
[
|
509
|
+
[0] {
|
510
|
+
"login_id" => [
|
511
|
+
[0] "user1",
|
512
|
+
[1] "login_id1"
|
513
|
+
]
|
514
|
+
},
|
515
|
+
[1] {
|
516
|
+
"login_id" => [
|
517
|
+
[0] "login2",
|
518
|
+
[1] "login_id2"
|
519
|
+
]
|
520
|
+
}
|
521
|
+
]
|
522
|
+
```
|
523
|
+
|
524
|
+
</details>
|
525
|
+
|
526
|
+
```rb
|
527
|
+
[14] test(main)> transaction { [u1, u2].map(&:save) }
|
528
|
+
```
|
529
|
+
|
530
|
+
<details><summary>Output:</summary>
|
531
|
+
|
532
|
+
```rb
|
533
|
+
D, [2024-12-12T22:29:14.179176 #37808] DEBUG -- : TRANSACTION (1.2ms) BEGIN
|
534
|
+
D, [2024-12-12T22:29:14.188392 #37808] DEBUG -- : User Update (9.1ms) UPDATE `users` SET `users`.`login_id` = 'login_id1' WHERE `users`.`id` = '1'
|
535
|
+
D, [2024-12-12T22:29:14.191429 #37808] DEBUG -- : User Update (2.6ms) UPDATE `users` SET `users`.`login_id` = 'login_id2' WHERE `users`.`id` = '2'
|
536
|
+
D, [2024-12-12T22:29:14.196802 #37808] DEBUG -- : TRANSACTION (5.2ms) COMMIT
|
537
|
+
[
|
538
|
+
[0] true,
|
539
|
+
[1] true
|
540
|
+
]
|
416
541
|
```
|
417
542
|
|
418
543
|
</details>
|
@@ -600,7 +725,7 @@ D, [2024-12-11T23:49:22.406631 #96381] DEBUG -- : (1.3ms) select 1 as num
|
|
600
725
|
You can define your own extension script. For example `.upcase_name` is defined on `UserProfile` by the sample extension which is specified in the `.army.sample.yml` file.
|
601
726
|
|
602
727
|
- https://github.com/nogahighland/active_record_mysql_repl/blob/9f7c91774b176e1204ed434dad2867721982c660/sample_config/.army.sample.yml#L5
|
603
|
-
- https://github.com/nogahighland/active_record_mysql_repl/blob/
|
728
|
+
- https://github.com/nogahighland/active_record_mysql_repl/blob/acaa44684ea4fd135225580431417d6ccd41a988/sample_config/.army.sample/extensions/hello.rb#L1-L9
|
604
729
|
|
605
730
|
```rb
|
606
731
|
[3] test(main)> UserProfile.last.upcase_name
|
@@ -1,10 +1,10 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
3
|
+
require "pry"
|
4
|
+
require "colorize"
|
5
|
+
require "active_support"
|
6
|
+
require "active_support/core_ext"
|
7
|
+
require "fileutils"
|
8
8
|
|
9
9
|
module ActiveRecordMysqlRepl
|
10
10
|
module CLI
|
@@ -15,34 +15,34 @@ module ActiveRecordMysqlRepl
|
|
15
15
|
return
|
16
16
|
end
|
17
17
|
|
18
|
-
if args[0] ==
|
18
|
+
if args[0] == "--zsh-completion"
|
19
19
|
puts ZshCompletion.generate
|
20
20
|
return
|
21
21
|
end
|
22
22
|
|
23
23
|
opts = CLI::Options.parse(args)
|
24
|
-
army_config = Config.load(opts[:c] || File.join(Dir.home,
|
24
|
+
army_config = Config.load(opts[:c] || File.join(Dir.home, ".army.yml"))
|
25
25
|
|
26
26
|
db_configs = Database::Configs.load(army_config.database_config)
|
27
27
|
db_config_key = opts[:d]
|
28
28
|
db_config = db_configs[db_config_key]
|
29
29
|
return puts db_configs.keys unless db_config
|
30
30
|
|
31
|
-
generate_erf = opts[:e] ==
|
31
|
+
generate_erf = opts[:e] == "erd"
|
32
32
|
|
33
33
|
SSHTunnel.tunnel(db_config) do |port|
|
34
34
|
Database::Connection.connect(db_config, port) do
|
35
35
|
Database::Loader.load_tables(db_config_key, army_config, port)
|
36
36
|
|
37
37
|
if generate_erf
|
38
|
-
require
|
38
|
+
require "rails_erd/diagram/graphviz"
|
39
39
|
puts "Generating ERD for #{db_config_key}_erd.pdf".gray
|
40
40
|
RailsERD::Diagram::Graphviz.create
|
41
|
-
FileUtils.mv(
|
42
|
-
|
41
|
+
FileUtils.mv("erd.pdf", "#{db_config_key}_erd.pdf")
|
42
|
+
next
|
43
43
|
end
|
44
44
|
|
45
|
-
require
|
45
|
+
require "active_record_mysql_repl/extensions"
|
46
46
|
if army_config.extensions_dir.present?
|
47
47
|
puts "Loading custom extensions from #{army_config.extensions_dir}".gray
|
48
48
|
Extensions.load_external(army_config.extensions_dir)
|
@@ -57,8 +57,8 @@ module ActiveRecordMysqlRepl
|
|
57
57
|
end
|
58
58
|
|
59
59
|
def self.clear_screen
|
60
|
-
system(
|
61
|
-
system(
|
60
|
+
system("clear")
|
61
|
+
system("cls")
|
62
62
|
end
|
63
63
|
end
|
64
64
|
end
|
@@ -1,29 +1,29 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
COMPLETION = <<~COMPLETION
|
4
|
-
#!/usr/bin/env bash
|
5
|
-
|
6
|
-
function _army() {
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
}
|
21
|
-
|
22
|
-
function _database {
|
23
|
-
|
24
|
-
}
|
25
|
-
|
26
|
-
compdef _army army
|
4
|
+
#!/usr/bin/env bash
|
5
|
+
|
6
|
+
function _army() {
|
7
|
+
_arguments \
|
8
|
+
'-c[path to .armyrc file]:.armyrc:_files' \
|
9
|
+
'-d[Database name]:database:->database' \
|
10
|
+
'-e[output erd]:output erd:->erd'
|
11
|
+
|
12
|
+
case "$state" in
|
13
|
+
database)
|
14
|
+
slice=("${words[@]:1}")
|
15
|
+
_values 'Database name' $(_database ${slice})
|
16
|
+
;;
|
17
|
+
erd)
|
18
|
+
_values 'Generate ERD' erd ''
|
19
|
+
esac
|
20
|
+
}
|
21
|
+
|
22
|
+
function _database {
|
23
|
+
army ${@}
|
24
|
+
}
|
25
|
+
|
26
|
+
compdef _army army
|
27
27
|
COMPLETION
|
28
28
|
|
29
29
|
module ActiveRecordMysqlRepl
|
@@ -1,9 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative
|
4
|
-
require_relative
|
5
|
-
require_relative
|
6
|
-
require_relative
|
3
|
+
require_relative "cli/options"
|
4
|
+
require_relative "cli/main"
|
5
|
+
require_relative "cli/zsh_completion"
|
6
|
+
require_relative "cli/erd"
|
7
7
|
|
8
8
|
module ActiveRecordMysqlRepl
|
9
9
|
module CLI; end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "yaml"
|
4
4
|
|
5
5
|
module ActiveRecordMysqlRepl
|
6
6
|
class Config
|
@@ -9,7 +9,7 @@ module ActiveRecordMysqlRepl
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def database_config
|
12
|
-
raise
|
12
|
+
raise "database_config is not defined" unless @database_config
|
13
13
|
File.join(@army_config_dir, @database_config)
|
14
14
|
end
|
15
15
|
|
@@ -29,10 +29,10 @@ module ActiveRecordMysqlRepl
|
|
29
29
|
|
30
30
|
def initialize(path, config)
|
31
31
|
@army_config_dir = File.dirname(File.absolute_path(path))
|
32
|
-
@database_config = config[
|
33
|
-
@associations = config[
|
34
|
-
@extensions_dir = config[
|
35
|
-
@pryrc = config[
|
32
|
+
@database_config = config["database_config"]
|
33
|
+
@associations = config["associations"]
|
34
|
+
@extensions_dir = config["extensions_dir"]
|
35
|
+
@pryrc = config["pryrc"]
|
36
36
|
end
|
37
37
|
end
|
38
38
|
end
|
@@ -36,18 +36,18 @@ module ActiveRecordMysqlRepl
|
|
36
36
|
columns.each do |column|
|
37
37
|
next if association_setting.present? && association_settings.ignore_columns(table_name).include?(column.name)
|
38
38
|
|
39
|
-
associatable = column.name.gsub(/_id$/,
|
40
|
-
next if associatable.blank? || associatable ==
|
39
|
+
associatable = column.name.gsub(/_id$/, "") if column.name.end_with?("_id")
|
40
|
+
next if associatable.blank? || associatable == "class" # reserved word
|
41
41
|
|
42
|
-
if analyzed_tables.
|
42
|
+
if analyzed_tables.key?(associatable.pluralize)
|
43
43
|
table.belongs_to << associatable.singularize if associatable
|
44
44
|
analyzed_tables[associatable.pluralize].has_many << table_name.pluralize
|
45
45
|
else
|
46
|
-
associatable_table_name = associatable.split(
|
47
|
-
if analyzed_tables.
|
48
|
-
|
46
|
+
associatable_table_name = associatable.split("_").last
|
47
|
+
table.belongs_to << if analyzed_tables.key?(associatable_table_name.pluralize)
|
48
|
+
{name: associatable, class_name: associatable_table_name.classify, foreign_key: :id}
|
49
49
|
else
|
50
|
-
|
50
|
+
{name: associatable, class_name: table_name.singularize.classify, foreign_key: :id}
|
51
51
|
end
|
52
52
|
end
|
53
53
|
end
|
@@ -71,12 +71,12 @@ module ActiveRecordMysqlRepl
|
|
71
71
|
end
|
72
72
|
|
73
73
|
def [](table)
|
74
|
-
table = (@association.keys - [
|
74
|
+
table = (@association.keys - ["ignore_columns"]) & [table]
|
75
75
|
@association[table.first] if table.present?
|
76
76
|
end
|
77
77
|
|
78
78
|
def ignore_columns(table)
|
79
|
-
@association.fetch(
|
79
|
+
@association.fetch("ignore_columns", {}).fetch(table, [])
|
80
80
|
end
|
81
81
|
end
|
82
82
|
end
|
@@ -27,7 +27,7 @@ module ActiveRecordMysqlRepl
|
|
27
27
|
private
|
28
28
|
|
29
29
|
class Config
|
30
|
-
attr_reader
|
30
|
+
attr_reader(*%i[
|
31
31
|
bastion
|
32
32
|
ssh_user
|
33
33
|
remote_host
|
@@ -36,7 +36,7 @@ module ActiveRecordMysqlRepl
|
|
36
36
|
user
|
37
37
|
password
|
38
38
|
prompt_color
|
39
|
-
]
|
39
|
+
])
|
40
40
|
|
41
41
|
def initialize(config)
|
42
42
|
@bastion = config["bastion"]
|
@@ -7,28 +7,26 @@ module ActiveRecordMysqlRepl
|
|
7
7
|
|
8
8
|
def self.connect(db_config, port)
|
9
9
|
conn = {
|
10
|
-
adapter:
|
11
|
-
host:
|
10
|
+
adapter: "mysql2",
|
11
|
+
host: "127.0.0.1",
|
12
12
|
port: port,
|
13
13
|
username: db_config.user,
|
14
14
|
password: db_config.password,
|
15
|
-
database: db_config.database
|
15
|
+
database: db_config.database
|
16
16
|
}
|
17
17
|
|
18
|
-
ActiveRecord::Base.logger = Logger.new(
|
19
|
-
|
18
|
+
ActiveRecord::Base.logger = Logger.new($stdout)
|
19
|
+
ActiveRecord::Base.establish_connection(conn)
|
20
20
|
|
21
21
|
puts "Ensureing connection to #{db_config.database} on port 127.0.0.1:#{port}".gray
|
22
22
|
|
23
23
|
tables = nil
|
24
|
-
(1..MAX_RETRY-1).to_a.each do |time|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
next
|
31
|
-
end
|
24
|
+
(1..MAX_RETRY - 1).to_a.each do |time|
|
25
|
+
tables = ActiveRecord::Base.connection.tables
|
26
|
+
rescue => e
|
27
|
+
puts "#{e}, Retry #{time}/#{MAX_RETRY}".red
|
28
|
+
sleep time * time
|
29
|
+
next
|
32
30
|
end
|
33
31
|
|
34
32
|
if tables.blank?
|
@@ -1,8 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
3
|
+
require "json"
|
4
|
+
require "active_record"
|
5
|
+
require "active_support/all"
|
6
6
|
|
7
7
|
module ActiveRecordMysqlRepl
|
8
8
|
module Database
|
@@ -13,13 +13,12 @@ module ActiveRecordMysqlRepl
|
|
13
13
|
tables = ActiveRecord::Base.connection.tables
|
14
14
|
association_settings = army_config.associations.present? ? Associations.load(army_config.associations) : {}
|
15
15
|
association_setting = association_settings[db_config_key]
|
16
|
-
analyzed_tables =
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
end
|
16
|
+
analyzed_tables = if association_setting.present?
|
17
|
+
Associations.analyze(tables, association_setting)
|
18
|
+
else
|
19
|
+
Associations.analyze(tables)
|
20
|
+
end
|
21
|
+
|
23
22
|
skipped = []
|
24
23
|
analyzed_tables.each do |analyzed_table|
|
25
24
|
# defer model definition for tables with `has_many: :xxx, through: xxx` associations
|
@@ -1,9 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative
|
4
|
-
require_relative
|
5
|
-
require_relative
|
6
|
-
require_relative
|
3
|
+
require_relative "database/configs"
|
4
|
+
require_relative "database/association"
|
5
|
+
require_relative "database/connection"
|
6
|
+
require_relative "database/loader"
|
7
7
|
|
8
8
|
module ActiveRecordMysqlRepl
|
9
9
|
module Database; end
|
@@ -5,29 +5,28 @@ module ActiverecordMysqlRepl
|
|
5
5
|
module ActiveRecord
|
6
6
|
module Base
|
7
7
|
def as(name = nil)
|
8
|
-
|
8
|
+
reflect_on_all_associations(name).map(&:name)
|
9
9
|
end
|
10
10
|
|
11
11
|
def describe
|
12
12
|
[
|
13
|
-
"# #{
|
14
|
-
exec_sql("DESCRIBE #{
|
15
|
-
exec_sql("SHOW INDEX FROM #{
|
13
|
+
"# #{table_name}",
|
14
|
+
exec_sql("DESCRIBE #{table_name}").tab(:h),
|
15
|
+
exec_sql("SHOW INDEX FROM #{table_name}").tab(:h)
|
16
16
|
].join("\n")
|
17
17
|
end
|
18
18
|
|
19
|
-
|
20
|
-
|
19
|
+
alias_method :desc, :describe
|
20
|
+
alias_method :d, :describe
|
21
21
|
|
22
22
|
def show_ddl
|
23
|
-
exec_sql("SHOW CREATE TABLE #{
|
23
|
+
exec_sql("SHOW CREATE TABLE #{table_name}")[0]["Create Table"]
|
24
24
|
end
|
25
25
|
|
26
|
-
|
26
|
+
alias_method :ddl, :show_ddl
|
27
27
|
end
|
28
28
|
|
29
29
|
::ActiveRecord::Base.extend(Base)
|
30
30
|
end
|
31
31
|
end
|
32
32
|
end
|
33
|
-
|
@@ -4,17 +4,20 @@ module ActiverecordMysqlRepl
|
|
4
4
|
module Extensions
|
5
5
|
module Hash
|
6
6
|
def method_missing(name, *args)
|
7
|
-
if (
|
7
|
+
if (keys & [name, name.to_s]).present?
|
8
8
|
self[name] || self[name.to_s]
|
9
|
-
elsif name.to_s.end_with?(
|
9
|
+
elsif name.to_s.end_with?("=") && keys.include?(name.to_s[0..-2])
|
10
10
|
name = name[0..-2]
|
11
|
-
self[name] = args.first if
|
12
|
-
self[name.to_sym] = args.first if
|
11
|
+
self[name] = args.first if key?(name)
|
12
|
+
self[name.to_sym] = args.first if key?(name.to_sym)
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
16
|
+
def respond_to_missing?
|
17
|
+
true
|
18
|
+
end
|
19
|
+
|
16
20
|
::Hash.include(Hash)
|
17
21
|
end
|
18
22
|
end
|
19
23
|
end
|
20
|
-
|
@@ -1,28 +1,28 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
3
|
+
require "terminal-table"
|
4
|
+
require "csv"
|
5
|
+
require "clipboard"
|
6
|
+
require "json"
|
7
7
|
|
8
8
|
module ActiverecordMysqlRepl
|
9
9
|
module Extensions
|
10
10
|
module Object
|
11
11
|
def tabulate(orientation = nil)
|
12
|
-
arr = if
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
12
|
+
arr = if is_a?(Array)
|
13
|
+
self
|
14
|
+
elsif is_a?(::ActiveRecord::Relation)
|
15
|
+
to_a
|
16
|
+
else
|
17
|
+
[self]
|
18
|
+
end
|
19
19
|
|
20
20
|
arr = arr.map do |e|
|
21
21
|
if e.is_a?(::ActiveRecord::Base)
|
22
|
-
values =
|
22
|
+
values = e.attributes.transform_values do |v|
|
23
23
|
next JSON.pretty_generate(v) if v.is_a?(Enumerable) && v.size > 0
|
24
|
-
next
|
25
|
-
next '""' if v ==
|
24
|
+
next "NULL" if v.nil?
|
25
|
+
next '""' if v == ""
|
26
26
|
v.to_s
|
27
27
|
end
|
28
28
|
next values
|
@@ -34,17 +34,17 @@ module ActiverecordMysqlRepl
|
|
34
34
|
raise "#{arr} contains Hashes with different keys".red unless arr.map(&:keys).uniq.size == 1
|
35
35
|
|
36
36
|
orientation = case orientation
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
37
|
+
when :vertical, :v
|
38
|
+
:vertical
|
39
|
+
when :horizontal, :h
|
40
|
+
:horizontal
|
41
|
+
else
|
42
|
+
if arr.first.keys.size > 5
|
43
|
+
:vertical
|
44
|
+
else
|
45
|
+
:horizontal
|
46
|
+
end
|
47
|
+
end
|
48
48
|
|
49
49
|
t = Terminal::Table.new
|
50
50
|
|
@@ -54,7 +54,7 @@ module ActiverecordMysqlRepl
|
|
54
54
|
t.rows = arr.map(&:values)
|
55
55
|
|
56
56
|
when :vertical
|
57
|
-
t.headings = [
|
57
|
+
t.headings = ["Name", "Value"]
|
58
58
|
arr.each.with_index do |row, i|
|
59
59
|
row.each { |col, val| t.add_row [col, val] }
|
60
60
|
t.add_separator if i < arr.size - 1
|
@@ -64,25 +64,25 @@ module ActiverecordMysqlRepl
|
|
64
64
|
t.to_s
|
65
65
|
end
|
66
66
|
|
67
|
-
|
68
|
-
|
69
|
-
|
67
|
+
alias_method :tab, :tabulate
|
68
|
+
alias_method :table, :tabulate
|
69
|
+
alias_method :to_table, :tabulate
|
70
70
|
|
71
71
|
def csv(orientation = nil)
|
72
|
-
arr = if
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
72
|
+
arr = if is_a?(Array)
|
73
|
+
self
|
74
|
+
elsif is_a?(::ActiveRecord::Relation)
|
75
|
+
to_a
|
76
|
+
else
|
77
|
+
[self]
|
78
|
+
end
|
79
79
|
|
80
80
|
arr = arr.map do |e|
|
81
81
|
if e.is_a?(::ActiveRecord::Base)
|
82
|
-
values =
|
82
|
+
values = e.attributes.transform_values do |v|
|
83
83
|
next JSON.pretty_generate(v) if v.is_a?(Enumerable) && v.size > 0
|
84
|
-
next
|
85
|
-
next '""' if v ==
|
84
|
+
next "NULL" if v.nil?
|
85
|
+
next '""' if v == ""
|
86
86
|
v.to_s
|
87
87
|
end
|
88
88
|
next values
|
@@ -94,17 +94,17 @@ module ActiverecordMysqlRepl
|
|
94
94
|
raise "#{arr} contains Hashes with different keys".red unless arr.map(&:keys).uniq.size == 1
|
95
95
|
|
96
96
|
orientation = case orientation
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
97
|
+
when :vertical, :v
|
98
|
+
:vertical
|
99
|
+
when :horizontal, :h
|
100
|
+
:horizontal
|
101
|
+
else
|
102
|
+
if arr.first.keys.size > 5
|
103
|
+
:vertical
|
104
|
+
else
|
105
|
+
:horizontal
|
106
|
+
end
|
107
|
+
end
|
108
108
|
|
109
109
|
CSV.generate do |csv|
|
110
110
|
case orientation
|
@@ -121,11 +121,11 @@ module ActiverecordMysqlRepl
|
|
121
121
|
end
|
122
122
|
|
123
123
|
def copy
|
124
|
-
Clipboard.copy(
|
124
|
+
Clipboard.copy(to_s)
|
125
125
|
end
|
126
|
-
|
126
|
+
alias_method :cp, :copy
|
127
127
|
|
128
|
-
|
128
|
+
alias_method :j, :to_json
|
129
129
|
|
130
130
|
def jp
|
131
131
|
JSON.pretty_generate(JSON.parse(to_json))
|
@@ -135,4 +135,3 @@ module ActiverecordMysqlRepl
|
|
135
135
|
|
136
136
|
::Object.include(Extensions::Object)
|
137
137
|
end
|
138
|
-
|
@@ -13,11 +13,11 @@ module ActiverecordMysqlRepl
|
|
13
13
|
end
|
14
14
|
|
15
15
|
model.column_names.each do |column|
|
16
|
-
define_method("#{model.table_name.singularize}_by_#{column}") do
|
16
|
+
define_method(:"#{model.table_name.singularize}_by_#{column}") do
|
17
17
|
model.where(column => self)
|
18
18
|
end
|
19
19
|
|
20
|
-
define_method("#{model.table_name.singularize}_#{column}_like") do
|
20
|
+
define_method(:"#{model.table_name.singularize}_#{column}_like") do
|
21
21
|
model.where("#{column} like ?", "%#{self}%")
|
22
22
|
end
|
23
23
|
end
|
@@ -29,4 +29,3 @@ module ActiverecordMysqlRepl
|
|
29
29
|
Enumerable.include Tabler
|
30
30
|
end
|
31
31
|
end
|
32
|
-
|
@@ -1,10 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative
|
4
|
-
require_relative
|
5
|
-
require_relative
|
6
|
-
require_relative
|
7
|
-
require_relative
|
3
|
+
require_relative "extensions/object"
|
4
|
+
require_relative "extensions/global"
|
5
|
+
require_relative "extensions/active_record"
|
6
|
+
require_relative "extensions/hash"
|
7
|
+
require_relative "extensions/tabler"
|
8
8
|
|
9
9
|
module ActiveRecordMysqlRepl
|
10
10
|
module Extensions
|
@@ -15,4 +15,3 @@ module ActiveRecordMysqlRepl
|
|
15
15
|
end
|
16
16
|
end
|
17
17
|
end
|
18
|
-
|
@@ -1,14 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require "net/ssh"
|
4
|
+
require "net/ssh/gateway"
|
5
5
|
|
6
6
|
module ActiveRecordMysqlRepl
|
7
7
|
module SSHTunnel
|
8
8
|
EPHEMERAL_PORT = 0
|
9
9
|
|
10
10
|
def self.tunnel(db_config)
|
11
|
-
|
11
|
+
unless db_config.bastion
|
12
|
+
return yield(db_config.port) if block_given?
|
13
|
+
end
|
12
14
|
|
13
15
|
puts "Establishing ssh tunnel to #{db_config.remote_host}:#{db_config.port} via #{db_config.ssh_user}#{db_config.bastion}".gray
|
14
16
|
|
@@ -1,11 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative "active_record_mysql_repl/version"
|
4
|
-
require_relative
|
5
|
-
require_relative
|
6
|
-
require_relative
|
7
|
-
require_relative
|
8
|
-
require_relative 'active_record_mysql_repl/version'
|
4
|
+
require_relative "active_record_mysql_repl/config"
|
5
|
+
require_relative "active_record_mysql_repl/cli"
|
6
|
+
require_relative "active_record_mysql_repl/database"
|
7
|
+
require_relative "active_record_mysql_repl/ssh_tunnel"
|
9
8
|
# extensions should be loaded after database connection is established
|
10
9
|
# require_relative 'active_record_mysql_repl/extensions'
|
11
10
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: active_record_mysql_repl
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Hiroki Kishi
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-12-
|
11
|
+
date: 2024-12-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: byebug
|
@@ -38,6 +38,20 @@ dependencies:
|
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: 0.50.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: standard
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
56
|
name: activerecord
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|