Gutentag

repository·main·Indexed 19 days ago

https://github.com/pat/gutentag

A modular tagging extension for ActiveRecord that provides tag management via string names and flexible querying. It supports Ruby 3.0-4.0 and Rails/ActiveRecord 6.1-8.1, with legacy support for older versions via v2.6.2 and v2.4.1. Key features include the `tag_names` accessor for easy tag assignment, the `tagged_with` method for filtering records using :any, :all, or :none logic, and customizable tag normalization and validation.

Tokens
2.8K
Snippets
16
Records
18
Agent score
66%

What's inside gutentag

  1. Set up Gutentag migrations

    main

    After adding the gem to your Gemfile, you must import and run the migrations to create the necessary database tables for tags and taggings.

    Important for UUID users: If you use UUID primary keys, you must manually alter the generated migration files to use UUIDs for the taggable_id foreign key column before running db:migrate.

    bundle exec rake gutentag:install:migrations
    bundle exec rails generate gutentag:migration_versions
    bundle exec rake db:migrate
  2. Install Gutentag via Gemfile

    main

    To install Gutentag in a Rails project, add it to your Gemfile with a version constraint.

    Note on compatibility:

    • Ruby 3.0-4.0 and Rails/ActiveRecord 6.1-8.1 are officially supported.
    • For older versions (Ruby 2.4-2.7 or ActiveRecord 4.0-6.0), use Gutentag v2.6.2.
    • For very old versions (Ruby 2.2 or ActiveRecord 3.2), use Gutentag v2.4.1.
    gem 'gutentag', '~> 3.0'
  3. Extend Gutentag models safely in Rails

    main

    If you need to include modules into Gutentag::Tag or other Gutentag models, wrap the include inside a to_prepare hook in a Rails initializer. This ensures the extensions are loaded correctly across all environments.

    # config/initializers/gutentag.rb
    Rails.application.config.to_prepare do
      Gutentag::Tag.include TagExtensions
    end
  4. Configure tag validations

    main

    By default, Gutentag validates the presence of the tag name, case-insensitive uniqueness, and maximum length. You can override this by assigning a custom object to Gutentag.tag_validations. The object must respond to call and accept the model as an argument.

    Gutentag.tag_validations = CustomTagValidations
  5. Configure tag normalisation

    main

    Tag normalisation converts supplied values into consistent string names. The default behavior is to convert the value to a string and then to lowercase. To customize this, assign a lambda or object that responds to call to Gutentag.normaliser.

    Gutentag.normaliser = lambda { |value| value.to_s.upcase }
  6. Integrate Gutentag with ActiveRecord models

    main

    To add tagging functionality to an ActiveRecord model, use Gutentag::ActiveRecord.call(ModelName). This method configures the model by adding the necessary associations, callbacks, and methods required for tagging.

    When called, it performs the following setup:

    • Associations: Adds has_many :taggings (polymorphic via :as => :taggable) and has_many :tags (through :taggings).
    • Attributes: Adds a tag_names attribute.
    • Callbacks: Registers after_save :persist_tags and after_commit :reset_tag_names (on create and update).
    • Methods: Extends the model with Gutentag::ActiveRecord::ClassMethods and includes Gutentag::ActiveRecord::InstanceMethods.
    class Article < ActiveRecord::Base
      Gutentag::ActiveRecord.call(self)
    end
  7. Enable tagging on an ActiveRecord model

    main

    To make a model taggable, call Gutentag::ActiveRecord.call self within the class definition. This establishes the necessary tag associations.

    class Article < ActiveRecord::Base
      # ...
      Gutentag::ActiveRecord.call self
      # ...
    end
  8. Manage tags using tag_names

    main

    Instead of managing Gutentag::Tag instances directly, you can use the tag_names accessor to get or set tags using an array of strings.

    Note: Changes to tag_names are not persisted immediately. You must call .save on the model instance to commit the changes to the database.

    article.tag_names #=> ['pancakes', 'melbourne', 'ruby']
    article.tag_names << 'portland'
    article.tag_names -= ['ruby']
    
    # Persist changes
    article.tag_names << 'ruby'
    article.save
  9. Retrieve tag names for a scope

    main

    Use Gutentag::Tag.names_for_scope to get an array of all tag names used within a specific model class or an ActiveRecord relation.

    # All tag names used by the Article model
    Gutentag::Tag.names_for_scope(Article)
    
    # Tag names used within a specific subset of articles
    Gutentag::Tag.names_for_scope(Article.where(:created_at => 1.week.ago..1.second.ago))
  10. Query models with tagged_with

    main

    Use the tagged_with method to find records based on tags. You can pass :names, :tags (instances), or :ids.

    The :match option controls the logic:

    • :any (default): Returns records matching any of the provided tags (OR logic).
    • :all: Returns records matching all provided tags (AND logic).
    • :none: Returns records matching none of the provided tags.
    # Match ANY of the names (OR logic)
    Article.tagged_with(:names => ['tag1', 'tag2'], :match => :any)
    
    # Match ALL of the IDs (AND logic)
    Article.tagged_with(:ids => [tag_a.id, tag_b.id], :match => :all)
    
    # Match NONE of the IDs
    Article.tagged_with(:ids => [tag_a.id, tag_b.id], :match => :none)
  11. Database schema for Gutentag (Non-Rails usage)

    main

    If using Gutentag outside of Rails, you must manually create the following tables to match the required schema:

    create_table :gutentag_tags do |t|
      t.string :name,           null: false, index: {unique: true}
      t.bigint :taggings_count, null: false, index: true, default: 0
      t.timestamps              null: false
    end
    
    create_table :gutentag_taggings do |t|
      t.references :tag,      null: false, index: true, foreign_key: {to_table: :gutentag_tags}
      t.references :taggable, null: false, index: true, polymorphic: true
      t.timestamps            null: false
    end
    add_index :gutentag_taggings, [:taggable_type, :taggable_id, :tag_id], unique: true, name: "gutentag_taggings_uniqueness"
  12. Use tag_names to access or set tags

    main

    You can interact with the tags of an ActiveRecord model using the tag_names getter and setter. This is useful for synchronizing a collection of tag names with a tags association.

    # Accessing tag names
    names = article.tag_names
    # => ['ruby', 'rails']
    
    # Setting tag names
    article.tag_names = ['coding', 'web']
    article.save