Rails2.x探索一: ActiveResource
最近,准备抽时间研究一下rails2.0,希望能在这里和大家一起分享学习过程。这次先研究一下ActiveResource吧,说起这个,就不得不提提REST 和 rails on REST了。
一 REST
REST全称Representational State Transfer,翻译过来就是对象化状态转变
是Roy Fielding博士在2000年他的博士论文中提出来的一种软件架构风格。
它是一个抽象的概念,是一种为了实现这一互联网的超媒体分布式系统的行动指南。利用任何的技术都可以实现这种理念。而实现这一软件架构最著名的就是HTTP协议。通常我们所说的REST也是REST/HTTP,在实际中往往把REST理解为基于HTTP的REST软件架构。
REST把网络上所有资源进行唯一的定位,并且告诉我们对于资源(包括网络资源)只需要四种行为:创建(Create)、获取(Read)、更新(Update)和销毁(DELETE)就可以完成对其 操作和处理了。而这个原则是源自于我们对于数据库表的数据操作:insert 、select、update和delete。而这正好可以与HTTP协议提供的GET、POST、PUT和DELETE方法相对应起来。
因此,我个人理解的REST就是对现有的资源提供了标准的CRUD访问接口,并利用http协议的提供的访问方式使它能够通过web访问。同时又提供了基于XML等的多种访问格式。
更多REST资料可以参考
http://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm
http://rest.blueoxen.net/cgi-bin/wiki.pl?BenjaminsRESTTutorial
http://ihower.idv.tw/blog/archives/category/rest
二 Rails on REST
Ruby on Rails 1.2 的一個重要特征就是引入了 REST,如果你明白了REST的概念,就可以看看关于REST,Rails为我们提供了些什么。
1)通过在route.rb中的定义 map.resources :users,你就可以通过URL访问对应的接口,
GET: /users => [:action => ‘index’]
GET: /users.xml => [:action => ‘index’, :format => ‘xml’]
GET: /users/1 => [:action => ’show’, :id => 1]
GET: /users/1;edit => [:action => ‘edit’, :id => 1]
GET: /users/1.xml => [:action => ’show’, :id => 1, :format => ‘xml’]
POST: /users => [:action => ‘create’]
PUT: /users/1 => [:action => ‘update’, :id => 1]
DELETE: /users/1 => [:action => ‘destroy’, :id => 1]
这是最简单的,也是Rails提供给你的默认方式。
当然你还可以通过定制,完成自己想要的访问方式。比如说map.resources :users
里添加
:collection => {:sort => :put}, #表示通过sort方法访问所有users,并指定用 HTTP 的put方式 (其他http访问方式有 :get/:post/:put/:delete或:any)
:member => {:deactivate => :delete}, ##通过deactivate方法访问某个user,指定用 HTTP 的delete方式
:new => {:generate => :post}, #通过generate方法new一个user,指定用 HTTP 的post方式
:path_prefix => ‘/teams/:team_id’, #提供的访问前缀,比如使用/team/1/users来访问user列表
:name_prefix => ‘my_’ #在rails产生的 helper 前加prefix,
除此之外,rails还产生了 一些helpers,供view来访问
另外,你还可以使用嵌套的方式访问
如map.resources :users do |user|
user.resources :blogs
end
此时的访问就是/users/1/blogs了
同时rails产生的helps也变为
整理一下Resource的routes,可以总结为:
collection 资源列表、member 某个资源、new 新资源,通过搭配不同 HTTP的请求方式 ,然后与 Controller 里的 action 对应,完成一整套的rails提供的REST访问。
如/users + get 对应 User controlle的index, /users + post 对应 User controlle的new, /user/id + get 对应 User controlle的show 等等。不过这个例子的action是默认的,如果你希望访问到Controller中自己的action上,就参照前面讲的方法,在routes里改写一下咯
2) 说完了routes,就看看rails1.2.x提供的scaffold_resource,大家都知道scaffold,其实scaffold_resource可以简单的认为scaffold+ REST。
通过scaffold_resource,rails还能自动帮你在routes.rb中加入map.resources
还等什么,那就试试吧.
>rails myrestapp
>cd myrestapp
>ruby script\generate scaffole_resource user name:string
具体的细节可以参考命令ruby script\generate scaffole_resource
3) 关于REST,rails更伟大的地方在于你可以使用 respond_to 来轻松处理不同格式的请求
respond_to do |format|
format.html # show.rhtml
format.xml { render :xml => @user.to_xml }
end
这里format只是html和xml,当然也可以是.js | .ics | .rss | .atom | .yaml 或者以后出来的某种格式,呵呵,这个扩展性确实很强。
三 ActiveResource
写了这么多,终于说完了rest,现在可以说一下ActiveResource了,在rails2.x中加入了ActiveResource,如果你理解了REST,就一定知道REST中,把可访问的东东都定义为Resource,rails已经为我们提供了生成的REST接口的方法,通过在route.rb中对map.resource的定义,我们就可以轻松的将我们数据库中的model(暂且叫这个)作为resource,这其实只完成了关键的一步。哪一步,就是完成了server-side 的部分,从软件的角度讲,就是提供了服务的接口,就类似于 web service提供的接口,接口有了,我们就可以通过某种方式访问了,对。如果这时候我们在浏览器的地址栏上输入
xxxx/users,我们就得到了user的列表。
你一定想不到,有了ActiveResouce后,我们竟然可以通过它,像使用本地ActiveRecord一样使用远程的资源(REST提供的),只是此时,不是Database来提供数据,而是ActiveResouce来提供数据了。哈哈,这真是让我大跌眼镜。确实有点不可思议哦。
好了,让我们现在来实践一下.
如果你的rails不是2.x版本,请升级到2.x版本,如果你实在不想升级
呵呵,那你就从rails的svn(http://dev.rubyonrails.org/svn/rails/trunk)上专门下载ActiveResouce吧,这部分就不细说了。
1) 创建提供REST接口的服务端res_serv
>rails res_serv
>cd res_serv
>ruby script\generate scaffold_resource user name:string
运行后,可以通过http://localhost:3000/users访问了
2) 创建提供REST接口的服务端res_client
>rails res_client
添加model/user.rb,内容如下:
class User < ActiveResource::Base
self.site = "http://localhost:3000"
end
3) 好了,精彩的开始了:
执行 ruby script\console
====find======
敲入User.find 1 。
发现服务器端的控制台显示请求:
127.0.0.1 - - [10/Jan/2008:15:00:06 China Standard Time] "GET /users/1.xml HTT
- -> /users/1.xml
客户端返回结果:#<User:0x3775cb0 @prefix_options={}, @attributes={"name"=>"hehe", "id"=>1}>, 看,已经取到user对象了。
====save=====
u = User.new(:name => 'hello')
u.save
服务器端的控制台显示请求:
127.0.0.1 - - [10/Jan/2008:15:00:31 China Standard Time] "POST /users.xml HTTP/1.1" 201 1
- -> /users.xml
客户端返回结果:true,表示成功,可以看看是否在服务器端存进去了,呵呵
====destroy====
u.destroy
服务器端的控制台显示请求:
127.0.0.1 - - [10/Jan/2008:15:02:27 China Standard Time] "DELETE /users/5.xml HTTP/1.1" 200
- -> /users/5.xml
客户端返回结果: #<Net::HTTPOK 200 OK readbody=true>
====list=======
User.find(:all)
服务器端的控制台显示请求:
127.0.0.1 - - [10/Jan/2008:15:03:02 China Standard Time] "GET /users.xml HTTP/1.1" 200 270
- -> /users.xml
客户端返回结果:NoMethodError: undefined method `collect!' for #<Hash:0x36e41e8>
hoho,出错了,让我们换种方式
在服务器端加句话:
map.resources :users, :collection => { :index => :get }
再在客户端这样调用:
User.get(:index)
服务器端的控制台显示请求:
127.0.0.1 - - [10/Jan/2008:15:03:40 China Standard Time] "GET /users/index.xml HTTP/1.1" 200 27
- -> /users/index.xml
返回结果:{"user"=>[{"name"=>"111", "id"=>1}, {"name"=>"222", "id"=>2}, {"name"=>"333", "id"=>3}]}
呵呵,可以了,这个偶需要再继续研究一下。
最后让我们看看源码
def find(*arguments)
scope = arguments.slice!(0)
ptions = arguments.slice!(0) || {}
case scope
when :all then find_every(options)
when :first then find_every(options).first
when :one then find_one(options)
else find_single(scope, options)
end
end
private
# Find every resource
def find_every(options)
case from = options[:from]
when Symbol
instantiate_collection(get(from, options[:params]))
when String
path = "#{from}#{query_string(options[:params])}"
instantiate_collection(connection.get(path, headers) || [])
else
prefix_options, query_options = split_options(options[:params])
path = collection_path(prefix_options, query_options)
instantiate_collection( (connection.get(path, headers)|| []), prefix_options )
end
end
def instantiate_collection(collection, prefix_options = {})
collection.collect! { |record| instantiate_record(record, prefix_options) }
end
可以看出是通过connection.get(path, headers)访问的
进到Connection:
def get(path, headers = {})
format.decode(request(:get, path, build_request_headers(headers)).body)
end
private
# Makes request to remote service.
def request(method, path, *arguments)
logger.info "#{method.to_s.upcase} #{site.scheme}://#{site.host}:#{site.port}#{path}" if logger
result = nil
time = Benchmark.realtime {result = http.send(method, path, *arguments) }
logger.info "--> #{result.code} #{result.message} (#{result.body ? result.body : 0}b %.2fs)" % time if logger
handle_response(result)
end
而request方法最终是调用了 http的send
def http
http = Net::HTTP.new(@site.host, @site.port)
http.use_ssl = @site.is_a?(URI::HTTPS)
http.verify_mode = OpenSSL::SSL::VERIFY_NONE if http.use_ssl
http
end
总结:
呵呵,一句话, ActiveResource真是个好东西,让我们好好享受一下吧。
一 REST
REST全称Representational State Transfer,翻译过来就是对象化状态转变
是Roy Fielding博士在2000年他的博士论文中提出来的一种软件架构风格。
它是一个抽象的概念,是一种为了实现这一互联网的超媒体分布式系统的行动指南。利用任何的技术都可以实现这种理念。而实现这一软件架构最著名的就是HTTP协议。通常我们所说的REST也是REST/HTTP,在实际中往往把REST理解为基于HTTP的REST软件架构。
REST把网络上所有资源进行唯一的定位,并且告诉我们对于资源(包括网络资源)只需要四种行为:创建(Create)、获取(Read)、更新(Update)和销毁(DELETE)就可以完成对其 操作和处理了。而这个原则是源自于我们对于数据库表的数据操作:insert 、select、update和delete。而这正好可以与HTTP协议提供的GET、POST、PUT和DELETE方法相对应起来。
因此,我个人理解的REST就是对现有的资源提供了标准的CRUD访问接口,并利用http协议的提供的访问方式使它能够通过web访问。同时又提供了基于XML等的多种访问格式。
更多REST资料可以参考
http://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm
http://rest.blueoxen.net/cgi-bin/wiki.pl?BenjaminsRESTTutorial
http://ihower.idv.tw/blog/archives/category/rest
二 Rails on REST
Ruby on Rails 1.2 的一個重要特征就是引入了 REST,如果你明白了REST的概念,就可以看看关于REST,Rails为我们提供了些什么。
1)通过在route.rb中的定义 map.resources :users,你就可以通过URL访问对应的接口,
GET: /users => [:action => ‘index’]
GET: /users.xml => [:action => ‘index’, :format => ‘xml’]
GET: /users/1 => [:action => ’show’, :id => 1]
GET: /users/1;edit => [:action => ‘edit’, :id => 1]
GET: /users/1.xml => [:action => ’show’, :id => 1, :format => ‘xml’]
POST: /users => [:action => ‘create’]
PUT: /users/1 => [:action => ‘update’, :id => 1]
DELETE: /users/1 => [:action => ‘destroy’, :id => 1]
这是最简单的,也是Rails提供给你的默认方式。
当然你还可以通过定制,完成自己想要的访问方式。比如说map.resources :users
里添加
:collection => {:sort => :put}, #表示通过sort方法访问所有users,并指定用 HTTP 的put方式 (其他http访问方式有 :get/:post/:put/:delete或:any)
:member => {:deactivate => :delete}, ##通过deactivate方法访问某个user,指定用 HTTP 的delete方式
:new => {:generate => :post}, #通过generate方法new一个user,指定用 HTTP 的post方式
:path_prefix => ‘/teams/:team_id’, #提供的访问前缀,比如使用/team/1/users来访问user列表
:name_prefix => ‘my_’ #在rails产生的 helper 前加prefix,
除此之外,rails还产生了 一些helpers,供view来访问
| helpers | HTTP | Path | 对应的Action |
|---|---|---|---|
| users_path | GET | /users | index |
| user_path(id) | GET | /users/1 | show |
| new_user_path | GET | /users/new | new |
| users_path | POST | /users | create |
| edit_user_path(id) | GET | /users/1;edit | edit |
| user_path(id) | PUT | /user/1 | update |
| user_path(id) | DELETE | /users/1 | destroy |
另外,你还可以使用嵌套的方式访问
如map.resources :users do |user|
user.resources :blogs
end
此时的访问就是/users/1/blogs了
同时rails产生的helps也变为
| blogs_path(@user) | /users/1/blogs |
| blog_path(@user, @blog ) | /uses/1/blogs/5 |
整理一下Resource的routes,可以总结为:
collection 资源列表、member 某个资源、new 新资源,通过搭配不同 HTTP的请求方式 ,然后与 Controller 里的 action 对应,完成一整套的rails提供的REST访问。
如/users + get 对应 User controlle的index, /users + post 对应 User controlle的new, /user/id + get 对应 User controlle的show 等等。不过这个例子的action是默认的,如果你希望访问到Controller中自己的action上,就参照前面讲的方法,在routes里改写一下咯
2) 说完了routes,就看看rails1.2.x提供的scaffold_resource,大家都知道scaffold,其实scaffold_resource可以简单的认为scaffold+ REST。
通过scaffold_resource,rails还能自动帮你在routes.rb中加入map.resources
还等什么,那就试试吧.
>rails myrestapp
>cd myrestapp
>ruby script\generate scaffole_resource user name:string
具体的细节可以参考命令ruby script\generate scaffole_resource
3) 关于REST,rails更伟大的地方在于你可以使用 respond_to 来轻松处理不同格式的请求
respond_to do |format|
format.html # show.rhtml
format.xml { render :xml => @user.to_xml }
end
这里format只是html和xml,当然也可以是.js | .ics | .rss | .atom | .yaml 或者以后出来的某种格式,呵呵,这个扩展性确实很强。
三 ActiveResource
写了这么多,终于说完了rest,现在可以说一下ActiveResource了,在rails2.x中加入了ActiveResource,如果你理解了REST,就一定知道REST中,把可访问的东东都定义为Resource,rails已经为我们提供了生成的REST接口的方法,通过在route.rb中对map.resource的定义,我们就可以轻松的将我们数据库中的model(暂且叫这个)作为resource,这其实只完成了关键的一步。哪一步,就是完成了server-side 的部分,从软件的角度讲,就是提供了服务的接口,就类似于 web service提供的接口,接口有了,我们就可以通过某种方式访问了,对。如果这时候我们在浏览器的地址栏上输入
xxxx/users,我们就得到了user的列表。
你一定想不到,有了ActiveResouce后,我们竟然可以通过它,像使用本地ActiveRecord一样使用远程的资源(REST提供的),只是此时,不是Database来提供数据,而是ActiveResouce来提供数据了。哈哈,这真是让我大跌眼镜。确实有点不可思议哦。
好了,让我们现在来实践一下.
如果你的rails不是2.x版本,请升级到2.x版本,如果你实在不想升级
呵呵,那你就从rails的svn(http://dev.rubyonrails.org/svn/rails/trunk)上专门下载ActiveResouce吧,这部分就不细说了。
1) 创建提供REST接口的服务端res_serv
>rails res_serv
>cd res_serv
>ruby script\generate scaffold_resource user name:string
运行后,可以通过http://localhost:3000/users访问了
2) 创建提供REST接口的服务端res_client
>rails res_client
添加model/user.rb,内容如下:
class User < ActiveResource::Base
self.site = "http://localhost:3000"
end
3) 好了,精彩的开始了:
执行 ruby script\console
====find======
敲入User.find 1 。
发现服务器端的控制台显示请求:
127.0.0.1 - - [10/Jan/2008:15:00:06 China Standard Time] "GET /users/1.xml HTT
- -> /users/1.xml
客户端返回结果:#<User:0x3775cb0 @prefix_options={}, @attributes={"name"=>"hehe", "id"=>1}>, 看,已经取到user对象了。
====save=====
u = User.new(:name => 'hello')
u.save
服务器端的控制台显示请求:
127.0.0.1 - - [10/Jan/2008:15:00:31 China Standard Time] "POST /users.xml HTTP/1.1" 201 1
- -> /users.xml
客户端返回结果:true,表示成功,可以看看是否在服务器端存进去了,呵呵
====destroy====
u.destroy
服务器端的控制台显示请求:
127.0.0.1 - - [10/Jan/2008:15:02:27 China Standard Time] "DELETE /users/5.xml HTTP/1.1" 200
- -> /users/5.xml
客户端返回结果: #<Net::HTTPOK 200 OK readbody=true>
====list=======
User.find(:all)
服务器端的控制台显示请求:
127.0.0.1 - - [10/Jan/2008:15:03:02 China Standard Time] "GET /users.xml HTTP/1.1" 200 270
- -> /users.xml
客户端返回结果:NoMethodError: undefined method `collect!' for #<Hash:0x36e41e8>
hoho,出错了,让我们换种方式
在服务器端加句话:
map.resources :users, :collection => { :index => :get }
再在客户端这样调用:
User.get(:index)
服务器端的控制台显示请求:
127.0.0.1 - - [10/Jan/2008:15:03:40 China Standard Time] "GET /users/index.xml HTTP/1.1" 200 27
- -> /users/index.xml
返回结果:{"user"=>[{"name"=>"111", "id"=>1}, {"name"=>"222", "id"=>2}, {"name"=>"333", "id"=>3}]}
呵呵,可以了,这个偶需要再继续研究一下。
最后让我们看看源码
def find(*arguments)
scope = arguments.slice!(0)
ptions = arguments.slice!(0) || {}
case scope
when :all then find_every(options)
when :first then find_every(options).first
when :one then find_one(options)
else find_single(scope, options)
end
end
private
# Find every resource
def find_every(options)
case from = options[:from]
when Symbol
instantiate_collection(get(from, options[:params]))
when String
path = "#{from}#{query_string(options[:params])}"
instantiate_collection(connection.get(path, headers) || [])
else
prefix_options, query_options = split_options(options[:params])
path = collection_path(prefix_options, query_options)
instantiate_collection( (connection.get(path, headers)|| []), prefix_options )
end
end
def instantiate_collection(collection, prefix_options = {})
collection.collect! { |record| instantiate_record(record, prefix_options) }
end
可以看出是通过connection.get(path, headers)访问的
进到Connection:
def get(path, headers = {})
format.decode(request(:get, path, build_request_headers(headers)).body)
end
private
# Makes request to remote service.
def request(method, path, *arguments)
logger.info "#{method.to_s.upcase} #{site.scheme}://#{site.host}:#{site.port}#{path}" if logger
result = nil
time = Benchmark.realtime {result = http.send(method, path, *arguments) }
logger.info "--> #{result.code} #{result.message} (#{result.body ? result.body : 0}b %.2fs)" % time if logger
handle_response(result)
end
而request方法最终是调用了 http的send
def http
http = Net::HTTP.new(@site.host, @site.port)
http.use_ssl = @site.is_a?(URI::HTTPS)
http.verify_mode = OpenSSL::SSL::VERIFY_NONE if http.use_ssl
http
end
总结:
呵呵,一句话, ActiveResource真是个好东西,让我们好好享受一下吧。
TAG:

