{"id":13427777,"url":"https://github.com/rails-engine/action-store","last_synced_at":"2025-04-06T18:13:08.178Z","repository":{"id":55081428,"uuid":"80890819","full_name":"rails-engine/action-store","owner":"rails-engine","description":"Store different kind of actions (Like ❤️, Follow 👁, Star ⭐, Block ...) in one table via ActiveRecord Polymorphic Association.","archived":false,"fork":false,"pushed_at":"2024-09-27T15:18:57.000Z","size":179,"stargazers_count":406,"open_issues_count":3,"forks_count":29,"subscribers_count":9,"default_branch":"main","last_synced_at":"2025-03-30T17:04:48.272Z","etag":null,"topics":["activerecord","bookmark","favorite","follow","followers","likes","star","subscribe","watch"],"latest_commit_sha":null,"homepage":"https://rails-engine.github.io/action-store/","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rails-engine.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-02-04T03:34:37.000Z","updated_at":"2025-02-27T05:15:14.000Z","dependencies_parsed_at":"2024-04-10T05:38:42.620Z","dependency_job_id":"eaba3b58-cbca-44e5-b54e-5c66013acd19","html_url":"https://github.com/rails-engine/action-store","commit_stats":{"total_commits":96,"total_committers":5,"mean_commits":19.2,"dds":0.05208333333333337,"last_synced_commit":"16bf546f583ad4f5bfbc0dda766e1728ec88352a"},"previous_names":[],"tags_count":19,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rails-engine%2Faction-store","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rails-engine%2Faction-store/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rails-engine%2Faction-store/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rails-engine%2Faction-store/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rails-engine","download_url":"https://codeload.github.com/rails-engine/action-store/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247526753,"owners_count":20953143,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["activerecord","bookmark","favorite","follow","followers","likes","star","subscribe","watch"],"created_at":"2024-07-31T01:00:40.253Z","updated_at":"2025-04-06T18:13:08.159Z","avatar_url":"https://github.com/rails-engine.png","language":"Ruby","readme":"## ActionStore\n\n[![Gem Version](https://badge.fury.io/rb/action-store.svg)](https://badge.fury.io/rb/action-store) [![build](https://github.com/rails-engine/action-store/workflows/build/badge.svg)](https://github.com/rails-engine/action-store/actions?query=workflow%3Abuild)\n\nStore different kinds of actions (Like, Follow, Star, Block, etc.) in a single table via ActiveRecord Polymorphic Associations.\n\n- Like Posts/Comment/Reply ...\n- Watch/Subscribe to Posts\n- Follow Users\n- Favorite Posts\n- Read Notifications/Messages\n\nAnd more and more.\n\n[中文介绍和使用说明](https://ruby-china.org/topics/32262)\n\n## Basic table struct\n\n| Column                     | Chars Limit | Description                                                                 |\n| -------------------------- | ----------- | --------------------------------------------------------------------------- |\n| `action_type`              | 64          | The type of action [like, watch, follow, star, favorite]                    |\n| `action_option`            | 64          | Secondary option for storing your custom status, or null if unneeded.       |\n| `target_type`, `target_id` | 64          | Polymorphic Association for different `Target` models [User, Post, Comment] |\n| `user_type`                | 64          | Polymorphic Association for different user model [User, Group, Member]      |\n\n### Uniqueness\n\n\u003e version: \"\u003e= 0.4.0\"\n\nThe 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.\n\n## Usage\n\n```rb\ngem 'action-store'\n```\n\nand run `bundle install`\n\nGenerate Migrations:\n\n```bash\n$ rails g action_store:install\ncreate  config/initializers/action_store.rb\nmigration 20170208024704_create_actions.rb from action_store\n```\n\nand run `rails db:migrate`.\n\n### Define Actions\n\nUse `action_store` to define actions:\n\n```rb\n# app/models/user.rb\nclass User \u003c ActiveRecord::Base\n  action_store \u003caction_type\u003e, \u003ctarget\u003e, opts\nend\n```\n\n#### Convention Over Configuration:\n\n| action, target                                                                                         | Target Model  | Target `counter_cache_field` | User `counter_cache_field` | Target has_many                                                 | User has_many                                             |\n| ------------------------------------------------------------------------------------------------------ | ------------- | ---------------------------- | -------------------------- | --------------------------------------------------------------- | --------------------------------------------------------- |\n| `action_store :like, :post`                                                                            | `Post`        |                              |                            | `has_many :like_by_user_actions`, `has_many :like_by_users`     | `has_many :like_post_actions`, `has_many :like_posts`     |\n| `action_store :like, :post, counter_cache: true`                                                       | `Post`        | `likes_count`                |                            | `has_many :like_by_user_actions`, `has_many :like_by_users`     | `has_many :like_post_actions`, `has_many :like_posts`     |\n| `action_store :star, :project, class_name: 'Repository'`                                               | `Repository ` | `stars_count`                | `star_projects_count`      | `has_many :star_by_user_actions`, `has_many :star_by_users`     |\n| `action_store :follow, :user`                                                                          | `User`        | `follows_count`              | `follow_users_count`       | `has_many :follow_by_user_actions`, `has_many :follow_by_users` | `has_many :follow_user_actions`, `has_many :follow_users` |\n| `action_store :follow, :user, counter_cache: 'followers_count', user_counter_cache: 'following_count'` | `User`        | `followers_count `           | `following_count `         | `has_many :follow_by_user_actions`, `has_many :follow_by_users` | `has_many :follow_user_actions`, `has_many :follow_users` |\n\nfor example:\n\n```rb\n# app/models/user.rb\nclass User \u003c ActiveRecord::Base\n  action_store :like, :post, counter_cache: true\n  action_store :star, :post, counter_cache: true, user_counter_cache: true\n  action_store :follow, :post\n  action_store :like, :comment, counter_cache: true\n  action_store :follow, :user, counter_cache: 'followers_count', user_counter_cache: 'following_count'\nend\n```\n\n### Counter Cache\n\nAdd counter_cache field to target and user tables.\n\n```rb\nadd_column :users, :star_posts_count, :integer, default: 0\nadd_column :users, :followers_count, :integer, default: 0\nadd_column :users, :following_count, :integer, default: 0\n\nadd_column :posts, :likes_count, :integer, default: 0\nadd_column :posts, :stars_count, :integer, default: 0\n\nadd_column :comments, :likes_count, :integer, default: 0\n```\n\n#### Example usage:\n\n@user likes @post\n\n```rb\nirb\u003e User.create_action(:like, target: @post, user: @user)\ntrue\nirb\u003e @user.create_action(:like, target: @post)\ntrue\nirb\u003e @post.reload.likes_count\n1\n```\n\n@user1 unlikes @user2\n\n```rb\nirb\u003e User.destroy_action(:follow, target: @post, user: @user)\ntrue\nirb\u003e @user.destroy_action(:like, target: @post)\ntrue\nirb\u003e @post.reload.likes_count\n0\n```\n\nCheck if @user1 likes @post\n\n```rb\nirb\u003e action = User.find_action(:follow, target: @post, user: @user)\nirb\u003e action = @user.find_action(:like, target: @post)\nirb\u003e action.present?\ntrue\n```\n\n**Other following use cases:**\n\n```rb\n# @user1 -\u003e follow @user2\nirb\u003e @user1.create_action(:follow, target: @user2)\nirb\u003e @user1.reload.following_count\n=\u003e 1\nirb\u003e @user2.reload.followers_count\n=\u003e 1\nirb\u003e @user1.follow_user?(@user2)\n=\u003e true\n\n# @user2 -\u003e follow @user1\nirb\u003e @user2.create_action(:follow, target: @user1)\nirb\u003e @user2.follow_user?(@user1)\n=\u003e true\n\n# @user1 -\u003e follow @user3\nirb\u003e @user1.create_action(:follow, target: @user3)\n\n# @user1 -\u003e unfollow @user3\nirb\u003e @user1.destroy_action(:follow, target: @user3)\n```\n\n**Subscribe cases:**\n\nSometimes, you may need use `action_option` option.\n\nFor 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.\n\nSo, 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`.\n\n```rb\nirb\u003e User.create_action(:subscribe, target: @issue, user: @user)\nirb\u003e @user.subscribe_issue?(@issue)\n=\u003e true\n\nirb\u003e User.create_action(:subscribe, target: @issue, user: @user, action_option: \"ignore\")\nirb\u003e @user.subscribe_issue?(@issue)\n=\u003e true\n\nirb\u003e action = User.find_action(:subscribe, target: @issue, user: @user)\nirb\u003e action.action_option\n=\u003e \"ignore\"\n\nirb\u003e @issue.subscribe_by_user_actions.count\n=\u003e 1\nirb\u003e @issue.subscribe_by_user_actions.where(action_option: nil).count\n=\u003e 0\n```\n\n## Use different tables for store actions.\n\n\u003e since 1.1.0\n\nSometimes, 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.\n\nCreate a migration and model\n\n```bash\n$ rails g migration create_likes\n```\n\nAnd then modify this migration file to let this table have same struct like the `actions`\n\n```rb\nclass CreateLikes \u003c ActiveRecord::Migration[6.1]\n  def change\n    create_table :likes do |t|\n      t.string :action_type, null: false\n      t.string :action_option\n      t.string :target_type\n      t.bigint :target_id\n      t.string :user_type\n      t.bigint :user_id\n\n      t.timestamps\n    end\n\n    add_index :likes, %i[user_type user_id action_type]\n    add_index :likes, %i[target_type target_id action_type]\n    add_index :likes, %i[action_type target_type target_id user_type user_id], unique: true, name: :uk_likes_target_user\n  end\nend\n```\n\nCreate a `app/model/like.rb` model file:\n\n```rb\nclass Like \u003c Action\n  self.table_name = \"likes\"\nend\n```\n\nAnd then change you `action_store` define to special some case to use Like model:\n\n```rb\n# app/models/user.rb\nclass User \u003c ActiveRecord::Base\n  action_store :like, :post, counter_cache: true, action_class_name: \"Like\"\n  action_store :like, :comment, counter_cache: true, action_class_name: \"Like\"\nend\n```\n\nNow, `user.like_post`, `user.like_comment` will store the actions into `likes` table.\n\n## Built-in relations and methods\n\nWhen you call `action_store`, ActionStore will define many-to-many relations for User and Target models.\n\nFor example:\n\n```rb\nclass User \u003c ActiveRecord::Base\n  action_store :like, :post\n  action_store :block, :user\nend\n```\n\nDefines many-to-many relations:\n\n- For User model: `\u003caction\u003e_\u003ctarget\u003es` (like_posts)\n- For Target model: `\u003caction\u003e_by_users` (like_by_users)\n\n```rb\n# for User model\nhas_many :like_post_actions\nhas_many :like_posts, through: :like_post_actions\n## as user\nhas_many :block_user_actions\nhas_many :block_users, through: :block_user_actions\n## as target\nhas_many :block_by_user_actions\nhas_many :block_by_users, through: :block_by_user_actions\n\n# for Target model\nhas_many :like_by_user_actions\nhas_many :like_by_users, through: :like_user_actions\n```\n\nAnd `User` model will now have methods:\n\n- @user.create_action(:like, target: @post)\n- @user.destroy_action(:like, target: @post)\n- @user.find_action(:like, target: @post)\n- @user.like_post(@post)\n- @user.like_post?(@post)\n- @user.unlike_post(@post)\n- @user.block_user(@user1)\n- @user.unblock_user(@user1)\n- @user.like_post_ids\n- @user.block_user_ids\n- @user.block_by_user_ids\n","funding_links":[],"categories":["Active Record","Ruby"],"sub_categories":["Omniauth"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frails-engine%2Faction-store","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frails-engine%2Faction-store","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frails-engine%2Faction-store/lists"}