You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Rails Devise实现患者与医生双角色独立注册表单需求

我来帮你搞定这个医生预约平台的角色区分和注册表单需求,一步步来:

1. 扩展User模型,添加角色标识与医生专属字段

首先得给User模型加上角色区分,以及医生需要的额外字段。

  • 生成迁移文件:
rails generate migration AddRoleAndDoctorFieldsToUsers role:string specialty:text license_number:string
rails db:migrate
  • 修改User模型,添加验证和角色判断方法:
# app/models/user.rb
class User < ApplicationRecord
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable

  # 定义允许的角色类型,避免非法值
  ROLES = %w[patient doctor].freeze

  validates :role, presence: true, inclusion: { in: ROLES }
  # 只有医生角色需要验证专属字段
  validates :specialty, :license_number, presence: true, if: :doctor?

  def patient?
    role == 'patient'
  end

  def doctor?
    role == 'doctor'
  end
end
2. 配置Devise,实现两套独立注册表单

默认的Devise注册流程是单表单,我们需要自定义注册控制器和视图,区分患者和医生注册。

  • 生成自定义的Devise注册控制器:
rails generate devise:controllers users -c registrations
  • 修改路由,指定使用自定义控制器,并添加独立的注册入口:
# config/routes.rb
Rails.application.routes.draw do
  devise_for :users, controllers: { registrations: 'users/registrations' }

  # 给患者和医生分别设置专属注册页面路由
  get '/register/patient', to: 'users/registrations#new_patient', as: :new_patient_registration
  get '/register/doctor', to: 'users/registrations#new_doctor', as: :new_doctor_registration
end
  • 修改自定义注册控制器,添加两个注册页面的逻辑,并允许接收额外参数:
# app/controllers/users/registrations_controller.rb
class Users::RegistrationsController < Devise::RegistrationsController
  before_action :configure_sign_up_params, only: [:create]

  # 患者注册页面:默认设置role为patient
  def new_patient
    build_resource({ role: 'patient' })
    respond_with resource
  end

  # 医生注册页面:默认设置role为doctor
  def new_doctor
    build_resource({ role: 'doctor' })
    respond_with resource
  end

  protected

  # 允许Devise接收role和医生专属字段
  def configure_sign_up_params
    devise_parameter_sanitizer.permit(:sign_up, keys: [:role, :specialty, :license_number])
  end
end
  • 创建两套注册视图:
    先把Devise默认的注册视图复制到app/views/users/registrations/(可以通过rails generate devise:views生成默认视图,再复制修改),然后新建两个专属视图:

    患者注册视图(new_patient.html.erb):

<h2>患者注册</h2>

<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
  <%= render "devise/shared/error_messages", resource: resource %>

  <%# 隐藏字段自动设置角色为patient %>
  <%= f.hidden_field :role, value: 'patient' %>

  <div class="field">
    <%= f.label :email %><br />
    <%= f.email_field :email, autofocus: true, autocomplete: "email" %>
  </div>

  <div class="field">
    <%= f.label :password %>
    <% if @minimum_password_length %>
      <em>(<%= @minimum_password_length %> characters minimum)</em>
    <% end %><br />
    <%= f.password_field :password, autocomplete: "new-password" %>
  </div>

  <div class="field">
    <%= f.label :password_confirmation %><br />
    <%= f.password_field :password_confirmation, autocomplete: "new-password" %>
  </div>

  <div class="actions">
    <%= f.submit "注册为患者" %>
  </div>
<% end %>

<%= render "devise/shared/links" %>

医生注册视图(new_doctor.html.erb),额外添加医生专属字段:

<h2>医生注册</h2>

<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
  <%= render "devise/shared/error_messages", resource: resource %>

  <%# 隐藏字段自动设置角色为doctor %>
  <%= f.hidden_field :role, value: 'doctor' %>

  <div class="field">
    <%= f.label :email %><br />
    <%= f.email_field :email, autofocus: true, autocomplete: "email" %>
  </div>

  <div class="field">
    <%= f.label :password %>
    <% if @minimum_password_length %>
      <em>(<%= @minimum_password_length %> characters minimum)</em>
    <% end %><br />
    <%= f.password_field :password, autocomplete: "new-password" %>
  </div>

  <div class="field">
    <%= f.label :password_confirmation %><br />
    <%= f.password_field :password_confirmation, autocomplete: "new-password" %>
  </div>

  <%# 医生专属字段 %>
  <div class="field">
    <%= f.label :specialty %><br />
    <%= f.text_area :specialty, placeholder: "请填写你的专科领域" %>
  </div>

  <div class="field">
    <%= f.label :license_number %><br />
    <%= f.text_field :license_number, placeholder: "请填写你的执业证号" %>
  </div>

  <div class="actions">
    <%= f.submit "注册为医生" %>
  </div>
<% end %>

<%= render "devise/shared/links" %>
3. 实现医生角色的预约功能

接下来要给医生角色关联预约功能,需要创建Appointment模型并建立关联。

  • 生成Appointment模型,关联患者和医生(都是User模型):
rails generate model Appointment patient:references doctor:references appointment_date:datetime status:string
rails db:migrate
  • 修改Appointment模型:
# app/models/appointment.rb
class Appointment < ApplicationRecord
  belongs_to :patient, class_name: 'User', foreign_key: 'patient_id'
  belongs_to :doctor, class_name: 'User', foreign_key: 'doctor_id'

  validates :appointment_date, presence: true
  # 预约状态默认pending,可选值:pending(待确认)、confirmed(已确认)、canceled(已取消)
  validates :status, inclusion: { in: %w[pending confirmed canceled] }, default: 'pending'
end
  • 在User模型中添加预约关联:
# app/models/user.rb
class User < ApplicationRecord
  # ... 之前的代码 ...

  # 患者拥有的预约(作为预约发起方)
  has_many :patient_appointments, class_name: 'Appointment', foreign_key: 'patient_id', dependent: :destroy
  # 医生收到的预约(作为预约接收方)
  has_many :doctor_appointments, class_name: 'Appointment', foreign_key: 'doctor_id', dependent: :destroy
end
  • 创建Appointments控制器,处理预约的创建、查看和状态更新:
# app/controllers/appointments_controller.rb
class AppointmentsController < ApplicationController
  before_action :authenticate_user!

  # 根据当前用户角色显示对应预约列表
  def index
    if current_user.doctor?
      @appointments = current_user.doctor_appointments.order(appointment_date: :asc)
    elsif current_user.patient?
      @appointments = current_user.patient_appointments.order(appointment_date: :asc)
    else
      redirect_to root_path, alert: '无权限访问此页面'
    end
  end

  # 患者创建预约页面
  def new
    if current_user.patient?
      @appointment = current_user.patient_appointments.new
      @doctors = User.where(role: 'doctor') # 列出所有医生供选择
    else
      redirect_to root_path, alert: '仅患者可发起预约'
    end
  end

  # 提交预约申请
  def create
    if current_user.patient?
      @appointment = current_user.patient_appointments.new(appointment_params)
      if @appointment.save
        redirect_to appointments_path, notice: '预约申请已提交,请等待医生确认'
      else
        @doctors = User.where(role: 'doctor')
        render :new
      end
    else
      redirect_to root_path, alert: '仅患者可发起预约'
    end
  end

  # 医生更新预约状态
  def update
    @appointment = Appointment.find(params[:id])
    # 只有预约对应的医生才能修改状态
    if current_user.doctor? && @appointment.doctor == current_user
      if @appointment.update(appointment_status_params)
        redirect_to appointments_path, notice: '预约状态已更新'
      else
        redirect_to appointments_path, alert: '状态更新失败,请重试'
      end
    else
      redirect_to root_path, alert: '无权限修改此预约'
    end
  end

  private

  def appointment_params
    params.require(:appointment).permit(:doctor_id, :appointment_date)
  end

  def appointment_status_params
    params.require(:appointment).permit(:status)
  end
end
  • 添加预约相关路由:
# config/routes.rb
Rails.application.routes.draw do
  # ... 之前的路由 ...

  resources :appointments, only: [:index, :new, :create, :update]
end
4. 测试验证

现在可以测试整个流程:

  • 访问/register/patient注册患者账号,查看是否能正常创建,且role自动设为patient
  • 访问/register/doctor注册医生账号,填写专科和执业证号,验证字段必填规则
  • 患者登录后,进入/appointments/new选择医生和预约时间,提交预约申请
  • 医生登录后,进入/appointments查看收到的预约,更新状态为确认或取消

内容的提问来源于stack exchange,提问作者xbrain88

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 07:18:42