[Tutorial]核心代码
- 1.用户模型
- 2.注册
- 3.登录和退出
- 4.更新、显示和删除用户
- 5.账户激活和密码重设
- 6.用户的微博
- 7.关注用户
1. 用户模型
添加bcrypt这个gem到gemfile文件中:
gem 'bcrypt'
添加三个迁移文件:
rails g model User name:string email:string
rails g migration add_index_to_users_email
rails g migration add_password_digest_to_users password_digest:string
(其中第二个文件的内容为:add_index :users, :email, unique: true)
(其中第三个文件的内容为:add_column :users, :password_digest, :string)
在模型文件中添加的代码如下:
before_save { self.email = email.downcase }
validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, format: { with: VALID_EMAIL_REGEX }, uniqueness: { case_sensitive: false }
has_secure_password
validates :password, presence: true, length: { minimum: 6 }
2. 注册
建立资源和单个路由
resources :users
match "/signup", to: "users#new", via: "get"
建立users_controller.rb文件
#使用controller支架
rails g controller users
class UsersController < ApplicationController
def new
@user = User.new
end
def create
@user = User.new(user_params)
if @user.save
redirect_to user_path(@user)
else
render 'new'
end
end
def show
@user = User.find(params[:id])
end
private
def user_params
params.require(:user).permit(:name, :email, :password, :password_confirmation)
end
end
在views/users文件中建立显示new.html.erb和show.html.erb文件
其中new.html.erb文件显示为如下所示:
<% if @user.errors.any? %>
<ul>
<% @user.errors.full_messages.each do |msg| %>
<li>
<%= msg %>
</li>
<% end %>
</ul>
<% end %>
<%= form_for :user, url: users_path do |f| %>
<%= f.label :name %>
<%= f.text_field :name %></br>
<%= f.label :email %>
<%= f.text_field :email %></br>
<%= f.label :password %>
<%= f.password_field :password %></br>
<%= f.label :password_confirmation %>
<%= f.password_field :password_confirmation %></br>
<%= f.submit %>
<% end %>
其中show.html.erb文件显示为如下所示:
<%= @user.name %></br>
<%= @user.email %></br>
<%= link_to "new", new_user_path%>
3. 登录和退出
三种方式实现登录和退出,分别是浏览器关闭后自动退出,自动记住登录状态,勾选“记住我”选项时才记住用户的登录状态。
- 第一种方式:浏览器关闭后自动退出
生成session controller
rails g controller Sessions new
设置routes.rb
get 'login' => 'sessions#new'
post 'login' => 'sessions#create'
delete 'logout' => 'sessions#destroy'
在sessions_helper.rb文件中建立SessionsHelper模块,并且将该模块导入到application_controller.rb文件中,具体方式通过如下的代码添加到application_controller.rb中:
include SessionsHelper
SessionsHelper这个模块中的代码具体如下所示:
module SessionsHelper
def log_in(user)
session[:user_id] = user.id
end
def current_user
@current_user ||= User.find_by(id: session[:user_id])
end
def logged_in?
!current_user.nil?
end
def log_out
session.delete(:user_id)
@current_user = nil
end
end
生成的sessions_controller.rb文件中的代码如下所示:
class SessionsController < ApplicationController
def new
end
def create
user = User.find_by(email: params[:session][:email].downcase)
if user && user.authenticate(params[:session][:password])
log_in user
redirect_to user_path(user)
else
render 'new'
end
end
def destroy
log_out
redirect_to root_path
end
end
另外,在users_controller.rb文件中的create这个action中,也要添加如下的语句:
log_in user
在sessions文件夹中的new.html.erb中添加如下代码:
<%= form_for(:session, url: login_path) do |f| %>
<%= f.label :email %>
<%= f.email_field :email%></br>
<%= f.label :password %>
<%= f.password_field :password %></br>
<%= f.submit %>
<% end %>
最后通过在在标题中显示代码以标识是否登录,其中header.html.erb这个文件中的代码如下所示:
<% if logged_in? %>
<%= link_to "退出", logout_path, method: :delete, data: {confirm: "are you sure?"}%>
<% else %>
<%= link_to "登录", login_path %>
<%= link_to "注册", signup_path %>
<% end%>
- 第二种方式:自动记住登录状态
在用户登录这个部分,has_secure_password这个方法,实现的原理是用户输入明码,但是数据库存入的被加密的明码,如果要实现登录,就要对用户的明码进行加密,并与数据库中被加密的明码进行比较,如果相等,就能登录成功,具体代码如下所示:
user = User.find_by(email: params[:session][:email].downcase)
if user && user.authenticate(params[:session][:password])
记住用户的登录状态也是使用同样的原理:
- 生成随机字符串,当做记忆令牌;
- 把这个令牌存入浏览器的 cookie 中,并把过期时间设为未来的某个日期;
- 在数据库中存储令牌的摘要;
- 在浏览器的 cookie 中存储加密后的用户 ID;
- 如果 cookie 中有用户的 ID,就用这个 ID 在数据库中查找用户,并且检查 cookie 中的记忆令牌和数据库中的哈希摘要是否匹配。
在上面的ID就类似于用户登录过程中的email,随机字符串类似于用户登录过程中的明码
具体代码如下所示:
添加用户所需的remember_digest到用户模型中
rails g migration add_remember_digest_to_users remember_digest:string
生成一个随机令牌和指定字符串的哈希摘要(类似于用户明码和加密后的用户明码)
def User.new_token
SecureRandom.urlsafe_base64
end
def User.digest(string)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine.cost
BCrypt::Password.create(string, cost: cost)
end
使用一个虚拟属性remember_token来对随机令牌和指定字符串的哈希摘要进行关联:
class User < ActiveRecord::Base
attr_accessor :remember_token
def remember
self.remember_token = User.new_token
update_attribute(:remember_digest, User.digest(remember_token))
end
end
指定的令牌和hash摘要是否匹配:
def authenticated?(remember_token)
BCrypt::Password.new(remember_digest).is_password?(remember_token)
end
将记忆令牌存入到浏览器的cookie
cookies.permanent[:remember_token] = remember_token
在浏览器的cookie中存储加密后的用户ID
cookies.permanent.signed[:user_id] = user.id
通过用户解密后的用户ID查找用户
User.find_by(id: cookies.signed[:user_id])
在sessions_helper.rb中添加如上提到的功能代码:
def remember(user)
user.remember
cookies.permanent.signed[:user_id] = user.id
cookies.permanent[:remember_token] = user.remember_token
end
#同时在用户登录和用户注册的代码中同时添加语句remember user
def current_user
if (user_id = session[:user_id])
@current_user ||= User.find_by(id: user_id)
elsif (user_id = cookies.signed[:user_id])
user = User.find_by(id: user_id)
if user && user.authenticated?(cookies[:remember_token])
log_in user
@current_user = user
end
end
end
忘记用户:清除remember_digest, cookies
代码如下所示:
#user.rb文件中添加forget方法
def forget
update_attribute(:remember_digest, nil)
end
#在sessions_helper.rb中添加forget(user)方法
def forget(user)
user.forget
cookies.delete(:user_id)
cookies.delete(:remember_token)
end
#在sessions_helper.rb中的log_out方法中添加forget语句
def log_out
forget(current_user)
sessions.delete(:user_id)
@current_user = nil
end
解决两个问题
- 第一个问题: 在同一个浏览器中存在多个登录页面,当其中一个页面退出的时候,current_user为nil,然后点击另外一个页面的退出按钮,则会报错,nilclass.
user.forget #=>user为nil,因此不存在此方法
解决的方法是添加如下代码:
#在sessions_controller.rb中的destroy这个action中添加 if logged_in?
def destroy
log_out if logged_in?
redirect_to root_path
end
上面的代码logged_in?语句在用户退出的时候起了作用,当用户退出之后,则logged_in?返回为false,则logout_out这条语句不被执行,直接执行下面的语句
- 第二个问题:在两个浏览器中,如果其中一个浏览器退出,则会将数据库中remember_digest值设置为nil值,那么其他的浏览器在调用remember_digest值的过程中会报错,报错的语句出现的如下代码中:
def authenticated?(remember_token)
BCrypt::Password.new(remember_digest).is_password?(remember_token)
end
修正的方式如下所示:
def authenticated?(remember_token)
return false if remember_digest.nil?
BCrypt::Password.new(remember_digest).is_password?(remember_token)
end
上面的代码在remember_digest为nil的情况下,后面的代码将被忽略。
-
第三种方式:使用记住我复选框
在sessions/new.html.erb文件中添加复选框,代码如下所示:
<%= f.lable :remember_me do %>
<%= f.check_box :remember_me %>
<span>remember me on this computer </span>
<% end %>
在controllers/sessions_controller.rb文件中添加如下代码:
def create
params[:session][:remember_me] == '1' ? remember(user) : forget(user)
end
3. 更新、显示和删除用户
更新用户,UsersController.rb文件中的代码内容如下所示:
def edit
@user = User.find(params[:id])
end
def update
@user = User.find(params[:id])
if @user.update_attributes(user_params)
redirect_to @user
else
render 'edit'
end
end
users/edit.html.erb文件代码内容如下所示:
<% if @user.errors.any? %>
<ul>
<% @user.errors.full_messages.each do |msg| %>
<%= msg %>
<% end %>
</ul>
<% end %>
<%= form_for(@user) do |f| %>
<%= f.label :name %>
<%= f.text_field :name %></br>
<%= f.label :email %>
<%= f.text_field :email %></br>
<%= f.label :password %>
<%= f.password_field :password %></br>
<%= f.label :password %>
<%= f.password_field :password %></br>
<%= f.submit %>
<% end %>
在_header.html.erb文件中如下所示:
<%= link_to "编辑", edit_user_path(current_user) %>
只有登录的用户才能编辑
在UsersController.rb中添加如下代码:
before_action :logged_in_user, only: [:edit, :update]
def logged_in_user
unless logged_in?
redirect_to login_url
end
end
只有用户自己才能编辑自己
在SessionsHelper.rb中添加如下代码:
def current_user?(user)
user == current_user
end
在UsersController.rb中添加如下代码:
before_action :correct_user, only: [:edit, :update]
def correct_user
@user = User.find(params[:id])
redirect_to(root_url) unless current_user?(@user)
end
友好的转向
在SessionHelper.rb中添加如下代码:
def redirect_back_or(default)
redirect_to(session[:forwarding_url] || default)
session.delete(:forwarding_url)
end
def store_location
session[:forwarding_url] = request.url if request.get?
end
在users_controller.rb文件中添加store_location:
def logged_in_user
unless logged_in?
store_location
redirect_to login_url
end
end
在sessions_controller.rb文件中添加如下代码:
def create
user = User.find_by(email: params[:session][:email].downcase)
if user && user.authenticate(params[:session][:password])
log_in user
params[:session][:remember_me] == '1' ? remember(user) : forget(user)
redirect_back_or user
else
render 'new'
end
end
显示所有用户
在UsersController.rb文件中添加如下的代码:
def index
@users = User.all
end
需要登录之后的用户才能看见全部的用户,UsersController.rb文件中添加如下所示:
before_action :logged_in_user, only: [:index, :edit, :update]
在_header.html.erb文件中添加如下代码:
<%= link_to "用户", users_path %>
添加管理员权限
#终端执行下面代码
rails g migration add_admin_to_users admin:boolean
#编辑此代码
def change
add_column :users, :admin, :boolean, default: false
end
为用户添加管理员权限的三种方法:
#终端使用用户创建方法
user = User.create(name: "demo", email: "demo@foxmail.com", password_confirmation: "111111", password: "111111", admin: true )
#在db/seeds.rb文件中添加如上的代码
#在终端中使用toggle方法
user = User.first
user.toggle!(:admin)
删除用户
user_controller.rb文件中添加如下代码
before_action :logged_in_user, only: [:index,:edit, :update, :destroy]
before_action :admin_user, only: :destroy
def destroy
User.find(params[:id]).destroy
redirect_to users_path
end
private
def admin_user
redirect_to(root_url) unless current_user.admin?
end
在users/index.html.erb文件中添加如下代码:
<% if current_user.admin? && !current_user?(user) %>
<%= link_to "delete", user, method: :delete, data: {confirm: "are you sure?"}%>
<% end %>
5.账户激活和密码重设
5.1账户激活
账户激活的原理和注册用户以及记住用户的原理差不多
- 用户一开始处于“未激活”状态
- 用户注册后,生成一个激活令牌和对象的激活摘要
- 把激活摘要存储在数据库中,然后给用户发送一封邮件,提供包括激活令牌和用户电子邮件地址的链接
- 用户点击这个链接之后,使用电子邮件地址查找用户,并且对比令牌和摘要
- 如果令牌和摘要匹配,就把状态由“未激活”改为“已激活”
使用控制器生成命令:
rails g controller AccountActivations --no-test-framework
生成所需的资源路由:
resources :account_activations, only: [:edit]
生成model
rails g migration add_activation_to_users activation_digest:string activated:boolean activated_at:datetime
生成的迁移文件如下所示:
class AddActivationToUsers < ActiveRecord::Migration
def change
add_column :users, :activation_digest, :string
add_column :users, activated, :boolean, default: false
add_column :users, :activated_at, :datetime
end
end
在model中更新如下方法:
class User < ActiveRecord::Base
attr_accessor :remember_token, :activation_token
before_save :downcase_email
before_create :create_activation_digest
private
def downcase_email
self.email = email.downcase
end
def create_activation_digest
self.activation_token = User.new_token
self.activation_digest = User.digest(activation_token)
end
end
5.2 使用邮件程序
生成mailer的controller文件
rails generate mailer UserMailer account_activation password_reset
更改controller文件
def account_activation
mail(to: "zheng_jiajun@foxmail.com", subject: "welcome to my site")
end
对config/environments/development.rb文件进行配置
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
address: "smtp.gmail.com",
port: 587,
user_name: "zhengjiajun121",
password: "xxxx",
authentication: "plain",
enable_starttls_auto: true
}
对上面的文件进行测试
需要开通VPN
#rails console
irb: UserMailer.account_activation.deliver_now
5.3 实现通用的authenticated?方法
#models/user.rb
def authenticated?(attribute, token)
digest = self.send("#{attribute}_digest")
return false if digest.nil?
BCrypt::Password.new(digest).is_password?(token)
end
#更改sessions_helper.rb文件
def current_user
if user && user.authenticated?(:remember, cookies[:remember_token])
end
5.4 账户激活和发送邮件
#激活账户的controllerapp/controllers/account_activations_controller.rb
class AccountActivationsController < ApplicationController
def edit
user = User.find_by(email: params[:email])
if user && !user.activated? && user.authenticated?(:activation, params[:id])
user.update_attribute(:activated, true)
user.update_attribute(:activated_at, Time.zone.now)
log_in user
flash[:notice] = "account activated"
redirect_to user
else
flash[:notive] = "invalid activation link"
redirect_to root_url
end
end
end
class User < ActiveRecord::Base
#抽象出激活方法
def activate
update_attribute(:activated, true)
update_attribute(:activated_at, Time.zone.now)
end
#发送邮件
def send_activation_email
UserMailer.account_activation.deliver_now
end
end
#app/controllers/users_controller.rb文件中
class UsersController < ApplicationController
def create @user = User.new(user_params)
if @user.save
#抽象出的发送邮件方法
@user.send_activation_email
flash[:info] = "Please check your email to activate your account."
redirect_to root_url
else
render 'new'
end
end
end
#app/controllers/account_activations_controller.rb
class AccountActivationsController < ApplicationController
def edit
user = User.find_by(email: params[:email])
if user && !user.activated? && user.authenticated?(:activation, params[:id])
#抽象出的激活方法
user.activate
log_in user
flash[:notice] = "account activated"
redirect_to user
else
flash[:notice] = "invalid activation link"
redirect_to root_url
end
end
end
5.5 显示邮件内容
#编辑user_mailer.rb
def account_activation(user)
@user = user
mail(to: user.email, subject: "account activation")
end
#编辑account_activation.html.erb
<%= link_to "activate", edit_account_activation_url(@user.activation_token, email: @user.email) %>
#编辑account_activation.text.erb
<%= edit_account_activation_url(@user.activation_token, email: @user.email) %>
class User < ActiveRecord::Base
#添加self
def send_activation_email
UserMailer.account_activation(self).deliver_now
end
end
#编辑config/environments/development.rb,添加host地址
host = "localhost:3000"
config.action_mailer.default_url_options = { host: host }
5.6 不让未激活的用户登录
#编辑sessions_controller.rb文件内容
def create
user = User.find_by(email: params[:session][:email].downcase)
if user && user.authenticate(params[:session][:password])
if user.activated?
log_in user
params[:session][:remember_me] == '1' ? remember(user) : forget(user)
redirect_back_or user
else
flash[:notice] = "the accout is exist, but not activated"
redirect_to new_user_url
end
else
render 'new'
end
end
end
5.7 密码重设
#生成资源控制器
rails generate controller PasswordResets new edit
#生成路由
config/route.rb
Rails.application.routes.draw do
resources :password_resets, only: [:new, :create, :edit, :update]
end
#app/views/sessions/new.html.erb中添加忘记密码链接
<%= link_to "(forgot password)", new_password_reset_path %>
#生成model
rails generate migration add_reset_to_users reset_digest:string reset_sent_at:datetime
#设置密码页面app/views/password_resets/new.html.erb
<%= form_for(:password_reset, url: password_resets_path) do |f| %>
<%= f.label :email %>
<%= f.email_field :email%>
<%= f.submit "Submit"%>
<% end %>
#app/controllers/password_resets_controller.rb中设置动作
class PasswordResetsController < ApplicationController
def new
end
def create
@user = User.find_by(email: params[:password_reset][:email].downcase)
if @user
@user.create_reset_digest
@user.send_password_reset_email
flash[:notice] = "Email sent with password reset instructions"
redirect_to root_url
else flash.now[:notice] = "Email address not found"
render 'new'
end
end
def edit
end
end
#model中设置需要的方法app/models/user.rb
class User < ActiveRecord::Base
attr_accessor :remember_token, :activation_token, :reset_token
def create_reset_digest
self.reset_token = User.new_token
update_attribute(:reset_digest, User.digest(reset_token))
update_attribute(:reset_sent_at, Time.zone.now)
end
def send_password_reset_email
UserMailer.password_reset(self).deliver_now
end
end
#改变邮件程序
#app/mailers/user_mailer.rb
class UserMailer < ApplicationMailer
def password_reset(user)
@user = user mail to: user.email, subject: "Password reset"
end
end
#app/views/user_mailer/password_reset.html.erb
<%= link_to "Reset password", edit_password_reset_url(@user.reset_token, email: @user.email) %>
#重设密码表单app/views/password_resets/edit.html.erb
<%= form_for(@user, url: password_reset_path(params[:id])) do |f| %>
<%= hidden_field_tag :email, @user.email %>
<%= f.label :password %>
<%= f.password_field :password, class: 'form-control' %>
<%= f.label :password_confirmation, "Confirmation" %>
<%= f.password_field :password_confirmation, class: 'form-control' %>
<%= f.submit "Update password", class: "btn btn-primary" %>
<% end %>
#设置app/controllers/password_resets_controller.rb
class PasswordResetsController < ApplicationController
before_action :get_user, only: [:edit, :update]
before_action :valid_user, only: [:edit, :update]
before_action :check_expiration, only: [:edit, :update]
def new
end
def create @user = User.find_by(email: params[:password_reset][:email].downcase)
if @user
@user.create_reset_digest
@user.send_password_reset_email
flash[:info] = "Email sent with password reset instructions"
redirect_to root_url
else
flash.now[:danger] = "Email address not found"
render 'new'
end
end
def edit
end
def update
if params[:user][:password].empty?
@user.errors.add(:password, "can't be empty")
render 'edit'
elsif
@user.update_attributes(user_params)
log_in @user
flash[:success] = "Password has been reset."
redirect_to @user
else
render 'edit'
end
end
private
def user_params
params.require(:user).permit(:password, :password_confirmation)
end
# 事前过滤器
def get_user
@user = User.find_by(email: params[:email])
end
# 确保是有效用户
def valid_user
unless (@user && @user.activated? && @user.authenticated?(:reset, params[:id]))
redirect_to root_url
end
end
# 检查重设令牌是否过期
def check_expiration
if @user.password_reset_expired?
flash[:danger] = "Password reset has expired."
redirect_to new_password_reset_url
end
end
end
#在app/models/user.rb中定义 password_reset_expired?方法
class User < ActiveRecord::Base
def password_reset_expired?
reset_sent_at < 2.hours.ago
end
end
6 用户的微博
#建立模型
rails generate model Micropost content:text user:references
#具体示例
class CreateMicroposts < ActiveRecord::Migration
def change
create_table :microposts do |t|
t.text :content
t.references :user, index: true, foreign_key: true
t.timestamps null: false
end
#按照时间顺序查找某个用户的微博
add_index :microposts, [:user_id, :created_at]
end
end
#数据验证
class Micropost < ActiveRecord::Base
belongs_to :user
validates :user_id, presence: true
#默认数据按照时间降序排列
default_scope -> { order(created_at: :desc) }
validates :content, presence: true, length: { maximum: 140 }
end
class User < ActiveRecord::Base
has_many :microposts, dependent: :destroy
end
#显示微博
rails g controller microposts
#app/views/users/show.html.erb
#下面的语句会寻找/microposts/_micropost.html.erb文件
<%= render @microposts %>
#app/views/microposts/_micropost.html.erb
<li><%= micropost.content %></li>
#在app/controllers/users_controller.rb中添加实例变量
class UsersController < ApplicationController
def show
@user = User.find(params[:id])
@microposts = @user.microposts
end
end
#添加配置文件
resources :microposts, only: [:create, :destroy]
#logged_in_user方法移到 ApplicationController
#app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
# 确保用户已登录
def logged_in_user
unless logged_in?
store_location flash[:danger] = "Please log in."
redirect_to login_url
end
end
end
#设置app/controllers/microposts_controller.rb
class MicropostsController < ApplicationController
before_action :logged_in_user, only: [:create, :destroy]
def create
@micropost = current_user.microposts.build(micropost_params)
if @micropost.save
flash[:notice] = "Micropost created!"
redirect_to root_url
else
render 'home/index'
end
end
def destroy
end
private
def micropost_params
params.require(:micropost).permit(:content)
end
end
#下面要实现用户在主页面和用户页面都可以发送微博
#app/views/static_pages/home.html.erb
<% if logged_in? %>
<%= render 'shared/user_info' %>
<%= render 'shared/micropost_form' %>
<% else %>
this is the home page
<% end %>
#app/views/shared/_user_info.html.erb
<h1><%= current_user.name %></h1>
<span><%= link_to "view my profile", current_user %></span>
<span><%= pluralize(current_user.microposts.count, "micropost") %></span>
#app/views/shared/_micropost_form.html.erb
<%= form_for(@micropost) do |f| %>
<%= f.text_area :content, placeholder: "Compose new micropost..." %>
<%= f.submit "Post", class: "btn btn-primary" %>
<% end %>
#app/controllers/home_controller.rb中定义index
class HomeController < ApplicationController
def home @micropost = current_user.microposts.build if logged_in?
end
end
#删除微博
#app/controllers/microposts_controller.rb
class MicropostsController < ApplicationController
before_action :logged_in_user, only: [:create, :destroy]
before_action :correct_user, only: :destroy
def destroy @micropost.destroy
flash[:success] = "Micropost deleted"
redirect_to request.referrer || root_url
end
private
def correct_user
@micropost =current_user.microposts.find_by(id: params[:id])
redirect_to root_url if @micropost.nil?
end
end
#app/views/microposts/_micropost.html.erb
<% if current_user?(micropost.user) %>
<%= link_to "delete", micropost, method: :delete, data: { confirm: "You sure?" } %>
<% end %>
#动态流原型
#app/models/user.rb
class User < ActiveRecord::Base
def feed
Micropost.where("user_id = ?", id)
end
end
class HomeController < ApplicationController
def home if logged_in?
@micropost = current_user.microposts.build
@feed_items = current_user.feed
end
end
#在首页使用feed_items
#app/views/shared/_feed.html.erb
<% if @feed_items.any? %>
<%= render @feed_items %>
<% end %>
#@feed_items中的元素都是 Micropost类的实例。所以,Rails 会在对应资源的视图文件夹中寻找正确的局部视图:
app/views/microposts/_micropost.html.erb
7.1关系的模型
#生成模型
rails generate model Relationship follower_id:integer followed_id:integer
#模型中建立索引
class CreateRelationships < ActiveRecord::Migration
def change
create_table :relationships do |t|
t.integer :follower_id
t.integer :followed_id
t.timestamps null: false
end
add_index :relationships, :follower_id
add_index :relationships, :followed_id
add_index :relationships, [:follower_id, :followed_id], unique: true
end
end
#app/models/relationship.rb中建立用户关系
class Relationship < ActiveRecord::Base
belongs_to :follower, class_name: "User"
belongs_to :followed, class_name: "User"
validates :follower_id, presence: true
validates :followed_id, presence: true
end
#app/models/user.rb中建立用户关系
#下面的内容可以参考之前的post_tag文章总结,本质上是一样的
#其中has_many和belongs_to适用于class_name
#has_many through适用于source
#following是我关注的用户,followers是我关注的用户
class User < ActiveRecord::Base
has_many :active_relationships, class_name: "Relationship",
foreign_key: "follower_id",
dependent: :destroy
has_many :following, through: :active_relationships,
source: :followed
has_many :passive_relationships, class_name: "Relationship",
foreign_key: "followed_id",
dependent: :destroy
has_many :followers, through: :passive_relationships,
source: :follower
end
#app/models/user.rb中定义辅助方法
class User < ActiveRecord::Base
# 关注另一个用户
def follow(other_user)
active_relationships.create(followed_id: other_user.id)
end
# 取消关注另一个用户
def unfollow(other_user)
active_relationships.find_by(followed_id: other_user.id).destroy
end
# 如果当前用户关注了指定的用户,返回 true
def following?(other_user)
following.include?(other_user)
end
end
7.2 关系视图的常规实现
#建立路由config/routes.rb
resources :users do
member do
get :following, :followers
end
end
resources :relationships, only: [:create, :destroy]
#显示微博数量的局部视图app/views/share/_stats.html.erb
#下面这段代码在主页和用户界面都会有显示,主页中不存在@user,因此使用current_user,但是用户界面中不存在
#current_user,因此只能使用@user
<% @user ||= current_user %>
<a href="<%= following_user_path(@user) %>">
<%= @user.following.count %> following
</a>
<a href="<%= followers_user_path(@user) %>">
<%= @user.followers.count %>followers
</a>
#在app/views/home/index.html.erb中添加如上内容
<%= render 'share/stats' %>
#添加取消和关注视图app/views/users/_follow_form.html.erb
<% unless current_user?(@user) %>
<% if current_user.following?(@user) %>
<%= render 'unfollow' %>
<% else %>
<%= render 'follow' %>
<% end %>
<% end %>
#app/views/users/_follow.html.erb添加关注用户界面
<%= form_for(current_user.active_relationships.build) do |f| %>
<%= hidden_field_tag :followed_id, @user.id %>
<%= f.submit "Follow"%>
<% end %>
#app/views/users/_unfollow.html.erb取消关注页面
<%=form_for(current_user.active_relationships.find_by(followed_id: @user.id), html: { method: :delete }) do |f| %>
<%= f.submit "Unfollow" %>
<% end %>
#app/controllers/users_controller.rb添加动作
class UsersController < ApplicationController
before_action :logged_in_user, only: [:index, :edit, :update, :destroy, :following, :followers]
def following
@user = User.find(params[:id])
@users = @user.following
render 'show_follow'
end
def followers
@user = User.find(params[:id])
@users = @user.followers
render 'show_follow'
end
end
#app/controllers/relationships_controller.rb添加动作
class RelationshipsController < ApplicationController
before_action :logged_in_user
def create
user = User.find(params[:followed_id])
current_user.follow(user)
redirect_to user
end
def destroy
user = Relationship.find(params[:id]).followed
current_user.unfollow(user)
redirect_to user
end
end