metka 2.3.3 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/lint_code.yml +9 -6
- data/.github/workflows/lint_docs.yml +15 -16
- data/.github/workflows/release.yml +23 -0
- data/.github/workflows/tests.yml +97 -0
- data/.gitignore +16 -7
- data/.rubocop.yml +8 -16
- data/.ruby-version +1 -1
- data/Gemfile +1 -1
- data/README.md +273 -236
- data/Rakefile +9 -4
- data/assets/metka-icon.svg +23 -0
- data/assets/metka-logo.svg +28 -0
- data/benchmark/Gemfile +17 -0
- data/benchmark/README.md +170 -0
- data/benchmark/benchmark.rb +718 -0
- data/benchmark/results.sqlite.txt +131 -0
- data/benchmark/results.txt +150 -0
- data/bin/setup +7 -0
- data/docs/superpowers/plans/2026-08-18-cloud-table-naming.md +316 -0
- data/docs/superpowers/specs/2026-08-18-cloud-table-naming-design.md +105 -0
- data/forspell.dict +3 -1
- data/gemfiles/rails71.gemfile +6 -0
- data/gemfiles/rails72.gemfile +6 -0
- data/gemfiles/rails80.gemfile +6 -0
- data/gemfiles/rails81.gemfile +6 -0
- data/gemfiles/rubocop.gemfile +2 -2
- data/lib/generators/metka/strategies/index/index_generator.rb +77 -0
- data/lib/generators/metka/strategies/index/templates/migration.rb.erb +89 -0
- data/lib/generators/metka/strategies/table/table_generator.rb +83 -0
- data/lib/generators/metka/strategies/table/templates/migration.rb.erb +128 -0
- data/lib/generators/metka/strategies/table/templates/migration.sqlite.rb.erb +93 -0
- data/lib/metka/generic_parser.rb +28 -14
- data/lib/metka/model.rb +121 -51
- data/lib/metka/query_builder.rb +45 -49
- data/lib/metka/tag_list.rb +16 -8
- data/lib/metka/tags_query.rb +93 -0
- data/lib/metka/version.rb +1 -1
- data/lib/metka.rb +24 -10
- data/metka.gemspec +21 -16
- metadata +45 -92
- data/.github/workflows/specs.yml +0 -86
- data/.rspec +0 -2
- data/Gemfile.lock +0 -201
- data/gemfiles/rails52.gemfile +0 -6
- data/gemfiles/rails6.gemfile +0 -6
- data/gemfiles/rails61.gemfile +0 -6
- data/lib/generators/metka/strategies/materialized_view/materialized_view_generator.rb +0 -73
- data/lib/generators/metka/strategies/materialized_view/templates/migration.rb.erb +0 -54
- data/lib/generators/metka/strategies/view/templates/migration.rb.erb +0 -26
- data/lib/generators/metka/strategies/view/view_generator.rb +0 -70
- data/lib/metka/query_builder/all_tags_query.rb +0 -11
- data/lib/metka/query_builder/any_tags_query.rb +0 -11
- data/lib/metka/query_builder/base_query.rb +0 -48
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
Seeding 10000 posts per gem, 5 tags each, vocabulary of 100 tags
|
|
2
|
+
|
|
3
|
+
Bulk seed wall time (10000 posts):
|
|
4
|
+
metka 0.10 s
|
|
5
|
+
metka (table) 0.08 s
|
|
6
|
+
metka (index) 0.10 s
|
|
7
|
+
acts-as-taggable-on 31.43 s
|
|
8
|
+
gutentag 35.30 s
|
|
9
|
+
|
|
10
|
+
Storage (tables + indexes) for 10000 posts:
|
|
11
|
+
metka 0.62 MB
|
|
12
|
+
metka (table) 0.63 MB
|
|
13
|
+
acts-as-taggable-on 12.53 MB
|
|
14
|
+
gutentag 7.20 MB
|
|
15
|
+
metka (index) 1.39 MB
|
|
16
|
+
|
|
17
|
+
========================================================================
|
|
18
|
+
QUERY: tagged with ALL of 2 tags -> load records (~25 rows)
|
|
19
|
+
========================================================================
|
|
20
|
+
ruby 4.0.6 (2026-07-14 revision 03b6d3f889) +PRISM [x86_64-linux]
|
|
21
|
+
Warming up --------------------------------------
|
|
22
|
+
metka 47.000 i/100ms
|
|
23
|
+
metka (index) 715.000 i/100ms
|
|
24
|
+
acts-as-taggable-on 326.000 i/100ms
|
|
25
|
+
gutentag 197.000 i/100ms
|
|
26
|
+
Calculating -------------------------------------
|
|
27
|
+
metka 398.137 (± 2.3%) i/s (2.51 ms/i) - 2.021k in 5.076143s
|
|
28
|
+
metka (index) 7.199k (± 2.9%) i/s (138.90 μs/i) - 36.465k in 5.064972s
|
|
29
|
+
acts-as-taggable-on 3.189k (± 3.3%) i/s (313.59 μs/i) - 15.974k in 5.009333s
|
|
30
|
+
gutentag 1.965k (± 4.4%) i/s (508.87 μs/i) - 9.850k in 5.012329s
|
|
31
|
+
|
|
32
|
+
Comparison:
|
|
33
|
+
metka (index): 7199.4 i/s
|
|
34
|
+
acts-as-taggable-on: 3188.8 i/s - 2.26x slower
|
|
35
|
+
gutentag: 1965.2 i/s - 3.66x slower
|
|
36
|
+
metka: 398.1 i/s - 18.08x slower
|
|
37
|
+
|
|
38
|
+
========================================================================
|
|
39
|
+
QUERY: tagged with ANY of 2 tags -> count
|
|
40
|
+
========================================================================
|
|
41
|
+
ruby 4.0.6 (2026-07-14 revision 03b6d3f889) +PRISM [x86_64-linux]
|
|
42
|
+
Warming up --------------------------------------
|
|
43
|
+
metka 38.000 i/100ms
|
|
44
|
+
metka (index) 397.000 i/100ms
|
|
45
|
+
acts-as-taggable-on 41.000 i/100ms
|
|
46
|
+
gutentag 199.000 i/100ms
|
|
47
|
+
Calculating -------------------------------------
|
|
48
|
+
metka 379.318 (± 2.1%) i/s (2.64 ms/i) - 1.900k in 5.008996s
|
|
49
|
+
metka (index) 4.113k (± 3.7%) i/s (243.13 μs/i) - 20.644k in 5.019114s
|
|
50
|
+
acts-as-taggable-on 410.036 (± 2.4%) i/s (2.44 ms/i) - 2.091k in 5.099553s
|
|
51
|
+
gutentag 1.900k (± 6.4%) i/s (526.34 μs/i) - 9.552k in 5.027612s
|
|
52
|
+
|
|
53
|
+
Comparison:
|
|
54
|
+
metka (index): 4113.1 i/s
|
|
55
|
+
gutentag: 1899.9 i/s - 2.16x slower
|
|
56
|
+
acts-as-taggable-on: 410.0 i/s - 10.03x slower
|
|
57
|
+
metka: 379.3 i/s - 10.84x slower
|
|
58
|
+
|
|
59
|
+
========================================================================
|
|
60
|
+
TAG CLOUD: tag -> usage count across all 10000 posts
|
|
61
|
+
========================================================================
|
|
62
|
+
ruby 4.0.6 (2026-07-14 revision 03b6d3f889) +PRISM [x86_64-linux]
|
|
63
|
+
Warming up --------------------------------------
|
|
64
|
+
metka 11.000 i/100ms
|
|
65
|
+
metka (table) 1.275k i/100ms
|
|
66
|
+
acts-as-taggable-on 11.000 i/100ms
|
|
67
|
+
gutentag 8.000 i/100ms
|
|
68
|
+
Calculating -------------------------------------
|
|
69
|
+
metka 110.804 (± 2.7%) i/s (9.02 ms/i) - 561.000 in 5.062986s
|
|
70
|
+
metka (table) 12.696k (± 2.2%) i/s (78.76 μs/i) - 63.750k in 5.021086s
|
|
71
|
+
acts-as-taggable-on 110.668 (± 3.6%) i/s (9.04 ms/i) - 561.000 in 5.069223s
|
|
72
|
+
gutentag 87.688 (± 2.3%) i/s (11.40 ms/i) - 440.000 in 5.017818s
|
|
73
|
+
|
|
74
|
+
Comparison:
|
|
75
|
+
metka (table): 12696.5 i/s
|
|
76
|
+
metka: 110.8 i/s - 114.58x slower
|
|
77
|
+
acts-as-taggable-on: 110.7 i/s - 114.73x slower
|
|
78
|
+
gutentag: 87.7 i/s - 144.79x slower
|
|
79
|
+
|
|
80
|
+
========================================================================
|
|
81
|
+
WRITE: create one post with 5 tags
|
|
82
|
+
========================================================================
|
|
83
|
+
ruby 4.0.6 (2026-07-14 revision 03b6d3f889) +PRISM [x86_64-linux]
|
|
84
|
+
Warming up --------------------------------------
|
|
85
|
+
metka 776.000 i/100ms
|
|
86
|
+
metka (table) 683.000 i/100ms
|
|
87
|
+
metka (index) 522.000 i/100ms
|
|
88
|
+
acts-as-taggable-on 27.000 i/100ms
|
|
89
|
+
gutentag 28.000 i/100ms
|
|
90
|
+
Calculating -------------------------------------
|
|
91
|
+
metka 7.783k (± 3.7%) i/s (128.48 μs/i) - 39.576k in 5.084760s
|
|
92
|
+
metka (table) 6.895k (± 4.4%) i/s (145.04 μs/i) - 34.833k in 5.052142s
|
|
93
|
+
metka (index) 5.349k (± 5.0%) i/s (186.96 μs/i) - 27.144k in 5.074779s
|
|
94
|
+
acts-as-taggable-on 268.380 (± 5.2%) i/s (3.73 ms/i) - 1.350k in 5.030173s
|
|
95
|
+
gutentag 284.410 (± 6.3%) i/s (3.52 ms/i) - 1.428k in 5.020920s
|
|
96
|
+
|
|
97
|
+
Comparison:
|
|
98
|
+
metka: 7783.3 i/s
|
|
99
|
+
metka (table): 6894.7 i/s - 1.13x slower
|
|
100
|
+
metka (index): 5348.8 i/s - 1.46x slower
|
|
101
|
+
gutentag: 284.4 i/s - 27.37x slower
|
|
102
|
+
acts-as-taggable-on: 268.4 i/s - 29.00x slower
|
|
103
|
+
|
|
104
|
+
========================================================================
|
|
105
|
+
WRITE: replace the tag list of an existing post
|
|
106
|
+
========================================================================
|
|
107
|
+
ruby 4.0.6 (2026-07-14 revision 03b6d3f889) +PRISM [x86_64-linux]
|
|
108
|
+
Warming up --------------------------------------
|
|
109
|
+
metka 1.199k i/100ms
|
|
110
|
+
metka (table) 1.200k i/100ms
|
|
111
|
+
metka (index) 1.196k i/100ms
|
|
112
|
+
acts-as-taggable-on 19.000 i/100ms
|
|
113
|
+
gutentag 19.000 i/100ms
|
|
114
|
+
Calculating -------------------------------------
|
|
115
|
+
metka 12.670k (± 4.2%) i/s (78.93 μs/i) - 63.547k in 5.015591s
|
|
116
|
+
metka (table) 12.604k (± 4.1%) i/s (79.34 μs/i) - 63.600k in 5.045863s
|
|
117
|
+
metka (index) 12.696k (± 2.7%) i/s (78.77 μs/i) - 64.584k in 5.087111s
|
|
118
|
+
acts-as-taggable-on 416.435 (±90.3%) i/s (2.40 ms/i) - 2.090k in 5.018792s
|
|
119
|
+
gutentag 808.708 (± Inf%) i/s (1.24 ms/i) - 4.047k in 5.004277s
|
|
120
|
+
|
|
121
|
+
Comparison:
|
|
122
|
+
metka (index): 12695.6 i/s
|
|
123
|
+
metka: 12669.9 i/s - same-ish: difference falls within error
|
|
124
|
+
metka (table): 12604.4 i/s - same-ish: difference falls within error
|
|
125
|
+
gutentag: 808.7 i/s - 15.70x slower
|
|
126
|
+
acts-as-taggable-on: 416.4 i/s - 30.49x slower
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
Strategy integrity after all suites (0 = aggregate matches a live aggregation):
|
|
130
|
+
table mismatching tags: 0
|
|
131
|
+
index mismatching pairs: 0
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
Seeding 10000 posts per gem, 5 tags each, vocabulary of 100 tags
|
|
2
|
+
|
|
3
|
+
Bulk seed wall time (10000 posts):
|
|
4
|
+
metka 0.21 s
|
|
5
|
+
metka (table) 0.17 s
|
|
6
|
+
acts-as-taggable-array-on 0.17 s
|
|
7
|
+
tag_columns 0.16 s
|
|
8
|
+
acts-as-taggable-on 45.57 s
|
|
9
|
+
gutentag 50.24 s
|
|
10
|
+
|
|
11
|
+
Storage (tables + indexes) for 10000 posts:
|
|
12
|
+
metka 2.68 MB
|
|
13
|
+
metka (table) 2.75 MB
|
|
14
|
+
acts-as-taggable-array-on 2.68 MB
|
|
15
|
+
tag_columns 2.68 MB
|
|
16
|
+
acts-as-taggable-on 18.17 MB
|
|
17
|
+
gutentag 10.97 MB
|
|
18
|
+
|
|
19
|
+
========================================================================
|
|
20
|
+
QUERY: tagged with ALL of 2 tags -> load records (~25 rows)
|
|
21
|
+
========================================================================
|
|
22
|
+
ruby 4.0.6 (2026-07-14 revision 03b6d3f889) +PRISM [x86_64-linux]
|
|
23
|
+
Warming up --------------------------------------
|
|
24
|
+
metka 597.000 i/100ms
|
|
25
|
+
acts-as-taggable-array-on 757.000 i/100ms
|
|
26
|
+
tag_columns 101.000 i/100ms
|
|
27
|
+
acts-as-taggable-on 213.000 i/100ms
|
|
28
|
+
gutentag 124.000 i/100ms
|
|
29
|
+
Calculating -------------------------------------
|
|
30
|
+
metka 6.003k (± 8.2%) i/s (166.59 μs/i) - 30.447k in 5.072240s
|
|
31
|
+
acts-as-taggable-array-on 6.725k (± 8.6%) i/s (148.69 μs/i) - 34.065k in 5.065229s
|
|
32
|
+
tag_columns 985.914 (± 3.1%) i/s (1.01 ms/i) - 4.949k in 5.019708s
|
|
33
|
+
acts-as-taggable-on 2.292k (± 6.7%) i/s (436.38 μs/i) - 11.502k in 5.019218s
|
|
34
|
+
gutentag 1.299k (± 8.8%) i/s (769.73 μs/i) - 6.572k in 5.058649s
|
|
35
|
+
|
|
36
|
+
Comparison:
|
|
37
|
+
acts-as-taggable-array-on: 6725.3 i/s
|
|
38
|
+
metka: 6002.7 i/s - same-ish: difference falls within error
|
|
39
|
+
acts-as-taggable-on: 2291.6 i/s - 2.93x slower
|
|
40
|
+
gutentag: 1299.2 i/s - 5.18x slower
|
|
41
|
+
tag_columns: 985.9 i/s - 6.82x slower
|
|
42
|
+
|
|
43
|
+
========================================================================
|
|
44
|
+
QUERY: tagged with ANY of 2 tags -> count
|
|
45
|
+
========================================================================
|
|
46
|
+
ruby 4.0.6 (2026-07-14 revision 03b6d3f889) +PRISM [x86_64-linux]
|
|
47
|
+
Warming up --------------------------------------
|
|
48
|
+
metka 421.000 i/100ms
|
|
49
|
+
acts-as-taggable-array-on 466.000 i/100ms
|
|
50
|
+
tag_columns 66.000 i/100ms
|
|
51
|
+
acts-as-taggable-on 74.000 i/100ms
|
|
52
|
+
gutentag 106.000 i/100ms
|
|
53
|
+
Calculating -------------------------------------
|
|
54
|
+
metka 4.575k (±10.3%) i/s (218.60 μs/i) - 23.155k in 5.061751s
|
|
55
|
+
acts-as-taggable-array-on 4.479k (±12.4%) i/s (223.24 μs/i) - 22.834k in 5.097452s
|
|
56
|
+
tag_columns 678.434 (± 2.7%) i/s (1.47 ms/i) - 3.432k in 5.058707s
|
|
57
|
+
acts-as-taggable-on 788.456 (± 2.7%) i/s (1.27 ms/i) - 3.996k in 5.068134s
|
|
58
|
+
gutentag 1.027k (± 7.5%) i/s (973.91 μs/i) - 5.194k in 5.058492s
|
|
59
|
+
|
|
60
|
+
Comparison:
|
|
61
|
+
metka: 4574.5 i/s
|
|
62
|
+
acts-as-taggable-array-on: 4479.5 i/s - same-ish: difference falls within error
|
|
63
|
+
gutentag: 1026.8 i/s - 4.46x slower
|
|
64
|
+
acts-as-taggable-on: 788.5 i/s - 5.80x slower
|
|
65
|
+
tag_columns: 678.4 i/s - 6.74x slower
|
|
66
|
+
|
|
67
|
+
========================================================================
|
|
68
|
+
TAG CLOUD: tag -> usage count across all 10000 posts
|
|
69
|
+
========================================================================
|
|
70
|
+
ruby 4.0.6 (2026-07-14 revision 03b6d3f889) +PRISM [x86_64-linux]
|
|
71
|
+
Warming up --------------------------------------
|
|
72
|
+
metka 21.000 i/100ms
|
|
73
|
+
metka (table) 986.000 i/100ms
|
|
74
|
+
acts-as-taggable-array-on 20.000 i/100ms
|
|
75
|
+
tag_columns 19.000 i/100ms
|
|
76
|
+
acts-as-taggable-on 12.000 i/100ms
|
|
77
|
+
gutentag 16.000 i/100ms
|
|
78
|
+
Calculating -------------------------------------
|
|
79
|
+
metka 212.110 (± 2.8%) i/s (4.71 ms/i) - 1.071k in 5.049256s
|
|
80
|
+
metka (table) 9.025k (± 4.6%) i/s (110.81 μs/i) - 45.356k in 5.025682s
|
|
81
|
+
acts-as-taggable-array-on 203.695 (± 2.0%) i/s (4.91 ms/i) - 1.020k in 5.007496s
|
|
82
|
+
tag_columns 197.664 (± 2.0%) i/s (5.06 ms/i) - 1.007k in 5.094517s
|
|
83
|
+
acts-as-taggable-on 127.192 (± 3.1%) i/s (7.86 ms/i) - 648.000 in 5.094650s
|
|
84
|
+
gutentag 161.495 (± 3.1%) i/s (6.19 ms/i) - 816.000 in 5.052782s
|
|
85
|
+
|
|
86
|
+
Comparison:
|
|
87
|
+
metka (table): 9024.8 i/s
|
|
88
|
+
metka: 212.1 i/s - 42.55x slower
|
|
89
|
+
acts-as-taggable-array-on: 203.7 i/s - 44.31x slower
|
|
90
|
+
tag_columns: 197.7 i/s - 45.66x slower
|
|
91
|
+
gutentag: 161.5 i/s - 55.88x slower
|
|
92
|
+
acts-as-taggable-on: 127.2 i/s - 70.95x slower
|
|
93
|
+
|
|
94
|
+
========================================================================
|
|
95
|
+
WRITE: create one post with 5 tags
|
|
96
|
+
========================================================================
|
|
97
|
+
ruby 4.0.6 (2026-07-14 revision 03b6d3f889) +PRISM [x86_64-linux]
|
|
98
|
+
Warming up --------------------------------------
|
|
99
|
+
metka 176.000 i/100ms
|
|
100
|
+
metka (table) 156.000 i/100ms
|
|
101
|
+
acts-as-taggable-array-on 154.000 i/100ms
|
|
102
|
+
tag_columns 157.000 i/100ms
|
|
103
|
+
acts-as-taggable-on 19.000 i/100ms
|
|
104
|
+
gutentag 18.000 i/100ms
|
|
105
|
+
Calculating -------------------------------------
|
|
106
|
+
metka 1.619k (± 6.9%) i/s (617.73 μs/i) - 8.096k in 5.001165s
|
|
107
|
+
metka (table) 1.495k (± 5.8%) i/s (668.77 μs/i) - 7.488k in 5.007742s
|
|
108
|
+
acts-as-taggable-array-on 1.633k (± 5.8%) i/s (612.19 μs/i) - 8.316k in 5.090931s
|
|
109
|
+
tag_columns 1.599k (± 7.7%) i/s (625.58 μs/i) - 8.007k in 5.009026s
|
|
110
|
+
acts-as-taggable-on 203.548 (± 9.3%) i/s (4.91 ms/i) - 1.026k in 5.040586s
|
|
111
|
+
gutentag 196.158 (±11.7%) i/s (5.10 ms/i) - 990.000 in 5.046963s
|
|
112
|
+
|
|
113
|
+
Comparison:
|
|
114
|
+
acts-as-taggable-array-on: 1633.5 i/s
|
|
115
|
+
metka: 1618.8 i/s - same-ish: difference falls within error
|
|
116
|
+
tag_columns: 1598.5 i/s - same-ish: difference falls within error
|
|
117
|
+
metka (table): 1495.3 i/s - same-ish: difference falls within error
|
|
118
|
+
acts-as-taggable-on: 203.5 i/s - 8.03x slower
|
|
119
|
+
gutentag: 196.2 i/s - 8.33x slower
|
|
120
|
+
|
|
121
|
+
========================================================================
|
|
122
|
+
WRITE: replace the tag list of an existing post
|
|
123
|
+
========================================================================
|
|
124
|
+
ruby 4.0.6 (2026-07-14 revision 03b6d3f889) +PRISM [x86_64-linux]
|
|
125
|
+
Warming up --------------------------------------
|
|
126
|
+
metka 140.000 i/100ms
|
|
127
|
+
metka (table) 121.000 i/100ms
|
|
128
|
+
acts-as-taggable-array-on 145.000 i/100ms
|
|
129
|
+
tag_columns 147.000 i/100ms
|
|
130
|
+
acts-as-taggable-on 13.000 i/100ms
|
|
131
|
+
gutentag 11.000 i/100ms
|
|
132
|
+
Calculating -------------------------------------
|
|
133
|
+
metka 8.293k (± 9.2%) i/s (120.58 μs/i) - 41.580k in 5.013907s
|
|
134
|
+
metka (table) 8.131k (± 9.6%) i/s (122.98 μs/i) - 40.656k in 4.999930s
|
|
135
|
+
acts-as-taggable-array-on 7.766k (± 9.6%) i/s (128.77 μs/i) - 38.860k in 5.003909s
|
|
136
|
+
tag_columns 7.396k (± 8.2%) i/s (135.22 μs/i) - 37.044k in 5.008958s
|
|
137
|
+
acts-as-taggable-on 189.648 (± Inf%) i/s (5.27 ms/i) - 962.000 in 5.072563s
|
|
138
|
+
gutentag 182.869 (± Inf%) i/s (5.47 ms/i) - 924.000 in 5.052787s
|
|
139
|
+
|
|
140
|
+
Comparison:
|
|
141
|
+
metka: 8292.9 i/s
|
|
142
|
+
metka (table): 8131.3 i/s - same-ish: difference falls within error
|
|
143
|
+
acts-as-taggable-array-on: 7765.9 i/s - same-ish: difference falls within error
|
|
144
|
+
tag_columns: 7395.6 i/s - same-ish: difference falls within error
|
|
145
|
+
acts-as-taggable-on: 189.6 i/s - 43.73x slower
|
|
146
|
+
gutentag: 182.9 i/s - 45.35x slower
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
Strategy integrity after all suites (0 = aggregate matches a live UNNEST..GROUP BY):
|
|
150
|
+
table mismatching tags: 0
|
data/bin/setup
CHANGED
|
@@ -6,3 +6,10 @@ gem install bundler --conservative
|
|
|
6
6
|
bundle check || bundle install
|
|
7
7
|
|
|
8
8
|
RAILS_ENV=test bundle exec rake dummy:db:migrate:reset
|
|
9
|
+
|
|
10
|
+
# SQLite resets by deleting the file, not through db:migrate:reset: the rake
|
|
11
|
+
# composite drops the file while the booted app still holds an open handle, so
|
|
12
|
+
# the migrations land on the unlinked inode and vanish when the process exits.
|
|
13
|
+
# A fresh process migrating into a fresh file has no such problem.
|
|
14
|
+
rm -f test/dummy/db/metka_test.sqlite3
|
|
15
|
+
DB=sqlite RAILS_ENV=test bundle exec rake dummy:db:migrate
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
# Cloud Table Naming Implementation Plan
|
|
2
|
+
|
|
3
|
+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
4
|
+
|
|
5
|
+
**Goal:** Rename the table-strategy generator's derived summary-table name from `tagged_<source_table>` to `<source_table>_<columns>_cloud` (e.g. `songs_tags_cloud`), matching the index strategy's `<source_table>_<column>_index` pattern.
|
|
6
|
+
|
|
7
|
+
**Architecture:** One code change — the `table_name` derivation in the table generator. The ERB migration templates interpolate `<%= table_name %>` everywhere, so they need no changes. Everything else is fixtures and docs modeling the new convention: generator tests, the dummy app's hand-written equivalent migration/model/test, the README, and the benchmark.
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** Ruby gem (metka), Rails generators, Minitest, dummy Rails app under `test/dummy` running on PostgreSQL or SQLite (`DB=sqlite` env var switches the adapter).
|
|
10
|
+
|
|
11
|
+
**Spec:** `docs/superpowers/specs/2026-08-18-cloud-table-naming-design.md`
|
|
12
|
+
|
|
13
|
+
**Reference — the naming rule:** derived name is `#{source_table_name}_#{source_columns.join("_and_")}_cloud`, uniformly. No special case for the default `tags` column. `--table-name` still overrides. Migration name stays `create_#{table_name}_table`.
|
|
14
|
+
|
|
15
|
+
**Reference — what does NOT change:** trigger names (`metka_ins_on_<source_table>_<columns>` — derived from the source table), the `tag_name`/`taggings_count` columns, both ERB templates, all runtime code in `lib/metka/`, and the historical benchmark results files (`benchmark/results*.txt` are recorded outputs of past runs — leave them).
|
|
16
|
+
|
|
17
|
+
**Test environment setup (once, before Task 1):**
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
cd /home/igor/Work/metka/.claude/worktrees/tagging-table-naming-5cd0c9
|
|
21
|
+
bundle install
|
|
22
|
+
rm -f test/dummy/db/*.sqlite3 test/dummy/db/schema.rb
|
|
23
|
+
RAILS_ENV=test DB=sqlite bundle exec rake dummy:db:migrate
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Always prefix rake/test commands with `RAILS_ENV=test` — the dummy app only configures the `test` environment. Do NOT use `dummy:db:migrate:reset` under SQLite: locally it leaves an empty database and dumps a stale empty `schema.rb` that then makes `maintain_test_schema` wipe the DB on test boot (CI's SQLite job uses plain `dummy:db:migrate` for the same reason). To reset, delete the `.sqlite3` file and any `test/dummy/db/schema.rb`, then `dummy:db:migrate`.
|
|
27
|
+
|
|
28
|
+
SQLite always works locally. PostgreSQL runs the other template branch; if a local server is available (`psql -h localhost -U postgres -c 'select 1'` succeeds), also run the PG variants of each verification step (`bundle exec rake dummy:db:create dummy:db:migrate:reset`, no `DB` env var). If PG is unavailable, say so in the final report — CI runs both.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
### Task 1: Generator derivation (TDD)
|
|
33
|
+
|
|
34
|
+
**Files:**
|
|
35
|
+
- Modify: `test/generators/strategies/table_generator_test.rb`
|
|
36
|
+
- Modify: `lib/generators/metka/strategies/table/table_generator.rb:62-67`
|
|
37
|
+
|
|
38
|
+
- [ ] **Step 1: Update the generator test's expected names**
|
|
39
|
+
|
|
40
|
+
In `test/generators/strategies/table_generator_test.rb`:
|
|
41
|
+
|
|
42
|
+
1. Change the migration constant (line 8):
|
|
43
|
+
|
|
44
|
+
```ruby
|
|
45
|
+
MIGRATION = "db/migrate/create_notes_tags_cloud_table.rb"
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
2. Replace every `tagged_notes` in the assertion regexes with `notes_tags_cloud`. That is exactly these lines: 24 (`CREATE TABLE`), 28 (`INSERT INTO ... (tag_name, taggings_count)`), 44 (`DROP TABLE IF EXISTS`), 73 (`INSERT INTO` inside the lock-ordering test), 80–82 (`CREATE OR REPLACE FUNCTION metka_ins/upd/del_notes_tags_cloud`), 93–95 (`DROP FUNCTION IF EXISTS metka_ins/upd/del_notes_tags_cloud`). Do NOT touch the trigger-name assertions (lines 32–34, 38–40, `metka_ins_on_notes` etc.) — trigger names derive from the source table and stay the same.
|
|
49
|
+
|
|
50
|
+
3. Add two new tests at the end of the class (before the closing `end`, after the `if SQLITE`/`else` block), covering the multi-column derivation and the override — both previously untested:
|
|
51
|
+
|
|
52
|
+
```ruby
|
|
53
|
+
test "derives the name from the source columns when given" do
|
|
54
|
+
prepare_destination
|
|
55
|
+
run_generator [ "--source-table-name=notes", "--source-columns=tags", "genres" ]
|
|
56
|
+
|
|
57
|
+
assert_migration "db/migrate/create_notes_tags_and_genres_cloud_table.rb",
|
|
58
|
+
/CREATE TABLE notes_tags_and_genres_cloud/i
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
test "respects an explicit --table-name" do
|
|
62
|
+
prepare_destination
|
|
63
|
+
run_generator [ "--source-table-name=notes", "--table-name=note_cloud" ]
|
|
64
|
+
|
|
65
|
+
assert_migration "db/migrate/create_note_cloud_table.rb", /CREATE TABLE note_cloud/i
|
|
66
|
+
end
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
- [ ] **Step 2: Run the generator tests to verify they fail**
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
DB=sqlite bundle exec ruby -Itest test/generators/strategies/table_generator_test.rb
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Expected: FAIL — every test errors or fails because the generator still emits `db/migrate/*_create_tagged_notes_table.rb`, so `assert_migration "db/migrate/create_notes_tags_cloud_table.rb"` finds no migration.
|
|
76
|
+
|
|
77
|
+
- [ ] **Step 3: Change the derivation**
|
|
78
|
+
|
|
79
|
+
In `lib/generators/metka/strategies/table/table_generator.rb`, replace the `table_name` method (lines 62–67):
|
|
80
|
+
|
|
81
|
+
```ruby
|
|
82
|
+
def table_name
|
|
83
|
+
return options[:table_name] if options[:table_name]
|
|
84
|
+
|
|
85
|
+
"#{source_table_name}_#{source_columns_names}_cloud"
|
|
86
|
+
end
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
(The `columns_sequence` local and its default-columns special case disappear entirely; `source_columns_names` already joins with `_and_`.)
|
|
90
|
+
|
|
91
|
+
Also update the generator's `desc` LONGDESC if it mentions the derived name — it currently doesn't (it only shows the flags), so no change expected there; just confirm.
|
|
92
|
+
|
|
93
|
+
- [ ] **Step 4: Run the generator tests to verify they pass**
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
DB=sqlite bundle exec ruby -Itest test/generators/strategies/table_generator_test.rb
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Expected: PASS (SQLite runs 11 tests after the two added in Step 1). If PostgreSQL is available, also run without `DB=sqlite` — it exercises the PG branch (function-name assertions): PASS, 13 tests.
|
|
100
|
+
|
|
101
|
+
- [ ] **Step 5: Commit**
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
git add lib/generators/metka/strategies/table/table_generator.rb test/generators/strategies/table_generator_test.rb
|
|
105
|
+
git commit -m "feat(generator): Derive cloud table name as <source>_<columns>_cloud
|
|
106
|
+
|
|
107
|
+
The table strategy summary table default changes from
|
|
108
|
+
tagged_<source_table> to <source_table>_<columns>_cloud
|
|
109
|
+
(songs_tags_cloud), mirroring the index strategy's
|
|
110
|
+
<source_table>_<column>_index naming. Generation-time only:
|
|
111
|
+
existing installs keep their tagged_* tables; --table-name
|
|
112
|
+
still overrides.
|
|
113
|
+
|
|
114
|
+
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>"
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
### Task 2: Dummy app models the new convention
|
|
120
|
+
|
|
121
|
+
**Files:**
|
|
122
|
+
- Rename: `test/dummy/db/migrate/11_create_tagged_table_posts_table.rb` → `test/dummy/db/migrate/11_create_table_posts_tags_cloud_table.rb`
|
|
123
|
+
- Rename: `test/dummy/app/models/tagged_table_post.rb` → `test/dummy/app/models/table_posts_tags_cloud.rb`
|
|
124
|
+
- Rename: `test/metka/tagged_table_post_test.rb` → `test/metka/table_posts_tags_cloud_test.rb`
|
|
125
|
+
|
|
126
|
+
The dummy source table is `table_posts` (migration 10), so the new summary table name is `table_posts_tags_cloud`.
|
|
127
|
+
|
|
128
|
+
- [ ] **Step 1: Rename and edit the dummy migration**
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
git mv test/dummy/db/migrate/11_create_tagged_table_posts_table.rb test/dummy/db/migrate/11_create_table_posts_tags_cloud_table.rb
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Then in the renamed file:
|
|
135
|
+
- Class name: `CreateTaggedTablePostsTable` → `CreateTablePostsTagsCloudTable` (must match the new filename or Rails raises on migrate).
|
|
136
|
+
- Replace all `tagged_table_posts` → `table_posts_tags_cloud` (both the SQLite and PostgreSQL branches: CREATE TABLE, INSERT INTO, UPDATE, DELETE FROM, DROP TABLE, and the PG function names `metka_ins/upd/del_tagged_table_posts` → `metka_ins/upd/del_table_posts_tags_cloud`, including the `DROP FUNCTION IF EXISTS` lines).
|
|
137
|
+
- Leave trigger names (`metka_*_on_table_posts*`) untouched.
|
|
138
|
+
|
|
139
|
+
Sanity check — this file must end up matching what the updated generator emits. After editing, verify with:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
grep -c "tagged" test/dummy/db/migrate/11_create_table_posts_tags_cloud_table.rb
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Expected: `0`.
|
|
146
|
+
|
|
147
|
+
- [ ] **Step 2: Rename and edit the dummy model**
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
git mv test/dummy/app/models/tagged_table_post.rb test/dummy/app/models/table_posts_tags_cloud.rb
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
New content (the explicit `table_name` is required — Rails infers the plural `table_posts_tags_clouds` from the class name):
|
|
154
|
+
|
|
155
|
+
```ruby
|
|
156
|
+
# frozen_string_literal: true
|
|
157
|
+
|
|
158
|
+
class TablePostsTagsCloud < ActiveRecord::Base
|
|
159
|
+
self.table_name = "table_posts_tags_cloud"
|
|
160
|
+
end
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
- [ ] **Step 3: Rename and edit the test**
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
git mv test/metka/tagged_table_post_test.rb test/metka/table_posts_tags_cloud_test.rb
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
In the renamed file: class `TaggedTablePostTest` → `TablePostsTagsCloudTest`, and every `TaggedTablePost` constant reference → `TablePostsTagsCloud` (10 lines contain it, including the class name; `grep -n TaggedTablePost` afterwards must return nothing). Everything else (fixtures, `TablePost` writes, trigger assertions) stays.
|
|
170
|
+
|
|
171
|
+
- [ ] **Step 4: Recreate the dummy DB and run the suite**
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
rm -f test/dummy/db/*.sqlite3 test/dummy/db/schema.rb
|
|
175
|
+
RAILS_ENV=test DB=sqlite bundle exec rake dummy:db:migrate
|
|
176
|
+
RAILS_ENV=test DB=sqlite bundle exec ruby -Itest test/metka/table_posts_tags_cloud_test.rb
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Expected: migration succeeds (the renamed migration runs under its new class name); test PASS. If PostgreSQL is available, repeat with `bundle exec rake dummy:db:migrate:reset` / the test command without `DB=sqlite`.
|
|
180
|
+
|
|
181
|
+
- [ ] **Step 5: Commit**
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
git add -A test/dummy test/metka/table_posts_tags_cloud_test.rb
|
|
185
|
+
git commit -m "test(dummy): Rename summary table fixtures to table_posts_tags_cloud
|
|
186
|
+
|
|
187
|
+
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>"
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
### Task 3: README
|
|
193
|
+
|
|
194
|
+
**Files:**
|
|
195
|
+
- Modify: `README.md` (table-strategy section, ~lines 336–410)
|
|
196
|
+
|
|
197
|
+
- [ ] **Step 1: Update the derived-name examples and model example**
|
|
198
|
+
|
|
199
|
+
Exact edits (line numbers as of commit 281f0ea):
|
|
200
|
+
|
|
201
|
+
1. Line 345 — the migration-template link: `test/dummy/db/migrate/11_create_tagged_table_posts_table.rb` → `test/dummy/db/migrate/11_create_table_posts_tags_cloud_table.rb` (both the link target and any visible filename).
|
|
202
|
+
2. Line 347: `the resulting `tagged_notes` table` → `the resulting `notes_tags_cloud` table`.
|
|
203
|
+
3. Line 357 — replace the model sentence with one that carries the pluralization gotcha:
|
|
204
|
+
|
|
205
|
+
```markdown
|
|
206
|
+
And you can also create a model to work with the table as with a Rails model — set the table name explicitly, since Rails would infer the plural `notes_tags_clouds` from the class name:
|
|
207
|
+
|
|
208
|
+
```ruby
|
|
209
|
+
class NotesTagsCloud < ApplicationRecord
|
|
210
|
+
self.table_name = "notes_tags_cloud"
|
|
211
|
+
end
|
|
212
|
+
```
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
4. Lines 378–385 — the `TaggedSong` model example in "Migrating from on-the-fly tag clouds". This is not a plain rename: like item 3, the new class name needs an explicit table name. Replace the code block and the sentence after it:
|
|
216
|
+
|
|
217
|
+
```markdown
|
|
218
|
+
```ruby
|
|
219
|
+
class SongsTagsCloud < ActiveRecord::Base
|
|
220
|
+
self.table_name = "songs_tags_cloud"
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
Song.tag_cloud # before
|
|
224
|
+
SongsTagsCloud.pluck(:tag_name, :taggings_count) # after
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Sorting and limiting that used to happen in Ruby becomes a normal query: `SongsTagsCloud.order(taggings_count: :desc).limit(50)`.
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
5. Lines 392–406 (the reseeding SQL in the same section): every `tagged_songs` → `songs_tags_cloud` (4 occurrences: two `DELETE FROM`, two `INSERT INTO`). Read the surrounding prose of that section and update any other derived-name mentions to match.
|
|
231
|
+
|
|
232
|
+
- [ ] **Step 2: Verify no stale names remain**
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
grep -n "tagged_notes\|tagged_songs\|tagged_table_posts\|TaggedNote\|TaggedTablePost\|TaggedSong" README.md
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Expected: no output. (`tagged_with` and prose like "tagged columns" are unrelated and must remain.)
|
|
239
|
+
|
|
240
|
+
- [ ] **Step 3: Commit**
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
git add README.md
|
|
244
|
+
git commit -m "docs: Document <source>_<columns>_cloud naming in README
|
|
245
|
+
|
|
246
|
+
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>"
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
### Task 4: Benchmark
|
|
252
|
+
|
|
253
|
+
**Files:**
|
|
254
|
+
- Modify: `benchmark/benchmark.rb`
|
|
255
|
+
|
|
256
|
+
- [ ] **Step 1: Rename the benchmark's table-strategy table**
|
|
257
|
+
|
|
258
|
+
In `benchmark/benchmark.rb`, replace:
|
|
259
|
+
- every `tagged_metka_table_posts` → `metka_table_posts_tags_cloud` (SQL strings at lines ~172–341, the model's `self.table_name` at 378, `relation_size` at 480, `cloud_mismatches` at 705);
|
|
260
|
+
- the PG function names `metka_ins/upd/del_tagged_metka_table_posts` → `metka_ins/upd/del_metka_table_posts_tags_cloud`;
|
|
261
|
+
- the model class `TaggedMetkaTablePost` (line 377) → `MetkaTablePostsTagsCloud`, and its one usage (line ~558, the tag-cloud read benchmark).
|
|
262
|
+
|
|
263
|
+
Leave `benchmark/results.txt` / `benchmark/results.sqlite.txt` untouched — they are recorded outputs of past runs. `benchmark/README.md` never names the table literally; no change.
|
|
264
|
+
|
|
265
|
+
- [ ] **Step 2: Verify**
|
|
266
|
+
|
|
267
|
+
```bash
|
|
268
|
+
ruby -c benchmark/benchmark.rb
|
|
269
|
+
grep -n "tagged_metka\|TaggedMetka" benchmark/benchmark.rb
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
Expected: `Syntax OK`, then no grep output. (Running the benchmark itself needs a seeded DB and minutes of runtime — out of scope; the SQL mirrors the dummy migration verified in Task 2.)
|
|
273
|
+
|
|
274
|
+
- [ ] **Step 3: Commit**
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
git add benchmark/benchmark.rb
|
|
278
|
+
git commit -m "bench: Rename table strategy table to metka_table_posts_tags_cloud
|
|
279
|
+
|
|
280
|
+
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>"
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
### Task 5: Full-suite verification and sweep
|
|
286
|
+
|
|
287
|
+
- [ ] **Step 1: Run the whole suite on SQLite**
|
|
288
|
+
|
|
289
|
+
```bash
|
|
290
|
+
rm -f test/dummy/db/*.sqlite3 test/dummy/db/schema.rb
|
|
291
|
+
RAILS_ENV=test DB=sqlite bundle exec rake dummy:db:migrate
|
|
292
|
+
RAILS_ENV=test DB=sqlite bundle exec rake test
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
Expected: 0 failures, 0 errors.
|
|
296
|
+
|
|
297
|
+
- [ ] **Step 2: Run the whole suite on PostgreSQL (if available)**
|
|
298
|
+
|
|
299
|
+
```bash
|
|
300
|
+
bundle exec rake dummy:db:create dummy:db:migrate:reset
|
|
301
|
+
bundle exec rake test
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
Expected: 0 failures, 0 errors. If no local PG server, note it in the report — CI covers it.
|
|
305
|
+
|
|
306
|
+
- [ ] **Step 3: Final repo sweep for stale names**
|
|
307
|
+
|
|
308
|
+
```bash
|
|
309
|
+
grep -rn "tagged_notes\|tagged_songs\|tagged_table_posts\|tagged_metka\|TaggedNote\|TaggedTablePost\|TaggedMetka\|TaggedSong" --include="*.rb" --include="*.erb" --include="*.md" . | grep -v benchmark/results | grep -v docs/superpowers
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
Expected: no output (the spec/plan docs and historical results files legitimately mention old names).
|
|
313
|
+
|
|
314
|
+
- [ ] **Step 4: Commit any stragglers found by the sweep**
|
|
315
|
+
|
|
316
|
+
Only if Step 3 surfaced something; fix and commit with an appropriate message.
|