active_record_mysql_repl 0.1.3 → 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (27) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +729 -13
  3. data/lib/active_record_mysql_repl/cli/erd.rb +1 -1
  4. data/lib/active_record_mysql_repl/cli/main.rb +19 -14
  5. data/lib/active_record_mysql_repl/cli/zsh_completion.rb +23 -23
  6. data/lib/active_record_mysql_repl/cli.rb +4 -4
  7. data/lib/active_record_mysql_repl/config.rb +7 -7
  8. data/lib/active_record_mysql_repl/database/association.rb +10 -10
  9. data/lib/active_record_mysql_repl/database/configs.rb +2 -2
  10. data/lib/active_record_mysql_repl/database/connection.rb +11 -13
  11. data/lib/active_record_mysql_repl/database/loader.rb +10 -5
  12. data/lib/active_record_mysql_repl/database.rb +4 -4
  13. data/lib/active_record_mysql_repl/extensions/active_record.rb +8 -8
  14. data/lib/active_record_mysql_repl/extensions/global.rb +0 -1
  15. data/lib/active_record_mysql_repl/extensions/hash.rb +8 -5
  16. data/lib/active_record_mysql_repl/extensions/object.rb +53 -54
  17. data/lib/active_record_mysql_repl/extensions/tabler.rb +2 -3
  18. data/lib/active_record_mysql_repl/extensions.rb +5 -6
  19. data/lib/active_record_mysql_repl/ssh_tunnel.rb +5 -3
  20. data/lib/active_record_mysql_repl/version.rb +1 -1
  21. data/lib/active_record_mysql_repl.rb +4 -5
  22. data/sample_config/{.pryrc.sample → .army.sample/.pryrc.sample} +2 -0
  23. data/sample_config/{associations.sample.yml → .army.sample/associations.sample.yml} +1 -1
  24. data/sample_config/{databases.sample.yml → .army.sample/databases.sample.yml} +2 -2
  25. data/sample_config/.army.sample/extensions/hello.rb +9 -0
  26. data/sample_config/.army.sample.yml +4 -4
  27. metadata +20 -5
data/README.md CHANGED
@@ -1,38 +1,754 @@
1
1
  # ActiveRecordMysqlRepl
2
2
 
3
- TODO: Delete this and the text below, and describe your gem
3
+ ## Prerequisites
4
4
 
5
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/active_record_mysql_repl`. To experiment with that code, run `bin/console` for an interactive prompt.
5
+ ### Ruby version 3.0.0 or higher
6
6
 
7
- ## Installation
7
+ e.g) Using rbenv
8
+
9
+ ```bash
10
+ $ rbenv install 3.3.6
11
+ ```
8
12
 
9
- TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.
13
+ ### mysql-client 8.0
10
14
 
11
- Install the gem and add to the application's Gemfile by executing:
15
+ e.g) on macOS
12
16
 
13
17
  ```bash
14
- bundle add UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
18
+ $ brew install mysql-client@8.0
19
+ $ gem install mysql2 -v 0.5.6 -- --with-ldflags=-L$(brew --prefix zstd)/lib --with-mysql-dir=/opt/homebrew/opt/mysql-client@8.4
15
20
  ```
16
21
 
17
- If bundler is not being used to manage dependencies, install the gem by executing:
22
+ ## Installation
23
+
24
+ Just by installing the gem, you can use the `army` command.
18
25
 
19
26
  ```bash
20
- gem install UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
27
+ $ gem install active_record_mysql_repl
28
+ Successfully installed active_record_mysql_repl-x.x.x
29
+ Parsing documentation for active_record_mysql_repl-x.x.x
30
+ Installing ri documentation for active_record_mysql_repl-x.x.x
31
+ Done installing documentation for active_record_mysql_repl after 0 seconds
32
+ 1 gem installed
33
+ ```
34
+
35
+ ```
36
+ $ army
37
+ ActiveRecordMysqlRepl Version: x.x.x
38
+ ```
39
+
40
+ If you want to use the zsh completion, you can add the following line to your `.zshrc` file.
41
+
42
+ ```sh
43
+ $ eval "$(army --zsh-completion)"
44
+ ```
45
+
46
+ Then you can use the completion feature like below.
47
+
48
+ ```sh
49
+ $ army [TAB]
50
+ option
51
+ -c -- path to .armyrc file
52
+ -d -- Database name
53
+ -e -- output erd
21
54
  ```
22
55
 
23
56
  ## Usage
24
57
 
25
- TODO: Write usage instructions here
58
+ ### Sample Configurations
26
59
 
27
- ## Development
60
+ From the following link, you can Download the sample configuration files
61
+ https://github.com/nogahighland/active_record_mysql_repl/tree/main/sample_config
62
+
63
+ Also you can try with sample database on your local MySQL server
64
+ https://github.com/nogahighland/active_record_mysql_repl/blob/main/sample_config/sample_db.sql
65
+
66
+ ### Sample Database
67
+
68
+ ```sql
69
+ CREATE DATABASE test;
70
+ ```
71
+
72
+ The sample configuration assumes the sample database is running on
73
+ 127.0.0.1:3306/test (user: root, password: root)
74
+
75
+ ```
76
+ $ mysql -u root -p test < sample_db.sql
77
+ ```
78
+
79
+ ![image](https://github.com/user-attachments/assets/ddc59020-42db-4240-91db-76dae560fdf5)
80
+
81
+ This ER Diagram can be generated by the following command
82
+
83
+ ```sh
84
+ army -c sample_config/.army.sample.yml -d test -e erd
85
+ ```
86
+
87
+ ### Showcases
88
+
89
+ ```sh
90
+ army -c /path/to/sample_config/.army.sample.yml -d test
91
+ Ensureing connection to test on port 127.0.0.1:33060
92
+ Loading tables
93
+ Loading custom extensions from /path/to/sample_config/./.army.sample/extensions
94
+ ```
95
+
96
+ Now you can access to the table classes as `User` if the table name is `users`.
97
+
98
+ ```rb
99
+ [1] test(main)> User.all
100
+ ```
101
+
102
+ <details><summary>output:</summary>
103
+
104
+ ```rb
105
+ D, [2024-12-12T12:57:38.581127 #2816] DEBUG -- : User Load (10.5ms) SELECT `users`.* FROM `users`
106
+ [
107
+ [0] #<User:0x0000000123188de0> {
108
+ :id => "1",
109
+ :login_id => "user1",
110
+ :profile_id => 1
111
+ }
112
+ ]
113
+ ```
114
+
115
+ </details>
116
+
117
+ ---
118
+
119
+ `.d` method shows the schema of the table. Let's see the `Order` table schema.
120
+
121
+ ```rb
122
+ [8] test(main)> Order.d
123
+ ```
124
+
125
+ <details><summary>output:</summary>
126
+
127
+ ```
128
+ D, [2024-12-12T12:32:20.100309 #2816] DEBUG -- : (10.6ms) DESCRIBE orders
129
+ D, [2024-12-12T12:32:20.107976 #2816] DEBUG -- : (6.9ms) SHOW INDEX FROM orders
130
+ # orders
131
+ +---------+-------------+------+-----+---------+-------+
132
+ | Field | Type | Null | Key | Default | Extra |
133
+ +---------+-------------+------+-----+---------+-------+
134
+ | id | varchar(64) | NO | PRI | | |
135
+ | user_id | varchar(64) | NO | | | |
136
+ | item_id | varchar(64) | NO | | | |
137
+ +---------+-------------+------+-----+---------+-------+
138
+ +--------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
139
+ | Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression |
140
+ +--------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
141
+ | orders | 0 | PRIMARY | 1 | id | A | 1 | | | | BTREE | | | YES | |
142
+ +--------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
143
+ ```
144
+
145
+ </details>
146
+
147
+ ---
148
+
149
+ `.ddl` method shows the DDL of the table.
150
+
151
+ ```rb
152
+ [9] test(main)> Order.ddl
153
+ ```
154
+
155
+ <details><summary>output:</summary>
156
+
157
+ ```
158
+ D, [2024-12-12T12:32:21.878444 #2816] DEBUG -- : (8.7ms) SHOW CREATE TABLE orders
159
+ CREATE TABLE `orders` (
160
+ `id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
161
+ `user_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
162
+ `item_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
163
+ PRIMARY KEY (`id`)
164
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin
165
+ ```
166
+
167
+ </details>
168
+
169
+ ---
170
+
171
+ `models` is globally defined to get and array of all the table classes. By `.map(&:d)` you can see the schema of all the tables.
172
+
173
+ https://github.com/nogahighland/active_record_mysql_repl/blob/main/lib/active_record_mysql_repl/extensions/global.rb#L10-L12
174
+
175
+ ```rb
176
+ [6] test(main)> puts models.map(&:d)
177
+ ```
178
+
179
+ <details><summary>output:</summary>
180
+
181
+ ```
182
+ D, [2024-12-12T01:26:36.677988 #25446] DEBUG -- : Brand Load (2.1ms) SELECT `brands`.* FROM `brands`
183
+ D, [2024-12-12T01:26:36.681307 #25446] DEBUG -- : Category Load (2.8ms) SELECT `categories`.* FROM `categories`
184
+ D, [2024-12-12T01:26:36.683967 #25446] DEBUG -- : Item Load (2.0ms) SELECT `items`.* FROM `items`
185
+ D, [2024-12-12T01:26:36.687282 #25446] DEBUG -- : Order Load (3.0ms) SELECT `orders`.* FROM `orders`
186
+ D, [2024-12-12T01:26:36.691204 #25446] DEBUG -- : UserProfile Load (3.6ms) SELECT `user_profiles`.* FROM `user_profiles`
187
+ D, [2024-12-12T01:26:36.694081 #25446] DEBUG -- : User Load (2.2ms) SELECT `users`.* FROM `users`
188
+ # users
189
+ +------------+-------------+------+-----+---------+-------+
190
+ | Field | Type | Null | Key | Default | Extra |
191
+ +------------+-------------+------+-----+---------+-------+
192
+ | id | varchar(64) | NO | PRI | | |
193
+ | login_id | varchar(64) | NO | | | |
194
+ | profile_id | int | YES | | | |
195
+ +------------+-------------+------+-----+---------+-------+
196
+ +-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
197
+ | Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression |
198
+ +-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
199
+ | users | 0 | PRIMARY | 1 | id | A | 1 | | | | BTREE | | | YES | |
200
+ +-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
201
+ # user_profiles
202
+ +-------+-------------+------+-----+---------+-------+
203
+ | Field | Type | Null | Key | Default | Extra |
204
+ +-------+-------------+------+-----+---------+-------+
205
+ | id | varchar(64) | NO | PRI | | |
206
+ | name | varchar(64) | NO | | | |
207
+ +-------+-------------+------+-----+---------+-------+
208
+ +---------------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
209
+ | Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression |
210
+ +---------------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
211
+ | user_profiles | 0 | PRIMARY | 1 | id | A | 1 | | | | BTREE | | | YES | |
212
+ +---------------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
213
+ # orders
214
+ +---------+-------------+------+-----+---------+-------+
215
+ | Field | Type | Null | Key | Default | Extra |
216
+ +---------+-------------+------+-----+---------+-------+
217
+ | id | varchar(64) | NO | PRI | | |
218
+ | user_id | varchar(64) | NO | | | |
219
+ | item_id | varchar(64) | NO | | | |
220
+ +---------+-------------+------+-----+---------+-------+
221
+ +--------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
222
+ | Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression |
223
+ +--------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
224
+ | orders | 0 | PRIMARY | 1 | id | A | 1 | | | | BTREE | | | YES | |
225
+ +--------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
226
+ # items
227
+ +-------------+-------------+------+-----+---------+-------+
228
+ | Field | Type | Null | Key | Default | Extra |
229
+ +-------------+-------------+------+-----+---------+-------+
230
+ | id | varchar(64) | NO | PRI | | |
231
+ | category_id | varchar(64) | NO | | | |
232
+ | brand_id | varchar(64) | NO | | | |
233
+ +-------------+-------------+------+-----+---------+-------+
234
+ +-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
235
+ | Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression |
236
+ +-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
237
+ | items | 0 | PRIMARY | 1 | id | A | 1 | | | | BTREE | | | YES | |
238
+ +-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
239
+ # categories
240
+ +-----------+-------------+------+-----+---------+-------+
241
+ | Field | Type | Null | Key | Default | Extra |
242
+ +-----------+-------------+------+-----+---------+-------+
243
+ | id | varchar(64) | NO | PRI | | |
244
+ | name | varchar(64) | NO | | | |
245
+ | parent_id | varchar(64) | NO | | | |
246
+ +-----------+-------------+------+-----+---------+-------+
247
+ +------------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
248
+ | Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression |
249
+ +------------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
250
+ | categories | 0 | PRIMARY | 1 | id | A | 2 | | | | BTREE | | | YES | |
251
+ +------------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
252
+ # brands
253
+ +-------+-------------+------+-----+---------+-------+
254
+ | Field | Type | Null | Key | Default | Extra |
255
+ +-------+-------------+------+-----+---------+-------+
256
+ | id | varchar(64) | NO | PRI | | |
257
+ | name | varchar(64) | NO | | | |
258
+ +-------+-------------+------+-----+---------+-------+
259
+ +--------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
260
+ | Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression |
261
+ +--------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
262
+ | brands | 0 | PRIMARY | 1 | id | A | 1 | | | | BTREE | | | YES | |
263
+ +--------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
264
+ ```
265
+
266
+ </details>
267
+
268
+ ---
269
+
270
+ To get the last record of order,
271
+
272
+ ```rb
273
+ [1] test(main)> Order.last
274
+ ```
275
+
276
+ <details><summary>output:</summary>
277
+
278
+ ```rb
279
+ D, [2024-12-11T23:45:49.743857 #96076] DEBUG -- : Order Load (5.5ms) SELECT `orders`.* FROM `orders` ORDER BY `orders`.`id` DESC LIMIT 1
280
+ #<Order:0x00000001293677c8> {
281
+ :id => "1",
282
+ :user_id => "1",
283
+ :item_id => "1"
284
+ }
285
+ ```
286
+
287
+ </details>
288
+
289
+ ---
290
+
291
+ You can get the last order's user because `user_id` column exists on `orders` table. For the same reason you can get the order's item and its category too.
292
+ All the associations are chainable by the native functionality of ActiveRecord.
293
+
294
+ ```rb
295
+ [5] test(main)> Order.last.user
296
+ ```
297
+
298
+ <details><summary>output:</summary>
299
+
300
+ ```rb
301
+ D, [2024-12-11T23:46:11.154678 #96076] DEBUG -- : Order Load (6.9ms) SELECT `orders`.* FROM `orders` ORDER BY `orders`.`id` DESC LIMIT 1
302
+ D, [2024-12-11T23:46:11.176424 #96076] DEBUG -- : User Load (2.9ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = '1' LIMIT 1
303
+ #<User:0x000000012961fda8> {
304
+ :id => "1",
305
+ :login_id => "user1",
306
+ :profile_id => 1
307
+ }
308
+ ```
309
+
310
+ </details>
311
+
312
+ ```rb
313
+ [7] test(main)> Order.last.item
314
+ ```
315
+
316
+ <details><summary>output:</summary>
317
+
318
+ ```rb
319
+ D, [2024-12-11T23:46:17.098874 #96076] DEBUG -- : Order Load (3.5ms) SELECT `orders`.* FROM `orders` ORDER BY `orders`.`id` DESC LIMIT 1
320
+ D, [2024-12-11T23:46:17.112940 #96076] DEBUG -- : Item Load (2.2ms) SELECT `items`.* FROM `items` WHERE `items`.`id` = '1' LIMIT 1
321
+ #<Item:0x000000012a416c40> {
322
+ :id => "1",
323
+ :category_id => "2",
324
+ :brand_id => "1"
325
+ }
326
+ ```
327
+
328
+ </details>
329
+
330
+ ```rb
331
+ [8] test(main)> Order.last.item.category
332
+ ```
333
+
334
+ <details><summary>output:</summary>
335
+
336
+ ```rb
337
+ D, [2024-12-11T23:46:21.104696 #96076] DEBUG -- : Order Load (2.1ms) SELECT `orders`.* FROM `orders` ORDER BY `orders`.`id` DESC LIMIT 1
338
+ D, [2024-12-11T23:46:21.106696 #96076] DEBUG -- : Item Load (1.5ms) SELECT `items`.* FROM `items` WHERE `items`.`id` = '1' LIMIT 1
339
+ D, [2024-12-11T23:46:21.117169 #96076] DEBUG -- : Category Load (2.5ms) SELECT `categories`.* FROM `categories` WHERE `categories`.`id` = '2' LIMIT 1
340
+ #<Category:0x000000012a5d7b38> {
341
+ :id => "2",
342
+ :name => "category2",
343
+ :parent_id => "1"
344
+ }
345
+ ```
346
+
347
+ </details>
348
+
349
+ ---
350
+
351
+ A category's parent is fetched from the same table because `parent_id`'s `parent` is treated by `ActiveRecordMysqlRepl` as inplicitly pointing to the same table.
352
+
353
+ ```rb
354
+ [9] test(main)> Order.last.item.category.parent
355
+ ```
356
+
357
+ <details><summary>output:</summary>
358
+
359
+ ```rb
360
+ D, [2024-12-11T23:46:23.553443 #96076] DEBUG -- : Order Load (2.0ms) SELECT `orders`.* FROM `orders` ORDER BY `orders`.`id` DESC LIMIT 1
361
+ D, [2024-12-11T23:46:23.555918 #96076] DEBUG -- : Item Load (2.1ms) SELECT `items`.* FROM `items` WHERE `items`.`id` = '1' LIMIT 1
362
+ D, [2024-12-11T23:46:23.560142 #96076] DEBUG -- : Category Load (3.8ms) SELECT `categories`.* FROM `categories` WHERE `categories`.`id` = '2' LIMIT 1
363
+ D, [2024-12-11T23:46:23.562496 #96076] DEBUG -- : Category Load (1.3ms) SELECT `categories`.* FROM `categories` WHERE `categories`.`id` = '1' LIMIT 1
364
+ #<Category:0x000000012a73dd60> {
365
+ :id => "1",
366
+ :name => "category1",
367
+ :parent_id => ""
368
+ }
369
+ ```
370
+
371
+ </details>
372
+
373
+ ---
374
+
375
+ You can get user's profile by `.profile` because the custom association is defined here
376
+
377
+ https://github.com/nogahighland/active_record_mysql_repl/blob/main/sample_config/.army.sample/associations.sample.yml#L4-L8
378
+
379
+ ```rb
380
+ [6] test(main)> Order.last.user.profile
381
+ ```
382
+
383
+ <details><summary>output:</summary>
384
+
385
+ ```rb
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
387
+ D, [2024-12-12T18:23:41.389954 #2816] DEBUG -- : User Load (2.4ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = '1' LIMIT 1
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
389
+ #<UserProfile:0x000000012330a010> {
390
+ :id => "1",
391
+ :name => "user1"
392
+ }
393
+ ```
394
+
395
+ </details>
396
+
397
+ ---
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`
401
+
402
+ https://github.com/nogahighland/active_record_mysql_repl/blob/f52f04770425723f632220bb8031bf4b402020a6/sample_config/.army.sample/associations.sample.yml#L10-L13
403
+
404
+ ```rb
405
+ [1] test(main)> User.last.login
406
+ ```
407
+
408
+ <details><summary>output:</summary>
409
+
410
+ ```rb
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>
28
417
 
29
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
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
+ ]
541
+ ```
542
+
543
+ </details>
544
+
545
+ ---
546
+
547
+ By `tab` (an alias to `tabulate`), the filtered records are shown in a table format. `.tab(orientation)` accepts the `:h(orizontal)` (default when <5 columns), and `:v(ertival)` (default when >=5 columns)
548
+
549
+ https://github.com/nogahighland/active_record_mysql_repl/blob/9f7c91774b176e1204ed434dad2867721982c660/lib/active_record_mysql_repl/extensions/object.rb#L11
550
+
551
+ ```rb
552
+ [3] test(main)> Order.all.tab
553
+ ```
554
+
555
+ <details><summary>output:</summary>
556
+
557
+ ```rb
558
+ D, [2024-12-11T23:48:46.013935 #96381] DEBUG -- : Order Load (2.2ms) SELECT `orders`.* FROM `orders`
559
+ +----+---------+---------+
560
+ | id | user_id | item_id |
561
+ +----+---------+---------+
562
+ | 1 | 1 | 1 |
563
+ +----+---------+---------+
564
+ ```
565
+
566
+ </details>
567
+
568
+ ---
569
+
570
+ ```rb
571
+ [4] test(main)> Order.all.tab(:v)
572
+ ```
573
+
574
+ <details><summary>output:</summary>
575
+
576
+ ```rb
577
+ D, [2024-12-11T23:48:49.461243 #96381] DEBUG -- : Order Load (3.2ms) SELECT `orders`.* FROM `orders`
578
+ +---------+-------+
579
+ | Name | Value |
580
+ +---------+-------+
581
+ | id | 1 |
582
+ | user_id | 1 |
583
+ | item_id | 1 |
584
+ +---------+-------+
585
+ ```
586
+
587
+ </details>
588
+
589
+ ---
590
+
591
+ `.j` is aliased to `.to_json` and `.jp` is defined to generate pretty json.
592
+
593
+ https://github.com/nogahighland/active_record_mysql_repl/blob/9f7c91774b176e1204ed434dad2867721982c660/lib/active_record_mysql_repl/extensions/object.rb#L128-L132
594
+
595
+ ```rb
596
+ [5] test(main)> Order.all.j
597
+ ```
598
+
599
+ <details><summary>output:</summary>
600
+
601
+ ```rb
602
+ D, [2024-12-11T23:48:55.267761 #96381] DEBUG -- : Order Load (4.4ms) SELECT `orders`.* FROM `orders`
603
+ [{"id":"1","user_id":"1","item_id":"1"}]
604
+ ```
605
+
606
+ </details>
607
+
608
+ ```rb
609
+ [6] test(main)> Order.all.jp
610
+ ```
611
+
612
+ <details><summary>output:</summary>
613
+
614
+ ```rb
615
+ D, [2024-12-11T23:48:57.250492 #96381] DEBUG -- : Order Load (1.8ms) SELECT `orders`.* FROM `orders`
616
+ [
617
+ {
618
+ "id": "1",
619
+ "user_id": "1",
620
+ "item_id": "1"
621
+ }
622
+ ]
623
+ ```
624
+
625
+ </details>
626
+
627
+ ---
628
+
629
+ `.csv(orientation)` is defined to generate csv format. The default orientation is same as `.tab`.
630
+
631
+ https://github.com/nogahighland/active_record_mysql_repl/blob/9f7c91774b176e1204ed434dad2867721982c660/lib/active_record_mysql_repl/extensions/object.rb#L71
632
+
633
+ ```rb
634
+ [1] test(main)> Order.all.csv
635
+ ```
636
+
637
+ <details><summary>output:</summary>
638
+
639
+ ```
640
+ D, [2024-12-12T01:29:12.844339 #26252] DEBUG -- : Order Load (2.7ms) SELECT `orders`.* FROM `orders`
641
+ id,user_id,item_id
642
+ 1,1,1
643
+ ```
644
+
645
+ </details>
646
+
647
+ ---
648
+
649
+ `.cp` is defined to copy to clipboard the receiver object's `.to_s` representation.
650
+
651
+ ```rb
652
+ [7] test(main)> Order.all.jp.cp
653
+ ```
654
+
655
+ <details><summary>output:</summary>
656
+
657
+ ```rb
658
+ D, [2024-12-11T23:48:59.443202 #96381] DEBUG -- : Order Load (2.0ms) SELECT `orders`.* FROM `orders`
659
+ true
660
+ ```
661
+
662
+ </details>
663
+
664
+ ---
665
+
666
+ `.exec_sql` is defined to execute the sql query and return the result as an array of hash.
667
+
668
+ ```rb
669
+ [8] test(main)> exec_sql('select 1')
670
+ ```
671
+
672
+ <details><summary>output:</summary>
673
+
674
+ ```rb
675
+ D, [2024-12-11T23:49:15.879841 #96381] DEBUG -- : (1.2ms) select 1
676
+ [
677
+ [0] {
678
+ "1" => 1
679
+ }
680
+ ]
681
+ ```
682
+
683
+ </details>
684
+
685
+ ---
686
+
687
+ ```rb
688
+ [9] test(main)> exec_sql('select 1').tab
689
+ ```
690
+
691
+ <details><summary>output:</summary>
692
+
693
+ ```rb
694
+ D, [2024-12-11T23:49:17.187358 #96381] DEBUG -- : (2.1ms) select 1
695
+ +---+
696
+ | 1 |
697
+ +---+
698
+ | 1 |
699
+ +---+
700
+ ```
701
+
702
+ </details>
703
+
704
+ ---
705
+
706
+ ```rb
707
+ [10] test(main)> exec_sql('select 1 as num').tab
708
+ ```
709
+
710
+ <details><summary>output:</summary>
711
+
712
+ ```
713
+ D, [2024-12-11T23:49:22.406631 #96381] DEBUG -- : (1.3ms) select 1 as num
714
+ +-----+
715
+ | num |
716
+ +-----+
717
+ | 1 |
718
+ +-----+
719
+ ```
720
+
721
+ </details>
722
+
723
+ ---
724
+
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.
726
+
727
+ - https://github.com/nogahighland/active_record_mysql_repl/blob/9f7c91774b176e1204ed434dad2867721982c660/sample_config/.army.sample.yml#L5
728
+ - https://github.com/nogahighland/active_record_mysql_repl/blob/acaa44684ea4fd135225580431417d6ccd41a988/sample_config/.army.sample/extensions/hello.rb#L1-L9
729
+
730
+ ```rb
731
+ [3] test(main)> UserProfile.last.upcase_name
732
+ ```
733
+
734
+ <details><summary>output:</summary>
735
+
736
+ ```
737
+ D, [2024-12-12T12:09:10.987656 #60808] DEBUG -- : UserProfile Load (1.7ms) SELECT `user_profiles`.* FROM `user_profiles` ORDER BY `user_profiles`.`id` DESC LIMIT 1
738
+ USER1
739
+ ```
740
+
741
+ </details>
742
+
743
+ ---
744
+
745
+ ## Development
30
746
 
31
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
747
+ TODO
32
748
 
33
749
  ## Contributing
34
750
 
35
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/active_record_mysql_repl. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/active_record_mysql_repl/blob/main/CODE_OF_CONDUCT.md).
751
+ Bug reports and pull requests are welcome on GitHub at https://github.com/nogahifhland/active_record_mysql_repl. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/nogahighland/active_record_mysql_repl/blob/main/CODE_OF_CONDUCT.md).
36
752
 
37
753
  ## License
38
754