make_taggable 1.0.0 → 1.1.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/CHANGELOG.md +20 -1
- data/CONTRIBUTING.md +10 -4
- data/UPGRADING.md +4 -0
- data/db/migrate/6_add_unowned_taggings_unique_index.rb +18 -0
- data/docs/database.md +27 -10
- data/lib/make_taggable/version.rb +1 -1
- metadata +6 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9313e4b6fc5d1b81f44b56665f2dbf6c6a8c8a8213f492b00e17e1e20c0a27bb
|
|
4
|
+
data.tar.gz: ef32dbb42368817bea0791803f1013f1481d58ca50992551131483b7fac416ab
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 750c882a7d5836848cfd7d536896403cb619868121a817d9aa89ac2aed7e36d6f3d9cf70be5be04e1dd428e35484957b1f49ba17a2e7ef50f2a67adc271d7510
|
|
7
|
+
data.tar.gz: d14dba688a46a5e48507a082d7b65071aa8e0197df657156c3859652fcabd8eca5e0c5256033f7a9c4665d197c87177f0d9107c010e811d671fc1de2011b4d1f
|
data/CHANGELOG.md
CHANGED
|
@@ -5,7 +5,26 @@ All notable changes to this project are documented here.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project
|
|
6
6
|
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
-
## [1.
|
|
8
|
+
## [1.1.0] - 2026-08-22
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- A migration adding `taggings_unowned_idx`, a partial unique index that stops duplicate unowned
|
|
13
|
+
taggings at the database level. `taggings_idx` never could: it spans the nullable tagger columns,
|
|
14
|
+
and SQL compares nulls as distinct, so only the model validation stood in the way and a validation
|
|
15
|
+
cannot win a race. MySQL has no partial indexes, so the migration is a no-op there.
|
|
16
|
+
|
|
17
|
+
Install it with `rails make_taggable_engine:install:migrations`. If it fails, the table already
|
|
18
|
+
holds duplicates -- see [docs/database.md](docs/database.md).
|
|
19
|
+
|
|
20
|
+
### Internal
|
|
21
|
+
|
|
22
|
+
- The suite runs in random order. Examples were leaking state into each other: tagging declarations
|
|
23
|
+
on the shared models, and the library configuration -- one example leaving `remove_unused_tags`
|
|
24
|
+
on could make an unrelated example fail a foreign key check. Both are now snapshotted and restored
|
|
25
|
+
around every example.
|
|
26
|
+
|
|
27
|
+
## [1.0.0] - 2026-08-22
|
|
9
28
|
|
|
10
29
|
### Breaking
|
|
11
30
|
|
data/CONTRIBUTING.md
CHANGED
|
@@ -47,10 +47,16 @@ bundle exec appraisal rake
|
|
|
47
47
|
|
|
48
48
|
### A note on test ordering
|
|
49
49
|
|
|
50
|
-
The suite runs in
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
50
|
+
The suite runs in random order, so an example that depends on a sibling having run first will fail
|
|
51
|
+
sooner or later. Reproduce a failure with the seed it reports:
|
|
52
|
+
|
|
53
|
+
```shell
|
|
54
|
+
bundle exec rspec --order random:12345
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Examples are free to change global state — the tagging declarations on the shared models, and the
|
|
58
|
+
`MakeTaggable` configuration — because `spec_helper.rb` snapshots both and restores them after every
|
|
59
|
+
example. Anything else you make global is yours to clean up.
|
|
54
60
|
|
|
55
61
|
## Documentation
|
|
56
62
|
|
data/UPGRADING.md
CHANGED
|
@@ -23,6 +23,10 @@ This release renames the declaration methods and changes how delimiters are esca
|
|
|
23
23
|
|
|
24
24
|
4. Requirements are now Ruby 3.2 and Active Record 7.2 or newer.
|
|
25
25
|
|
|
26
|
+
Note for anyone upgrading past 1.0: a later migration adds a unique index
|
|
27
|
+
preventing duplicate unowned taggings. If it fails, your taggings table
|
|
28
|
+
already holds duplicates -- docs/database.md has a snippet to clear them.
|
|
29
|
+
|
|
26
30
|
Install any new migrations:
|
|
27
31
|
|
|
28
32
|
rails make_taggable_engine:install:migrations
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
class AddUnownedTaggingsUniqueIndex < ActiveRecord::Migration[7.2]
|
|
2
|
+
# taggings_idx spans tagger_id and tagger_type, which are NULL on every
|
|
3
|
+
# tagging nobody owns. SQL compares NULLs as distinct, so that index does not
|
|
4
|
+
# stop two identical unowned taggings -- only the model validation does, and a
|
|
5
|
+
# validation cannot win a race between two concurrent writes.
|
|
6
|
+
#
|
|
7
|
+
# A partial index closes it. MySQL has no partial indexes, so it keeps the
|
|
8
|
+
# validation on its own.
|
|
9
|
+
def change
|
|
10
|
+
return if MakeTaggable::Utils.using_mysql?
|
|
11
|
+
|
|
12
|
+
add_index MakeTaggable.taggings_table,
|
|
13
|
+
[:tag_id, :taggable_id, :taggable_type, :context],
|
|
14
|
+
unique: true,
|
|
15
|
+
where: "tagger_id IS NULL",
|
|
16
|
+
name: "taggings_unowned_idx"
|
|
17
|
+
end
|
|
18
|
+
end
|
data/docs/database.md
CHANGED
|
@@ -34,22 +34,39 @@ That suits read-heavy tagging. If your application writes taggings in bulk, the
|
|
|
34
34
|
looking at — every index is maintained on insert, and several of the standalone ones are prefixes of
|
|
35
35
|
composites that already exist. Drop what your queries do not use.
|
|
36
36
|
|
|
37
|
-
###
|
|
37
|
+
### Duplicate unowned taggings
|
|
38
38
|
|
|
39
39
|
`taggings_idx` is unique across
|
|
40
40
|
`[tag_id, taggable_id, taggable_type, context, tagger_id, tagger_type]`. Because `tagger_id` and
|
|
41
|
-
`tagger_type` are null
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
`tagger_type` are null on every tagging nobody owns, and SQL compares nulls as distinct, that index
|
|
42
|
+
does not stop two identical unowned taggings. Only the model validation does, and a validation
|
|
43
|
+
cannot win a race between two concurrent writes.
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
Migration 6 closes it with a partial unique index, `taggings_unowned_idx`, covering
|
|
46
|
+
`[tag_id, taggable_id, taggable_type, context]` where `tagger_id IS NULL`.
|
|
47
|
+
|
|
48
|
+
**MySQL has no partial indexes**, so it keeps the validation on its own and the migration is a no-op
|
|
49
|
+
there. If duplicate taggings would be a serious problem on MySQL, the usual workaround is a
|
|
50
|
+
generated column holding a sentinel for the null tagger, indexed uniquely alongside the rest.
|
|
51
|
+
|
|
52
|
+
If the migration fails with a uniqueness error, the table already contains duplicates. Remove them
|
|
53
|
+
first:
|
|
46
54
|
|
|
47
55
|
```ruby
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
56
|
+
duplicates = MakeTaggable::Tagging
|
|
57
|
+
.where(tagger_id: nil)
|
|
58
|
+
.group(:tag_id, :taggable_id, :taggable_type, :context)
|
|
59
|
+
.having("COUNT(*) > 1")
|
|
60
|
+
.pluck(Arel.sql("MIN(id), COUNT(*)"))
|
|
61
|
+
|
|
62
|
+
duplicates.each do |keep_id, _count|
|
|
63
|
+
tagging = MakeTaggable::Tagging.find(keep_id)
|
|
64
|
+
MakeTaggable::Tagging
|
|
65
|
+
.where(tagger_id: nil, tag_id: tagging.tag_id, taggable_id: tagging.taggable_id,
|
|
66
|
+
taggable_type: tagging.taggable_type, context: tagging.context)
|
|
67
|
+
.where.not(id: keep_id)
|
|
68
|
+
.delete_all
|
|
69
|
+
end
|
|
53
70
|
```
|
|
54
71
|
|
|
55
72
|
## PostgreSQL
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: make_taggable
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Matthew Kennedy
|
|
@@ -46,6 +46,7 @@ files:
|
|
|
46
46
|
- db/migrate/3_change_tag_name_collation_mysql.rb
|
|
47
47
|
- db/migrate/4_add_index_to_tags.rb
|
|
48
48
|
- db/migrate/5_add_index_to_taggings.rb
|
|
49
|
+
- db/migrate/6_add_unowned_taggings_unique_index.rb
|
|
49
50
|
- docs/caching.md
|
|
50
51
|
- docs/configuration.md
|
|
51
52
|
- docs/contexts.md
|
|
@@ -116,6 +117,10 @@ post_install_message: |
|
|
|
116
117
|
|
|
117
118
|
4. Requirements are now Ruby 3.2 and Active Record 7.2 or newer.
|
|
118
119
|
|
|
120
|
+
Note for anyone upgrading past 1.0: a later migration adds a unique index
|
|
121
|
+
preventing duplicate unowned taggings. If it fails, your taggings table
|
|
122
|
+
already holds duplicates -- docs/database.md has a snippet to clear them.
|
|
123
|
+
|
|
119
124
|
Install any new migrations:
|
|
120
125
|
|
|
121
126
|
rails make_taggable_engine:install:migrations
|