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

如何在Sinatra中拆分项目任务创建URL,避免无任务项目?

解决方案

方案1:利用Session暂存未持久化的Project

核心思路是创建项目时先不存入数据库,把项目属性暂存在Session中,直到完成任务创建并验证通过后,再一次性保存Project和关联的Tasks,从根源避免孤立项目出现。

步骤与示例代码:

  • 项目创建提交时,仅验证字段合法性,不持久化,将属性存入Session
  • 任务创建页面从Session取出属性构建未保存的Project实例
  • 任务验证通过后,通过事务批量保存Project和Tasks
# 项目创建POST路由
post '/projects' do
  @project = Project.new(params[:project])
  if @project.valid?
    # 暂存项目属性到Session
    session[:pending_project] = @project.attributes
    redirect "/projects/pending/tasks/new"
  else
    erb :'projects/new'
  end
end

# 任务创建GET路由
get '/projects/pending/tasks/new' do
  # 从Session构建未持久化的Project实例
  @project = Project.new(session[:pending_project])
  erb :'tasks/new'
end

# 任务提交POST路由
post '/projects/pending/tasks' do
  @project = Project.new(session[:pending_project])
  @tasks = params[:tasks].map { |tp| @project.tasks.build(tp) }
  
  # 验证所有任务和项目合法性
  if @project.valid? && @tasks.all?(&:valid?)
    # 事务确保原子性:要么都保存成功,要么都失败
    Project.transaction do
      @project.save!
      @tasks.each(&:save!)
    end
    session.delete(:pending_project)
    redirect "/projects/#{@project.id}"
  else
    erb :'tasks/new'
  end
end

方案2:给Project添加状态字段区分草稿与活跃项目

核心思路是通过状态标记区分未完成的项目草稿和已就绪的活跃项目,业务逻辑只处理活跃项目,避免草稿干扰任务时长计算等场景。

步骤与示例代码:

  1. 给projects表添加status字段(字符串类型,默认值draft)
  2. 新建项目默认保存为draft状态
  3. 任务验证通过后,将项目状态改为active
  4. 业务逻辑中过滤草稿项目
# Project模型
class Project < ActiveRecord::Base
  has_many :tasks, dependent: :destroy
  validates :status, inclusion: { in: ['draft', 'active'] }
  # 默认查询仅返回活跃项目,按需调整
  default_scope { where(status: 'active') }

  def activate!
    update!(status: 'active') if tasks.any?
  end
end

# 项目创建POST路由
post '/projects' do
  @project = Project.new(params[:project].merge(status: 'draft'))
  if @project.save
    redirect "/projects/#{@project.id}/tasks/new"
  else
    erb :'projects/new'
  end
end

# 任务提交POST路由
post '/projects/:id/tasks' do
  @project = Project.unscoped.find(params[:id]) # 跳过默认查询范围,找到草稿项目
  @tasks = params[:tasks].map { |tp| @project.tasks.build(tp) }
  
  if @tasks.all?(&:valid?)
    Project.transaction do
      @tasks.each(&:save!)
      @project.activate!
    end
    redirect "/projects/#{@project.id}"
  else
    erb :'tasks/new'
  end
end

# 任务时长计算示例(自动排除草稿项目)
def total_task_hours
  Project.joins(:tasks).sum('tasks.hours')
end

方案3:临时表/令牌关联跨请求事务(进阶)

如果对数据一致性要求极高,可以用临时表存储未完成的项目数据,或生成唯一令牌关联创建流程:

  • 创建项目时生成唯一令牌,将数据存入临时表
  • 任务创建页面携带令牌,提交时通过令牌找到临时数据
  • 验证通过后迁移到正式表并保存任务
  • 设置超时清理机制,定期删除未完成的临时数据

该方案实现复杂度较高,适合极端场景,多数情况下前两种方案已足够应对。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 17:07:41