make_taggable 1.0.0 → 1.1.1
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 +35 -1
- data/CONTRIBUTING.md +16 -4
- data/README.md +45 -15
- data/UPGRADING.md +4 -0
- data/db/migrate/6_add_unowned_taggings_unique_index.rb +18 -0
- data/docs/caching.md +1 -1
- data/docs/contexts.md +6 -1
- data/docs/database.md +27 -10
- data/docs/getting-started.md +6 -2
- data/docs/querying.md +13 -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: 7d3a7c89a48d79f6d985b8290d88007368ecdf983789a7e4881662724a1060a9
|
|
4
|
+
data.tar.gz: dda60e219b50e10b8948b6b9b260e703d8815f43bd200b1c434875144ae68071
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7aa7e8b6c7190cbc8fbdcaf54fd5f49bf8f877c3161c7466e7df98eefd8f14eaa208391b47fa5a328db563d489d5e9a6f82b3bfd4b0ce770cdedbf3423f958bc
|
|
7
|
+
data.tar.gz: 0e868727368e57e72fe627cbbc4b7a4dc2077e459d6ac9131732788fec26a88037bd7ed12cb86a35f3b8308b3df7fa238319c24628f39e698c073e7d0dd8fa68
|
data/CHANGELOG.md
CHANGED
|
@@ -5,7 +5,41 @@ 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.1] - 2026-08-22
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- Documentation examples used `params.expect` and `ActiveRecord::Migration[8.0]`, neither of which
|
|
13
|
+
exists on Active Record 7.2 -- the version the gem promises to support. Both are corrected, and
|
|
14
|
+
`spec/docs_spec.rb` now fails the build if either creeps back.
|
|
15
|
+
|
|
16
|
+
### Internal
|
|
17
|
+
|
|
18
|
+
- Added documentation checks to the suite: every Ruby block in the README and `docs/` must parse,
|
|
19
|
+
example migrations must declare a version the floor accepts, version-sensitive calls must be
|
|
20
|
+
shown alongside an alternative, and relative links must resolve. They run on every Rails version
|
|
21
|
+
in the matrix.
|
|
22
|
+
|
|
23
|
+
## [1.1.0] - 2026-08-22
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
|
|
27
|
+
- A migration adding `taggings_unowned_idx`, a partial unique index that stops duplicate unowned
|
|
28
|
+
taggings at the database level. `taggings_idx` never could: it spans the nullable tagger columns,
|
|
29
|
+
and SQL compares nulls as distinct, so only the model validation stood in the way and a validation
|
|
30
|
+
cannot win a race. MySQL has no partial indexes, so the migration is a no-op there.
|
|
31
|
+
|
|
32
|
+
Install it with `rails make_taggable_engine:install:migrations`. If it fails, the table already
|
|
33
|
+
holds duplicates -- see [docs/database.md](docs/database.md).
|
|
34
|
+
|
|
35
|
+
### Internal
|
|
36
|
+
|
|
37
|
+
- The suite runs in random order. Examples were leaking state into each other: tagging declarations
|
|
38
|
+
on the shared models, and the library configuration -- one example leaving `remove_unused_tags`
|
|
39
|
+
on could make an unrelated example fail a foreign key check. Both are now snapshotted and restored
|
|
40
|
+
around every example.
|
|
41
|
+
|
|
42
|
+
## [1.0.0] - 2026-08-22
|
|
9
43
|
|
|
10
44
|
### Breaking
|
|
11
45
|
|
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
|
|
|
@@ -58,6 +64,12 @@ Prose documentation lives in [docs/](docs). API documentation is YARD comments i
|
|
|
58
64
|
the style already there: a `##` opening line, a description, a blank line, then tags with real Ruby
|
|
59
65
|
types.
|
|
60
66
|
|
|
67
|
+
`spec/docs_spec.rb` holds the prose to the versions the gemspec promises. It checks that every Ruby
|
|
68
|
+
block parses, that no example declares a migration version newer than the supported floor, that
|
|
69
|
+
calls the floor does not have are shown alongside an alternative, and that every relative link
|
|
70
|
+
resolves. It runs with the rest of the suite, so it runs on every Rails version in the matrix --
|
|
71
|
+
including the oldest, which is where documentation written on the newest tends to break.
|
|
72
|
+
|
|
61
73
|
Build it locally with `bundle exec yard doc`.
|
|
62
74
|
|
|
63
75
|
## Releasing
|
data/README.md
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# MakeTaggable
|
|
2
2
|
|
|
3
|
-
[](https://rubygems.org/gems/make_taggable)
|
|
4
|
+
[](https://rubygems.org/gems/make_taggable)
|
|
5
|
+
[](https://github.com/MatthewKennedy/make_taggable/actions/workflows/ci.yml)
|
|
5
6
|
[](https://github.com/MatthewKennedy/make_taggable/actions/workflows/standard-ci.yml)
|
|
6
7
|
|
|
7
8
|
Tagging for Active Record models, across any number of named contexts.
|
|
@@ -12,7 +13,7 @@ order they were added in, and be cached on the record for display.
|
|
|
12
13
|
|
|
13
14
|
## Requirements
|
|
14
15
|
|
|
15
|
-
| | |
|
|
16
|
+
| Component | Supported |
|
|
16
17
|
|---|---|
|
|
17
18
|
| Ruby | 3.2 or newer |
|
|
18
19
|
| Active Record | 7.2 or newer |
|
|
@@ -35,44 +36,73 @@ rails db:migrate
|
|
|
35
36
|
|
|
36
37
|
```ruby
|
|
37
38
|
class Book < ApplicationRecord
|
|
38
|
-
make_taggable # the :tags context
|
|
39
|
-
make_taggable :genres #
|
|
39
|
+
make_taggable # the :tags context, for free-form tags
|
|
40
|
+
make_taggable :genres # a context of your own, for a curated set
|
|
40
41
|
end
|
|
41
42
|
```
|
|
42
43
|
|
|
44
|
+
Each context is a separate set of tags, with its own list:
|
|
45
|
+
|
|
43
46
|
```ruby
|
|
44
|
-
book = Book.create!(title: "Dune",
|
|
47
|
+
book = Book.create!(title: "Dune", genre_list: "sci-fi, classic")
|
|
48
|
+
|
|
49
|
+
book.genre_list # => ["sci-fi", "classic"]
|
|
50
|
+
```
|
|
45
51
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
52
|
+
Add and remove individual tags. Nothing is written until you save:
|
|
53
|
+
|
|
54
|
+
```ruby
|
|
55
|
+
book.genre_list.add("space opera")
|
|
56
|
+
book.genre_list.remove("classic")
|
|
49
57
|
book.save
|
|
50
58
|
|
|
51
|
-
book.genre_list
|
|
59
|
+
book.genre_list # => ["sci-fi", "space opera"]
|
|
60
|
+
book.genres # => [#<MakeTaggable::Tag name: "sci-fi">, #<MakeTaggable::Tag name: "space opera">]
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Assigning replaces the whole list, and the two contexts never touch each other:
|
|
64
|
+
|
|
65
|
+
```ruby
|
|
66
|
+
book.tag_list = "desert, chosen-one, re-read"
|
|
52
67
|
book.save
|
|
68
|
+
|
|
69
|
+
book.tag_list # => ["desert", "chosen-one", "re-read"]
|
|
70
|
+
book.genre_list # => ["sci-fi", "space opera"]
|
|
53
71
|
```
|
|
54
72
|
|
|
55
73
|
Find them again:
|
|
56
74
|
|
|
57
75
|
```ruby
|
|
58
76
|
Book.tagged_with("sci-fi") # carries this tag
|
|
59
|
-
Book.tagged_with(["sci-fi", "
|
|
77
|
+
Book.tagged_with(["sci-fi", "space opera"]) # carries both
|
|
60
78
|
Book.tagged_with(["sci-fi", "fantasy"], any: true) # carries either
|
|
61
|
-
Book.tagged_with(["
|
|
79
|
+
Book.tagged_with(["fantasy"], exclude: true) # carries neither
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Scope a query to one context, and tags in the others stop counting:
|
|
83
|
+
|
|
84
|
+
```ruby
|
|
85
|
+
Book.tagged_with("sci-fi", on: :genres) # => [#<Book title: "Dune">]
|
|
86
|
+
Book.tagged_with("sci-fi", on: :tags) # => []
|
|
87
|
+
```
|
|
62
88
|
|
|
63
|
-
|
|
89
|
+
Counts, for tag clouds and "most used" lists:
|
|
90
|
+
|
|
91
|
+
```ruby
|
|
92
|
+
Book.tag_counts_on(:genres) # tags carrying a `count`
|
|
64
93
|
Book.top_genres(10)
|
|
65
94
|
```
|
|
66
95
|
|
|
67
96
|
Tags are ordinary attributes as far as your controller is concerned:
|
|
68
97
|
|
|
69
98
|
```ruby
|
|
70
|
-
params.expect(book: [:title, :tag_list])
|
|
99
|
+
params.expect(book: [:title, :tag_list]) # Rails 8
|
|
100
|
+
params.require(:book).permit(:title, :tag_list) # Rails 7.2
|
|
71
101
|
```
|
|
72
102
|
|
|
73
103
|
## Documentation
|
|
74
104
|
|
|
75
|
-
| | |
|
|
105
|
+
| Guide | Covers |
|
|
76
106
|
|---|---|
|
|
77
107
|
| [Getting started](docs/getting-started.md) | Install, first tagged model, reading and writing |
|
|
78
108
|
| [Tag contexts](docs/contexts.md) | Multiple contexts, ordered tags, contexts created at runtime |
|
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/caching.md
CHANGED
|
@@ -10,7 +10,7 @@ There is no setting. Add a column named `cached_<singular context>_list` and cac
|
|
|
10
10
|
itself on for that context:
|
|
11
11
|
|
|
12
12
|
```ruby
|
|
13
|
-
class AddCachedTagListToBooks < ActiveRecord::Migration[
|
|
13
|
+
class AddCachedTagListToBooks < ActiveRecord::Migration[7.2]
|
|
14
14
|
def change
|
|
15
15
|
add_column :books, :cached_tag_list, :string
|
|
16
16
|
add_column :books, :cached_genre_list, :string
|
data/docs/contexts.md
CHANGED
|
@@ -46,10 +46,15 @@ Calling `make_taggable` again adds contexts rather than replacing them, which is
|
|
|
46
46
|
subclass extend its parent:
|
|
47
47
|
|
|
48
48
|
```ruby
|
|
49
|
+
class Book < ApplicationRecord
|
|
50
|
+
make_taggable
|
|
51
|
+
end
|
|
52
|
+
|
|
49
53
|
class Manual < Book
|
|
50
54
|
make_taggable :audiences
|
|
51
55
|
end
|
|
52
56
|
|
|
57
|
+
Book.tag_types # => [:tags]
|
|
53
58
|
Manual.tag_types # => [:tags, :audiences]
|
|
54
59
|
```
|
|
55
60
|
|
|
@@ -134,7 +139,7 @@ tags, `Market.count` returns every tag in the table, and reloading a record give
|
|
|
134
139
|
`MakeTaggable::Tag` back. Add the column to get real separation:
|
|
135
140
|
|
|
136
141
|
```ruby
|
|
137
|
-
class AddTypeToTags < ActiveRecord::Migration[
|
|
142
|
+
class AddTypeToTags < ActiveRecord::Migration[7.2]
|
|
138
143
|
def change
|
|
139
144
|
add_column MakeTaggable.tags_table, :type, :string
|
|
140
145
|
add_index MakeTaggable.tags_table, :type
|
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
|
data/docs/getting-started.md
CHANGED
|
@@ -83,15 +83,19 @@ class BooksController < ApplicationController
|
|
|
83
83
|
private
|
|
84
84
|
|
|
85
85
|
def book_params
|
|
86
|
-
params.expect(book: [:title, :tag_list])
|
|
86
|
+
params.expect(book: [:title, :tag_list]) # Rails 8
|
|
87
|
+
# params.require(:book).permit(:title, :tag_list) # Rails 7.2
|
|
87
88
|
end
|
|
88
89
|
end
|
|
89
90
|
```
|
|
90
91
|
|
|
92
|
+
`params.expect` arrived in Rails 8. On Rails 7.2 use `require` and `permit`, as commented above.
|
|
93
|
+
|
|
91
94
|
To accept an array of tags from a multi-select, permit it as one:
|
|
92
95
|
|
|
93
96
|
```ruby
|
|
94
|
-
params.expect(book: [:title, {tag_list: []}])
|
|
97
|
+
params.expect(book: [:title, {tag_list: []}]) # Rails 8
|
|
98
|
+
params.require(:book).permit(:title, tag_list: []) # Rails 7.2
|
|
95
99
|
```
|
|
96
100
|
|
|
97
101
|
## Find tagged records
|
data/docs/querying.md
CHANGED
|
@@ -6,18 +6,18 @@
|
|
|
6
6
|
pagination.
|
|
7
7
|
|
|
8
8
|
```ruby
|
|
9
|
-
|
|
9
|
+
Book.tagged_with("sci-fi").order(published_at: :desc).limit(20)
|
|
10
10
|
```
|
|
11
11
|
|
|
12
12
|
By default every tag given must be present:
|
|
13
13
|
|
|
14
14
|
```ruby
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
Book.tagged_with(["sci-fi", "classic"]) # carries sci-fi AND classic
|
|
16
|
+
Book.tagged_with(["sci-fi", "classic"], any: true) # carries sci-fi OR classic
|
|
17
|
+
Book.tagged_with(["sci-fi", "classic"], exclude: true) # carries NEITHER
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
Passing nothing matches nothing. `
|
|
20
|
+
Passing nothing matches nothing. `Book.tagged_with([])` and `Book.tagged_with("")` both return an
|
|
21
21
|
empty relation rather than every record — worth knowing when the tags come from user input.
|
|
22
22
|
|
|
23
23
|
### Options
|
|
@@ -27,7 +27,7 @@ empty relation rather than every record — worth knowing when the tags come fro
|
|
|
27
27
|
| `:any` | Match records carrying at least one of the tags |
|
|
28
28
|
| `:exclude` | Match records carrying none of the tags |
|
|
29
29
|
| `:match_all` | Match records carrying only these tags and no others |
|
|
30
|
-
| `:wild` | Match tags *containing* the given text, i.e. `%
|
|
30
|
+
| `:wild` | Match tags *containing* the given text, i.e. `%sci%` |
|
|
31
31
|
| `:on` | Restrict to one context |
|
|
32
32
|
| `:owned_by` | Restrict to tags applied by one tagger |
|
|
33
33
|
| `:order_by_matching_tag_count` | With `:any`, order by how many tags matched, most first |
|
|
@@ -37,15 +37,15 @@ empty relation rather than every record — worth knowing when the tags come fro
|
|
|
37
37
|
`:wild` combines with `:any` or `:exclude`:
|
|
38
38
|
|
|
39
39
|
```ruby
|
|
40
|
-
|
|
40
|
+
Book.tagged_with(["sci", "clas"], any: true, wild: true)
|
|
41
41
|
```
|
|
42
42
|
|
|
43
43
|
Contexts are matched one call at a time, so chain to combine them:
|
|
44
44
|
|
|
45
45
|
```ruby
|
|
46
|
-
|
|
47
|
-
.tagged_with(["
|
|
48
|
-
.tagged_with(["
|
|
46
|
+
Book
|
|
47
|
+
.tagged_with(["sci-fi", "fantasy"], on: :genres, any: true)
|
|
48
|
+
.tagged_with(["desert", "space"], on: :tags, any: true)
|
|
49
49
|
```
|
|
50
50
|
|
|
51
51
|
### Case sensitivity
|
|
@@ -105,6 +105,9 @@ noticeably cheaper on a large table.
|
|
|
105
105
|
|
|
106
106
|
## Related records
|
|
107
107
|
|
|
108
|
+
These examples switch to a `User` model tagged on `:skills` — sharing tags is easiest to picture
|
|
109
|
+
with people.
|
|
110
|
+
|
|
108
111
|
Records sharing tags with this one, ordered by how many tags matched:
|
|
109
112
|
|
|
110
113
|
```ruby
|
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.1
|
|
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
|