在请求之间保留ActiveRecord对象

时间:2014-08-24 05:21:27

标签: ruby-on-rails session activerecord ruby-on-rails-4.1

我有一个名为Document的ActiveRecord模型,并围绕它实现了CRUD操作。我只是在验证失败时在请求之间持久存在Document实例时遇到问题(因为我想在发生这种情况时重定向到另一个页面)。

首先,我尝试将实例存储在flash会话中:

# documents_controller.rb

def new
  @document = flash[:document] || Document.new
end

def create
  document = Document.new(document_params)
  if document.save
    return redirect_to documents_path
  end
  flash[:document] = document
  redirect_to new_document_path
end

使用上面的代码,我希望实际的Document实例存储在flash会话中,但它变成了一个看起来像#<Document:0xad32368>的字符串。在线搜索了一段时间后,我发现由于某些原因你无法在会话中存储ActiveRecord对象。

关于将对象的id存储在flash会话中有很多建议,但我不能这样做,因为正如您所看到的,该对象尚未存储在数据库中。

接下来,我尝试在重定向之后重构Document实例,利用实例的attributes方法(返回可存储在会话中的可序列化哈希):

# documents_controller.rb

def new
  @document = Document.new(flash[:document_hash] || {})
end

def create
  ...
  flash[:document_attributes] = document.attributes
  redirect_to new_document_path
end

这几乎解决了这个问题,但不保留验证错误(document.errors)的部分除外。此外,如果这用于保存已存储在数据库中的实例(在更新Document实例时验证失败的情况下),我不确定原始属性和新属性之间的哪个将被保留

现在我已经想出了尝试的想法。谁有这个合适的解决方案?

修改

您可能想知道为什么我仍然需要重定向到另一个页面而不是仅仅在new方法中呈现新文档视图模板或create操作。我这样做是因为我的视图中有一些东西依赖于当前的控制器方法。例如,当您在文档创建页面上时,我有一个需要突出显示的选项卡(通过检查action_name == "new" and controller_name == "documents"来完成)。如果我这样做:

def create
  ...
  render action: "new"
end

标签不会突出显示,因为action_name现在为create。我也不能只添加其他条件来突出显示标签action_name == "create",因为文档也可以从索引页面(documents_path)创建。文档也可以从索引页面(documents_path)或详细信息页面(document_path(document))更新,如果验证在update方法中失败,我想重定向到上一页。

2 个答案:

答案 0 :(得分:2)

如果我真的需要在请求之间伪造某些东西(你设置的所有变量在请求之间丢失),我将通常将相关属性放入新表单中的隐藏字段中。

在你的情况下,这是矫枉过正。在您的代码中,您正在重定向,这会导致新的请求:

def create
  document = Document.new(document_params)
  if document.save
    return redirect_to documents_path
  end
  flash[:document] = document
  redirect_to new_document_path
end

您可以使用render action: 'action_to_render'轻松渲染其他操作的输出,而不是重定向。所以在你的例子中,这可能是:

def create
  @document = Document.new(document_params)
  if @document.save
    render action: 'index'
  else
    render action: 'new'
  end
end

可以简化为:

def create
  @document = Document.new(document_params)
  action_to_render = @document.save ? 'index' : 'new'
  render action_to_render
end

如果您需要操作中的额外逻辑,则可以将逻辑重构为从两个操作调用的方法,或简单地call the other action from the current one

偶尔会很好,但我要提醒的是,不得不过多地渲染渲染,这通常表明架构很差。

修改

给定新突出显示的约束,另一个选项可能是使new和create方法相同。删除new操作和路由,并为GET和PATCH请求创建答案。该操作可能类似于:

def create
  @document = Document.new(document_params)
  request.patch? && @document.save && redirect_to( documents_path )
end

我实际上对几乎所有的控制器都使用了与此类似的东西,因为它会显着地干掉事物(因为你可以删除额外的可能相同的视图)

另一种选择是只使用一个实例变量来跟踪此实例中的活动选项卡,并使其余代码更清晰。

答案 1 :(得分:0)

<强>解决

我能够使用ActiveSupport::Cache::Store为其制作解决方法(正如@AntiFun所建议的那样)。首先,我创建了一个fake_flash方法,它与flash会话非常相似,只是它使用缓存来存储数据,它看起来像这样:

def fake_flash(key, value)
  if value
    Rails.cache.write key, value
  else
    object = Rails.cache.read key
    Rails.cache.delete key
    object
  end
end

然后我就像flash会话一样使用它。

# documents_controller.rb

def new
  ...
  @document = fake_flash[:document] || Document.new
  ...
end

def create
  document = Document.new document_params
  ...
  # if validation fails
  fake_flash :document, document
  redirect_to new_document_page
end