[Tutorial]核心代码

2016-02-01  本文已影响122人  Jayzen
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])

记住用户的登录状态也是使用同样的原理:

在上面的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

解决两个问题

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这条语句不被执行,直接执行下面的语句

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的情况下,后面的代码将被忽略。

<%= 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
上一篇下一篇

猜你喜欢

热点阅读