HasAttachedTags
Provides a general-purpose, typed tagging mechanism for ActiveRecord models.
HasAttachedTags does:
- Use tag types (e.g. Allowing a "Jane" character tag to be separate from a "Jane" artist tag.)
- Allow a model to have multiple, separate lists of tags
- Allow a model to specify only a single tag, rather than a list
- Allow a model to have multiple lists of the same kind of tag (e.g. A user preferences object with an allow-list and a block-list of tags.)
- Use only run-of-the-mill ActiveRecord models and associations, without introducing any new infrastructural types.
HasAttachedTags does not:
- Parse tags from comma-separate lists
- Embed tag information directly onto a model
- Add any specialized methods for interacting with tags
- Monkey-patch ActiveRecord or any other library
In general, HasAttachedTags handles only the storage and association of tags, and leaves other concerns to the application.
Installation
Add this line to your application's Gemfile:
gem 'has_attached_tags'And then execute:
$ bundle
Or install it yourself as:
$ gem install has_attached_tags
To generate configuration and locale files:
$ rails g has_attached_tags:install
To generate the database migration:
$ rails g has_attached_tags:migration
Namespacing
By default, HasAttachedTags with map its Tag and Tagging models into the local application's top-level scope for easy access.
This simplifies operations like:
# Fully-qualified access:
tag = HasAttachedTags::Tag.of_type('place').find_or_create_by(name: 'Home')
# Simplified access:
tag = Tag.of_type('place').find_or_create_by(name: 'Home')If this naming conflicts with existing types in your application, or you simply do not want this behaviour, remove these lines from config/initializers/has_attached_tags.rb:
Tag     = HasAttachedTags::Tag
Tagging = HasAttachedTags::TaggingUsage
In most cases, it's simplest to install HasAttachedTags globally by adding it to the ApplicationRecord base class:
class ApplicationRecord < ActiveRecord::Base
  extend HasAttachedTags
  # ...
endAlternatively, it's possible to install the tag DSL only for specific models:
class Image < ActiveRecord::Base
  extend HasAttachedTags
  # ...
endAttaching Tags to Models
To define a model that accepts tags, the has_many_tags macro helper can be used:
class Image < ApplicationRecord
  has_many_tags :characters
endWe refer to this association between the model and its tags as an "attachment".
Tags attachments can be used like any other has_many association, without any special methods or magic:
image = Image.new
image.characters # => #<ActiveRecord::Associations::CollectionProxy [...]>
image.characters << Tag.find_by(name: 'J', type: 'character')
image.saveValidations will prevent assigning unsupported tag types to an attachment:
image.characters << Tag.find_by(name: 'J', type: 'character')
image.valid? # => true
image.characters << Tag.find_by(name: 'safe', type: 'rating')
image.valid? # => falseSingular Tag Attachments
If only a single tag should be attached to a model, use has_one_tag macro helper:
class Image < ApplicationRecord
  has_one_tag :rating
endSimilarly, the has_one_tag attachment uses has_one associations under the hood.
image = Image.new
image.rating # => nil
image.rating = Tag.new(name: 'safe', type: 'rating')
image.rating # => #<Tag id: nil, name: "safe", type: "rating", ...>Marking Tags as Required
Sometimes, it might be necessary to validate that as tag is attached to a model. To do this, a presence: true validation can be used, however, a required option is provided for convenience:
class Image < ApplicationRecord
  has_many_tags :artists, required: true
endThe Image model will now validate that at least 1 artist tag is attached.
image = Image.new
image.valid? # => false
image.artists << Tag.new(name: 'J', type: 'artist')
image.valid? # => trueThis option is available for both has_one_tag and has_many_tags attachments.
Specifying a Tag Type
By default, the name of the tags attachment will be used to infer the tag type. (For example has_one_tag :rating will assume "rating" or has_many_tags :characters will assume "character".)
This value can be customized using the type option:
class Image < ApplicationRecord
  has_one_tag :source, type: 'website'
endThis option is available for both has_one_tag and has_many_tags attachments.
Development
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.
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 tags, and push the .gem file to rubygems.org.
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/mintyfresh/has_attached_tags. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.
License
The gem is available as open source under the terms of the MIT License.
Code of Conduct
Everyone interacting in the HasAttachedTags project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.