Ruby on Rails has_many :through实现行程预订开发问题
问题说明
- 需求为基于
user_id和trip_id创建预订记录,目前已尝试在trips/show视图编写提交表单 - 目标是展示完整行程列表供用户选择,选中后提交对应
user_id和trip_id存入数据库 - 疑问点:在Trip所属的
trips视图路径下编写提交表单是否可行;has_many :through关联的运行逻辑;预订逻辑需要独立控制器实现还是可以直接写在TripsController中
现有代码问题修正
1. 表单ID赋值写反+字段类型错误
当前trips/show视图内的表单把两个外键值填反,且使用明文输入框传ID存在篡改风险,修正后代码:
<%# 采用后端取当前用户ID的安全写法,这里不需要传user_id隐藏域 %> <%= form_with model: @reservation, local: true, url: reservations_path do |form| %> <%= form.hidden_field :trip_id, value: @trip.id %> <%= form.submit "确认预订", class: "btn btn-primary" %> <% end %>
安全提示:不要通过前端表单传递user_id参数,直接在后端从登录会话(Current.user)中取用户ID,避免恶意用户篡改参数为其他用户创建预订。
2. TripsController缺失实例初始化
当前TripsController#show动作未初始化预订实例,会导致表单渲染报错,补充代码:
def show @reservation = Reservation.new end
3. 路由配置错误
表单中填写的save_reservation_path不存在,使用Rails默认RESTful路由即可,预订创建请求直接提交到reservations_path,对应ReservationsController#create动作,无需额外自定义路由。
4. ReservationsController逻辑错误
- 缺失
set_reservation前置回调绑定 - 参数白名单写法错误,
user_id和trip_id为单值整数,不是数组
修正后核心代码:
class ReservationsController < ApplicationController # 补充前置回调 before_action :set_reservation, only: %i[ show edit update destroy ] # POST /reservations def create # 直接从当前登录用户构建关联预订,无需前端传user_id @reservation = Current.user.reservations.build(trip_id: reservation_params[:trip_id]) respond_to do |format| if @reservation.save format.html { redirect_to trips_path, notice: "行程预订成功" } format.json { render :show, status: :created, location: @reservation } else format.html { redirect_back fallback_location: trips_path, alert: "预订失败" } format.json { render json: @reservation.errors, status: :unprocessable_entity } end end end # 其余index/show/edit/update/destroy动作保持原有逻辑即可 private def set_reservation @reservation = Reservation.find(params[:id]) end # 修正参数白名单 def reservation_params params.require(:reservation).permit(:trip_id) end end
has_many :through 关联原理 这个关联用于实现两个模型通过中间表建立多对多关系,你的业务场景完全符合这个关联的适用条件:
- 1个用户可以预订多个行程
- 1个行程可以被多个用户预订
- Reservation就是中间关联表,存储
user_id和trip_id两个外键
三个模型的关联定义如下:
# app/models/user.rb class User < ApplicationRecord has_many :reservations has_many :trips, through: :reservations end # app/models/trip.rb class Trip < ApplicationRecord has_many :reservations has_many :users, through: :reservations end # app/models/reservation.rb class Reservation < ApplicationRecord belongs_to :user belongs_to :trip end
关联生效后可以直接调用关联方法操作数据,无需手动拼接外键查询:
- 获取当前用户的所有预订行程:
Current.user.trips - 获取某个行程的所有预订用户:
@trip.users - 直接为当前用户创建预订:
Current.user.reservations.create(trip: @trip)
控制器选型说明
不要把预订逻辑写在TripsController中,按照Rails RESTful设计规范,每个独立资源对应独立控制器:
- 行程(Trip)的增删改查逻辑全部放在TripsController
- 预订(Reservation)作为独立资源,创建、取消、查询等逻辑全部放在ReservationsController
在trips视图路径下编写预订表单完全可行,表单的提交目标地址和视图文件所属的控制器没有绑定关系,这是Rails开发中的常规写法,不存在实现问题。
内容的提问来源于stack exchange,提问作者HASHPE
相关产品推荐
相关产品推荐

