Ruby on rails 实战圣经:RESTful 应用程序

什么是 RESTful

The first 90%of the code accounts for the first 90% of the development time. The remaining10% of the code accounts for the other 90% of the development time. – TomCargill, 贝尔实验室的面向对象程序专

RESTful路由设计是Rails的独到创新,它使用了REST概念来建立一整组的命名路由(namedroutes)

什么是REST? 表象化状态转变RepresentationalState Transfer,简称REST,是Roy Fielding博士在2000年他的博士论文中提出来的一种软件架构风格。相较于SOAPXML-RPC更为简洁容易使用,也是众多网络服务中最为普遍的API格式,像是AmazonYahoo!Google等提供的API服务均有REST接口。

REST有主要有两个核心精神:1. 使用Resource来当做识别的资源,也就是使用一个URL网址来代表一个Resource 2. 同一个Resource则可以有不同的Representations格式变化。这一章的路由实现了Resource概念,而Representation则是用respond_to方法来实现,我们会在Controller一章介绍到。

关于REST的理论可以参考笔者整理的什么是RESTRESTful?。不过,了解理论并不是在Rails中使用RESTful路由的前提条件,所以大可以跳过不甚理解没关系。我们只要知道它可以带来什么技术上的具体好处,以及如何使用就足够了

RESTful带给Rails最大的好处是:它帮助我们用一种比较标准化的方式来命名跟组织ControllersActions。在没有RESTful之前,我们上一章介绍了典型路由设计方式,也就是一个个指定ControllerAction,虽然十分地简便,但是却没有什么准则。同一个Action让不同的开发者设计,就很可能放在不同的Controller之下,更常见的是让一个Controller放太多不相关的Action,造成单一Controller过于庞大。

RESTful带入Rails路由系统的点子,出自它对应了HTTP动词POSTGETPUTDELETE到数据的新增、读取、更新、删除等四项操作。一旦将HTTP动词考虑进来,如此我们就将原本的路由

·         /events/create

·         /events/show/1

·         /events/update/1

·         /events/destroy/1

变成

·         POST/events对应到Controller中的create action

·         GET/events/1对应到Controller中的show action

·         PUT/events/1对应到Controller中的update action

·         DELETE/events/1对应到Controller中的destroy action

什么是HTTP method?在HTTP 1.1通讯协议中制定了九种动词(Verbs)来跟服务器沟通,分别是HEADGETPOSTPUTDELETETRACEOPTIONSCONNECTPATCH。其中最常见的就是GETPOSTGET用来读取数据,这个动作不应该造成任何数据变更。而POST用于送出数据,这个动作不会被快取。而因为HTML只能送出GET或透过窗体送出POSTRails为了突破这个限制,在POST加上一个隐藏参数_method=PUT_method=DELETE就可以当做PUTDELETE请求了

HTTP GET和其他动词最大的差别在于它被认为是一个纯读取、不会修改任何数据的操作,不像POSTPUTDELETE会修改服务器上的数据。我们一般用浏览器GET网页,可以回上一页或重新整理,但是POST网页要重新整理时,浏览器会提示你是否要在执行一次,就是这个道理

Rails用这套惯例来大大简化了路由设定。那程序该怎么写呢? 我们在config/routes.rb加入以下一行程序:

resources :events
    
    

如此就会自动建立四个命名路由(namedroutes),搭配四个HTTP动词,对应到七个Actions。它的实际作用,就如同以下的设定:

get    '/events'          => "events#index",   :as => "events"
     
     
post   '/events'          => "events#create",  :as => "events"
     
     
get    '/events/:id'      => "events#show",    :as => "event"
     
     
put    '/events/:id'      => "events#update",  :as => "event"
     
     
delete '/events/:id'      => "events#destroy", :as => "event"
     
     
get    '/events/new'      => "events#new",     :as => "new_event"
     
     
get    '/events/:id/edit' => "events#edit",    :as => "edit_event"
    
    

用这张表格会更清楚:

Helper

GET

POST

PUT

DELETE

event_path(@event)

/events/1
show action

 

/events/1
update action

/events/1
destroy action

events_path

/events
index action

/events
create action

   

edit_event_path(@event)

/events/1/edit
edit action

    

new_event_path

/events/new
new action

    

注意到这七个Action方法的名字,Rails是定好的,无法修改。这一套惯例建议你背起来,你可以这样记忆:

·         showneweditupdatedestroy是单数,对单一元素操

·         indexcreate是复数,对群集操

·         event_path(@event)需要参数,根据HTTP动词决定showupdatedestroy

·         events_path毋需参数,根据HTTP动词决定indexcreate

因此,最后我们不写:

link_to event.name, :controller => 'events', :action => :show , :id => event.id
    
    

而改写成:

link_to event.name, event_path(event)
    
    

而且只需记得resources就可以写出URL Helper

[custom route]_event[s]_path( event ), :method => GET | POST | PUT | DELETE
    
    

_path结尾是相对网址,而_url结尾则会加上完整Domain网址。

浏览器支持PUTDELETE吗?Rails其实偷藏了_method参数。HTML规格只定义了GET/POST,所以HTML窗体是没有PUT/DELETE的。但是XmlHttpRequest规格(也就是Ajax用的)有定义GET/POST/PUT/DELETE/HEAD/OPTIONS

修改成一个RESTful版本的CRUD

根据第一节所学到RESTful技巧,接续上一章的CRUD应用程序,来改造成RESTful应用程序,相信各位读者可以从中发现到RESTful所带来的简洁好处。让我们开始动手修改吧:

Step. 1

编辑config/routes.rb,加入一个Resources

resources :events
    
    

请加在上方,routes.rb里面越上面的规则优先权较高。

Step. 2

编辑app/views/events/index.html.erb,修改各个link_to的路径:

<% @events.each do |event| %>
     
     
  <li>
     
     
    <%= link_to event.name, event_path(event) %>
     
     
    <%= link_to 'edit', edit_event_path(event) %>
     
     
    <%= button_to 'delete', event_path(event), :method => :delete %>
     
     
  </li>
     
     
<% end %>
     
     
</ul>
     
     

   
   
    
     
   
   
<%= link_to 'new event', new_event_path %>
    
    

注意到删除的地方,我们多一个参数:method => :delete。非GET的操作,顾及网页亲和力可以把link_to改成用button_tolink_to如果浏览器的JavaScript没开,就会无法送出GET之外的操作。button_to就无此困扰,因为Rails是产生form卷标夹带_method参数。

Step. 3

编辑app/views/events/show.html.erb,修改link_to的路径:

<%= @event.name %>
     
     
<%= simple_format(@event.description) %>
     
     

   
   
    
     
   
   
<p><%= link_to 'back to index', events_path %></p>
    
    
Step. 4

修改app/views/events/new.html.erb的窗体送出位置如下:

<%= form_for @event, :url => events_path do |f| %>
    
    

在本例中,你也可以完全省略:url参数,Rails可以根据@event推算出路由

Step. 5

修改app/views/events/edit.html.erb的窗体送出位置如下:

<%= form_for @event, :url => event_path(@event), :method => :put do |f| %>
    
    

:url:method也可以省略,Rails会根据@event是新建的还是修改来推算出要不要使用PUT

Step. 6

修改app/controllers/events_controller.rb,将create Actiondestroy Action里的redirect_to改成

redirect_to events_url
    
    

update Action中的redirect_to改成

redirect_to event_url(@event)
    
    
Step.7

一旦完成RESTful之后,我们在上一章一开始设定的典型路由就用不到了,编辑config/routes.rb将以下程序注释掉:

# This is a legacy wild controller route that's not recommended for RESTful applications.
     
     
# Note: This route will make all actions in every controller accessible via GET requests.
     
     
# match ':controller(/:action(/:id(.:format)))'
    
    

前两行的注释告诉你,这种典型路由已经不被新的RESTful风格所推荐使用。特别是它会让所有Actions都可以透过GET读取到,例如接收窗体的create Action最好只允许POST请求,但是打开典型路由就会让GET请求也可以作用

常见错误

Unknown action

明明有在config/routes.rb里面定义了resources路由,但是出现以下的Unknown action错误:

Unknown action

排除打错字之外,其原因多半是跟routes.rb里面的定义顺序有关。注意到在routes.rb里面,越上面的路由规则越优先,例如如果你定义成:

Demo::Application.routes.draw do
     
     
    match ':controller(/:action(/:id(.:format)))'
     
     
    resources :events
     
     
end
    
    

那么网址/events/4就会优先比对到:controller/:action而去找4这个Action,这就错了。

Routing Error

这错误通常发生在link_to里,它抱怨找不到适合的路由规则来产生网址:

Routing Error

如果你是用典型路由,那么如以下程序乱给一个不存在的Controller,就会产生一样的错误了:

link_to "foobar", :controller => "No such controller", :action => "blah"
    
    

因为{:controller => "No such controller", :action =>"blah" }比对不出有这个路由规则。但是如果是用RESTful路由呢?那多半是因为参数传错了,例如:

link_to "Show", event_path(@foobar)
    
    

这个@foobar没有定义所以是nilevent_path(@foobar)Rails内部来说等同于{:controller => "events", :action => "show", :id =>nil },这就造成了找不到路由的错误,它必须知道:id才能知道是那一个活动的showAction网址。

使用respond_to

respond_to可以让我们在同一个Action中,支持不同的数据格式,例如XMLJSONAtom等。让我们来实现看看。

Atom是一种基于XML的供稿格式,被设计为RSS的替代品,广泛应用于Blog feed

Step. 1

修改app/controllers/events_controller.rbindex Action加上XMLJSONAtom的支持,其中to_xmlto_jsonActiveRecord内建的方法:

def index
     
     
  @events = Event.page(params[:page]).per(5)
     
     

   
   
    
     
   
   
  respond_to do |format|
     
     
    format.html # index.html.erb
     
     
    format.xml { render :xml => @events.to_xml }
     
     
    format.json { render :json => @events.to_json }
     
     
    format.atom { @feed_title = "My event list" } # index.atom.builder
     
     
  end
     
     
end
    
    

新增app/views/events/index.atom.builder文件,内容如下:

atom_feed do |feed|
     
     
  feed.title( @feed_title )
     
     
  feed.updated( @events.last.created_at )
     
     
  @events.each do |event|
     
     
    feed.entry(event) do |entry|
     
     
      entry.title( event.name )
     
     
      entry.content( event.description, :type => 'html' )
     
     
    end
     
     
  end
     
     
end
    
    

打开浏览器分别浏览看看http://localhost:3000/events.xmlhttp://localhost:3000/events.jsonhttp://localhost:3000/events.atom这几个附档名不同的网址。

Step. 2

修改app/controllers/events_controller.rbshow Action加上XMLJSON的支持,这回我们试试看比较手工的方式,用Builder格式来建构XML,以及手动组Hash再转成JSON字符串:

def show
     
     
  @event = Event.find(params[:id])
     
     
  respond_to do |format|
     
     
    format.html { @page_title = @event.name } # show.html.erb
     
     
    format.xml # show.xml.builder
     
     
    format.json { render :json => { id: @event.id, name: @event.name }.to_json }
     
     
  end
     
     
end
    
    

注意到{ id: @event.id, name: @event.name }Ruby 1.9才支持的语法,使用Ruby 1.8的朋友请改用{ :id => @event.id, :name => @event.name }

编辑app/views/events/show.xml.builder

xml.event do |e|
     
     
  e.name @event.name
     
     
  e.description @event.description
     
     
end
    
    

打开浏览器分别浏览看看http://localhost:3000/events/1.xmlhttp://localhost:3000/events/1.json等网址。

Step. 3

如果想要加上这些格式的超链接,可以在URL Helper中传入:format参数。让我们修改app/views/events/index.html.erb加上不同格式的超链接:

<% @events.each do |event| %>
     
     
  <li>
     
     
    <%= link_to event.name, event_path(event) %>
     
     
    <%= link_to " (XML)", event_path(event, :format => :xml) %>
     
     
    <%= link_to " (JSON)", event_path(event, :format => :json) %>
     
     
    <%= link_to 'edit', edit_event_path(event) %>
     
     
    <%= button_to 'delete', event_path(event), :method => :delete %>
     
     
  </li>
     
     
<% end %>
     
     
</ul>
     
     

   
   
    
     
   
   
<%= link_to 'new event', new_event_path %>
     
     
<%= link_to "Atom feed", events_path(:format => :atom) %>
    
    

行数统计

到目前为止,总共写了多少程序了呢?Rails提供了一个简单的指令可以知道:

$ bundle exec rake stats
    
    

就会输出这样的表格:

+----------------------+-------+-------+---------+---------+-----+-------+
     
     
| Name                 | Lines |   LOC | Classes | Methods | M/C | LOC/M |
     
     
+----------------------+-------+-------+---------+---------+-----+-------+
     
     
| Controllers          |    86 |    61 |       2 |       7 |   3 |     6 |
     
     
| Helpers              |     4 |     4 |       0 |       0 |   0 |     0 |
     
     
| Models               |     2 |     2 |       1 |       0 |   0 |     0 |
     
     
| Libraries            |     0 |     0 |       0 |       0 |   0 |     0 |
     
     
| Integration tests    |     0 |     0 |       0 |       0 |   0 |     0 |
     
     
| Functional tests     |    49 |    39 |       1 |       0 |   0 |     0 |
     
     
| Unit tests           |    11 |     6 |       2 |       0 |   0 |     0 |
     
     
+----------------------+-------+-------+---------+---------+-----+-------+
     
     
| Total                |   152 |   112 |       6 |       7 |   1 |    14 |
     
     
+----------------------+-------+-------+---------+---------+-----+-------+
     
     
  Code LOC: 67     Test LOC: 45     Code to Test Ratio: 1:0.7
    
    

其中LOC是指不包含空行的行数。

如何除错?

如果是Model中的程序,你可以在命令行下输入rails console,然后在Console中调用看看Model的方法看看正确与否。而除错ControllerViews一个简单的方法是你可以使用debug这个Helper方法,例如在app/views/events/show.html.erb中插入:

<%= debug(@event) %>
    
    

这样就会输出@event这个值的详细内容。不过,更为常见的是使用Logger来记录信息到log/development.log里。

关于Logger

Rails环境中,你可以直接使用logger或是Rails.logger来拿到这个Logger对象,它有几个方法可以调用:

·         logger.debug除错用的讯息,Production环境会忽

·         logger.info值得记录的一般讯

·         logger.warn值得记录的警告讯

·         logger.error错误讯息,但还不到网站无法执行的地

·         logger.fatal严重错误到网站无法执行的讯

例如,你想要观察程序中变量@event的值,你可以插入以下程序到要观察的程序段落之中:

Rails.logger.debug("event: #{@event.inspect}")
    
    

开一个指令窗口执行tail-f log/development.log来观察log文件,接着开浏览器跑实际跑过这段程序,你就会在log/development.log看到除错讯息了。

Production环境中,log/production.log会逐渐长大,可以使用logrotate 定期整理 Rails Log 文件

Rails也可以使用断点的除错方式,请编辑Gemfile打开以下的注释:

# To use debugger (ruby-debug for Ruby 1.8.7+, ruby-debug19 for Ruby 1.9.2+)
     
     
# gem 'ruby-debug'
     
     
gem 'ruby-debug19', :require => 'ruby-debug'
    
    

然后在要设定断点的地方调用debugger方法,你的服务器程序或Console就会在这里停下来让你检查。不过,会必须要用到这招的情形不多就是了。

Debugger screenshot

我们会在测试一章介绍如何撰写测试程序。撰写单元测试可以大大降低除错时间。

更多在线资源

·         GettingStarted with Rails http://guides.rubyonrails.org/getting_started.html

·         DebuggingRails Applications http://guides.rubyonrails.org/debugging_rails_applications.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值