`
liuqiang
  • 浏览: 161184 次
  • 性别: 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 %>


 

 

 

 

分享到:
评论
15 楼 QuakeWang 2008-09-07  
不明白这和文件保存有什么关系,你可以举个具体的例子么?
我写的代码是指对你主贴中对于多个model先手工调用valid?,然后依次调用save的简化:利用AR的build添加,在save主model之前会自动调用valid?方法,包括级联对象。
14 楼 liuqiang 2008-09-07  
<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>
13 楼 QuakeWang 2008-09-07  
根据我的经验,如果一个复杂的表单提交牵涉到多个model,这多个model之间往往都是有关联的,而且会有一个主要模型。利用ActiveRecord提供的级联操作,你的例子可以这样简化:
  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 => "new"
  end

示意代码,仅供参考,具体可以查看association_collection.rb的build方法。
12 楼 gigix 2008-09-07  
liuqiang 写道
注意,您还是把您这个方法的调用,说明下

class ApplicationController < ActionController::Base
  around_filter :transaction_filter
  def transaction_filter(&block)
    TransactionFilter.new.filter(self, &block)
  end
11 楼 liuqiang 2008-09-07  
<p> </p>
<div class='quote_title'>gigix 写道</div>
<div class='quote_div'>
<div class='quote_title'>liuqiang 写道</div>
<div class='quote_div'>呵呵,针对20个表单的过滤器,<span style='color: #ff0000;'>代码肯定不止20行</span>,2者代码都不是很多,没必要斤斤计较啦,综合考虑,您再仔细琢磨琢磨,哪种方法比较爽</div>
<br/>8行 <br/>
<pre name='code' class='ruby'>class TransactionFilter
  def filter(controller)
    return yield if controller.request.get?
    ActiveRecord::Base.transaction do
      yield
    end
  end
end</pre>
<br/>你大概还需要2~3行把它声明在application.rb里面 <br/>作为比较,我这个项目大概有150~200个action <br/><br/>重点在于: <br/>(1)重复 <br/>(2)不必要的思考</div>
<p> </p>
<p> 好吧,比吧,unless [@user, @city, @order].map(&amp;:valid?).include?(false)  这是个条件表达式,不算一行代码,所以我这个是0行代码 </p>
<p>注意,您还是把您这个方法的调用,也说明下</p>
<p> </p>
<p>PS:才发现我们的做法可能并不冲突</p>
10 楼 open2ye 2008-09-07  
其实现在是两个好的方法中选一个更好的.

名人都说爱说的: "经验告诉我"

在满足需求的情况下.

哪种方法代码少, 就用哪种

至于性能, 到发生了再说
9 楼 gigix 2008-09-07  
liuqiang 写道
呵呵,针对20个表单的过滤器,代码肯定不止20行,2者代码都不是很多,没必要斤斤计较啦,综合考虑,您再仔细琢磨琢磨,哪种方法比较爽

8行
class TransactionFilter
  def filter(controller)
    return yield if controller.request.get?
    ActiveRecord::Base.transaction do
      yield
    end
  end
end

你大概还需要2~3行把它声明在application.rb里面
作为比较,我这个项目大概有150~200个action

重点在于:
(1)重复
(2)不必要的思考
8 楼 liuqiang 2008-09-07  
<div class='quote_title'>gigix 写道</div>
<div class='quote_div'>
<div class='quote_title'>liuqiang 写道</div>
<div class='quote_div'>“不必要做的事情就应该避免”,关键是我这种方法又不复杂,关键地方就一行代码,你就是指望机器,写的代码也比这个多,要不怎么号称是优雅呢,:)</div>
<br/>20个action就是20行</div>
<p> </p>
<p> 呵呵,针对20个表单的过滤器,代码肯定不止20行,2者代码都不是很多,没必要斤斤计较啦,综合考虑,您再仔细琢磨琢磨,哪种方法比较爽</p>
7 楼 gigix 2008-09-07  
liuqiang 写道
“不必要做的事情就应该避免”,关键是我这种方法又不复杂,关键地方就一行代码,你就是指望机器,写的代码也比这个多,要不怎么号称是优雅呢,:)

20个action就是20行
6 楼 liuqiang 2008-09-07  
<div class='quote_title'>写道</div>
<div class='quote_div'>你举的例子本来是没有图片的。我以为我们一直在讨论一个只与数据相关的“全对或不修改”的问题。 <br/>我不太欣赏“不必要做的事情就应该避免”这种态度──如果是对机器来说的话。这种态度应该用在人身上,至于机器,没有问题之前就让它这么着,有了问题再说。 <br/>如果把负担扔给数据库就意味着减少程序员的负担,经验告诉我这个生意通常值得做。 </div>
<p> </p>
<p> </p>
<p>哈哈,举例子嘛,搞那么复杂干嘛,说真的,捣鼓这几种方法倒是牺牲了我一下午时间。</p>
<p> </p>
<p>至于“全对或不修改”的问题,是您主动提出来的:)。<br/><br/>“不必要做的事情就应该避免”,关键是我这种方法又不复杂,关键地方就一行代码,你就是指望机器,写的代码也比这个多,怎么简洁在怎么来 </p>
5 楼 gigix 2008-09-07  
liuqiang 写道
复杂性:还拿图片说事,你又需要删除图片吧,倒算不上是很复杂,但您和您的系统以及数据库做了很多不必要的事情,所以相对复杂了点。
总之,不必要做的事情为什么不去避免呢,不要把负担全部扔给数据库。建议您还是先看看我提出的方法,希望评批指正,多谢

你举的例子本来是没有图片的。我以为我们一直在讨论一个只与数据相关的“全对或不修改”的问题。
我不太欣赏“不必要做的事情就应该避免”这种态度──如果是对机器来说的话。这种态度应该用在人身上,至于机器,没有问题之前就让它这么着,有了问题再说。
如果把负担扔给数据库就意味着减少程序员的负担,经验告诉我这个生意通常值得做。
4 楼 liuqiang 2008-09-07  
<div class='quote_title'>gigix 写道</div>
<div class='quote_div'>
<div class='quote_title'>liuqiang 写道</div>
<div class='quote_div'>Rails所提供的ActiveRecord 对于复杂的数据库事务控制还是比较吃力,对于跨数据库的分布式事务支持也相对弱。</div>
<br/>显然你这个例子不是这种情况。你需要的只是一个“全对或不做修改”的事务保障,一个filter就搞定了,你甚至都不需要在实现action的时候知道有这么一回事。 <br/></div>
<p> </p>
<p>恩,我明白了,我确实明白了,您老确实是用事务解决这个问题的。</p>
<p> </p>
<p>性能:好吧,你总需要撤销对象吧,另外,假如你先前一个对象新建的时候涉及到图片保存操作,这算不算影响性能?您明白这个意思吧。</p>
<p> </p>
<p>复杂性:还拿图片说事,你又需要删除图片吧,倒算不上是很复杂,但您和您的系统以及数据库做了很多不必要的事情,所以相对复杂了点。</p>
<p> </p>
<p>总之,不必要做的事情为什么不去避免呢,不要把负担全部扔给数据库。建议您还是先看看我提出的方法,希望评批指正,多谢</p>
3 楼 gigix 2008-09-07  
liuqiang 写道
Rails所提供的ActiveRecord 对于复杂的数据库事务控制还是比较吃力,对于跨数据库的分布式事务支持也相对弱。

显然你这个例子不是这种情况。你需要的只是一个“全对或不做修改”的事务保障,一个filter就搞定了,你甚至都不需要在实现action的时候知道有这么一回事。
liuqiang 写道
性能当然相对较低啦,本来不该存的东西你存了,然后你又去撤销他,本来就是多余的操作

有趣的是我看到很多人在使用MySQL的时候根本就没有关闭它的autocommit功能,似乎他们也没有抱怨什么性能低之类的,你明白什么意思吗?
2 楼 liuqiang 2008-09-07  
<p>gigix 写道</p>
<div class='quote_div'>
<div class='quote_title'>引用</div>
<div class='quote_div'>3 利用事务,基于第一种方法之上,如果任何一个模型合法性出现合法性问题,将采取数据库回滚操作,个人认为这种方法不仅复杂,性能也不高。</div>
<br/>复杂在哪里? <br/>性能不高又在哪里?</div>
<p> </p>
<p> 真快。</p>
<p> </p>
<p>前段时间有人倒是问过这个问题, Rails所提供的ActiveRecord 对于复杂的数据库事务控制还是比较吃力,对于跨数据库的分布式事务支持也相对弱。</p>
<p> </p>
<p>性能当然相对较低啦,本来不该存的东西你存了,然后你又去撤销他,本来就是多余的操作</p>
1 楼 gigix 2008-09-07  
引用
3 利用事务,基于第一种方法之上,如果任何一个模型合法性出现合法性问题,将采取数据库回滚操作,个人认为这种方法不仅复杂,性能也不高。

复杂在哪里?
性能不高又在哪里?

相关推荐

    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