【问题标题】:Persisting ActiveRecord objects between requests在请求之间持久化 ActiveRecord 对象
【发布时间】:2014-08-24 05:21:27
【问题描述】:

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

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

# 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 实例存储在闪存会话中,但它变成了一个看起来有点像#<Document:0xad32368> 的字符串。在网上搜索了一会儿,发现由于某些原因,不能在会话中存储 ActiveRecord 对象。

有很多建议只是将对象的id 存储在闪存会话中,但我不能这样做,因为如您所见,对象尚未存储在数据库中。

接下来,我尝试在重定向后重构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 实例时验证失败的情况下),我不确定原始属性和新属性之间的哪个会被持久化。

现在我已经想尽了办法。任何对此有一个体面的解决方案的人?

编辑:

您可能想知道为什么我仍然必须重定向到另一个页面,而不是只呈现新的文档视图模板或create 方法中的new 操作。我这样做是因为我的视图中有一些东西依赖于当前的控制器方法。例如,当您在文档创建页面上时,我有一个需要突出显示的选项卡(通过检查 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方法验证失败,我想重定向到上一页。

【问题讨论】:

  • 我不是 100% 确定你为什么要这样做,但也许可以查看在请求之间缓存和存储数据的文档。请记住设置超时,这样您就不会遇到问题。 api.rubyonrails.org/classes/ActiveSupport/Cache/Store.html
  • @Anti-Fun 根据 ActiveSupport Cache 文档,ActiveSupport::Cache::Store can store any serializable Ruby object.。而 ActiveRecord 对象不能存储在会话中的原因是因为一些序列化问题。所以我猜(不确定,还没有尝试过)缓存 ActiveRecord 对象仍会将其存储为字符串?
  • 缓存封送对象(因此可以序列化几乎任何东西)而会话(从 4.1 开始)默认使用 json。

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


【解决方案1】:

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

在你的情况下,这太过分了。在您的代码中,您正在重定向,这会导致一个新请求:

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

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

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

【讨论】:

  • 感谢您的回复。我很清楚使用render 方法来呈现不同的动作或视图模板,但正如我在我的问题中所说的(如果不是很清楚,我很抱歉),我不能使用这种方式,因为我的某些元素视图取决于当前操作。例如,当当前操作为new(使用action_name 检索)时,我有需要突出显示的选项卡,但如果我从create 方法调用render action: 'new'action_name 的值将是@ 987654334@,使标签不突出显示。
  • 您是否看到我对“如果您需要动作中的额外逻辑,您可以将逻辑重构为从两个动作中调用的方法,或者简单地从当前动作中调用另一个动作。”的编辑?将标签突出显示包含在 OP 中是一件好事。
  • 好的,我将编辑我的问题以包含它。谢谢。
  • @Arnelle Balane - 见编辑。也不要忽视第一行。通常最适合多步骤表单等,但可能与此处相关。祝你好运。
  • 感谢您的建议,但这并不是我现在想要的。我当前的代码库将需要进行很多更改。不过,我会记下这些,在未来的日子里可能会有用。谢谢。
【解决方案2】:

已解决

我能够使用ActiveSupport::Cache::Store(如@AntiFun 所建议的那样)为其制定解决方法。首先,我创建了一个fake_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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-08-29
    • 2013-06-05
    • 2013-08-22
    • 1970-01-01
    • 2011-04-23
    • 2011-03-01
    • 1970-01-01
    相关资源
    最近更新 更多