#219 Active Model
- Download:
- source codeProject Files in Zip (92.6 KB)
- mp4Full Size H.264 Video (12.7 MB)
- m4vSmaller H.264 Video (8.03 MB)
- webmFull Size VP8 Video (20.9 MB)
- ogvFull Size Theora Video (18.2 MB)
在第193篇【视频, 文本】中,介绍了一种不使用实际数据库的ActiveRecord的用法。实际这只是因为需求而进行的hack的做法,因为ActiveRecord没有设计这样用。不过在Rails 3里,这个情况已经改变。在Rails3里ActiveModel是设计来解决不想存储到数据库的数据的需求。
这篇的主题就是介绍ActiveModel的用法。在正式介绍开始前,我们先看看我们将要用来展示的例子。
截图展示一个用rails 脚手架创建的应用,其中Message是一个ActiveRecord的Model。也就是脚手架默认的把Model关联到数据库。我们将要做的是,我们只是把消息通过email发送但是不保持消息本身。
当然,在你要开始使用ActiveModel的时候你应该确认你是不是真的有这样的需求不使用数据库,因为,把数据库存到数据库可以作为一种数据的备份,或者用来进行后台的异步操作(译者晓夜注:比如当前例子的message可以存到数据库后异步调用发送)。对于我们的演示而言,我们就是要演示怎么使用ActiveModel,也没什么好考虑。那么,首先,看一下Message的Model:
class Message < ActiveRecord::Base validates_presence_of :name validates_format_of :email, :with => /^[-a-z0-9_+\.]+\@([-a-z0-9]+\.)+[a-z0-9]{2,4}$/i validates_length_of :content, :maximum => 500 end
从Message Model的源代码我们可以看到,Message是继承自ActiveRecord::Base
,也就是我们要不使用数据库,那么我们就要解除来自ActiveRecord的继承关系。然而,显然当我们去掉对应的ActiveRecord继承,那么对应的输入验证也就会失效。也就是我们将使用ActiveModel来支持这样的输入验证,这点在后文我们会看到。
查看Rails 3的源代码会看到 activerecord 和 activemodel的文件夹。实际上,Rails核心团队把所有和数据库无关的代码放到ActiveModel下。所以,ActiveRecord实际上依赖ActiveModel,而ActiveModel是经过了测试,并且可以独立于ActiveRecord使用的。
查看ActiveModel目录如下:
从代码列表我们可以大概看出来,这个目录下将包括如下功能处理回调,dirty tracking, 序列化和验证,等等。比较显然,最后的三个文件是我们需要的。
打开validations的源代码,我们会看到如下的注释。从注释我们会知道使用验证相当容易,只要把加载 Validations模块和提供getter方法给我们要验证的属性。
# == Active Model Validations # # Provides a full validation framework to your objects. # # A minimal implementation could be: # # class Person # include ActiveModel::Validations # # attr_accessor :first_name, :last_name # # validates_each :first_name, :last_name do |record, attr, value| # record.errors.add attr, 'starts with z.' if value.to_s[0] == ?z # end # end #
那么,我们可以根据注释修改我们的Message Model如下:
class Message include ActiveModel::Validations attr_accessor :name, :email, :content validates_presence_of :name validates_format_of :email, :with => /^[-a-z0-9_+\.]+\@([-a-z0-9]+\.)+[a-z0-9]{2,4}$/i validates_length_of :content, :maximum => 500 end
那么,当前我们修改后的代码会有两个问题:1. 当我们创建Message
的时候,Message.new
方法会因为我们的initializer方法中不能创建接收参数的数据库插入操作而失效。2. 同样的save方法,原本会保存到数据库现在也将失效。
class MessagesController < ApplicationController def new @message = Message.new end def create @message = Message.new(params[:message]) if @message.save # TODO send message here flash[:notice] = "Message sent! Thank you for contacting us." redirect_to root_url else render :action => 'new' end end end
我们首先修改保存的问题,因为我们并不需要保存message本身,所以save方法只需要进行验证操作就可以。也就是我们只需要用@message.valid?
方法代替@message.save
方法就可以了。
def create @message = Message.new(params[:message]) if @message.valid? # TODO send message here flash[:notice] = "Message sent! Thank you for contacting us." redirect_to root_url else render :action => 'new' end end
至于,创建的问题,我们可以创建一个initialize
的方法来接收传入Message
的哈希参数,在initializer方法中,通过调用send
(译者晓夜:调用变量对应的方法)方法给指定的属性赋值。
class Message include ActiveModel::Validations attr_accessor :name, :email, :content validates_presence_of :name validates_format_of :email, :with => /^[-a-z0-9_+\.]+\@([-a-z0-9]+\.)+[a-z0-9]{2,4}$/i validates_length_of :content, :maximum => 500 def initialize(attributes = {}) attributes.each do |name, value| send("#{name}=", value) end end end
看起来应该没有什么问题了,可是当我们重新加载页面会看到如下错误:
问题是在于我们没有在Message中定义to_key方法,form_for默认的认为我们model提供这样的方法,那么,我们需要添加这个功能。
实际上,在我们不知道Rails都需要我们的model提供什么功能的时候,我们可以加载ActiveModel::Lint::Tests模块到我们的modle测试中。在Lint::Tests模块中展示出包括to_key在内的Rails定义的model需要提供的方法。 至于,如何解决缺少方法的问题,我们可以加载一组ActiveRecord的模块来解决。 Conversion
是其中包括to_key方法的模块。我们也需要Naming
方法,这里我们不使用include而是使用extend因为我们需要类方法(译者晓夜:对应于include类实例方法)。
我们引入了Conversion
,那么我们需要重新定义persisted?方法,因为我们不需要使用数据库所以,我们呢需要persisted方法返回false
。所以,我们的Message Model将会修改如下:
class Message include ActiveModel::Validations include ActiveModel::Conversion extend ActiveModel::Naming attr_accessor :name, :email, :content validates_presence_of :name validates_format_of :email, :with => /^[-a-z0-9_+\.]+\@([-a-z0-9]+\.)+[a-z0-9]{2,4}$/i validates_length_of :content, :maximum => 500 def initialize(attributes = {}) attributes.each do |name, value| send("#{name}=", value) end end def persisted? false end end
这时,我们再刷新页面我们会看到已经能够正常显示了,也就是说我们的Message model已经提供了Rails3需要的方法。提交form我们会发现验证的功能也可以正常使用了。
实际上我们只是介绍了ActiveModel中很少的一部分功能。ActiveModel中还有更多值得了解的方法,幸好这部分的代码结构和注释写的很好,在你需要使用的时候,只要参考对应的源代码就可以很好的了解如何使用。