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

11 minute read

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 __

How to make follow function with Rails is referred to, especially this article is very helpful for detailed explanation of the association regarding follow function. became!

Assumption

environment Rails 5.2.4.2 ・Ruby 2.6.5 ・Slim · Devise

In addition, it is assumed that User-related functions have been implemented. __

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

↓ It looks like this after implementation. (I’m sorry that it looks simple…) ↓ ![ezgif.com-video-to-gif (3).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/606750/bfea6053-4f10-2dd3-(d493-062139b771a4.gif)

Implementation of follow/unfollow function

1. Relationship model creation

$ rails g model Relationship user:references follow:references

__ The relationships table created here will be an intermediate table for the users who follow and the users who follow. __

__ ・Allow the intermediate table to recognize the user model that follows user and the user model that follows follow. __


2. Editing the relationships migration file

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 __user and follow are used to separate the referenced models is that the following user and the following user must be considered as different models. __

t.references :user, foreign_key: true: __user The foreign key to the model is put. __

t.references :follow, foreign_key: {to_table: :users }: __follow It has a foreign key to the model. The follow model is a fictitious model name that does not exist, and I am creating it freely at this time. Therefore, by using {to_table: :users }, we are teaching the referencing model. __

t.index [:user_id, :follow_id], unique: true: The unique combination of __user_id and follow_id prevents data duplication, that is, the same user is followed twice. Is being prevented. __


3. Save DB contents

$ rails db:migrate


4. Describe the association for the 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":__follow It is described that it is owned by the class, but as described 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 followed by a user =================
  has_many :relationships, foreign_key: "user_id",
                           dependent: :destroy
  has_many :followings, through: :relationships, source: :follow
#================================================= =========================

#============== Association with a user who follows a user ================
  has_many :passive_relationships, class_name: "Relationship",
                                   foreign_key: "follow_id",
                                   dependent: :destroy
  has_many :followers, through: :passive_relationships, source: :user
#================================================= ==========================
end

__ ・In the above Relationship model file, an association for a user to access the user who is following and an association for a user to access the user who is following are described. I will explain later in detail about what that means. __

5-1. First, a description of the association with a user that a certain user is following!

has_many :relationships, foreign_key: "user_id", dependent: :destroy:__ This declares an association that allows a certain User to access the foreign key user_id of the intermediate table relationships. I am. __

__ I think it would be easier to understand if you imagine it as follows! __

Screenshot 2020-06-27 13.44.10.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 of the intermediate table relationships. I will. __

__ It looks like the image below! __

Screenshot 2020-06-27 13.57.34.png

__-The above two lines will allow you to get the users that User is following by setting User.followings. Once you can get this, you can easily display a list of followers of a User. __

5-2. Next is the explanation of the association with the user who follows 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. Passive_relationships is a fictitious model that I arbitrarily made here, so I am teaching the referencing model as class_name: “Relationship”. __

The image is as follows!

Screenshot 2020-06-27 14.13.33.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__ ・With the above two lines, if you use User.followers, it will be possible to get the users who follow User. With this, you can easily display a list of users who are following a user! __


app/models/user.rb


class User <ApplicationRecord
  .
  .
  .
  #<Omission of association-related description>
  .
  .
  .
  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
  • __Following?(other_user)__: __User.followings.include?(@user)` etc. to check if the User is following @user. When implementing a follow button, it is used to conditionally branch whether to display a follow button or a follow cancel button. __

  • __Follow(other_user)__: By setting __User.follow(@user), you can follow @user. You can't follow yourself with unless self == other_user`. Use find_or_create_by to refer to the id record of @user if it exists, and create it if it doesn’t exist. __

・__Unfollow(other_user)__: By setting __User.unfollow(@user)`, you can unfollow @user. __

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


7. Create 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 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] = "User unfollowed"
      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 create actions that follow users and destroy actions that do not follow users will work, so only create actions and destroy actions will be described. __ ―― ・Follow/unfollow button form sends data in the form of params[:relationship][:follow_id], so find(params[:relationships][:follow_id]) in the form of data To receive. __

  • before_action :set_user: __ Each action will always get @user, so I’ve defined the set_user private method and used before_action to get it. __

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

  • Destroy action `:__ The logged-in user has unfollowed the acquired user. It uses the unfollow(other_user) instance method defined in the User model file. __

  • __ Since the function to actually access the DB and follow/unfollow has been completed, next we will implement the `button for following/unfollowing! __


8. Implement 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: __ I switch the display of the button itself so that I can not follow myself. The user here refers to the @user obtained by the view that places this partial. __

8-1. Explanation of the follow cancel button

- if current_user.following?(user): __ Checks if the logged-in user is following the other user. If you are following, a follow button is displayed, and if you are not following, a 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 of @relationship` is an instance variable, and the relationship between the login user and the other user is acquired. __ __ (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 of the app/controllers/users_controller.rb file.) __

__url: relationship_path is the route to the destroy action of the relationships controller. We will describe the routing later. __

___Method: :delete` specifies the DELETE method in the HTTP request. __

__local: true must be described in form_with because the specification of form submission becomes 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 complete source code. __

Screenshot 2020-06-27 15.28.15.png

__:follow_id is specified, the data will be sent in the form of params[:relationship][:follow_id]. This data is received in the part defined by the set_user private method of the relationships controller. __

By specifying >__value: user.id, the id of the user you are 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 to follow other users. __

model: The @set_relationship instance variable of @set_relationship creates an empty model, and data is sent in the form of params[:relationship][:follow_id] with reference to this empty model. I will do so.__url: relationships_path is the route to the create action of the relationships controller. We will describe the routing later. __

__・= f.hidden_field :follow_id, value: user.id: Again, let’s check the actual completed source code. __

Screenshot 2020-06-27 15.47.01.png

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

__・Because the follow/unfollow button has been created, the next step is to set up the routing so that this data can be sent to the action normally! __


9. relationships Add a route to the controller.

config/routes.rb


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

__ ・$ rails routes | grep relationship command, check the added routes. __

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

__ ・I think you’ve confirmed that you can now follow/relate with relationships_path/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 I want to be able to follow or unfollow when jumping 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 "Edit profile", edit_user_path(current_user)

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

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


11. Define instance variables in the show action of app/controllers/users_controller.rb.

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): __ The instance variable referenced in the unfollow form. Here, by acquiring the relationships between current_user and @user, it becomes possible to send data like params[:relationship][:follow_id] in the form. __

@set_relationship = current_user.relationships.new:__ The instance variable referenced in the form to follow. Since we want to send data in the form of params[:relationship][:follow_id], we are creating an empty instance variable like this. __

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


Follow user/follower list display function

__ Add a link to app/views/users/show.html.slim to the list of followers/followers for that user. __

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 "Followers: #{@user.followers.count}", followers_user_path(@user.id)

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

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

__・To access follow users/followers, use the followings/followers defined in the User model file. __

@user.followings.count: __ Shows the number of users that the user is following. __ __ ・followings_user_path(@user.id)__: __ I want to specify the path where the URL becomes users/:id/followings, so describe as the left. We will describe 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 to 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

__ ・If you write it as above, check the route 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 jumps to the followers list page, and followers_user_path jumps to the followers list page, so that’s OK. __

__ Since we have nested both the followings action and the followers action in the users resource, we define two actions in the users controller. __


3. Add followings and followers actions 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, we get all the users @user is following, and in the followers action we get all the followers of @user. __


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

__ ・Create a followings.html.slim file and a followers.html.slim file 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)

__ ・The follow user/follow user list page has been created! __

Articles I’ve referred to

How to create a follow function with Rails

Tags: ,

Updated: