「Ruby on Rails Tutorial」のサンプルアプリをAngularJSとBootstrap3を使う形にして作成します。今回からフォロアーの機能を追加しています。まずフォロアーに関するモデル、アソシエーションを定義し、AngularJSのビューでフォロアーの数を表示します。
(1)フォロアーのモデル、アソシエーション、バリデーション定義作成
フォローしている人(follower_id)とフォローされている人(followed_id)を結びつけるRelationshipモデルを作成します。
1)モデルの作成
$ rails generate model Relationship follower_id:integer followed_id:integer
2)relationshipsテーブルを定義
各項目のインデックスと複合インデックスを追加します。
$ vi db/migrate/20150724080930_create_relationships.rb
class CreateRelationships < ActiveRecord::Migration def change create_table :relationships do |t| t.integer :follower_id t.integer :followed_id t.timestamps end add_index :relationships, :follower_id add_index :relationships, :followed_id add_index :relationships, [:follower_id, :followed_id], unique: true end end
3)マイグレーション実行
$ bundle exec rake db:migrate
4)relationshipsモデルにバリデーションチェック追加
$ vi app/models/relationship.rb
validates :follower_id, presence: true
validates :followed_id, presence: true
(2)ユーザーとフォローしているユーザーとのアソシエーション定義
そのユーザーがフォローしているユーザーである"followed_users"の関連を定義します。
1)Userモデルにアソシエーション追加
Relationshipテーブルは、follower_idとfollowed_idのカラムから構成されています。
ユーザーとそのユーザーがフォローしている人との関連を定義します
has_many throughを使って定義します。
①relationshipsモデルにアソシエーション定義
relationshipsは、二人のユーザー間の関係を示すので、フォローしているユーザーとフォローされている人の両方に属します。
Userモデルとのアソシエーションを"followed"というアソシエーション名で定義します。"follower"のアソシエーションは、フォロアーのアソシエーション定義に使用します。
$ vi app/models/relationship.rb
belongs_to :follower, class_name: "User"
belongs_to :followed, class_name: "User"
"follower"と"followed"はモデルではないので、class_nameでクラスを記述します。
②Userモデルにアソシエーションを定義
"followed_users"というアソシエーション名でhas_many :throughを使って定義します。
これによってrelationshipsテーブルのfollowed_idを使用してそのユーザがフォローしているユーザーを表すfollowed_users配列を作成できます。
$ vi app/models/user.rb
has_many :relationships, foreign_key: "follower_id", dependent: :destroy
has_many :followed_users, through: :relationships, source: :followed
・Railsは、外部キーを"<class>_id"の形式で記述されていると期待していますが、この場合は"relationship"テーブル内の"follower_id"を外部キーとして使用するので明示的に指定します。
・relationshipモデルには、"followed"と"follower"の2つのアソシエーションを定義しているのでどちらを使用するのか指定する必要があります。sourceを使って指定します。
・通常は下記のように指定しますが、"followed"の複数形がおかしくなるので、"followed_users"に変更し、"source:"パラメータで配列の元を明示しています。
has_many :followeds, through: :relationships
2)Userモデルにメソッド追加
上記で定義したhas_many :relationshipsのアソシエーションを使ってユーザー定義のメソッドを追加します。
$ vi app/models/user.rb
def following?(other_user) relationships.find_by(followed_id: other_user.id) end def follow!(other_user) relationships.create!(followed_id: other_user.id) end def unfollow!(other_user) relationships.find_by(followed_id: other_user.id).destroy end
①following?(other_user)
・"other_user"をすでにフォローしているか確認する。
・データベース上に存在するかチェックする。
②follow!(other_user)
・"other_user"をフォローするため"relationship"テーブルにレコードを作成する。
・感嘆符"!"をつけ、作成に失敗した場合は例外を発生するようにする。
③unfollow!(other_user)
・"other_user"のフォローを"relationship"テーブルから削除する。
(3)ユーザーとフォロアーのアソシエーション定義
そのユーザーをフォローしているユーザー"followers"の関連を定義します。
1)Userモデルにアソシエーション追加
①relationshipsモデルのアソシエーション定義を確認
$ more app/models/relationship.rb
belongs_to :follower, class_name: "User"
belongs_to :followed, class_name: "User"
②Userモデルにアソシエーション追加
"followers"というアソシエーション名でhas_many :throughを使って定義します。
これによってrelationshipsテーブルのfollower_idを使用してそのユーザーのフォロアーを表すfollowers配列を作成できます。
すでに定義済みの"followed_users"のアソシエーションの定義と対になる関係なので、逆リレーションシップによって定義します。
"reverse_relationships"に関する定義を2行追加します。
$ vi app/models/user.rb
has_many :reverse_relationships, foreign_key: "followed_id", class_name: "Relationship", dependent: :destroy has_many :followers, through: :reverse_relationships, source: :follower
2)コンソールで確認
ユーザーとフォローしている、フォロアーのリレーション定義が一通り出来たので、コンソールで確認します。
①ユーザーインスタンス作成
2.0.0-p247 :005 > user2=User.find_by(id: 2)
2.0.0-p247 :006 > user6=User.find_by(id: 6)
②"following?"メソッドを使ってお互いをフォローしているか確認
2.0.0p247 :021 > user2.following?(user6)
=> nil
2.0.0p247 :022 > user6.following?(user2)
=> nil
③"follow!"メソッドを使ってお互いフォロー
2.0.0p247 :023 > user2.follow!(user6)
(0.2ms) begin transaction
SQL (0.3ms) INSERT INTO "relationships" ("created_at", "followed_id", "follower_id", "updated_at") VALUES (?, ?, ?, ?) [["created_at", "2015-07-24 08:17:13.716171"], ["followed_id", 6], ["follower_id", 2], ["updated_at", "2015-07-24 08:17:13.716171"]]
(3.4ms) commit transaction
=> #
2.0.0p247 :024 > user6.follow!(user2)
④relationships、reverse_relationshipsのアソシエーション
2.0.0p247 :009 > user2.relationships
=> #
2.0.0p247 :010 > user2.reverse_relationships
Relationship Load (0.2ms) SELECT "relationships".* FROM "relationships" WHERE "relationships"."followed_id" = ? [["followed_id", 2]]
=> #
2.0.0p247 :015 > Relationship.all
Relationship Load (0.2ms) SELECT "relationships".* FROM "relationships"
=> #
2.0.0p247 :016 > user2.relationships.find_by(followed_id: user6)
Relationship Load (0.1ms) SELECT "relationships".* FROM "relationships" WHERE "relationships"."follower_id" = ? AND "relationships"."followed_id" = 6 LIMIT 1 [["follower_id", 2]]
=> #
⑤followed_users、followersのアソシエーション
下記アソシエーションを通じて取得します。
has_many :followers, through: :reverse_relationships, source: :follower
has_many :followed_users, through: :relationships, source: :followed
2.0.0p247 :025 > user2.followed_users.count
=> 1
2.0.0p247 :026 > user2.followers.count
=> 1
2.0.0p247 :027 > user6.followed_users.count
=> 1
2.0.0p247 :029 > user6.followers.count
=> 1
2.0.0p247 :032 > user2.followers.all
2.0.0p247 :033 > user2.followed_users.all
(4)フォローしている人、フォロアーのカウント数を表示
対象ユーザーがフォローしている人、フォロアーの人数を計算し、ホームページに表示します。
1)Railsコントローラのアクションにfollowed_users、followersを追加
$ vi app/controllers/sessions_controller.rb
def create user = User.find_by(email: session_params[:email].downcase) if user && user.authenticate(session_params[:password]) remember_token = User.new_remember_token cookies.permanent[:remember_token] = remember_token user.update_attribute(:remember_token, User.encrypt(remember_token)) gravatar_id = Digest::MD5::hexdigest(user.email.downcase) microposts = user.microposts @user_info = { user: user, gravatar_url: "https://secure.gravatar.com/avatar/#{gravatar_id}", microposts: microposts, feed: user.feed, followed_users: user.followed_users, followers: user.followers } render json: @user_info, status: :accepted, location: user else : def current_user remember_token = User.encrypt(cookies[:remember_token]) current_user ||= User.find_by(remember_token: remember_token) if current_user gravatar_id = Digest::MD5::hexdigest(current_user.email.downcase) microposts = current_user.microposts @user_info = { user: current_user, gravatar_url: "https://secure.gravatar.com/avatar/#{gravatar_id}", microposts: microposts, feed: current_user.feed, followed_users: current_user.followed_users, followers: current_user.followers } render json: @user_info, status: :accepted else head :no_content end end
2)AngularJSのビューを修整
homeビューにフォローしている人、フォロアー数の表示を追加します。
$ vi app/assets/templates/static_pages/home.html.erb
<div ng-controller="HomeCtrl"> <div ng-show="chkSignin().user.id > 0" class="row"> : <div class="col-sm-4"> : <div class="stats"> <a href="#"> <strong id="following" class="stat"> {\{chkSignin().followed_users.length}} </strong> following </a> <a href="#"> <strong id="followers" class="stat"> {\{chkSignin().followers.length}} </strong> followers </a> </div>
3)CSSの設定追加
$ vi app/assets/stylesheets/custom.css.scss
.stats { overflow: auto; border-top: 1px solid $grayLighter; a { float: left; padding: 0 10px; border-left: 1px solid $grayLighter; color: gray; &:first-child { padding-left: 0; border: 0; } &:hover { text-decoration: none; color: blue; } } strong { display: block; } }