在用rails进行开发时,最常见的操作的是前台提交表单,后台保存相关的模型对象,对于一个复杂的表单,可能需要保存的模型对象不止一个,但这些对象在保存之前都是要经过合法性检验的,请看如下的场景,一个表单提交了订单信息、用户基本资料、用户所在地,那么后台需要对用户、订单、地点这3个对象进行校验并做相关处理,通常会用到error_messages_for做错误信息输出, 具体有以下3种做法:
1
if @user.save && @order.save && @city.save
#跳转到成功页面
else
#返回原来页面
end
这里存在的问题是,加入前两个模型合法性校验通过,但最后一个模型出现问题,那么原本3个对象都不该被保存,但前两个对象已经被保存,所以存在严重的脏数据问题
2
if @user.valid? && @order.valid? && @city.valid?
@user.save
@order.save
@city.save
#跳转到成功页面
else
#返回原页面
end
这个方法倒是不存在脏数据问题,但是如果第一个模型对象出现合法性问题,那么程序将停止之后的合法性校验,所以显示返回页面的错误提示将不完整,严重影响了系统的用户体验。
3 利用事务,基于第一种方法之上,如果任何一个模型合法性出现合法性问题,将采取数据库回滚操作,个人认为这种方法不仅复杂,性能也不高。
那么以下有个比较简洁的方法解决这样的问题
def new
@users = User.new
@city = City.new
@order = Order.new
end
def create
@city = City.new params[:city]
@user = User.new params[:user]
@user.city = @city
@order = Order.new params[:order]
@order.user = @user
unless [@user, @city, @order].map(&:valid?).include?(false)
@user.save
@city.save
@order.save
redirect_to "/main/new"
else
render :action => "new"
end
end
关键在于这句:
unless [@user, @city, @order].map(&:valid?).include?(false)
在保存之前就遍历各个模型,并运行valid?方法,之后判断结果列表中是否包括false,以此作为判断合法性的依据,并且不会造成脏数据的问题。
相关的view如下所示,关于错误汉化这里不做讨论
<%= error_messages_for :user %>
<%= error_messages_for :city %>
<%= error_messages_for :order %>
<% form_for :user, :url => "/main/create" do |f| %>
<fieldset>
<legend>用户信息</legend>
<ol>
<li>
<%= f.label :name %>
<%= f.text_field :name %>
</li>
</ol>
</fieldset>
<% fields_for :city do |city| %>
<fieldset>
<legend>地点信息</legend>
<ol>
<li>
<%= city.label :code %>
<%= city.text_field :code %>
</li>
</ol>
</fieldset>
<% end %>
<% fields_for :order do |order| %>
<fieldset>
<legend>订单信息</legend>
<ol>
<li>
<%= order.label :price %>
<%= order.text_field :price %>
</li>
</ol>
</fieldset>
<% end %>
<%= f.submit '提交' %>
<% end %>