[RUBY] [Rails] I will explain the implementation procedure of the follow function using form_with.

Overview

I will write the procedure to implement the follow function using form_with!

We will implement the following functions __1. Ability to follow / unfollow users __ __2. Follow user / follower list display function __

I referred to the article How to make a follow function with Rails, especially for a detailed explanation of the association related to the follow function, etc. became!

Premise

environment ・ Rails 5.2.4.2 ・ Ruby 2.6.5 ・ Slim ・ Devise

__ In addition, it is assumed that User functions are implemented. __

__ How to set up a Rails application in the above environment __ -Procedure to install devise and Slim after setting up Rails application

__ ↓ It will look like this after implementation. (I'm sorry it looks simple ...) ↓ __ ezgif.com-video-to-gif (3).gif

Implementation of follow / unfollow function

1. Relationship model creation

$ rails g model Relationship user:references follow:references

__ · The relationships table created here is an intermediate table for the users to follow & the users to be followed. __

The intermediate table recognizes __ · ʻuseras the follower user model andfollow` as the follower user model. __


2. Edit relationships migration file

db/migrate/[timestamps]_create_relationships.rb


class CreateRelationships < ActiveRecord::Migration[5.2]
  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

The reason why the referenced models are separated by __ · user and follow is that the following user and the followed user must be considered as separate models. __

__ · t.references: user, foreign_key: true __: __user A foreign key for the model is set. __

__ · t.references: follow, foreign_key: {to_table :: users} __: __follow Foreign keys for the model. The follow model is a fictitious model name that does not exist, and I am creating it at this time. Therefore, by setting {to_table :: users}, the model of the reference source is taught. __

__ · t.index [: user_id,: follow_id], unique: true __: By giving uniqueness to the combination of __user_id and follow_id, data duplication is prevented, that is, the same user is followed twice. Is prevented. __


3. Save the contents of DB

$ rails db:migrate

4. Describe the association for User in the Relationship model.

app/models/relationship.rb


class Relationship < ApplicationRecord
  belongs_to :user
  belongs_to :follow, class_name: "User"
 
  validates :user_id, presence: true
  validates :follow_id, presence: true
end

__ · belongs_to: user__: __ This is the usual association. __

__ · belongs_to: follow, class_name:" User " __: __ It is described that it is owned by the follow class, but as mentioned above, follow is a fictitious class, so refer to it by class_name:" User " I will tell you the original model. __


5. Describe the association for Relationship in the User model file.

app/models/user.rb


class User < ApplicationRecord
 
#==============Association with a user that a user is following=================
  has_many :relationships, foreign_key: "user_id",
                           dependent: :destroy
  has_many :followings, through: :relationships, source: :follow
#==========================================================================
 
#==============Association with users who follow a user================
  has_many :passive_relationships, class_name: "Relationship",
                                   foreign_key: "follow_id",
                                   dependent: :destroy
  has_many :followers, through: :passive_relationships, source: :user
#===========================================================================
end

__ · The above Relationship model file describes the association for a user to access a user being followed and the association for a user to access a user being followed. In other words, the details will be described later in the figure. __

5-1. First, let's talk about the association with a user that a user is following!

__ · has_many: relationships, foreign_key:" user_id ", dependent:: destroy__: __ This declares an association` that allows a User to access the foreign key user_id in the intermediate table relationships. I am. __

__ I think it's easy to understand if you imagine it as follows! __

![Screenshot 2020-06-27 13.44.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/606750/8a356efb-abfc-dbfa-60ce -9aca47c5c2d3.png)

__ · has_many: followings, through :: relationships, source:: follow__: __ This declares an association that allows a User to access the following model through the follow_id in the intermediate table relationships. I will. __

__ The image looks like this! __

![Screenshot 2020-06-27 13.57.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/606750/1dc2397f-2f50-7056-5dde -32cda530340c.png)

__ ・ From the above two lines, you can get the users that User is following by setting ʻUser.followings`. Once this is available, you can easily display a list of followers of a certain User. __

5-2. Next is the explanation of the association with the user who is following a certain user!

__ · has_many: passive_relationships, class_name:" Relationship ", foreign_key:" follow_id ", dependent:: destroy__: __ This declares an association that allows a User to access the follow_id of passive_relationships. I am. Since passive_relationships is a fictitious model that I made on my own, I teach the model of the reference source as class_name: "Relationship". __

The image is as follows!

![Screenshot 2020-06-27 14.13.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/606750/56cc736f-ccb5-6e43-3202 -1e57d223520a.png)

__ · has_many: followers, through:: passive_relationships, source:: user__: __ This declares an association that allows a user to access followers through the user_id of passive_relationships. __

The image is as follows!

![Screenshot 2020-06-27 14.26.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/606750/6258ee85-2d12-5a92-66a2 -3f5281d57f3b.png)

__ ・ From the above two lines, if you use User.followers, you can get the users who follow User. With this, you can easily display a list of users who are following a certain user! __


6. Define follow-related instance methods in the User model file

app/models/user.rb


class User < ApplicationRecord
  .
  .
  .
  #<Association-related description omitted>
  .
  .
  .
  def following?(other_user)
    self.followings.include?(other_user)
  end

  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
end

-Check if User is following @user by setting __following? (Other_user) __: __ ʻUser.followings.include? (@User)`. When implementing the follow button, it is used to conditionally branch whether to display the button to follow or the button to unfollow. __

-By setting follow (other_user) : __ʻUser.follow (@user), you can follow @user. ʻUnless self == other_user to prevent you from following yourself. I use find_or_create_by to refer to the @user id record if it exists and create it if it doesn't. __

-By setting __ʻunfollow (other_user) __: __ ʻUser.unfollow (@user), you can unfollow @user. __

-Since we have defined the methods to follow and unfollow the __user, the next step is to create a relationships controller and implement the function to actually access the DB and follow / unfollow using these methods. I will do it! __


7. Create a relationships controller.

$ rails g controller relationships 

app/controllers/relationships_controller.rb


class RelationshipsController < ApplicationController

  before_action :set_user

  def create
    following = current_user.follow(@user)
    if following.save
      flash[:success] = "Followed the user"
      redirect_to @user
    else
      flash.now[:alert] = "Failed to follow user"
      redirect_to @user
    end
  end

  def destroy
    following = current_user.unfollow(@user)
    if following.destroy
      flash[:success] = "Unfollowed user"
      redirect_to @user
    else
      flash.now[:alert] = "Failed to unfollow user"
      redirect_to @user
    end
  end

  private
  def set_user
    @user = User.find(params[:relationship][:follow_id])
  end
end

-In the __relationships controller, only the create action that follows the user and the destroy action that unfollows work, so describe only the create action and destroy action. __    -From the __` follow / unfollow button form, data is sent in the form of params [: relationship] [: follow_id], so the data is in the form of find (params [: relationships] [: follow_id]). Receive. __

-before_action: set_user: __ Since @user is always acquired in each action, set_user private method is defined and then before_action is used to acquire it in advance. __

-__ create action __: __ The logged-in user (current_user) is following the acquired @user. I am using the follow (other_user) instance method defined in the User model file. __

-__ destroy action __: __ The logged-in user is unfollowing the acquired user. I am using the unfollow (other_user) instance method defined in the User model file. __

-__ Now that the function to actually access the DB and follow / unfollow is completed, the next step is to implement the button for actually following / unfollowing! __


8. Implement the follow / unfollow button with form_with.

__ ・ First, create a relationships / _follow_form.html.slim file under the app / views directory. __

ruby:app/views/relationships/_follow_form.html.slim


- unless current_user == user
  #Unfollow button
  - if current_user.following?(user)
    = form_with model: @relationship, url: relationship_path, method: :delete, local: true do |f|
      = f.hidden_field :follow_id, value: user.id
      = f.submit "Unfollow"
  #Follow button
  - else
    = form_with model: @set_relationship, url: relationships_path, local: true do |f|
      = f.hidden_field :follow_id, value: user.id
      = f.submit "Follow"

__ · -unless current_user == user__: __ The display of the button itself is switched so that you cannot follow yourself. The user here refers to the @user that the view that places this partial gets. __

8-1. Explanation of unfollow button

__ · -if current_user.following? (User) __: __ Checks if the logged-in user is following the other user. If you are following, the unfollow button is displayed, and if you are not following, the follow button is displayed. __

= form_with model: @relationship, url: relationship_path, method: :delete, local: true do |f|This is a button for unfollowing.

__model: @relationship @relationship is an instance variable and gets the relationship between the logged-in user and the other user. __ __ (If you place this follow button form in app / views / users / show.html.slim, you will define the @relationship instance variable in the show action in the app / controllers / users_controller.rb file.) __

__ʻUrl: relationship_path` is the routing to the relationships controller's destroy action. We will describe the routing later. __

__method:: delete specifies the DELETE method in the HTTP request. __

__local: true is always described in form_with because the specification of form submission will be Ajax and data cannot be submitted without this. __

__ ・ f.hidden_field: follow_id, value: user.id__: This part is easy to understand when you actually look at the completed source code. __

![Screenshot 2020-06-27 15.28.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/606750/6530abc7-b410-92c6-4f74 -3610f308bc01.png)

By specifying> __: follow_id, the data will be sent in the form of params [: relationship] [: follow_id]. The part defined by the set_user private method of the relationships controller receives this data. __

By specifying> __value: user.id, the id of the other user who is following is stored in: follow_id and the data is sent. __

8-2. Follow button

= form_with model: @set_relationship, url: relationships_path, local: true do |f|This is a form for following the other user.

The @set_relationship instance variable of model: @set_relationship generates an empty model, and the data is sent in the form of params [: relationship] [: follow_id] with reference to this empty model. To do so.

__ʻUrl: relationships_path` is the routing to the relationships controller's create action. We will describe the routing later. __

__ ・ = f.hidden_field: follow_id, value: user.id: Let's check the actual completed source code. __

![Screenshot 2020-06-27 15.47.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/606750/201d6a5e-3b2f-5e57-4155 -fd7326975490.png)

__ Same as the unfollow button, the data is sent in the form of params [: relationship] [: follow_id]. __

__ ・ Now that the follow / unfollow button has been created, it's time to set the routing so that this data is sent to the action normally! __


9. Add routing to the relationships controller.

config/routes.rb


Rails.application.routes.draw do
  .
  .
  #<Other routing omitted>
  .
  .
  resources :relationships, only: [:create, :destroy]

Check the added routes with the __ · $ rails routes | grep relationship command. __

$ rails routes | grep relationship
  relationships  POST   /relationships(.:format)                                                        relationships#create
  relationship   DELETE /relationships/:id(.:format)                                                             relationships#destroy

__ ・ You can now confirm that you can follow with relationships_path / unfollow with relationship_path. __


10. Place a follow / unfollow button.

__ ・ I would like to place it in the app / views / users / show.html.slim file in case you want to be able to follow or unfollow when you jump to the page of the other user. __

ruby:app/views/users/show.html.slim


h1 user profile page
p
  = "name: "
  = @user.username

#================Add follow button=============================

  = render 'relationships/follow_form', user: @user

#============================================================

p= link_to "Profile editing", edit_user_path(current_user)

__ · user: @user__: __ The user described in the partial refers to the @user defined in the show action. __

__-The button has been placed, but the button will not work as it is. As I mentioned earlier, you have to define the @relationship instance variable and the @set_relationship instance variable in the show action. __


11.app/controllers/users_controller.rbのshowアクションに、インスタンス変数を定義する。

app/controllers/users_controller.rb


class UsersController < ApplicationController
  .
  .
  .
  def show
    @user = User.find(params[:id])
    @relationship = current_user.relationships.find_by(follow_id: @user.id)  
    @set_relationship = current_user.relationships.new
  end
  .
  .
  .
end

__ · @ relationship = current_user.relationships.find_by (follow_id: @ user.id) __: __ This is the instance variable referenced in the unfollow form. By getting the relationships between current_user and @user here, you will be able to send data like params [: relationship] [: follow_id] in the form. __

__ · @ set_relationship = current_user.relationships.new __: __ Instance variable referenced in the form to follow. I want to send data in the form of params [: relationship] [: follow_id], so I create an empty instance variable like this. __

__ ・ This completes the implementation of the function to follow / unfollow users! Next, we will implement a function to display a list of users who are following / users who are following us! __


Follow user / follower list display function

1. First, create a link to the follow user / follower list page in the view.

__ · Add a link to the user's follower / follower list page in app / views / users / show.html.slim. __

ruby:app/views/users/show.html.slim


h1 user profile page
p
  = "name: "
  = @user.username

#==============Link to follow / follower list page=================

  = link_to "Follow: #{@user.followings.count}", followings_user_path(@user.id)
  = "/"
  = link_to "Follower: #{@user.followers.count}", followers_user_path(@user.id)

#============================================================

  = render 'relationships/follow_form', user: @user
p= link_to "Profile editing", edit_user_path(current_user)

__ · To access followers / followers, use the followings / followers defined in the User model file earlier. __

__ · @ user.followings.count __: __ Displays the number of users that the user is following. __ __ · followings_user_path (@ user.id) __: __ I want to specify the path where the URL is ʻusers /: id / followings`, so write as shown on the left. I'll write the routing later so that you can specify this type of path. __

__ ・ Access to the follower list page is described in the same way as accessing the follow user list page. __


2. Add a route to the follow / follower list page.

config/routes.rb


Rails.application.routes.draw do
  .
  .
  .
  resources :users do
    get :followings, on: :member
    get :followers, on: :member
  end
  .
  .
  .
end

__ ・ After writing as above, check the routing with the $ rails routes | grep follow command. __

$ rails routes | grep follow
  followings_user  GET    /users/:id/followings(.:format)                                                          users#followings
  followers_user   GET    /users/:id/followers(.:format)                                                           users#followers

__ ・ followings_user_path will jump to the follow user list page, and followers_user_path will jump to the follower list page, so this is OK. __

__ · Both the followings action and the followers action are nested in the users resource, so define two actions in the users controller. __


3. Add the followings action and followers action to the users controller.

app/controllers/users_controller.rb


class UsersController < ApplicationController
  .
  .
  .
  def followings
    @user = User.find(params[:id])
    @users = @user.followings.all
  end

  def followers
    @user = User.find(params[:id])
    @users = @user.followers.all
  end
  .
  .
  .

__ · In the followings action, all the users that @user is following are acquired, and in the followers action, all the followers of @user are acquired. __


4. Create a view that displays a list of followers / followers.

__ · Create the followings.html.slim and followers.html.slim files in the app / views / users directory. __

ruby:app/views/users/followings.html.slim


h2 followers

- @users.each do |user|
  hr
  = "username: "
  = link_to user.username, user_path(user.id)

ruby:app/views/users/followers.html.slim


h2 followers

- @users.each do |user|
  hr
  = "username: "
  = link_to user.username, user_path(user.id)

__ ・ This completes the creation of the follow user / follow user list page! __

Articles that I used as a reference

How to create a follow function in Rails

Recommended Posts

[Rails] I will explain the implementation procedure of the follow function using form_with.
[Rails] Implementation of search function using gem's ransack
[Rails 6] Implementation of inquiry function using Action Mailer
[Rails] Implementation of image enlargement function using lightbox2
[Rails] Set validation for the search function using Rakuten API (from the implementation of Rakuten API)
Ruby on Rails <2021> Implementation of simple login function (form_with)
I tried using the Server Push function of Servlet 4.0
[Rails] Implementation of multi-layer category function using ancestry "Preparation"
[Rails] Implementation of multi-layer category function using ancestry "seed"
[Rails 6] Implementation of search function
[Rails] Implementation of category function
[Rails] Implementation of tutorial function
[Rails] Implementation of like function
[Rails] Implementation of multi-layer category function using ancestry "Creation form"
[Rails] Implementation of tagging function using intermediate table (without Gem)
[Rails] Asynchronous implementation of like function
[Rails] Implementation of image preview function
[Rails] About implementation of like function
[Rails] Comment function implementation procedure memo
I will explain the nesting of for statements that kill beginners
I tried using the cache function of Application Container Cloud Service
[Rails] Implementation of coupon function (with automatic deletion function using batch processing)
[Rails] Implementation of tag function using acts-as-taggable-on and tag input completion function using tag-it
Implementation of user authentication function using devise (2)
[Ruby on Rails] Follow function implementation: Bidirectional
Implementation of user authentication function using devise (1)
Rails [For beginners] Implementation of comment function
Where the follow function implementation is stuck
Rails Basic CRUD function implementation procedure scaffold
[Rails 6] Implementation of SNS (Twitter) sharing function
Implementation of user authentication function using devise (3)
[Vue.js] Implementation of menu function Implementation version rails6
[Ruby on rails] Implementation of like function
[Vue.js] Implementation of menu function Vue.js introduction rails6
[Rails] I will write the mechanism and method of using payjp (API) very carefully (Credit card registration)
[Rails] Implementation procedure of the function to tag posts without gem + the function to narrow down and display posts by tags
Implementation of Ruby on Rails login function (Session)
[Rails] When I use form_with, the screen freezes! ??
[Rails] Implementation of retweet function in SNS application
[JQuery] Implementation procedure of AutoComplete function [Java / Spring]
[Rails] I made a draft function using enum
Rails follow function
[Rails] Implementation of batch processing using whenever (gem)
[Rails / devise] Implementation of account information editing function / Procedure for changing redirect destination
[Rails] Implementation of PV number ranking using impressionist
[Rails] Implementation of image slide show using Bootstrap 3
[Note] Summary of rails login function using devise ①
[Rails] Implementation procedure when public / private functions are added to the posting function
I tried using the profiler of IntelliJ IDEA
[Rails] Implementation of drag and drop function (with effect)
[Rails] Test of star evaluation function using Raty [Rspec]
Implementation of Ruby on Rails login function (devise edition)
I summarized the display format of the JSON response of Rails
[Ruby on Rails] Implementation of tagging function/tag filtering function
Rails6: Input the initial data of ActionText using seed
Implement user follow function in Rails (I use Ajax) ①
[Rails] Implementation of SNS authentication (Twitter, Facebook, Google) function
I can't get out of the Rails dbconsole screen
Try using the query attribute of Ruby on Rails
[rails] gem'payjp' implementation procedure
Follow function (Ajax) implementation