从Rails的svn资源库下载最新的Rails,我们会发现多了个activeresource包
从此Rails核心模块变为: ActiveRecord、ActionPack、ActionWebService、AcionMailer、ActiveResource、ActiveSupport和Railties
这里有几个问题:
[b]一,ActionController里的resources.rb文件与ActiveResource是什么关系?[/b]
当初我看到Edge Rails里多了activeresource这个包时,我第一反应就是ActionController::Resources被ActiveResource替代了吧?
但是我去ActionPack里一看,resources.rb文件依然存在,原来我的判断错了: [color=red]ActionController::Resources != ActiveResource[/color]
ActionController::Resources的工作就是创建了map.resource(s)这个DSL
这个DSL的底层含义是为一组或单个resource(其实是一组或单个对象)做两样事情:
a,生成一组named routes命名
b,为一组http动词(:get/:post/:put/:delete)做一个到一组action(index/new/create/show/edit/update/destroy)的映射
ActionController::Resources是REST风格的Rails实现,它的目的就是让我们做RESTful风格的Rails开发
按ActionController::Resources的指导,我们依照HTTP method规范开发系统,可以对html和xml两种response格式render内容
[color=blue]但我们慢慢发现,ActionController::Resources只为我们提供了REST web service的发布功能,而没有提供消费功能![/color]
ActiveResource的工作看看官方说法则不言而喻了:
[quote]
Active Resource (ARes) connects business objects and Representational State Transfer (REST)
web services. It implements object-relational mapping for REST webservices to provide transparent
proxying capabilities between a client (ActiveResource) and a RESTful service (which is provided by Simply RESTful
routing in ActionController::Resources).
[/quote]
[color=blue]ActiveResource不是ActionController::Resources的替代者,而是REST web service的消费者[/color]
Active Resource是一个proxy的角色,它连接model与service,加上一点胶水,让REST web service的调用简化无比
Active Resource的架构类似于ActiveRecord,等会我们看看代码来加深认识
[b]二,Rails有ActionWebService,还要ActiveResource干什么?[/b]
这个问题是刚才在JavaEye上看到的,其实应该说ActionWebService和ActionController::Resources+ActiveResource才有可比性
最大的区别就是ActionWebService是基于SOAP的,而ActionController::Resources和ActiveResource则是基于轻量的REST、XML的
ActionWebService是Rails对SOAP web service的封装,并提供Client端调用API
不过ActionWebService也支持XML-RPC协议
[b]三,ActiveResource尝鲜[/b]
看代码吧!
[code]
class Person < ActiveResource::Base
self.site = "http://api.people.com:3000/"
end
[/code]
Person类继承ActiveResource::Base,然后调用site=这个类方法,参数为一个URL
就这么简单,现在Person已经映射到如下RESTful resources位置: http://api.people.com:3000/people/
==== Create:
[code]
p = Person.create(:name => 'Robbin')
# POST http://api.people.com:3000/people.xml
# Submit:
# <person><name>Robbin</name></person>
[/code]
==== Find:
[code]
p = Person.find(1)
# GET http://api.people.com:3000/people/1.xml
# Response:
# <person><id>1</id><name>Robbin</name></person>
people = Person.find(:all)
# GET http://api.people.com:3000/people.xml
# Response:
# <people>
# <person><id>1</id><first>Robbin</first></person>
# <person><id>2</id><first>Gigix</first></person>
# </people>
[/code]
==== Update:
[code]
p = Person.find(1)
p.name = 'Hideto'
p.save
# PUT http://api.people.com:3000/people/1.xml
# Submit:
# <person><name>Hideto</name></person>
[/code]
==== Delete:
[code]
p = Person.find(2)
p.destroy
# DELETE http://api.people.com:3000/people/2.xml
[/code]
ActiveResource基于HTTP协议并充分使用HTTP规范里的动词:
* GET requests are used for finding and retrieving resources.
* POST requests are used to create new resources.
* PUT requests are used to update existing resources.
* DELETE requests are used to delete resources.
而Person类就是对http://api.people.com:3000/people/链接上的REST web service的代理,我们可以像使用ActiveRecord对象一样使用ActiveResource对象
[b]四、ActiveResource源码分析一二[/b]
active_resource/base.rb:
[code]
module ActiveResource
class Base
class << self
def site
if defined?(@site)
@site
elsif superclass != Object and superclass.site
superclass.site.dup.freeze
end
end
def site=(site)
@connection = nil
@site = site.nil? ? nil : create_site_uri_from(site)
end
def create(attributes = {})
returning(self.new(attributes)) { |res| res.save }
end
def find(*arguments)
scope = arguments.slice!(0)
options = 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
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 create_site_uri_from(site)
site.is_a?(URI) ? site.dup : URI.parse(site)
end
end
attr_accessor :attributes
def destroy
connection.delete(element_path, self.class.headers)
end
end
end
[/code]
我们看到find、create、destroy都是使用connection来做具体的操作,让我们再看connection.rb:
[code]
module ActiveResource
class Connection
def get(path, headers = {})
xml_from_response(request(:get, path, build_request_headers(headers)))
end
def delete(path, headers = {})
request(:delete, path, build_request_headers(headers))
end
def put(path, body = '', headers = {})
request(:put, path, body.to_s, build_request_headers(headers))
end
def post(path, body = '', headers = {})
request(:post, path, body.to_s, build_request_headers(headers))
end
private
def http
unless @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
end
@http
end
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.length}b %.2fs)" % time if logger
handle_response(result)
end
def handle_response(response)
case response.code.to_i
when 200...400
response
when 404
raise(ResourceNotFound.new(response))
when 405
raise(MethodNotAllowed.new(response))
when 409
raise(ResourceConflict.new(response))
when 422
raise(ResourceInvalid.new(response))
when 401...500
raise(ClientError.new(response))
when 500...600
raise(ServerError.new(response))
else
raise(ConnectionError.new(response, "Unknown response code: #{response.code}"))
end
end
end
end
[/code]
核心代码就是这些,最后只是调用Net::HTTP对象的get/post/put/delete方法,socket连接而已
主要是我们的REST web service host(如http://api.people.com:3000/)已经成功发布web服务,我们访问相应的url即可(如people.xml、people/1.xml)
ActiveResource以我们熟悉的ActiveRecord操作方式来封装和架构我们的REST web service客户端API以简化开发,我们只能说两个字:佩服!
[b]五、ActionController::Resources的一些改动[/b]
[url=http://dev.rubyonrails.org/changeset/6485]Changeset 6485[/url]
源码:
[code]
def map_collection_actions(map, resource)
resource.collection_methods.each do |method, actions|
actions.each do |action|
action_options = action_options_for(action, resource, method)
map.deprecated_named_route("#{action}_#{resource.name_prefix}#{resource.plural}", "#{resource.name_prefix}#{action}_#{resource.plural}", "#{resource.path}/#{action}", action_options)
map.deprecated_named_route("formatted_#{action}_#{resource.name_prefix}#{resource.plural}", "formatted_#{resource.name_prefix}#{action}_#{resource.plural}", "#{resource.path}/#{action}.:format", action_options)
end
end
end
[/code]
“/messages/1;edit”这种url已经替换成“/messages/1/edit”,早该这样了不是吗?
[url=http://dev.rubyonrails.org/ticket/8251]Ticket #8251[/url]
源码例子和上面一样,name_prefix放到path的最前面去了,action名字挪到它后面
从此Rails核心模块变为: ActiveRecord、ActionPack、ActionWebService、AcionMailer、ActiveResource、ActiveSupport和Railties
这里有几个问题:
[b]一,ActionController里的resources.rb文件与ActiveResource是什么关系?[/b]
当初我看到Edge Rails里多了activeresource这个包时,我第一反应就是ActionController::Resources被ActiveResource替代了吧?
但是我去ActionPack里一看,resources.rb文件依然存在,原来我的判断错了: [color=red]ActionController::Resources != ActiveResource[/color]
ActionController::Resources的工作就是创建了map.resource(s)这个DSL
这个DSL的底层含义是为一组或单个resource(其实是一组或单个对象)做两样事情:
a,生成一组named routes命名
b,为一组http动词(:get/:post/:put/:delete)做一个到一组action(index/new/create/show/edit/update/destroy)的映射
ActionController::Resources是REST风格的Rails实现,它的目的就是让我们做RESTful风格的Rails开发
按ActionController::Resources的指导,我们依照HTTP method规范开发系统,可以对html和xml两种response格式render内容
[color=blue]但我们慢慢发现,ActionController::Resources只为我们提供了REST web service的发布功能,而没有提供消费功能![/color]
ActiveResource的工作看看官方说法则不言而喻了:
[quote]
Active Resource (ARes) connects business objects and Representational State Transfer (REST)
web services. It implements object-relational mapping for REST webservices to provide transparent
proxying capabilities between a client (ActiveResource) and a RESTful service (which is provided by Simply RESTful
routing in ActionController::Resources).
[/quote]
[color=blue]ActiveResource不是ActionController::Resources的替代者,而是REST web service的消费者[/color]
Active Resource是一个proxy的角色,它连接model与service,加上一点胶水,让REST web service的调用简化无比
Active Resource的架构类似于ActiveRecord,等会我们看看代码来加深认识
[b]二,Rails有ActionWebService,还要ActiveResource干什么?[/b]
这个问题是刚才在JavaEye上看到的,其实应该说ActionWebService和ActionController::Resources+ActiveResource才有可比性
最大的区别就是ActionWebService是基于SOAP的,而ActionController::Resources和ActiveResource则是基于轻量的REST、XML的
ActionWebService是Rails对SOAP web service的封装,并提供Client端调用API
不过ActionWebService也支持XML-RPC协议
[b]三,ActiveResource尝鲜[/b]
看代码吧!
[code]
class Person < ActiveResource::Base
self.site = "http://api.people.com:3000/"
end
[/code]
Person类继承ActiveResource::Base,然后调用site=这个类方法,参数为一个URL
就这么简单,现在Person已经映射到如下RESTful resources位置: http://api.people.com:3000/people/
==== Create:
[code]
p = Person.create(:name => 'Robbin')
# POST http://api.people.com:3000/people.xml
# Submit:
# <person><name>Robbin</name></person>
[/code]
==== Find:
[code]
p = Person.find(1)
# GET http://api.people.com:3000/people/1.xml
# Response:
# <person><id>1</id><name>Robbin</name></person>
people = Person.find(:all)
# GET http://api.people.com:3000/people.xml
# Response:
# <people>
# <person><id>1</id><first>Robbin</first></person>
# <person><id>2</id><first>Gigix</first></person>
# </people>
[/code]
==== Update:
[code]
p = Person.find(1)
p.name = 'Hideto'
p.save
# PUT http://api.people.com:3000/people/1.xml
# Submit:
# <person><name>Hideto</name></person>
[/code]
==== Delete:
[code]
p = Person.find(2)
p.destroy
# DELETE http://api.people.com:3000/people/2.xml
[/code]
ActiveResource基于HTTP协议并充分使用HTTP规范里的动词:
* GET requests are used for finding and retrieving resources.
* POST requests are used to create new resources.
* PUT requests are used to update existing resources.
* DELETE requests are used to delete resources.
而Person类就是对http://api.people.com:3000/people/链接上的REST web service的代理,我们可以像使用ActiveRecord对象一样使用ActiveResource对象
[b]四、ActiveResource源码分析一二[/b]
active_resource/base.rb:
[code]
module ActiveResource
class Base
class << self
def site
if defined?(@site)
@site
elsif superclass != Object and superclass.site
superclass.site.dup.freeze
end
end
def site=(site)
@connection = nil
@site = site.nil? ? nil : create_site_uri_from(site)
end
def create(attributes = {})
returning(self.new(attributes)) { |res| res.save }
end
def find(*arguments)
scope = arguments.slice!(0)
options = 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
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 create_site_uri_from(site)
site.is_a?(URI) ? site.dup : URI.parse(site)
end
end
attr_accessor :attributes
def destroy
connection.delete(element_path, self.class.headers)
end
end
end
[/code]
我们看到find、create、destroy都是使用connection来做具体的操作,让我们再看connection.rb:
[code]
module ActiveResource
class Connection
def get(path, headers = {})
xml_from_response(request(:get, path, build_request_headers(headers)))
end
def delete(path, headers = {})
request(:delete, path, build_request_headers(headers))
end
def put(path, body = '', headers = {})
request(:put, path, body.to_s, build_request_headers(headers))
end
def post(path, body = '', headers = {})
request(:post, path, body.to_s, build_request_headers(headers))
end
private
def http
unless @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
end
@http
end
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.length}b %.2fs)" % time if logger
handle_response(result)
end
def handle_response(response)
case response.code.to_i
when 200...400
response
when 404
raise(ResourceNotFound.new(response))
when 405
raise(MethodNotAllowed.new(response))
when 409
raise(ResourceConflict.new(response))
when 422
raise(ResourceInvalid.new(response))
when 401...500
raise(ClientError.new(response))
when 500...600
raise(ServerError.new(response))
else
raise(ConnectionError.new(response, "Unknown response code: #{response.code}"))
end
end
end
end
[/code]
核心代码就是这些,最后只是调用Net::HTTP对象的get/post/put/delete方法,socket连接而已
主要是我们的REST web service host(如http://api.people.com:3000/)已经成功发布web服务,我们访问相应的url即可(如people.xml、people/1.xml)
ActiveResource以我们熟悉的ActiveRecord操作方式来封装和架构我们的REST web service客户端API以简化开发,我们只能说两个字:佩服!
[b]五、ActionController::Resources的一些改动[/b]
[url=http://dev.rubyonrails.org/changeset/6485]Changeset 6485[/url]
源码:
[code]
def map_collection_actions(map, resource)
resource.collection_methods.each do |method, actions|
actions.each do |action|
action_options = action_options_for(action, resource, method)
map.deprecated_named_route("#{action}_#{resource.name_prefix}#{resource.plural}", "#{resource.name_prefix}#{action}_#{resource.plural}", "#{resource.path}/#{action}", action_options)
map.deprecated_named_route("formatted_#{action}_#{resource.name_prefix}#{resource.plural}", "formatted_#{resource.name_prefix}#{action}_#{resource.plural}", "#{resource.path}/#{action}.:format", action_options)
end
end
end
[/code]
“/messages/1;edit”这种url已经替换成“/messages/1/edit”,早该这样了不是吗?
[url=http://dev.rubyonrails.org/ticket/8251]Ticket #8251[/url]
源码例子和上面一样,name_prefix放到path的最前面去了,action名字挪到它后面