Railsでフォロー機能などを簡単に実装できるGemを公開しました

Railsでユーザのフォローやブロック、ミュート、あるいは記事へのいいね機能などを簡単に実装できるacts_in_relationというGemを公開しました。

インストール方法

Gemfileに以下を書いてbundleするだけで使えます。

gem 'acts_in_relation'

使い方

acts_in_relationは、(1)UserとUserといった同一モデルへの関係、(2)UserとPostといった異なるモデル間の関係、の2つの実装をサポートしています。

ここではそれぞれについて説明します。

(1) ユーザへのフォロー機能の実装

たとえばユーザ同士にフォロー機能を実装するとします。 まず、User、Follow各モデルを生成します。

$ rails g model User name:string
$ rails g model Follow user_id:integer target_user_id:integer

Followテーブルにインデックスを張ります。

class CreateFollows < ActiveRecord::Migration
  def change
    create_table :follows do |t|
      t.integer :user_id
      t.integer :target_user_id
      t.timestamps
    end

    add_index :follows, [:user_id, :target_user_id], unique: true
  end
end

最後に、User、Follow各モデルにacts_in_relationメソッドを追加します。

class User < ActiveRecord::Base
  acts_in_relation with: :follow
end

class Follow < ActiveRecord::Base
  acts_in_relation :action, source: :user, target: :user
end

以上より、Userのインスタンスに以下のメソッドが追加されます。

  • user.follow(other_user)
  • user.unfollow(other_user)
  • user.following?(other_user)
  • user.following
  • other_user.followed_by?(user)
  • other_user.followers

以下に動作例を示します。

user       = User.create(name: 'hoge')
other_user = User.create(name: 'fuga')

# フォローする
user.follow other_user
user.following?(other_user)   #=> true
user.following                #=> <ActiveRecord::Associations::CollectionProxy [#<User id: 2, name: "fuga", created_at: "2015-01-10 01:57:52", updated_at: "2015-01-10 01:57:52">]>
other_user.followed_by?(user) #=> true
other_user.followers          #=> <ActiveRecord::Associations::CollectionProxy [#<User id: 1, name: "hoge", created_at: "2015-01-10 01:57:42", updated_at: "2015-01-10 01:57:42">]>

# フォローを解除する
user.unfollow other_user
user.following?(other_user)   #=> false
user.following                #=> <ActiveRecord::Associations::CollectionProxy []>
other_user.followed_by?(user) #=> false
other_user.followers          #=> <ActiveRecord::Associations::CollectionProxy []>

同じように、ブロックやTwitterのようなミュート機能も実装できます。 これもマイグレーションとモデルを追加するだけでよいです。

class User < ActiveRecord::Base
  acts_in_relation target: :user, with: [:follow, :block, :mute]
end

(2) 記事へのいいね機能の実装

UserとPostといった、異なるモデル間でも同様の機能を実装することができます。 手順は同じで、acts_in_relationメソッドの引数が少し異なるだけです。

もちろん、(1)と同時に定義することができます。

class User < ActiveRecord::Base
  acts_in_relation with: :follow

  acts_in_relation :source, target: :post, with: :like
end

class Post < ActiveRecord::Base
  acts_in_relation :target, source: :user, with: :like
end

class Like < ActiveRecord::Base
  acts_in_relation :action, source: :user, target: :post
end

以上より、UserとPostのインスタンスに以下のメソッドが追加されます。

  • user.like(post)
  • user.unlike(post)
  • user.liking?(post)
  • user.liking
  • post.liked_by?(user)
  • post.likers

考え方

acts_in_relationでは、Source、Target、Actionという3つの役割があります。 それぞれの説明と、(1)、(2)でどのモデルが該当するかを示します。

種類 内容 (1) (2)
Source Actionをとるモデル User User
Target Actionを受けるモデル User Post
Action Source/Target間でとられる行動 Follow Like

Actionは、withというシンボルを渡すことで定義します。

この3つを理解することで、このGemも便利に使えるのではと思います。

背景

ユーザへのフォローやブロック機能を実装するGemとしては、以下のようなものがあります。

しかし、どちらもフォローやブロックといったユーザ間での関係を定義するようハードコーディングされたものであり、ミュートや記事へのいいねといった機能の実装はできません。

私がサービスをしていく中で、これらGemを1段階抽象化したものが必要だと思い、実装にいたりました。

参考

このGemの実装にあたっては、@kennさんの以下の記事を多く参考にさせていただきました。 大変有益な情報に感謝いたします。

やりたいこと

  • 同一モデル間のActionでsourcetargetを2回書くのはDRYでないので省略可能にする
  • 異なるモデル間のSourceとTargetで同じwithを書かなければいけないのはDRYでないので省略可能にする
  • Generatorを実装する
  • エラーハンドリングを実装する
  • RDocを書く