GitPedia

Action store

Store different kind of actions (Like ❀️, Follow πŸ‘, Star ⭐, Block ...) in one table via ActiveRecord Polymorphic Association.

From rails-engineΒ·Updated March 4, 2026Β·View on GitHubΒ·

Store different kinds of actions (Like, Follow, Star, Block, etc.) in a single table via ActiveRecord Polymorphic Associations. The project is written primarily in Ruby, distributed under the MIT License license, first published in 2017. Key topics include: activerecord, bookmark, favorite, follow, followers.

Latest release: v1.1.3
October 6, 2023View Changelog β†’

ActionStore

Gem Version build

Store different kinds of actions (Like, Follow, Star, Block, etc.) in a single table via ActiveRecord Polymorphic Associations.

  • Like Posts/Comment/Reply ...
  • Watch/Subscribe to Posts
  • Follow Users
  • Favorite Posts
  • Read Notifications/Messages

And more and more.

δΈ­ζ–‡δ»‹η»ε’Œδ½Ώη”¨θ―΄ζ˜Ž

Basic table struct

ColumnChars LimitDescription
action_type64The type of action [like, watch, follow, star, favorite]
action_option64Secondary option for storing your custom status, or null if unneeded.
target_type, target_id64Polymorphic Association for different Target models [User, Post, Comment]
user_type64Polymorphic Association for different user model [User, Group, Member]

Uniqueness

version: ">= 0.4.0"

The have database unique index on fields: [action_type, target_type, target_id, user_type, user_id] for keep uniqueness for same action from user to target.

Usage

rb
gem 'action-store'

and run bundle install

Generate Migrations:

bash
$ rails g action_store:install create config/initializers/action_store.rb migration 20170208024704_create_actions.rb from action_store

and run rails db:migrate.

Define Actions

Use action_store to define actions:

rb
# app/models/user.rb class User < ActiveRecord::Base action_store <action_type>, <target>, opts end

Convention Over Configuration:

action, targetTarget ModelTarget counter_cache_fieldUser counter_cache_fieldTarget has_manyUser has_many
action_store :like, :postPosthas_many :like_by_user_actions, has_many :like_by_usershas_many :like_post_actions, has_many :like_posts
action_store :like, :post, counter_cache: truePostlikes_counthas_many :like_by_user_actions, has_many :like_by_usershas_many :like_post_actions, has_many :like_posts
action_store :star, :project, class_name: 'Repository'Repository stars_countstar_projects_counthas_many :star_by_user_actions, has_many :star_by_users
action_store :follow, :userUserfollows_countfollow_users_counthas_many :follow_by_user_actions, has_many :follow_by_usershas_many :follow_user_actions, has_many :follow_users
action_store :follow, :user, counter_cache: 'followers_count', user_counter_cache: 'following_count'Userfollowers_count following_count has_many :follow_by_user_actions, has_many :follow_by_usershas_many :follow_user_actions, has_many :follow_users

for example:

rb
# app/models/user.rb class User < ActiveRecord::Base action_store :like, :post, counter_cache: true action_store :star, :post, counter_cache: true, user_counter_cache: true action_store :follow, :post action_store :like, :comment, counter_cache: true action_store :follow, :user, counter_cache: 'followers_count', user_counter_cache: 'following_count' end

Counter Cache

Add counter_cache field to target and user tables.

rb
add_column :users, :star_posts_count, :integer, default: 0 add_column :users, :followers_count, :integer, default: 0 add_column :users, :following_count, :integer, default: 0 add_column :posts, :likes_count, :integer, default: 0 add_column :posts, :stars_count, :integer, default: 0 add_column :comments, :likes_count, :integer, default: 0

Example usage:

@user likes @post

rb
irb> User.create_action(:like, target: @post, user: @user) true irb> @user.create_action(:like, target: @post) true irb> @post.reload.likes_count 1

@user1 unlikes @user2

rb
irb> User.destroy_action(:follow, target: @post, user: @user) true irb> @user.destroy_action(:like, target: @post) true irb> @post.reload.likes_count 0

Check if @user1 likes @post

rb
irb> action = User.find_action(:follow, target: @post, user: @user) irb> action = @user.find_action(:like, target: @post) irb> action.present? true

Other following use cases:

rb
# @user1 -> follow @user2 irb> @user1.create_action(:follow, target: @user2) irb> @user1.reload.following_count => 1 irb> @user2.reload.followers_count => 1 irb> @user1.follow_user?(@user2) => true # @user2 -> follow @user1 irb> @user2.create_action(:follow, target: @user1) irb> @user2.follow_user?(@user1) => true # @user1 -> follow @user3 irb> @user1.create_action(:follow, target: @user3) # @user1 -> unfollow @user3 irb> @user1.destroy_action(:follow, target: @user3)

Subscribe cases:

Sometimes, you may need use action_option option.

For example, user to subscribe a issue (like GitHub Issue) on issue create, and they wants keep in subscribe list on unsubscribe for makesure next comment will not subscribe this issue again.

So, in this case, we should not use @user.unsubscribe_issue method to destroy action record, we need set a value on action_option to mark this subscribe is ignore.

rb
irb> User.create_action(:subscribe, target: @issue, user: @user) irb> @user.subscribe_issue?(@issue) => true irb> User.create_action(:subscribe, target: @issue, user: @user, action_option: "ignore") irb> @user.subscribe_issue?(@issue) => true irb> action = User.find_action(:subscribe, target: @issue, user: @user) irb> action.action_option => "ignore" irb> @issue.subscribe_by_user_actions.count => 1 irb> @issue.subscribe_by_user_actions.where(action_option: nil).count => 0

Use different tables for store actions.

since 1.1.0

Sometimes, you may want to store actions into different table, for example, Like scenarios often have a lot of data, we wants store them into likes table.

Create a migration and model

bash
$ rails g migration create_likes

And then modify this migration file to let this table have same struct like the actions

rb
class CreateLikes < ActiveRecord::Migration[6.1] def change create_table :likes do |t| t.string :action_type, null: false t.string :action_option t.string :target_type t.bigint :target_id t.string :user_type t.bigint :user_id t.timestamps end add_index :likes, %i[user_type user_id action_type] add_index :likes, %i[target_type target_id action_type] add_index :likes, %i[action_type target_type target_id user_type user_id], unique: true, name: :uk_likes_target_user end end

Create a app/model/like.rb model file:

rb
class Like < Action self.table_name = "likes" end

And then change you action_store define to special some case to use Like model:

rb
# app/models/user.rb class User < ActiveRecord::Base action_store :like, :post, counter_cache: true, action_class_name: "Like" action_store :like, :comment, counter_cache: true, action_class_name: "Like" end

Now, user.like_post, user.like_comment will store the actions into likes table.

Built-in relations and methods

When you call action_store, ActionStore will define many-to-many relations for User and Target models.

For example:

rb
class User < ActiveRecord::Base action_store :like, :post action_store :block, :user end

Defines many-to-many relations:

  • For User model: <action>_<target>s (like_posts)
  • For Target model: <action>_by_users (like_by_users)
rb
# for User model has_many :like_post_actions has_many :like_posts, through: :like_post_actions ## as user has_many :block_user_actions has_many :block_users, through: :block_user_actions ## as target has_many :block_by_user_actions has_many :block_by_users, through: :block_by_user_actions # for Target model has_many :like_by_user_actions has_many :like_by_users, through: :like_user_actions

And User model will now have methods:

  • @user.create_action(:like, target: @post)
  • @user.destroy_action(:like, target: @post)
  • @user.find_action(:like, target: @post)
  • @user.like_post(@post)
  • @user.like_post?(@post)
  • @user.unlike_post(@post)
  • @user.block_user(@user1)
  • @user.unblock_user(@user1)
  • @user.like_post_ids
  • @user.block_user_ids
  • @user.block_by_user_ids

Contributors

Showing top 5 contributors by commit count.

View all contributors on GitHub β†’

This article is auto-generated from rails-engine/action-store via the GitHub API.Last fetched: 6/28/2026