`
liuqiang
  • 浏览: 161182 次
  • 性别: Icon_minigender_1
  • 来自: 华东
社区版块
存档分类
最新评论

在rails中优雅的进行模型校验

    博客分类:
  • Ruby
阅读更多

      在用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 %>


 

 

 

 

分享到:
评论
35 楼 dualface 2008-09-08  
在保存模型时还要处理上传图片时,我自己的做法通常是在 before_save 中处理图片。
因为需要按照上传时间等信息生成 hash 后的图片文件名,再把图片文件名放入模型,最后保存模型。

在 before_save 中处理图片失败时可以抛出异常来引起事务回滚,如果放在 after_save 里面处理图片就不好办了吧?
34 楼 snow18 2008-09-08  
around_filter的方法确实比较巧妙,一开始看到就想起了spring,这个解决方案应该是优雅的,而且非常的通用。至于说什么删除图片的问题,根本不会存在,因为transaction fail的时候after_save这些根本也不会被调用的。用了快两年的rails,还没用过around_filter,汗。。。 这里学了一招 。
33 楼 gigix 2008-09-08  
right now 写道
另外我想问问gigix同学,你的方法中,在过滤器是不是要判断对哪个表单进行校验,怎么弄的啊?如果有些model的保存只涉及一个model也要去过滤下全修改全不修改吗?哎&hellip;&hellip;头疼

sigh
以前Spring那么火的时候你们在干什么呢…真疑惑…
对于绝大多数action,你不需要知道里面出了”什么“错,你只需要知道只要里面出了错,那么所有的数据库操作都应该回滚
对于少数action,你需要在回滚的同时再回调一些东西来收拾残局
就这么简单
32 楼 0701 2008-09-08  
<p> </p>
<div class='quote_title'>gigix 写道</div>
<div class='quote_div'>
<div class='quote_title'>dave liu 写道</div>
<div class='quote_div'>不管代码是怎么来的 代码中处理的方法似乎比gigix的好 gigix的有牛刀宰鸡的嫌疑</div>
<br/>如果你半年里每天对着这一两百个action你就不会这么想了</div>
<p> </p>
<p> 俺们做的系统需要处理的有这种需求的表单不会有上百个,而且无非加个判断而已,是可以接收的,而且gigix的方法貌似是存在的写文件的问题。</p>
<p> </p>
<p>另外我想问问gigix同学,你的方法中,在过滤器是不是要判断对哪个表单进行校验,怎么弄的啊?如果有些model的保存只涉及一个model也要去过滤下全修改全不修改吗?哎……头疼</p>
31 楼 gigix 2008-09-08  
dave liu 写道
不管代码是怎么来的   代码中处理的方法似乎比gigix的好   gigix的有牛刀宰鸡的嫌疑

如果你半年里每天对着这一两百个action你就不会这么想了
30 楼 dave liu 2008-09-08  
不管代码是怎么来的   代码中处理的方法似乎比gigix的好   gigix的有牛刀宰鸡的嫌疑
29 楼 liusong1111 2008-09-08  
淡定,淡定~
对人,对己
--
保证在一个transaction里是必须的.

28 楼 liuqiang 2008-09-08  
<div class='quote_title'>gigix 写道</div>
<div class='quote_div'>
<div class='quote_title'>liuqiang 写道</div>
<div class='quote_div'>恩,其实这个道理还是要讲明白滴,首先这个东东呢,是我之前在一个英文网站上看到类似的做法,一时想不起来在哪了。 <br/><br/>周末下午呢,我心血来潮,在我的机器上写了代码,做了相关的试验,证明确实是一个不错的办法,晚上呢,又一时发昏,把这个过程发到了坛子里面,事情就是这样,恩,是我不对,我不该把这个东西分享出来。</div>
<br/>你一直没注意到,这件事情分为两部分 <br/><br/>如何知道数据是非法的 <br/>以及 <br/>对于非法数据如何处置 <br/><br/>前一部分根本不需要在action里出现,尽管后一部分有时(仅仅是有时)需要在action里明确处理 <br/>分享固然很好,不过认真想清楚再分享会更好</div>
<p> </p>
<p> 谢谢您的建议!恩,淡定,淡定~ 。</p>
27 楼 gigix 2008-09-08  
liuqiang 写道
恩,其实这个道理还是要讲明白滴,首先这个东东呢,是我之前在一个英文网站上看到类似的做法,一时想不起来在哪了。

周末下午呢,我心血来潮,在我的机器上写了代码,做了相关的试验,证明确实是一个不错的办法,晚上呢,又一时发昏,把这个过程发到了坛子里面,事情就是这样,恩,是我不对,我不该把这个东西分享出来。

你一直没注意到,这件事情分为两部分

如何知道数据是非法的
以及
对于非法数据如何处置

前一部分根本不需要在action里出现,尽管后一部分有时(仅仅是有时)需要在action里明确处理
分享固然很好,不过认真想清楚再分享会更好
26 楼 liuqiang 2008-09-08  
<p>gigix同学的做法不错,大家可以参考下,不过请实践后再评价不迟,不是吗?</p>
<p> </p>
<p>PS:我在考虑怎么把该校验写到filter中</p>
<p> </p>
<p> </p>
<p> </p>
25 楼 dave liu 2008-09-08  
说实话  我觉得你的文章是抄的
24 楼 liuqiang 2008-09-08  
<div class='quote_title'>Jan 写道</div>
<div class='quote_div'>gigix同学的做法漂亮 <br/><br/>关于效率,一这里似乎不是效率第一的场合,二rails的map(&amp;:valid?)效率也不会高到哪儿去,到底哪个快,快多少,不做benchmark不好说。</div>
<p> </p>
<p>呜呜,麻烦各位在实际中去实践一下</p>
23 楼 Jan 2008-09-08  
gigix同学的做法漂亮

关于效率,一这里似乎不是效率第一的场合,二rails的map(&:valid?)效率也不会高到哪儿去,到底哪个快,快多少,不做benchmark不好说。
22 楼 liuqiang 2008-09-08  
<div class='quote_title'>zbage 写道</div>
<div class='quote_div'>此文出处: <br/><a href='http://jimneath.org/2008/09/06/multi-model-forms-validations-in-ruby-on-rails/' target='_blank'>http://jimneath.org/2008/09/06/multi-model-forms-validations-in-ruby-on-rails/</a></div>
<p><br/><br/>恩,貌似是看过这篇文章,不过大家可以帮着鉴定对比下,看看算不算侵权:0 ,记得还有一篇对这三种方法分析的更为详尽的文章,我找到后再把地址贴出来:)<br/><br/><br/>PS:我很想知道JE是怎么处理这种情况的 </p>
20 楼 liuqiang 2008-09-08  
见你LS的LS的LS,已经说了

你感兴趣可以试下老王的方法,看看能不能把所有model的出错信息全部调出来。
19 楼 wosmvp 2008-09-08  
<div class='quote_title'>liuqiang 写道</div>
<div class='quote_div'>
<div class='quote_title'>wosmvp 写道</div>
<div class='quote_div'>
<div class='quote_title'>liuqiang 写道</div>
<div class='quote_div'>
<div class='quote_title'>Quake Wang 写道</div>
<div class='quote_div'>根据我的经验,如果一个复杂的表单提交牵涉到多个model,这多个model之间往往都是有关联的,而且会有一个主要模型。利用ActiveRecord提供的级联操作,你的例子可以这样简化: <br/>
<pre name='code' class='ruby'>  def create
    @city = City.new params[:city]
    @user = @city.users.build params[:user]
    @order = @user.orders.build params[:order]
   
    @city.save ? redirect_to "/main/new" : render :action =&gt; "new"
  end
</pre>
<br/>示意代码,仅供参考,具体可以查看association_collection.rb的build方法。</div>
<p> </p>
<p> 我不知道您的这个方法如果遇到,某个次模型涉及到多媒体文件保存,是不是也可以级联撤销呢?</p>
</div>
<p><br/>build没有保存,只是相当于new,撤销什么?</p>
</div>
<p><br/>见你LS的回复,撤销是针对图片之类的文件,叫做删除更准确点,当然,如果你可以忍受系统中许多垃圾文件的话,可以不去删除这些文件。</p>
<p> </p>
<p> </p>
</div>
<p><br/>你说的是上传的附件吧,在没有保存他们应该都是只存在于内存的啊</p>
18 楼 liuqiang 2008-09-08  
<div class='quote_title'>wosmvp 写道</div>
<div class='quote_div'>
<div class='quote_title'>liuqiang 写道</div>
<div class='quote_div'>
<div class='quote_title'>Quake Wang 写道</div>
<div class='quote_div'>根据我的经验,如果一个复杂的表单提交牵涉到多个model,这多个model之间往往都是有关联的,而且会有一个主要模型。利用ActiveRecord提供的级联操作,你的例子可以这样简化: <br/>
<pre name='code' class='ruby'>  def create
    @city = City.new params[:city]
    @user = @city.users.build params[:user]
    @order = @user.orders.build params[:order]
   
    @city.save ? redirect_to "/main/new" : render :action =&gt; "new"
  end
</pre>
<br/>示意代码,仅供参考,具体可以查看association_collection.rb的build方法。</div>
<p> </p>
<p> 我不知道您的这个方法如果遇到,某个次模型涉及到多媒体文件保存,是不是也可以级联撤销呢?</p>
</div>
<p><br/>build没有保存,只是相当于new,撤销什么?</p>
</div>
<p><br/>见你LS的回复,撤销是针对图片之类的文件,叫做删除更准确点,当然,如果你可以忍受系统中许多垃圾文件的话,可以不去删除这些文件。</p>
<p> </p>
<p> </p>
17 楼 wosmvp 2008-09-08  
<div class='quote_title'>liuqiang 写道</div>
<div class='quote_div'>
<div class='quote_title'>Quake Wang 写道</div>
<div class='quote_div'>根据我的经验,如果一个复杂的表单提交牵涉到多个model,这多个model之间往往都是有关联的,而且会有一个主要模型。利用ActiveRecord提供的级联操作,你的例子可以这样简化: <br/>
<pre name='code' class='ruby'>  def create
    @city = City.new params[:city]
    @user = @city.users.build params[:user]
    @order = @user.orders.build params[:order]
   
    @city.save ? redirect_to "/main/new" : render :action =&gt; "new"
  end
</pre>
<br/>示意代码,仅供参考,具体可以查看association_collection.rb的build方法。</div>
<p> </p>
<p> 我不知道您的这个方法如果遇到,某个次模型涉及到多媒体文件保存,是不是也可以级联撤销呢?</p>
</div>
<p><br/>build没有保存,只是相当于new,撤销什么?</p>
16 楼 liuqiang 2008-09-07  
<div class='quote_title'>Quake Wang 写道</div>
<div class='quote_div'>不明白这和文件保存有什么关系,你可以举个具体的例子么? <br/>我写的代码是指对你主贴中对于多个model先手工调用valid?,然后依次调用save的简化:利用AR的build添加,在save主model之前会自动调用valid?方法,包括级联对象。</div>
<p> </p>
<p> 哦,那基本是一个意思了,文件保存的意思是,用户头像之类的在user保存的时候,头像图片也要写进文件系统,而利用事务处理的话,如果后续model合法性出错,头像图片写进文件的操作就是白做了,显然性能上做了不必要的牺牲。</p>
<p>你这个方法我和文中提到的第二种方法类似,所以保存之前对于所有model先手工调用valid?是合理的!</p>

相关推荐

    Ruby on Rails中文指南

    7. **哈希与符号化键(Hashes and Symbolized Keys)**:在Rails中,使用符号作为哈希键可以提高性能,因为符号是不可变的,且在内存中只存储一次。 8. **部分视图(Partials)**:部分视图是可重用的视图代码块,...

    在rails中 使用RSpec生产CHM文档

    标题 "在Rails中使用RSpec生产CHM文档" 暗示了这个话题是关于如何在Ruby on Rails(简称Rails)框架中使用RSpec测试工具来创建帮助文档,特别是以CHM(Microsoft Compiled HTML Help)格式。CHM文件是一种常见的...

    使用Aptana+Rails开发Rails Web应用(中文)

    安装过程中,遵循提示进行,确保选择自定义安装并勾选Rails相关的插件,以便在Aptana中获得对Rails的全面支持。 安装完成后,打开Aptana Studio,创建一个新的Rails项目。在“File”菜单中选择“New”然后点击...

    rails指南 中文版

    控制器是MVC架构中的C,它连接模型和视图,处理用户输入,调用模型进行业务逻辑处理,并将结果传递给视图进行展示。 3. **ActionView**:视图部分用于生成HTML输出,显示给用户。Rails提供了一套强大的模板系统,...

    应用Rails进行敏捷Web开发中文第三版

    在Rails中,开发者可以快速构建功能丰富的、数据驱动的Web应用。这本书会详细讲解如何利用Rails的MVC(Model-View-Controller)架构模式来组织代码,其中Model代表数据模型,View负责展示,Controller处理业务逻辑并...

    Ruby On Rails中文教材(PDF)

    4. **ActiveRecord**:这是Rails中的ORM(对象关系映射)库,它允许开发者用Ruby代码操作数据库,无需编写SQL。 5. **Scaffolding**:Rails的快速开发工具,能自动生成CRUD(创建、读取、更新、删除)操作的模型、...

    中文版rails教程

    在Ruby on Rails中,开发者可以快速构建功能丰富的动态网站,因为它提供了大量的内置功能和库,如数据库连接、ORM(对象关系映射)系统ActiveRecord、模板引擎ActionView以及路由系统ActionController等。...

    Rails 中mongrel的安装

    在Rails项目中安装Mongrel通常通过RubyGems进行,这是一个Ruby的包管理器。首先确保RubyGems已经安装,然后在命令行执行以下命令: ```bash gem install mongrel ``` 安装完成后,可以通过`mongrel_rails`命令来...

    Web开发敏捷之道--应用Rails进行敏捷Web开发 之 Depot代码。

    标题中的“Web开发敏捷之道--应用Rails进行敏捷Web开发 之 Depot代码”表明这是一个关于使用Ruby on Rails框架进行敏捷Web开发的示例项目,名为Depot。Ruby on Rails(简称Rails)是一个开源的Web应用程序框架,它...

    rails中使用javascript日期控件

    在Rails中,这通常是通过`&lt;%= f.date_field :date_field_name %&gt;`这样的辅助方法实现的,这里的`:date_field_name`是模型中的属性名。然后,我们可以使用WebCalendar.js为这个输入字段添加日期选择功能。在...

    关于rails学习中分页的示例

    在Rails中,最常用的分页库是`Kaminari`和`WillPaginate`。这里我们将以`Kaminari`为例进行讲解,因为它是近年来更受欢迎的选择,且具有简洁的API和良好的社区支持。 1. **安装Kaminari** 在Gemfile中添加`...

    Rails项目源代码

    在Rails中,可以使用Devise这样的gem来处理用户身份验证,提供注册、登录、密码重置等功能。用户模型通常会包含确认令牌、加密密码等相关字段。 4. **数据库和ActiveRecord**: Rails使用ActiveRecord作为ORM,它...

    Rails的中文乱码问题

    在Rails中,可以在`config/application.rb`或`config/environments/development.rb`中设置`config.encoding = "utf-8"`。 4. **数据库连接**:确认数据库连接器(如ActiveRecord)的连接参数中包含正确的字符集配置...

    Ruby on Rails实例开发

    在Rails中,通过ActiveRecord可以轻松地进行数据操作,如创建、读取、更新和删除(CRUD)记录。 HTML(超文本标记语言)和CSS(层叠样式表)是构建Web页面的基本元素。HTML负责结构化内容,而CSS则用于样式设计。在...

    rails敏捷开发的购物车系统

    在Rails中,可以使用ActiveRecord来操作数据库。创建购物车模型(Cart)和商品模型(Product),定义它们之间的关系,如多对多关系,通过一个关联表记录每个购物车包含的商品及其数量。使用`has_and_belongs_to_many...

    RailsGuides中那个blog程序源码

    **控制器(Controller)** 是模型和视图之间的桥梁,处理用户的请求,调用模型进行业务处理,并将数据传递给视图进行展示。`PostsController` 可能会包含如`index`(列出所有文章)、`show`(显示单篇文章)、`new`...

    rails2-sample

    这一章节将讨论Rails中的安全特性,如防止SQL注入、跨站脚本攻击(XSS)和跨站请求伪造(CSRF)等。通过实施这些防护措施,可以大大降低应用遭受攻击的风险。 #### 9. Advanced Topics(高级主题) 这一部分将覆盖...

Global site tag (gtag.js) - Google Analytics