193:非数据库数据模型(查看原视频)
一个经常被问到的问题是如何在Rails程序中创建一个不依赖于后端数据库的表单。本集当中我们就来告诉你如何做到这一点,我们将沿用前几集中用到的博客程序作为例子来说明。
我们要做的是给每一篇文章后面加上一个“分享这篇文章”的链接。这个链接会打开一个表单,让用户填入一些信息然后通过电子邮件来分享一篇文章。我们只是想用这些输入的信息来发送一封电子邮件,并不打算保留这些信息。那么,我们如何在没有相应的数据库表的情况下来创建一个表单和数据模型呢?
实现的方法是,我们先来创建一个有数据库表的数据模型,然后再修改程序把数据库表去掉。那么我们先来用Ryan Bates的nifty scaffold generator这个工具来生成一个基本结构。
因为我们需要提交一个表单、并且创建一个资源,所以我们必须先要创建一个数据模型。我们是要把一篇文章的细节通过电子邮件推荐给其他人,所以我们新的数据模型的名字就叫Recommendation。Recommendation数据模型中的包括的字段有,电子邮件的发送者、电子邮件的接收者、要推荐的文章的id号,以及发送的消息内容。相应的控制器中需要有new和create两个动作。我们可以通过下面的命令来创建这些基本结构:
script/generate nifty_scaffold recommendation from_email:string to_email:string article_id:integer message:text new create
虽然我们实际上不需要数据库表,但是接下来我们还是要执行数据迁移任务来创建数据库表(之后我们会回滚这个数据迁移任务)
rake db:migrate
有了数据模型和控制器,我们来加一个链接,使其调用RecommendationController
控制器中的new动作,传入的参数是我们要推荐的文章的id。
<p>
<%= link_to "Share this article", new_recommendation_path(:article_id => @article.id) %>
<%= link_to "Back to Articles", articles_path %>
</p>
在/app/views/articles/show.html.erb中加入推荐链接
注意到,我们通过链接给新创建的Recommendation数据模型传入文章的id,使其能够一直被引用。
接下来,我们来修改一下新建的基本结构中的视图,删掉其中的article_id以及标签字段,我们用一个隐藏字段来保存article_id。
<% title "New Recommendation" %>
<% form_for @recommendation do |f| %>
<%= f.error_messages %>
<%= f.hidden_field :article_id %>
<p>
<%= f.label :from_email %><br />
<%= f.text_field :from_email %>
</p>
<p>
<%= f.label :to_email %><br />
<%= f.text_field :to_email %>
</p>
<p>
<%= f.label :message %><br />
<%= f.text_area :message %>
</p>
<p><%= f.submit "Submit" %></p>
<% end %>
<p><%= link_to "Back to List", recommendations_path %></p>
修改/app/views/recommendations/new.html.erb
现在刷新文章页面,我们就可以看到“分享这篇文章”的链接了,点击链接我们可以看到下面的新建表单。
如果我们填写这个表单并且提交,我们就会在数据库中创建出一个新的Recommendation数据。但是现在的情况是,我们只想发送一封电子邮件,不需要把表单存入数据库。在Rails程序中,create动作通常是用来把一个新的数据模型保存到数据库中,不过那也不是绝对的。相反,这里我们只是想检查一下新的Recommendation数据模型是不是合法。
def create
@recommendation = Recommendation.new(params[:recommendation])
if @recommendation.valid?
flash[:notice] = "Successfully created recommendation."
redirect_to @recommendation
else
render :action => 'new'
end
end
现在,我们回滚最后的那个数据迁移任务,把Recommendation数据表删掉,然后看看我们的表单是不是还能正常工作。当然我们也要删除数据迁移文件。
rm db/migrate/*_recommendations.rb
删除了数据库表,我们刷新表单页面的时候会看到错误信息。不用说,程序肯定会报错说找不到Recommendation数据表。因为ActiveRecord依赖于每个数据模型关联的数据表。
那么我们如何创建一个不依赖于数据库表的数据模型呢?我们有很多方法,包括使用一些插件,但是今天我们要用的是在文章an entry on the Code Tunes blog中讲的方法。这个文章中讲的技巧包括如何重载ActiveRecord数据模型中的一些方法,然后手工来定义数据模型中的字段,而不用使用后台的数据库。在我们的Recommendation数据模型中,我们将加入两个重载的方法,然后用column类方法来像数据迁移文件中那样定义数据字段。
class Recommendation < ActiveRecord::Base
def self.columns() @columns ||= []; end
def self.column(name, sql_type = nil, default = nil, null = true)
columns << ActiveRecord::ConnectionAdapters::Column.new(name.to_s, default, sql_type.to_s, null)
end
column :from_email, :string
column :to_email, :string
column :article_id, :string
column :message, :text
end
在/app/models/recommendation.rb中定义字段
现在我们再刷新推荐文章的页面时,我们又可以看到正确的表单了。不过,数据字段现在是定义在数据模型类中了,而不是从数据库表中读取来的。
既然我们已经不用数据库了,你可能要问为什么还要让Recommendation类继承自ActiveRecord::Base。Rails中对于ActiveRecord的耦合度很低,我们可以很容易地创建一个不基于ActiveRecord的数据模型类。但是,让我们的数据模型类继承自ActiveRecord能给我们带来很多好处。也就是说我们可以用它的很多其它功能, 比如数据检查机制。我们可以在我们的数据模型中用ActiveRecord的数据检查机制来检查电子邮件地址的格式以及消息的长度是否合适。
validates_format_of :from_email, :to_email, :with => /^[-a-z0-9_+\.]+\@([-a-z0-9]+\.)+[a-z0-9]{2,4}$/i
validates_length_of :message, :maximum => 500
在
/app/models/recommendation.rb
中加入数据检查机制
有了这些数据检查机制,当我们提交的表单中有非法的电子邮件地址时,我们就可以看到Rails报告的错误,这个和有数据库支持的数据模型的情况是一样的。
另外一个让我们的数据模型继承自ActiveRecord的原因是我们依然可以使用数据关联关系。在Recommendation中,既然article_id是一个数据项,那么我们仍旧可以在数据模型中定义下面的关联,我们就可以随时获取到关联的Article对象了。
belongs_to :article
我们这个基于ActiveRecord的数据模型的行为和其他模型几乎一样,只是它不依赖于后台的数据库,它的字段定义是通过Ruby语言定义的。如果我们碰巧调用了一个需要访问数据库的方法,那么我们就会看到刚才报出的同样的异常,说数据库表不存在,不过我们是有办法解决的。
程序中剩下的部分是在正确提交表单以后发送一封电子邮件。我们在这里就不写这些代码了,因为这已经超出了本集的内容。如果你想知道如何发电子邮件,你可以看看第61集,那里面有这方面的内容。
创建这样的非数据库数据模型是一个很有用的技巧,不过你还是应该想想你是不是真的不需要把数据存储到数据库中。把用户提交的数据存储到数据库里是再简单不过的事情,但是即使你不会马上用到这些数据,还是要看是否有足够的理由来决定不把数据保存到数据库中。如果我们的邮件系统出问题了,没有其他措施的情况下,保存到数据库至少可以用来做备份。