PgTagsOn
pg_tags_on
- keep tags in a column. Supported column types are: character varying[]
, text[]
, integer[]
and jsonb[]
.
Requirements:
- Postgresql >= 12
- Rails >= 6
Note: this gem is in early stage of development.
Table of contents
- Installation
- Usage
- ActiveRecord model setup
- Records queries
- Tags
- Set record's tags
- Configuration
- Benchmarks
- Contributing
Installation
Add this line to your application's Gemfile:
gem 'pg_tags_on'
And then execute:
$ bundle
Or install it yourself as:
$ gem install pg_tags_on
Usage
ActiveRecord model setup
One or multiple columns that store tags can be specified:
class Entity < ActiveRecord::Base
pg_tags_on :tags
pg_tags_on :other_tags
end
In jsonb[]
columns, by default each tag is stored as an object with a single key. For example {"tag": "rubygems"}
. If you store multiple attributes in the objects, then you must set also has_attributes: true
.
class Entity < ActiveRecord::Base
pg_tags_on :tags, key: :tag # => [{tag: 'alpha'}, {tag: 'beta'}]
pg_tags_on :other_tags, key: :tag, has_attributes: true # => [{tag: 'alpha', created_by: 'mike', ...}, {tag: 'beta', created_by: 'john', ...}]
end
Validations
Maximum number of tags and maximum tag length can be validated. Errors will be injected into model's errors
object.
class Entity < ActiveRecord::Base
pg_tags_on :tags, limit: 10, length: 50 # limit to 10 tags and 50 chars. per tag.
end
Queries
pg_tags_on
registers Tags
class in model's predicate builder, so records can be filtered using ActiveRecord's DSL. Class name can be changed if you have conflicts or you don't like it, see the configuration section.
- Find records by tag:
Entity.where(tags: Tags.one('alpha'))
- Find records that have exact same tags as the list, order is not important:
Entity.where(tags: Tags.eq('alpha', 'beta', 'gamma')) # Array argument is allowed too for every method.
- Find records that includes all the tags from the list:
Entity.where(tags: Tags.all('alpha', 'beta', 'gamma'))
- Find records that includes any tag from the list:
Entity.where(tags: Tags.any('alpha', 'beta', 'gamma'))
- Find records that have all the tags included in the list:
Entity.where(tags: Tags.in('alpha', 'beta', 'gamma'))
All the above queries supports negation operator. Example:
Entity.where.not(tags: Tags.one('alpha'))
Tags
pg_tags_on
creates a proxy at the class level, with the same name as the column, that can be used to query or do operations on tags.
Tags queries:
Entity.tags.all
=> [#<PgTagsOn::Tag name: "alpha">, #<PgTagsOn::Tag name: "beta">, ... ]
Entity.tags.all_with_counts
=> [#<PgTagsOn::Tag name: "alpha", count: 10>, #<PgTagsOn::Tag name: "beta", count: 20>, ... ]
Entity.tags.count
=> 123
# filters can be applied
Entity.where(...).tags.all.where('name ilike ?', 'alp%')
=> [#<PgTagsOn::Tag name: "alpha">, #<PgTagsOn::Tag name: "alpine">, ... ]
Entity.where(...).tags.all_with_counts.where('name ilike ?', 'alp%')
=> [#<PgTagsOn::Tag name: "alpha", count: 10>, #<PgTagsOn::Tag name: "alpine", count: 20>, ... ]
Taggings:
Entity.tags.taggings
=> [#<PgTagsOn::Tag name: "alpha", entity_id: 1>, #<PgTagsOn::Tag name: "beta", entity_id: 1>, #<PgTagsOn::Tag name: "alpha", entity_id: 2>, ... ]
Create, update and delete methods are using, for performance reasons, Postgresql functions to manipulate the arrays, so ActiveRecord models are not instantiated. A frequent problem is to ensure uniqueness of the tags for a record, and this can be achieved at the database level by creating a before create/update row trigger.
# create
Entity.tags.create('alpha') # add tag to all records
Entity.tags.create(%w[alpha beta gamma]) # add many tags to all records
Entity.where(...).tags.create('alpha') # add tag to filtered records
# update
Entity.tags.update('alpha', 'a') # rename tag for all records
Entity.where(...).tags.update('alpha', 'a') # rename tag for filtered records
# delete
Entity.tags.delete('alpha') # delete tag from all records
Entity.tags.delete(%w[alpha beta gamma]) # delete many tags from all records
Entity.where(...).tags.delete('alpha') # delete tag from filtered records
# any of these methods accepts :returning option
Entity.tags.update('alpha', 'a', returning: "id,tags")
=> [[1, '{a}'], ...]
Set record's tags
By default pg_tags_on
does not add any logic in the way that the tags are set for the model and saved in database. It'll let all the transformations, like lowercase, strip spaces, unique etc..., at the programmer choice. Specific setters can be implemented.
Configuration
You can configure pg_tags_on
in an initializer config/initializers/pg_tags_on.rb
:
PgTagsOn.configure do |c|
c.query_class = 'Tagz'
end
Benchmarks
rake pg_tags_on:benchmark
Contributing
- Fork it ( http://github.com/cata-m/pg_tags_on/fork )
- Install gem dependencies:
bundle install
- Create a new feature or fix branch like: 'feature/new-feature' or 'fix/fix-some-issues'
- Write your modifications and make sure all tests pass:
bundle exec rake spec
- Commit your changes: git commit -am 'your changes'
- Push to the branch
- Create new pull request
License
The gem is available as open source under the terms of the MIT License.
Code of Conduct
Everyone interacting in the PgTagsOn project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.