Rails2.x探索一: ActiveResource

上一篇 / 下一篇  2008-01-09 17:51:40

最近,准备抽时间研究一下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来访问
helpersHTTP
Path对应的Action
users_pathGET/usersindex
user_path(id)GET/users/1show
new_user_pathGET/users/newnew
users_pathPOST/userscreate
edit_user_path(id)GET/users/1;editedit
user_path(id)PUT/user/1update
user_path(id)DELETE/users/1destroy

另外,你还可以使用嵌套的方式访问
如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:

引用 删除 小方   /   2008-11-17 14:50:28
引用 删除 Guest   /   2008-02-02 17:26:21
5
引用 删除 Guest   /   2008-01-16 12:19:35
5
引用 删除 weskycn   /   2008-01-16 00:04:46

很好,继续关注
 

评分:0

我来说两句

显示全部

:loveliness: :handshake :victory: :funk: :time: :kiss: :call: :hug: :lol :'( :Q :L ;P :$ :P :o :@ :D :( :)

日历

« 2009-01-08  
    123
45678910
11121314151617
18192021222324
25262728293031

我的存档

数据统计

  • 访问量: 163
  • 日志数: 2
  • 建立时间: 2008-01-09
  • 更新时间: 2008-01-09

RSS订阅

Open Toolbar