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

Ruby on Rails实现同一用户同一分类仅一个目标的创建限制问题

简洁实现同一用户同一分类仅一个目标的限制

你的代码逻辑冗余且未利用Rails原生验证机制,导致限制规则不生效。下面是更简洁可靠的解决方案:

1. 模型层加唯一性验证(核心保障)

直接在Objective模型中添加唯一性验证,同时必须在数据库层面添加唯一索引——避免多请求并发创建时绕过应用层验证的竞态问题:

# app/models/objective.rb
class Objective < ApplicationRecord
  belongs_to :user
  belongs_to :category
  has_many :periods, dependent: :destroy

  # 验证规则:同一用户同一分类只能存在一个目标
  validates :category_id, uniqueness: { 
    scope: :user_id, 
    message: "Tu as déjà un objectif appartenant à cette catégorie" 
  }
end

生成数据库迁移文件添加唯一索引:

rails generate migration AddUniqueIndexToObjectivesOnUserIdAndCategoryId

编辑迁移文件内容:

class AddUniqueIndexToObjectivesOnUserIdAndCategoryId < ActiveRecord::Migration[7.0]
  def change
    add_index :objectives, [:user_id, :category_id], unique: true
  end
end

执行迁移:

rails db:migrate

2. 简化控制器create方法

移除冗余判断逻辑,完全依托模型验证的结果处理流程:

def create
  @category = Category.find(params[:category_id])
  @objective = current_user.objectives.build(
    objective_params.merge(
      category_id: @category.id,
      start_date: Date.today
    )
  )

  if @objective.save
    # 批量创建周期,比循环单条创建更高效
    periods_data = @objective.total_periods.times.map do |i|
      {
        period_number: (Date.today + (i*7).days).strftime("%V"),
        period_score: 0,
        created_at: Date.today
      }
    end
    @objective.periods.create!(periods_data)

    # 仅在用户创建第一个目标时发送通知
    if current_user.objectives.count == 1
      target_post = Post.find_by(
        user: User.find_by(username: "Aitio"), 
        title: "Participez à un objectif en commun !"
      )
      if target_post
        ObjectiveNotification.with(post: target_post).deliver(current_user)
        unread_count = current_user.notifications.unread.count
        Turbo::StreamsChannel.broadcast_replace_to(
          "mynotif",
          target: "mynotif_#{current_user.id}_value",
          partial: 'shared/stream_mynotif',
          locals: { notifications: unread_count, userid: current_user.id }
        )
      end
    end
  else
    flash[:alert] = @objective.errors.full_messages.join(", ")
  end

  respond_to do |format|
    format.turbo_stream
  end
end

关键优化点

  • 双重验证保障:应用层验证给出友好提示,数据库索引彻底杜绝重复数据
  • 简化对象构建:用current_user.objectives.build直接关联用户,无需手动赋值user_id
  • 批量操作优化:周期创建改用批量插入,减少数据库交互次数
  • 移除冗余逻辑:原代码中current_user.objectives.count >=1、@used_category等判断完全多余,模型验证已覆盖重复创建场景
  • 异常友好处理:替换save!为save,避免因验证失败抛出500异常,改用错误信息提示用户

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 08:20:26