BETA

【Sinatra】でアソシエーション

投稿日:2019-12-03
最終更新:2019-12-03

Sinatraで、ActiveRecordの関連付けを使って、ユーザのフォロー/アンフォロー機能を作成します。

今回は、多対多の関係についてです。

1対多の関係はこちらの記事で解説しています。
https://qrunch.net/@sunnydayservice/entries/0Zi9ipwqcIBRvBDz?ref=qrunch

ActiveRecordを使用しているので、Railsとやることは変わりませんでした。

やること

1.中間テーブルの作成
2.各モデルに関連付けを記述する
3.モデルにフォロー/アンフォロー機能、フォローしているかの確認機能の追加
4.コントローラにViewから受け取った値をモデルに引き渡すコードの追加
5.Viewで、ボタンの作成(フォローしていない場合、フォローしている場合に分ける)

中間テーブルの作成

$ bundle exec rake db:create_migration NAME=create_relationships  

今回は、関連付けを表すためにrelationships テーブルを作成します。

上のコマンドで作成したファイルを編集します。

class CreateRelationships < ActiveRecord::Migration[6.0]  
  def change  
    create_table :relationships do |t|  
      t.references :user, foreign_key: true  
      t.references :follow, foreign_key: { to_table: :users }  

      t.timestamps  

      t.index [:user_id, :follow_id], unique: true  
    end  
  end  
end  

relationshipsテーブルに、user カラムと、follow カラムを作成します。
外部キーをusersテーブルに対して設定します。userカラムはuser(s)テーブルと同じ名前なので、自動で外部キーが設定されます。followカラムも、usersテーブルに対して外部キーを設定しています。

relationshipsテーブルのカラムはこんな感じで保存されます。(user_idも、follow_idもusersテーブルに紐づいています。)

各モデルに関連付けを記述する

userモデルと、relationshipモデルに追記します。ここで、モデルファイルとしてrelationship.rb を作成してください。

relationship.rb

class Relationship < ActiveRecord::Base  
    belongs_to :user  
    belongs_to :follow, class_name: 'User'  
end  

中間テーブルでは、1対多の関係を表しています。
user は、名前が同じなので自動でuserテーブルと関連付けされます。 一方follow
名前が違うので、class_name: 'User' としてあげないとuserテーブルと関連付けが出来ません。

user.rb

class User < ActiveRecord::Base      
    validates :name, presence: true, length: { maximum: 50 }  
    validates :email, presence: true, length: { maximum: 255 },  
                      format: { with: /\A[\w+\-.][email protected][a-z\d\-.]+\.[a-z]+\z/i },  
                      uniqueness: { case_sensitive: false }  

    has_secure_password  

    has_many :posts, dependent: :destroy  
    has_many :relationships  
    has_many :followings, through: :relationships, source: :follow  
    has_many :reverses_of_relationship, class_name: 'Relationship', foreign_key: 'follow_id'  
    has_many :followers, through: :reverses_of_relationship, source: :user  

    def follow(other_user)  
        unless self == other_user  
          self.relationships.find_or_create_by(follow_id: other_user.id)  
        end  
      end  

      def unfollow(other_user)  
        relationship = self.relationships.find_by(follow_id: other_user.id)  
        relationship.destroy if relationship  
      end  

      def following?(other_user)  
        self.followings.include?(other_user)  
      end  
end  

次はuserモデルです。

    has_many :relationships  
    has_many :followings, through: :relationships, source: :follow  
    has_many :reverses_of_relationship, class_name: 'Relationship', foreign_key: 'follow_id'  
    has_many :followers, through: :reverses_of_relationship, source: :user  

がミソです。
userカラムと、followカラムの関連付けを行っていることと、user_idもfollow_idもユーザテーブルのものだということを念頭に置いておいてください。

    has_many :relationships  
    has_many :followings, through: :relationships, source: :follow  

で、relationshipsテーブルと関連付けを行っています。through: :relationships, source: :follow とすることで、followカラムとの関連付けを行うことが出来ます。

    has_many :reverses_of_relationship, class_name: 'Relationship', foreign_key: 'follow_id'  
    has_many :followers, through: :reverses_of_relationship, source: :user  

では、上の関係と逆の関係を表しています。(relationshipsテーブルから、usersテーブルへのアクセス)

また、

    def follow(other_user)  
        unless self == other_user  
          self.relationships.find_or_create_by(follow_id: other_user.id)  
        end  
      end  

      def unfollow(other_user)  
        relationship = self.relationships.find_by(follow_id: other_user.id)  
        relationship.destroy if relationship  
      end  

      def following?(other_user)  
        self.followings.include?(other_user)  
      end  

では、フォロー機能、アンフォロー機能、確認機能の記述をしています。(コードの内容の説明は省きます。。)

コントローラにViewから受け取った値をモデルに引き渡すコードの追加

コントローラ側の処理を記述します。

user.rb

  post '/follow/:id' do  
    user = User.find(params[:id])  
    @current_user ||= User.find(session[:user_id]) if session[:user_id]  
    @current_user.follow(user)  
    redirect 'user/all'  
  end  

  post '/unfollow/:id' do  
    user = User.find(params[:id])  
    @current_user ||= User.find(session[:user_id]) if session[:user_id]  
    @current_user.unfollow(user)  
    redirect 'user/all'  
  end  

フォロー/アンフォロー共に、フォロー対象のuserのidと、ログイン中のユーザのidを取得し、モデルに引数を渡しています。

Viewで、ボタンの作成(フォローしていない場合、フォローしている場合に分ける)

以下のコードを追記します。

user/all.erb

                <% if current_user.following?(user) %>  
                    <form method="post" action="/user/unfollow/<%= user.id %>">   
                        <button class = "btn btn-outline-secondary" type="submit">unfollow</button>  
                    </form>             
                <% else %>  
                    <form method="post" action="/user/follow/<%= user.id %>">   
                        <button class = "btn btn-outline-secondary" type="submit">follow</button>  
                    </form>  
                <% end %>  

ログイン中のユーザが、他のユーザをフォローしているかどうかで、表示する処理を変えています。
こちらもそれほど難しくはないでしょう。

まとめ

Railsと同じでした。

技術ブログをはじめよう Qrunch(クランチ)は、プログラマの技術アプトプットに特化したブログサービスです
駆け出しエンジニアからエキスパートまで全ての方々のアウトプットを歓迎しております!
or 外部アカウントで 登録 / ログイン する
クランチについてもっと詳しく

この記事が掲載されているブログ

初学者の成長が垣間見れます

よく一緒に読まれる記事

0件のコメント

ブログ開設 or ログイン してコメントを送ってみよう