【发布时间】:2010-08-12 05:29:06
【问题描述】:
问题
有趣的问题。有趣,因为它看起来很随意,直到你开始考虑它。假设我允许人们根据其他项目创建项目。您可以打开 /items/new?id=3,与常规的新操作不同,您将看到一个预先填充了 item-3 值的表单,而不是一个空表单。因此,与您的平均默认新操作不同,我的操作如下所示:
def new
@item = params[:id] ? Item.find(params[:id]) : Item.new
end
因此,您可以使用 /items/new?id=1 之类的 url 访问操作,并且表单将预先填充来自 item-1 的数据。
到目前为止一切顺利。
现在您提交此数据。我的创建动作非常标准。
def create
@item = Item.new(params[:item])
if @item.save
# happy
else
# sad
end
end
这里只有一个问题。刚刚创建的新项目(我们称之为 item-2)需要知道爸爸是谁。在我们的例子中,爸爸是 item-1(因为原始表单预先填充了来自 item-1 的数据),因此 item-2 必须将 parent_id 设置为 1。只是,这不仅仅是一个不错的 -有。它必须坚如磐石,不可篡改。您不能欺骗 parent_id。
所以我的问题是 — 在基于现有项目创建新项目时,如何确保正确设置 parent_id?
我们将不胜感激一些帮助,但只是为了让您不认为这是一个简单的偶然问题,这里有一些您可能正在考虑的非解决方案。
非解决方案#1
因此,保存 parent_id 的常用解决方案是将其作为表单中的隐藏字段。
<% form_for @item do |f| %>
...
<%= f.hidden_field :parent_id, :value => params[:id] %>
...
<% end %>
但是在这里我可以很容易地在我的 html 中切换它。提交表单时,服务器不会有任何线索。服务器将简单地“相信”该项目是使用父级 [whatever] 创建的,而它应该是父级 1。
非解决方案 #2
在服务器上,我可以使用 session 来存储您正在编辑的项目。然后我的新动作将如下所示。
def new
if params[:id]
@item = Item.find(params[:id])
session[:creating_item] = params[:id].to_i
else
@item = Item.new
session[:creating_item] = :mine # this prevents loophole if session is cleared
end
end
我的创建操作将采用此值并将其设置为 parent_id。
def create
@item = Item.new(params[:item])
if session[:creating_item] == :mine
@item.parent_id = nil
elsif session[:creating_item].is_a?(Fixnum)
@item.parent_id = session[:creating_item]
else
# session was probably cleared
redirect_to root_url and return
end
if @item.save
# happy
else
# sad
end
end
如果您多次打开/items/new(例如在浏览器的多个选项卡中),这将不起作用。会话值每次都会被覆盖。因此,如果您提交了在/items/new?id=1 打开的表单,但您也打开了/items/new?id=2,那么会话中的 parent_id 将为 2。失败。
非解决方案#3
如果您在会话中将所有打开的父级保存在数组中,而不是仅仅覆盖单个值。好吧,我不会写很长的例子,我只想说,当任何打开的表单被提交时——你会知道它一定是你在会话中保存的父母之一(例如 1、2 或 3 ),但你仍然不知道是哪一个。然后,您可以将来自父级 1 的数据保存为来自父级 3 的数据。
【问题讨论】:
标签: ruby-on-rails security validation forms token