各类系统总会或多或少的需要一些赞、收藏、关注/订阅等功能。
每次我们都要重新开发么?不!那不是 DRY 的风格。
鉴于多次这类功能的实践总结,我发布了 ActionStore,一个可以帮你解决这类需求的库。
ActionStore 采用 Active Record 多态关联(Polymorphic Association)的方式存储各种类型的动作数据,例如:赞、喜欢、收藏、关注、订阅、屏蔽(靠你的想象,还可以干更多的事情)等等,各类 User -> Target 的场景。
因此你无需对这些动作重复重建表、Model,也不用重复写 like_topic
, unlike_topic
, @topic.like_by_users
, @user.like_topics
之类的逻辑函数。一切都由 ActionStore 内部帮你处理掉。
https://github.com/rails-engine/action-store
Field | 含义 |
---|---|
action_type |
action 类型 [like, watch, follow, star, favorite] |
user_type , user_id
|
多态的方式关联 User models [User, Person, Member] |
target_type , target_id
|
多态的方式关联 Target models [User, Post, Comment] |
gem 'action-store'
and run bundle install
$ rails g action_store:install
create config/initializers/action_store.rb
migration 20170208024704_create_actions.rb from action_store
将会把 Migration 放入你的项目里面,最后执行 rails db:migrate
你需要在 User model 里面使用 action_store
来定义 actions:
app/models/user.rb
class User < ActiveRecord::Base
# action_store <action_type>, <target>, opts
# 例如
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
action, target | Target Model | Target counter_cache_field
|
User counter_cache_field
|
Target has_many | User has_many |
---|---|---|---|---|---|
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
|
||
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
|
|
action_store :star, :project, class_name: 'Repository' |
Repository |
stars_count |
star_projects_count |
has_many :star_by_user_actions , has_many :star_by_users
|
|
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
|
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
|
ActionStore 内建 Counter Cache 的逻辑(实际上目前是完整统计的,以确保精确),如果你需要,你得在 Target, User 的表里面增加相应的字段。
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
irb> User.create_action(:like, target: @post, user: @user)
true
irb> @user.create_action(:like, target: @post)
true
irb> @post.reload.likes_count
1
irb> User.destroy_action(:follow, target: @post, user: @user)
true
irb> @user.destroy_action(:like, target: @post)
true
irb> @post.reload.likes_count
0
irb> action = User.find_action(:follow, target: @post, user: @user)
irb> action = @user.find_action(:like, target: @post)
irb> action.present?
true
User follow cases:
# @user1 -> follow @user2
@user1.create_action(:follow, target: @user2)
@user1.reload.following_count => 1
@user2.reload.followers_count_ => 1
@user1.follow_user?(@user2) => true
# @user2 -> follow @user1
@user2.create_action(:follow, target: @user1)
@user2.follow_user?(@user1) => true
# @user1 -> follow @user3
@user1.create_action(:follow, target: @user3)
# @user1 -> unfollow @user3
@user1.destroy_action(:follow, target: @user3)
since 1.1.0
某些时候,我们可能期望将 Action Store 存储到不同的表里面,比如 点赞、已读 这类场景数据规模可能会很大,这种时候我们可能有期望将这些动作存储到独立的表里面。
自从 action-store 1.1.0 版本开始,我们可以用 action_class_name
来定义 action 动作存储的 Model。
你需要创建一个新的 Model 和 Migration
$ rails g migration create_likes
然后修改 Migration 文件,将它的字段、索引保持和 actions
表一致,就像这样:
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
创建你的 Like model,并继承 Action
model:
# app/model/like.rb`
class Like < Action
# 指定表名称
self.table_name = "likes"
end
修改 action_store
的定义,使用 action_class_name
指定使用 Like
model 来存储 actions:
# 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
现在 user.like_post
, user.like_comment
这些动作将会存储到 likes
表了。
当你用 action_store
定义 actions 以后,ActionStore 会自动创建 Target 和 User 的多对多关联关系。
例如:
class User < ActiveRecord::Base
action_store :like, :post
action_store :block, :user
end
会定义 Many-to-Many 关系:
<action>_<target>s
(like_posts)<action>_by_users
(like_by_users)# 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
同时 User
model 将会得到这些函数:
@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
此方法将会替代 Ruby China 里面的类似功能,参见这个 ruby-china/homeland#857