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!
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 ...) ↓ __
$ 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 and
follow` as the follower user model. __
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. __
$ rails db:migrate
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. __
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. __
__ · 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. __
__ · 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! __
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! __
$ 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
! __
__ ・ 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. __
__ · -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. __
・= 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! __
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
. __
__ ・ 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. __
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! __
__ · 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. __
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. __
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. __
__ · 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! __
How to create a follow function in Rails
Recommended Posts