ActsAsTaggableArrayOn

Build Status

A simple implementation for tagging system based on a database array type. Only PostgreSQL Arrays are currently supported.

Installation

Add this line to your application's Gemfile:

gem "acts-as-taggable-array-on"

And then execute:

bundle

Setup

To use it, you need to have an array column to act as taggable - tags.

class CreateUser < ActiveRecord::Migration
  def change
    create_table :users do |t|
      t.string :tags, array: true, default: []
      t.timestamps
    end
    add_index :users, :tags, using: "gin"
  end
end
  • You can change from string to any other type here. But in case of any doubt, string is a great default.
  • Make sure not to lose default: [], it's important to always have empty array as default.

Run a migration:

rake db:migrate

Indicate that attribute is "taggable" in a Rails model, like this:

class User < ActiveRecord::Base
  taggable_array :tags
end

Types

We currently tested only following types for underlying arrays:

  • varchar[]
    • t.string :tags, array: true, default: []
    • add_column :users, :tags, :string, array: true, default: []
  • text[]
    • t.text :tags, array: true, default: []
    • add_column :users, :tags, :text, array: true, default: []
  • integer[]
    • t.integer :tags, array: true, default: []
    • add_column :users, :tags, :integer, array: true, default: []

Usage

#set
user.tags = ["awesome", "slick"]
user.tags = "{awesome,slick}"

#add
user.tags += ["awesome"]
user.tags += ["awesome", "slick"]
user.tags << "awesome"

#remove
user.tags -= ["awesome"]
user.tags -= ["awesome", "slick"]

Scopes

with_any_#{tag_name}

# Find a user with any of the tags
User.with_any_tags("awesome, slick")
User.with_any_tags(["awesome", "slick"])

with_all_#{tag_name}

# Find a user with all of the tags
User.with_all_tags("awesome, slick")
User.with_all_tags(["awesome", "slick"])

without_any_#{tag_name}

# Find a user without any of the tags
User.without_any_tags("awesome, slick")
User.without_any_tags(["awesome", "slick"])

without_all_#{tag_name}

# Find a user without all of the tags
User.without_all_tags("awesome, slick")
User.without_all_tags(["awesome", "slick"])

Class methods

all_#{tag_name}

User.all_tags
# ["awesome", "slick"]

You can use block to add scopes to the query.

User.all_tags { where(name: ["ken", "tom"]) }

Or simply use your existing scopes:

# scope :by_join_date, ->{order("created_at DESC")}
User.all_tags.by_join_date

SQL field is named "tag" and you can use it to modify the query.

User.where("tag like ?", "aws%").all_tags { where(name: ["ken", "tom"]) }

#{tag_name}_cloud

Calculates the number of occurrences of each tag.

User.tags_cloud
# [["slick" => 2], ["awesome" => 1]]

You can use block to add scopes to the query.

User.tags_cloud { where(name: ["ken", "tom"]) }

SQL fields are named "tag" and "count" and you can use them to modify the query.

User.where("tag like ?", "aws%").limit(10).order("count desc").tags_cloud { where(name: ["ken", "tom"]) }

Benchmark

Based on the article, I built simple benchmark app to compare only the main features ActsAsTaggableArrayOn has.

This result does NOT insist ActsAsTaggableArrayOn is better than acts-as-taggable-on since it provides much more features than this gem. In the case you need simple tag functionality, acts-as-taggable-array-on may be helpful to improve its performance.

% rake bench:write bench:find_by_id bench:find_by_tag
Deleted all ActsAsTaggableOn::Tag
Deleted all ActsAsTaggableOn::Tagging
Deleted all TaggableUser
Deleted all TaggableArrayUser
Finsihed to clean


###################################################################

bench:write
Rehearsal ---------------------------------------------------------
Using Taggable          6.950000   0.420000   7.370000 (  9.223704)
Using Postgres Arrays   0.710000   0.090000   0.800000 (  1.184734)
------------------------------------------------ total: 8.170000sec

                            user     system      total        real
Using Taggable          5.800000   0.340000   6.140000 (  7.842051)
Using Postgres Arrays   0.680000   0.090000   0.770000 (  1.117812)

###################################################################

bench:find_by_id
Rehearsal ---------------------------------------------------------
Using Taggable          1.490000   0.110000   1.600000 (  2.079776)
Using Postgres Arrays   0.240000   0.030000   0.270000 (  0.419430)
------------------------------------------------ total: 1.870000sec

                            user     system      total        real
Using Taggable          1.440000   0.100000   1.540000 (  2.023188)
Using Postgres Arrays   0.250000   0.040000   0.290000 (  0.434233)

###################################################################

bench:find_by_tag
Rehearsal ---------------------------------------------------------
Using Taggable          0.600000   0.040000   0.640000 (  1.107227)
Using Postgres Arrays   0.060000   0.000000   0.060000 (  0.060019)
------------------------------------------------ total: 0.700000sec

                            user     system      total        real
Using Taggable          0.600000   0.040000   0.640000 (  1.100302)
Using Postgres Arrays   0.030000   0.000000   0.030000 (  0.033001)
rake bench:write bench:find_by_id bench:find_by_tag  20.29s user 1.52s system 77% cpu 28.322 total

Development

  • To run testsuite you'll need to setup local PG database/user with rake db:create After that just running rspec should work.
  • Before submitting code for a review, please be sure to run bundle exec standardrb --fix

Contributing

  1. Fork it ( http://github.com/tmiyamon/acts-as-taggable-array-on/fork )
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create new Pull Request