Gem Version Build Status Open Source Helpers

Metka

Rails gem to manage tags with SonggreSQL array columns.

Installation

Add this line to your application's Gemfile:

gem 'metka'

And then execute:

$ bundle

Or install it yourself as:

$ gem install metka

Tag objects

rails g migration CreateSongs
class CreateSongs < ActiveRecord::Migration[5.0]
  def change
    create_table :songs do |t|
      t.string  :title
      t.string  :tags, array: true
      t.string  :genres, array: true
      t.timestamps
    end
  end
end
class Song < ActiveRecord::Base
  include Metka::Model(column: 'tags')
  include Metka::Model(column: 'genres')
end

@song = Song.new(title: 'Migrate tags in Rails to SonggreSQL')
@song.tag_list = 'top, chill'
@song.genre_list = 'rock, jazz, pop'
@song.save

Find tagged objects

.with_all_#column_name

Song.with_all_tags('top')
=> [#<Song id: 1, title: 'Migrate tags in Rails to SonggreSQL', tags: ['top', 'chill'], genres: ['rock', 'jazz', 'pop']]

Song.with_all_tags('top, 1990')
=> []

Song.with_all_tags('')
=> []

Song.with_all_genres('rock')
=> [#<Song id: 1, title: 'Migrate tags in Rails to SonggreSQL', tags: ['top', 'chill'], genres: ['rock', 'jazz', 'pop']]

.with_any_#column_name

Song.with_any_tags('chill')
=> [#<Song id: 1, title: 'Migrate tags in Rails to SonggreSQL', tags: ['top', 'chill'], genres: ['rock', 'jazz', 'pop']]

Song.with_any_tags('chill, 1980')
=> [#<Song id: 1, title: 'Migrate tags in Rails to SonggreSQL', tags: ['top', 'chill'], genres: ['rock', 'jazz', 'pop']]

Song.with_any_tags('')
=> []

Song.with_any_genres('rock, rap')
=> [#<Song id: 1, title: 'Migrate tags in Rails to SonggreSQL', tags: ['top', 'chill'], genres: ['rock', 'jazz', 'pop']]

.without_all_#column_name

Song.without_all_tags('top')
=> []

Song.without_all_tags('top, 1990')
=> [#<Song id: 1, title: 'Migrate tags in Rails to SonggreSQL', tags: ['top', 'chill'], genres: ['rock', 'jazz', 'pop']]

Song.without_all_tags('')
=> [#<Song id: 1, title: 'Migrate tags in Rails to SonggreSQL', tags: ['top', 'chill'], genres: ['rock', 'jazz', 'pop']]

Song.without_all_genres('rock, pop')
=> [#<Song id: 1, title: 'Migrate tags in Rails to SonggreSQL', tags: ['top', 'chill'], genres: ['rock', 'jazz', 'pop']]

Song.without_all_genres('rock')
=> []

.without_any_#column_name

Song.without_any_tags('top, 1990')
=> []

Song.without_any_tags('1990, 1980')
=> [#<Song id: 1, title: 'Migrate tags in Rails to SonggreSQL', tags: ['top', 'chill'], genres: ['rock', 'jazz', 'pop']]

Song.without_any_genres('rock, pop')
=> []

Song.without_any_genres('')
=> [#<Song id: 1, title: 'Migrate tags in Rails to SonggreSQL', tags: ['top', 'chill'], genres: ['rock', 'jazz', 'pop']]

Custom delimiter

By default, a comma is used as a delimiter to create tags from a string. You can make your own custom separator:

Metka.config.delimiter = [',', ' ', '\|']
parsed_data = Metka::GenericParser.instance.call('cool, data|I have')
parsed_data.to_a
=>['cool', 'data', 'I', 'have']

Tags with quote

parsed_data = Metka::GenericParser.instance.call("'cool, data', code")
parsed_data.to_a
=> ['cool, data', 'code']

Custom parser

By default we use generic_parser If you want use your custom parser you can do:

class Song < ActiveRecord::Base
  include Metka::Model(column: 'tags', parser: Your::Custom::Parser.instance)
  include Metka::Model(column: 'genres')
end

Custom parser must be a singleton class that has a .call method that accepts the tag string

Tag Cloud Strategies

There are several strategies to get tag statistics

View Strategy

Data about taggings will be agregated in SQL View. The easiest way to implement but the most slow on SELECT.

rails g metka:strategies:view --source-table-name=NAME_OF_TABLE_WITH_TAGS

The code above will generate a migration that creates view to store aggregated data about tag in NAME_OF_TABLE_WITH_TAGS table.

Lets take a look at real example. We have a notes table with tags column.

Column Type Default
id integer nextval('notes_id_seq'::regclass)
body text
tags character varying[] '{}'::character varying[]

Now lets generate a migration.

rails g metka:strategies:view --source-table-name=notes

The result would be:

# frozen_string_literal: true

class CreateTaggedNotesView < ActiveRecord::Migration[5.0]
  def up
    execute <<-SQL
    CREATE OR REPLACE VIEW tagged_notes AS

    SELECT UNNEST
      ( tags ) AS tag_name,
      COUNT ( * ) AS taggings_count
    FROM
      notes
    GROUP BY
      name;
    SQL
  end

  def down
    execute <<-SQL
      DROP VIEW tagged_notes;
    SQL
  end
end

Now lets take a look at tagged_notes view.

tag_name taggings_count
Ruby 124056
React 30632
Rails 28696
Crystal 6566
Elixir 3475

Now you can create TaggedNote model and work with the view like you usually do with Rails models.

Materialized View Strategy

Similar to the strategy above, but the view will be Materialized and refreshed with the trigger

rails g metka:strategies:materialized_view --source-table-name=NAME_OF_TABLE_WITH_TAGS

The code above will generate a migration that creates view to store aggregated data about tag in NAME_OF_TABLE_WITH_TAGS table.

Lets take a look at real example. We have a notes table with tags column.

Column Type Default
id integer nextval('notes_id_seq'::regclass)
body text
tags character varying[] '{}'::character varying[]

Now lets generate a migration.

rails g metka:strategies:materialized_view --source-table-name=notes

The migration code you can see here

Now lets take a look at tagged_notes materialized view.

Now you can create TaggedNote model and work with the view like you usually do with Rails models.

Table Strategy with Triggers

TBD

Inspired by

  1. ActsAsTaggableOn
  2. ActsAsTaggableArrayOn
  3. TagColumns

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/[USERNAME]/metka. 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.

Credits

JetRockets Metka is maintained by JetRockets.

License

The gem is available as open source under the terms of the MIT License.

Code of Conduct

Everyone interacting in the Metka project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.