rails - oyaji's Blog
rails fragement cache
之前介紹過 Page Cache,那是一個將所有動態內容都變成靜態頁面,藉由不經過 CGI ,來達到最大覆載度的技巧。但是他的應用性不夠廣泛,僅限於下面的用途
-
流量超級大的頁面
通常是 index page,一個網站可能有幾百個頁面,但是通常 index page 一個頁面就佔了三到五成的流量,而且 index page 通常每個網站都是一個小時,了不起 15 分鐘更新一次,這個時候用 index page 可以大幅度增加網站覆載度。 -
頁面修改的頻率遠遠小於讀取的頻率
例如 Blog 系統,我可能一天寫一篇文章,但是一天讀取我的 Blog 的人可能有幾千個,這個時候為什麼每次讀取頁面的時候都得去 DB 撈資料,組合,Render 出來呢?還不如用靜態頁面儲存,然後每次修改頁面時都重新產生新的 HTML 即可
但是,如果要利用 Page Cache 做到某些部份的功能實在有點麻煩。像是在同一個頁面裡面,有些地方是很少機會修改,希望能夠 Cache 起來,但是有些地方修改頻率超頻繁,不能夠用 Page Cache 來做。這個時候,我們就可以使用 Partial Cache 的方式來加快速度,在Rails 裡面 Partial Cache 叫做 Fragment Cache。
使用方式如下
在 View 裡面
選定要 Cache 的部份,將他用 cache block 包起來
<% cache do %> 我們要 cache 的 content <% end %>如此,當我們用 production mode 的時候,你就會發現會出現 tmp/cache/你的controller/你的action.cache 這個檔案,也就是 cache 的內容。並且 reload 的時候,那個 block 的 content 都是不變的。
理論上 cache 是 work 的,但是如果你去翻 log 時,你會發現 db 一樣會去做相關的 operation。原因是因為我們並沒有告訴 Rails ,Controller 裡面某段 code 是已經 cache 好的東西,不需要去執行。我們都知道 db 通常是最大的 bottleneck ,如果 cache 沒辦法避免 db operation,那這樣的 cache 也僅僅省去了 render 的時間,並沒有太多幫助。
在 Controller 裡面
為了避免這樣,我們要在 controller 裡面指明,當 Fragment Cache work 時,某些 operation 是可以不用執行的。
unless read_fragment :action => '這個action的名字' 跟 cache 有關的 operation 只要有 cache ,就不需要去執行的 code..... end
如此如果已經 cache 過了, Rails 就會避開這段 code 的執行。
要如何 Expire Cache ?
Expire Cache 也是相當的方便,在 controller 裡面使用 expire_fragment 即可。
expire_fragment :action => '這個action的名字'
以上都是最最最基本的 Fragment Cache 的機制,但是如果只知道這些東西,根本做不了太多事情,我們下次來談談比較實用的 Fragment Cache 機制。
上次介紹了如何使用最基本的 Fragment Cache,現在來加上一些小小的小 trick。上次介紹在 View 裡面要這樣使用Fragment Cache
<% cache do %> 我們要 cache 的 content <% end %>
Cache 會出現在 RailsRoot/tmp/cache/你的host/你的controller/你的action.cache 這個檔案裡面。假設上面的 action 是在 lala controller 的 haha action ,那們上面的寫法跟 下面的寫法效果是一樣的
<% cache(:controller => 'lala' , :action => 'haha' ) do %> 我們要 cache 的 content <% end %>
簡單講,就是最上面的 cache do 寫法算是預設值,寫不寫好像沒差,當這個頁面只有這裡需要 cache 時,這樣寫就很方便。不過如果這個 haha action 會依照後面接的 id 的不同顯示不同的內容時,你可以很直覺的加上 :id 這個選項
<% cache(:controller => 'lala' , :action => 'haha' , :id => params[:id] ) do %> 我們要 cache 的 content <% end %>
Cache 會出現在 RailsRoot/tmp/cache/你的host/你的controller/你的action/這個頁面的id.cache 這個檔案裡面。簡單講,又多了一層以 action 為名的目錄,目錄裡面每個 id 都有自己的 cache file。這樣的作法可以簡單依照變數的不同區分 cache file 。
不過?
但是老實說,到目前為止,這個 fragment cache 用途還是不大。我們一個 Action 只能使用一個 Partial Cache,那如果同一個頁面有兩個以上的地方要 cache 呢?Partial Cache 的概念就是一個網站有很多個 block 可以被許多頁面共用。假設某個頁面有兩個部份要 cache ,一個是 RSS ,另外一個是 Info 這個地方。那我們依照 Rails 聖經本的方法,使用 part 變數供我們使用
<% cache(:controller => 'lala' , :action => 'haha' , :id => params[:id] , :part => 'rss' ) do %> 我們要 cache 的 rss content <% end %> <% cache(:controller => 'lala' , :action => 'haha' , :id => params[:id] , :part => 'info' ) do %> 我們要 cache 的 info content <% end %>
如此,相關的 Cache 會產生在 RailsRoot/tmp/cache/你的host/你的controller/你的action/這個頁面的id.part=rss.cache 還有RailsRoot/tmp/cache/你的host/你的controller/你的action/這個頁面的id.part=info.cache 這兩個 file 裡面。仔細看就知道,他的命名機制就是 id. part=part_name.cache 這樣的寫法。很簡單吧。
到現在 Partial Cache 才從還好變成好用的階段。read_fragment 或是 exprie_fragment 都是使用
:controller => 'lala' , :action => 'haha' , :id => params[:id] , :part => 'info'
這樣的命名機制來操作,不難吧。
還有嗎?
到現在為止,我還沒看過 :part 還不夠的情況,不過如果真的覺得用 controller , action , id , part 都不夠區分你的 cache ,還有最後一招
<% cache(:controller => 'lala' , :action => 'haha' , :id => params[:id] , :part => 'rss' , :part2 => 'rss' , :part3 => 'rss' .... ) do %> 我們要 cache 的 rss content <% end %>
簡單講,其實 :part 只是聖經本這樣寫,所以在 Rails Fragment Cache 機制裡,id 以後的 fragment 命名方式不限制,而且數量不限制,只要你不嫌打字太累,你大可以用幾百層去命名你的 cache 。出來的 cache file 命名規則是這樣 ailsRoot/tmp/cache/你的host/你的controller/你的action/這個頁面的id.part=rss&part2=rss&part3=rss.cache。總之隨便你怎麼命名 :p
后续看到的文章:
×××××××××××××××××
I am using Devise as authenticating solution in Rails and I have a cached fragment :recent_users.
I want this fragment to expire when a new user is registered, changed or removed, so I put in my(manually created) users_controller.rb
class UsersController < ApplicationController cache_sweeper :user_sweeper, :only => [:create, :update, :destroy] ...
But my fragment does not expire when new creates or changes.
My user_sweeper contains basic prescriptions
class UserSweeper < ActionController::Caching::Sweeper observe User def after_save(user) expire_cache(user) end def after_destroy(user) expire_cache(user) end private def expire_cache(user) expire_fragment :recent_users end end
What am I doing wrong?
Problem solved!
I followed this steps and everything works:
$ mkdir app/controllers/users $ touch app/controllers/users/registrations_controller.rb
In registrations_controller.rb
class Users::RegistrationsController < Devise::RegistrationsController cache_sweeper :user_sweeper, :only => [:create, :update, :destroy] end
Problem was that Registrations in Devise is a separate controller.
rails model associations
class Student < ActiveRecord::Base has_many :calls has_one :call end class Call < ActiveRecord::Base belongs_to :student end
@students = Student.all(:include => [:call])
#include eql left outer join @students = Student.all(:select => "students.id, students.login, students.called_times, calls.updated_at as last_called_at", :joins => "left outer join calls on students.id = calls.student_id")
rails cache 学习
在Rails中做缓存是简单的,要开启cache的话
config.action_controller.perform_caching = true
默认情况下只有production是true,其他ENV都是false。
Rails中存储cache的方式多了去了,现在用的最多的应该是memcache吧。rails的guildes中有句Page caches are always stored on disk,貌似是所有的page cache都用filestore?在environment.rb中指定使用的store模式
ActionController::Base.cache_store = :file_store, "/path/to/cache/dir"
只要上面两句话都配置好了,下面就简单的在需要cache的地方加几条语句就成
1、Fragment cache
就是缓存一个片段,将需要缓存的内容放入一个block中即可
<% cache do %> something to cache <% end %>
如果要expire一个片段可以用expire_fragment方法,
expire_fragment(:controller => 'articles', :action => 'list')
2、Page cache和Action cache
class Article < AR::Base before_filter :authenticate, :only => [:edit, :create] caches_page :index caches_action :edit def index;end def edit;end def create expire_page :action => 'index' expire_action :action => 'edit' end end
貌似caches_action和caches_page的区别也就只是action需要request经过一次rails stack,那也就能给要cache的action加点filter了;而page的话就完全不会经过rails了,直接就访问静态缓存的文件去了。。。
3、使用Sweeper管理cache
Swepper就是一个Observer,会在监视到一个特定的Model发生某些特定的callback时执行特定的活动,比如expire缓存
class ArticleSweeper < AC::Caching::Sweeper observe Article def after_create expire_page :controller => 'articles', :action => 'index' expire_action :controller =>' articles', :action => 'edit' expire_fragment :controller => 'articles', :action => 'list' end end
4、浏览器端的缓存
通过stale?方法来判断Headers中的etag和last_modifiled是否与server上的一致
def show @article = Article.find(params[:id]) if stale?(:etag => @article, :last_modifiled=>@article.updated_at.utc) respond_to do |format| end end end
然后就是memcache相关的了,有时间再记录
cache and sweeper in rails
来自IBM 的文章:
真实世界中的 Rails:
rails中的缓存 高级页面缓存 优化ActiveRecord
这个文章超好,尤其是第一篇**************
×××××××××××××××××
class ProductsController < ActionController caches_page :index def index end end
class ProductsController < ActionController caches_page :index def index; end def create expire_page :action => :index end end
class ProductsController < ActionController before_filter :authenticate, :only => [ :edit, :create ] caches_page :index caches_action :edit def index; end def create expire_page :action => :index expire_action :action => :edit end def edit; end end
<% Order.find_recent.each do |o| %> <%= o.buyer.name %> bought <% o.product.name %> <% end %> <% cache do %> All available products: <% Product.find(:all).each do |p| %> <%= link_to p.name, product_url(p) %> <% end %> <% end %>
<% cache(:action => 'recent', :action_suffix => 'all_prods') do %> All available products: <% Product.find(:all).each do |p| %> <%= link_to p.name, product_url(p) %> <% end %> <% end %>
class StoreSweeper < ActionController::Caching::Sweeper # This sweeper is going to keep an eye on the Product model observe Product # If our sweeper detects that a Product was created call this def after_create(product) expire_cache_for(product) end # If our sweeper detects that a Product was updated call this def after_update(product) expire_cache_for(product) end # If our sweeper detects that a Product was deleted call this def after_destroy(product) expire_cache_for(product) end private def expire_cache_for(record) # Expire the list page now that we added a new product expire_page(:controller => '#{record}', :action => 'list') # Expire a fragment expire_fragment(:controller => '#{record}', :action => 'recent', :action_suffix => 'all_products') end end
class ProductsController < ActionController before_filter :authenticate, :only => [ :edit, :create ] caches_page :list caches_action :edit cache_sweeper :store_sweeper, :only => [ :create ] def list; end def create expire_page :action => :list expire_action :action => :edit end def edit; end end
class ProductsController < ActionController before_filter :authenticate, :only => [ :edit, :create ] caches_page :list caches_action :edit cache_sweeper :store_sweeper, :only => [ :create ] def list # Run a find query Product.find(:all) ... # Run the same query again Product.find(:all) end def create expire_page :action => :list expire_action :action => :edit end def edit; end end
rails cache managment
Rails 自身提供四种缓存方式,即 Page Cache, Action Cache,Fragment Cache 和 ActiveRecord Cache 这三种缓存。Page Cache 是最高效的缓存机制,他把整个页面以静态页面 HTML 的形式进行缓存,这对于不会经常发生变化的页面是非常有效的。Action Cache 是对某个 action 进行缓存,与 Page Cache 的区别在于:HTTP 请求会经过 Rails 应用服务器,直到所有的 before filters 都被处理,这种缓存就能处理 Page Cache 无法处理的如需要登录验证的页面,可以所验证的步骤加入 before filter 中,Fragment Cache 则为了缓存页面中的某一部分,同一个页面中的不同部分还可以采用不同的过期策略。对于 Rails 本身的缓存机制,我们可以写 sweeper 进行过期和清除的处理。ActiveRecord Cache 则是较新版本 Rails 中新推出的对 ActiveRecord 的缓存机制,使用 SQL 查询缓存,对于同一 action 里面同一 SQL 语句的数据库操作会使用缓存。
Rails 的缓存机制能非常有效的提升网站性能,Rails 默认是将缓存存在于文件系统中,这并不是适合生产环境下的存储方式,文件 IO 的效率有限,Rails 还支持在同一进程的内存中保存 Cache,但如果有多个 Rails application,它们之间不能共享缓存。我们这里推荐的是以 MemCached 的方式进行存储,这也是目前是流行的缓存存储方式。
Memcached 是由 Danga Interactive 开发,用于提升 LiveJournal.com 访问速度的。LiveJournal.com 每秒有几千次动态页面访问量,用户 700 万。Memcached 是一个具有极高性能的分布式内存对象缓存系统 , 基于一个存储键 / 值对的哈希表。其守护进程(daemon)是用 C 写的 , Memcached 将数据库负载大幅度降低,更好的分配资源,更快速访问。可以用各种其它语言去实现客户端。上文的介绍中已经安装了 Rails 的 Memcached 客户端,因为我们只需要在 Rails 应用程序中做如下配置:
config.cache_store = :mem_cache_store, 'localhost:11211'
|
便可以进行使用 MemCached 进行缓存数据。除了 Rails 本身的缓存机制,我们还直接用 Rails.cache 操作 Memcached 进行数据缓存,如,我们读取所有 blog 的数量,可以如下使用缓存:
blogs_count = Rails.cache.fetch("blogs_count") do
Blog.count
end
|
Rails 自身的 ActiveRecord 作用有限,只适用同一个 action 中的 SQL 查询语句进行缓存,我们需要一个更强大的 ActiveRecord 缓存,而 cache-money 更是为了解决如此问题而推出的。当 twitter 网站变得越来越稳定,逐渐摆脱被人拿来作为"Rails 无法扩展的"典型例子的阴影时,人们便期待 twitter 开发团队能向 Rails 社区有更多的贡献,cache-money 便是在 Starling 之后 twitter 团队贡献出来的另一个插件。cache-money 和 Hibernate 的二级缓存类似,是一个读写式(write-through)缓存。在 ActiveRecord 对象更新的时候不是将缓存中的数据清除,而是直接将更新的内容写入到缓存中去。
cache-money 有许多很棒的特性,如:缓存自动清除机制 ( 利用 after_save/after_destroy) ;支持事务,由于 Rails 的 Active Record 没有提供 after_commit 机制,目前常见的缓存插件在高并发下会出现缓存更新竞争冲突,而这个特性对于解决这个问题会很有帮助,可以通过 gem 来安装 cache-money:
gem sources -a http://gems.github.com
sudo gem install nkallen-cache-money
require 'cache_money'
|
production:
ttl: 604800
namespace: ...
sessions: false
debug: false
servers: localhost:11211
development:
....
|
config = YAML.load(IO.read(File.join(Rails_ROOT, "config", "Memcached.yml")))[Rails_ENV]
$memcache = MemCache.new(config)
$memcache.servers = config['servers']
$local = Cash::Local.new($memcache)
$lock = Cash::Lock.new($memcache)
$cache = Cash::Transactional.new($local, $lock)
class ActiveRecord::Base
is_cached :repository => $cache
end
|
使用 cache-money 非常方便,不需要额外的操作,只需要在你的 Model 里面进行简单的配置,如:
class User < ActiveRecord::Base
index :name
end
class Post < ActiveRecord::Base
index [:title, :author]
end
class Article < ActiveRecord::Base
version 7
index ...
end
|
然后便可以跟以前一样使用 Rails ActiveRecord 各种方法以及事务操作。如果你改变了数据库的表结构,你可以改变 Model 的版本号来使以前的缓存失效,而不需要重启 Memcached 服务器。
****************************
选用 Session 容器
Rails 提供了几个内建的 Session 容器。在所有我分析过的应用程序里,要么使用了将 Session 信息储存在你文件系统上独立文件的 PStore,要么用了和数据库打交道的 ActiveRecordStore。这两个方案都不甚理想,特别是拖累了缓存 Action 的页面(action cached pages)。这里提供两个好用得多的备选方案供大家参考:SQLSessionStore 和 MemCacheStore。
SQLSessionStore 通过以下手段避免了 ActiveRecordStore 相关的额外性能开支:
- 避免使用事务(对于 SQLSessionStore 的正确操作,它们并不是必要的)
- 撤销向数据库更新“created_at”和“updated_at”的操作
如果使用 MySQL,你应当保证使用 MyISAM 表来存储 Session 数据,因为它要比 InnoDB 表快很多,并且它不会强制你使用事务处理。前不久我又为 SQLSessionStore 增加了 Postgres 支持,不过,用于 Session 存储 Postgres 看起来要比 MySQL 慢得多。因此,如果你打算使用基于数据库的 Session 存储,我推荐为 Session 表安装 MySQL 数据库(我想不出一个基于 session id 的需要连接的更好用例(use case))。
MemCacheStore 要比 SQLSessionStore快得更多。我的测评结果显示,对于缓存了 Action 的页面它能够带来了 30% 的速度提升 。你得先安装 Eric Hodel 的 memcache client ,并在 environment.rb 中做相应配置之后才能正常使用。注意:Ruby-Memcache 还是不要去试的好(实在、实在慢得让人难以忍受)。
在我自己的项目中,我更倾向于使用基于数据库的 Session 存储,原因是可以使用 Rails 命令行或者数据库软件包提供的管理工具进行简单得管理。对于 MemCacheStore 你就得自己为它编写脚本了。另一方面,对于高访问量网站来说,内存缓存方式的扩展性更好一些,并且它随支持 Session 超时(session expiry)的 Rails 一起提供。
todo list
Comparing Open Source Reporting Tools for Use in the Enterprise
http://olex.openlogic.com/wazi/2008/open-source-reporting-tool-comparison-for-the-enterprise/
How to Integrate JasperReports with Ruby on Rails
http://wiki.rubyonrails.org/rails/pages/howtointegratejasperreports
SED&AWK学习资料
rails resource汇总贴
from http://wiki.jameswu.me/index.php/Ruby
一般性站点
英文资源
- Rails现实中的应用系统
- 很有用,至少可以了解到有哪些站点用到了Rails
- RoR官方站
- 既然是官方站点,必然是要经常看看的了
- RoR官方教程
- 相当不错的文档,后面还是需要多多学习
- Ruby Enterprise Edition
- RoR运行在这个平台上,效率会有很大的提高。
- RubyGems
- Wiki上的RoR
- RoR API文档
- RoR的23个应用程序
- RubyInside
- Rails Magazine
- Rails插件
- 官方的插件平台,可以搜索需要的插件
- RubyInstaller
- 在Windows上使用Ruby,那么安装这个是最为方便的
工具
Html/XML
- Nokogiri
- 一个据说比Hpricot快7倍的库
正则表达式
- Ruby正则表达式
- 这是一个在线的正则表达式分析工具,相当不错
- Ruby Expression
- Ruby表达式收集,蛮多的
单元测试
- RSpec
- 这算是新一代的测试框架了,超越了JUnit
中文资源
编辑器相关
- Rails开发工具之Vim
- 看起来不错,有很多人跟帖,里面有配置好的配置文件
- 还是得说说vim的强大
Rails
主机托管
- Rails Machine
- SoftLayer
- 看起来更加专业的一个Rails托管主机商,应该比RailsMachine更加强大。因为[1] 英文版版 里面就是从Railsmachine迁移过去的
- Capistrano
- Rails部署的必然选择,采用这个可以大幅度降低部署工作量
有用链接
- {|ihower.tw| blog }
- 台湾的,下面的链接也是这个人写的。这里面关于Rails3的文章很多
- Ruby on Rails 實戰手冊
- 台湾的一份Rails实战手册,相当不凑
- http://blog.xdite.net/
- 另外一个台湾的Rails博客
- Rails Cheat Sheet
- 所有关于Rails的常用命令都将搜罗进来
- Capistrano
- 自动化部署工具
- heroku
- Rails部署平台
Rails方案
图表类
- Rails 图表解决方案汇总 -- 总结得比较全面,基本上已经满足需求
JQuery
rails和jquery ajax的结合
REST on Rails指南5: respond_to
通过上一讲,我们已经对REST on Rails的基本框架有所了解,但是它是如何实现根据客户端的请求类型来返回不同类型的资源表示的呢?这就是我们这一讲所要讲的,秘密就在于respond_to。
首先让我们来看看我们在上一讲中生成的airports控制器的代码:
class AirportsController < ApplicationController
def index
@airports = Airport.find :all
respond_to do |format|
format.html # do nothing, allow Rails to render index.rhtml
format.js # do nothing, allow Rails to render index.rjs
format.xml { render :xml => @airports.to_xml }
end
end
end
我们以index方法为例,其它方法的实现大同小异。第一行代码很容易理解,获取所有的机场信息,但是接下来的代码就比较费解了,而这也正是REST on Rails的关键所在,那个respond_to是做什么的呢?
我们知道,在HTTP协议中,客户端会在他们的HTTP首部包含一些元信息(meta-information),这些元信息按照“字段:值”的方式来组织,HTTP协议预定义了很多标准字段,其中的一个字段就是“Accept-type“,它代表发送请求的客户端能够支持或者说理解的资源表示类型,如果没有为这个键指定值,服务端会认为客户端能够理解标准的HTML文档,当然,客户端可以为这个字段指定任意的符合MIME规范的类型值,假设客户端设置这个字段为”Accept-Type: text/xml“,则服务端必须返回资源的XML表示。
所以respond_to事实上就是根据HTTP首部的Accept-Type字段来决定向客户端返回那种类型的资源表示,如果不使用respond_to,我们的实现可能会是这个样子:
class AirportsController < ApplicationController
# Pretend that Rails will call our index action,
# and will pass in the value of the Accept-Type header
def index(client_format)
@airports = Airport.find :all
if client_format == “text/html”
# TO DO: render the default template
elsif client_format == “application/javascript”
# TO DO: return some javascript
elsif client_format == “application/xml” || client_format == “text/xml”
# TO DO: return some XML back the client
# … more elsif statements here for each MIME type you want to support
end
end
end
这很丑陋,不是吗?但它却相当直观,我想respond_to的作者可能最初也是这么写的,或者这段代码至少在他的脑海中闪现过,但立刻就被他否定了,因为它实在是太蹩脚了,所以他对这段代码进行了重整,于是有了respond_to。
respond_to do |format|
format.html # do nothing, allow Rails to render index.rhtml
format.js # do nothing, allow Rails to render index.rjs
format.xml { render :xml => @airports.to_xml }
end
但是Block内的代码看起来仍然比较古怪,事实上,如果我们理解了respond_to的设计思想,那么这段代码看起来就非常理所当然了。
respond_to基于这样的思想设计的,你不需要知道客户端的请求到底是那种类型,你只需要告诉Rails你准备支持那些类型的请求,Rails会自动帮你处理剩下的事情。
所以,这里我们告诉Rails,对于HTML和JS类型的请求,采用默认的实现,而对XML则使用我们在Block内提供的实现。
指南到这里就结束了,篇幅有限,我们只能对REST的基本概念和它在Rails中的简单实现做一个基本的介绍,REST on Rails的世界还有更多的东西等着你去探索。
我建议你尝试动手创建一个Rails应用,然后试试scaffold_resource生成器,阅读并试着理解生成的代码,然后尝试修改view和controller,事实上,比你想象的要简单的多,不是吗?
祝你好运同时期待你的反馈!
REST on Rails指南4:路由
通过上一讲我们了解到,RESTful设计的关键就是定义系统中的资源,这一讲我们将学习在Rails中,如何将请求路由到我们的资源,以及我们应该如何来处理它。
不过,有一点需要先说明:REST并不是Rails的一部分,在Rails出现之前,REST的概念已经存在很多年了,并且REST的应用也并不局限于Web,事实上,它也可以应用到其它各种应用软件的开发中。
资源就是控制器
在我们正式开始之前,我们需要首先明确,在Rails中,资源和model并不总是一对一的关系,有时资源仅仅只是你应用逻辑中的一个实体的抽象,并不需要 映射到你的数据库。但资源跟控制器总是一对一的,也就是每个资源都必须有一个与它相对应的控制器,并且你需要重新理解控制器,现在控制器只是REST接口 的具体实现,它的全部作用就是根据客户的请求返回资源的某种表示(HTML,XML等)。
所以,就像第2章讲的,我们不在需要去设计那无穷尽的API了,现在我们的控制器只需要定义7个方法:
- show,处理针对单个资源的GET请求
- create,处理POST请求,并将创建一个新资源
- update,处理PUT请求,并更新指定的资源
- destroy,处理DELETE请求,销毁一个资源
- index,处理针对多个资源的GET请求
- new,GET请求,返回一个用于创建资源的表单,
- edit,GET请求,返回一个用于更新资源的表单
Rails 会帮助我们将用户的请求路由到某个合适的方法,当然,你并不需要实现这全部的7个方法,如果你的系统不允许用户创建和修改资源,那么你只需要实现 index和show方法就可以了。
不过更有可能的一种情况是你觉得这7个方法根本不够,你当然可以选择向控制器添加新的方法,但这其实是因为你的设计遗漏了一些资源,因为我建议,在你向控制器添加新方法之前,最好先重新考虑下你的设计。
方法已经定义好了,下一步的任务就是将用户的请求路由到指定的方法,在router.rb中,你可能会看到这样的路由:
map.connect '/airports/:action/:id', :controller = 'airports'
这条语句将映射/airports/open/45到airports控制器的open方法,你可以通过params[:id]获取URL中的参数45。但是REST路由有些特殊,它需要同时考虑URL和请求的类型,因此同样是发往/airports/1的请求,如果是GET请求,它需要被路由到airports的show方法,而DELETE请求则需要被路由到DELETE方法。
不过幸运的是,从Rails1.2开始,我们不再需要通过map.connect来手动的配置REST路由,map.resources会帮我们搞定一切:
map.resources 'airports'
这句话将创建如下的路由规则:
- 针对/airports/ 的POST请求将被路由到create方法
- 针对/airports/1 的GET请求将被路由到show方法
- 针对/airports/1的PUT请求被路由到update方法
- 针对/airports/1 的DELETE请求被路由到destroy方法
- 针对/airports/ 的GET请求被路由到index方法
- 针对/airports/new 的GET请求被路由到new方法
- 针对/airports/1;edit 的GET请求被路由到edit方法
注意:最后一条逗号分隔的URL看起来很丑陋,但它们在Rails1.2.3中是合法的,不过不用苦恼,它们将在Rails2.0中被去除
现在我们已经完成了URL的路由,下面我们需要做的就是实现这些方法:
不过先别着急着码代码,从Rails1.2开始,我们有了一个新的生成器(generator):scaffold_resource,使用它我们可以很轻松的生成一个符合REST规范的Rails框架,它包含:
- 资源所对应的model
- 资源的migration文件
- 资源所对应的控制器,控制器已经包含了REST所需的7个方法的实现
- 这7个方法所对应的RHTML文件
- 一条映射用户请求的路由
让我们仍然从第三讲的例子开始,首先创建一个新程序,然后为它添加一个airport资源:
D:\study>rails REST
D:\study>cd REST
D:\study\REST>ruby script/generate scaffold_resource airport name:string designator:string
修改database.yml文件,设置好你的数据库链接,然后执行:
D:\study\REST>rake db:migrate
D:\study\REST>ruby script/server
现在定位你的浏览器到http://localhost:3000/airports/new,你应该已经可以创建一个新机场了,是不是很神奇?现在,让我们来看看airports_controller.rb,所有的东西都在那了。
你应该会在控制器代码中看到一些奇怪的respond_to块,这正是我们整个REST实现的关键所在,我们将在下一讲详细探讨respond_to的细节。
EST on Rails指南3: RESTful Design
通过上一讲,我们明白了为什么Web需要按照REST的方式来设计,而不是传统的面向对象编程的RPC方式,这一讲我们将通过一个实例来演示如何进行REST方式的Web设计,也就是让我们的设计变的RESTful。
航空公司的需求
我们假设你为一家航空公司工作,你的任务是为他们设计一个航班管理系统,它的功能包括:
- 允许公司员工通过Web前端来输入航班信息。航班信息包括航班的起飞和降落城市,以及起飞时间等。
- 允许客户通过手机查询他的航班信息。
- 允许第三方通过我们提供的API来获取我们的航班信息。
很没有难度,不是吗?如果你是个急性子,你甚至可能都顾不上将你无懈可击的设计转换成UML,就已经在你IDE的编辑框里输入了如下字符:
class FlightSchedule
def CancelFlight
......
但是慢着,在REST的世界里,我们不再需要操心这些,我们需要做的只有一件事情:
定义你的资源!
是的,就这一件,因为REST已经为我们定义好了用于操作这些资源的方法。
在这个例子里,我们首先会想到这几个资源:airports,airplanes,flights。当然可能还会有其它,但就让我们先从这几个开始吧!
我们首先要做的就是为这些资源分配URL,原则只有一个:尽可能的简单明了。
- /airports,通过这个URL可以访问所有的机场资源
- /airplanes,通过这个URL可以访问所有的飞机资源
- /flights,通过这个URL可以访问所有航班资源
还有:
- /airports/pudong,通过这个URL可以获取浦东机场的相关信息
- /airplanes/ZJ3543,通过这个URL可以获取编号为ZJ3543的飞机信息
- /flights/451,通过这个URL咋可以了解到航班451的起飞,降落城市已经起飞时间等信息。
方法已经准备好了
一旦你定义好了你的资源,整个设计也就完成了,因为,REST已经为你准备好了以下四个方法(并且不再需要其它的了):
- GET,获取资源
- POST,创建一个新资源
- PUT,更新已存在的资源
- DELETE,删除资源
通过HTTP调用这些方法
同样的,我们也不需要关心客户端如何来调用我们的方法,浏览器会帮我们搞定一切。
如果你仅仅只是在地址栏敲了个地址,然后按了下回车,浏览器会生成一条HTTP消息,并通过它来调用你输入的URL所代表的资源的GET方法。
如果你填写了一个表单,并点击了提交按钮,那么浏览器会将你填在表单中的信息组装成一条HTTP POST消息,并通过它来调用你想访问资源的POST方法。
但不幸的是,由于HTML的限制,目前你无法通过浏览器来调用资源的PUT和DELETE方法,不过这不重要,GET和POST对我们已经足够了。
好了,我们的基于REST的设计就这么完成了,下一讲,我们将演示如何使用Rest on Rails来快速优美的实现我们的设计。
REST on Rails指南2:无穷尽的API
通过上一讲,我认为你树立了这个概念:即Web其实是一组资源而不是网页的集合(如果你还不这么认为,那请你先返回再次阅读第一讲)。这一讲我们将从另一个侧面来讲解为什么要有REST?
面向对象设计与分析
如果你曾经学习过面向对象程序设计,那么你很可能会这样开始构建你的新程序:
- 首先,你需要定义你的问题域——你的程序要解决什么问题
- 然后,你会定义一个类,这个类的名字一般是名词
- 接着你会为这个类定义一些方法,方法的名字一般是动词
- 最用,通过调用其它类的方法,你的这个类顺利完成了它的使命
这看起来不错,事实上我曾经这么干了好多年,这种名词加动词的编程方法被成为“RPC”(远程过程调用),虽然我不明白那个Remote(远程)是指什么,但RPC的确是构建面向对象软件的一个重要方法,不过这种方式却并不适合Web开发。
让我们回到远古,假设现在是1992年(或者Web出现之前的随便什么日子),假设有这样的三家公司,他们需要开发这样三种应用:书籍贩卖,机票贩卖以及 卫星地图浏览。并且他们都遵照了面向对象的设计思想,同时出于长远考虑,他们都认为总有一天会有第三方的软件需要同他们的系统进行交互,因此,他们都实现 了他们各自的API。
现在,假设你的老板分配给你一个任务:为这三个系统设计一个统一的前端,你会怎么做呢?
我想你首先需要学习这三种完全不同的API,然后为每一个API设计一个UI控件,当用户操作UI控件时,对应的API就会被调用,你可能会通过你学到的一些设计模式知识来简化你的工作量,并使你的代码看起来尽可能酷一些。
无穷尽的API
当然这只是假设,但即使真的如此,在Web时代,你也不需要去学习那些无穷尽的糟糕API,你所要做的就是在你的电脑上安装一个浏览器,不是吗?浏览器对 于你将要访问的网站一无所知,但它却能够准确的返回你想要的,你可以通过它购买音乐,预定机票,甚至从任意远的距离来欣赏你家的屋顶。
这很神奇,不是吗?但是让我们设想一下,如果每个网站都有它们自己的API会是什么样子?如果你想在Amazon买本书,浏览器必须知道如何调用 Amazong.buy(),如果你想查看航班信息,那么浏览器需要知道如何调用UnitedAirlines.CheckFlights(),事实上, 这样通吃所有API的程序永远也不可能被开发出来。
所以这就决定了Web不可能是RPC式的,它只能是REST式的。
以资源为中心的设计
那么REST究竟是什么呢?按照维基百科的解释,REST是指Representational State Transfer。这是什么意思呢,简单的说,就是现在每个名词都不再拥有它们各自独一无二的动词了,在REST的世界里,所有名词拥有的动词都是一样 的,并且数量也很有限。换句话说,也就是所有的资源都提供了一组相同的API,这些API的实质就是允许随便什么客户端:
- 获取资源的某种表示
- 创建一个新资源
- 更新已存在的资源
- 销毁一个资源
等一下!那么究竟上面那个API可以让我“购买一本书”呢?搜索“下周二从纽约飞往洛杉矶的航班”又是哪个API完成的呢?
我们将在下一讲回答这个问题,但是如果你已经改变思维,不再认为”买书“就是一个网页,而是开始思考这其实是某个资源的创建,那么我想你其实应该已经知道答案了。