relax 0.0.7 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/relax/service.rb CHANGED
@@ -1,101 +1,32 @@
1
- require 'openssl'
2
- require 'net/https'
3
- require 'uri'
4
- require 'date'
5
- require 'base64'
6
- require 'erb'
7
-
8
1
  module Relax
9
- # Service is the starting point for any REST consumer written with Relax. It
10
- # is responsible for setting up the endpoint for the service, and issuing the
11
- # HTTP requests for each call made.
12
- #
13
- # == Extending Service
14
- #
15
- # When writing consumers, you should start by extending Service by inheriting
16
- # from it and calling its constructor with the endpoint for the service.
17
- #
18
- # === Example
19
- #
20
- # class Service < Relax::Service
21
- # ENDPOINT = 'http://example.com/services/rest/'
22
- #
23
- # def initialize
24
- # super(ENDPOINT)
25
- # end
26
- # end
27
- #
28
- # == Calling a Service
29
- #
30
- # Each call made to the service goes through the #call method of Service,
31
- # which takes in a Request object and a Response class. The Request object is
32
- # used to generate the query string that will be passed to the endpoint. The
33
- # Reponse class is instantiated with the body of the response from the HTTP
34
- # request.
35
- #
36
- # === Example
37
- #
38
- # This example show how to create a barebones call. This module can be then
39
- # included into your Service class.
40
- #
41
- # module Search
42
- # class SearchRequest < Relax::Request
43
- # end
44
- #
45
- # class SearchResponse < Relax::Response
46
- # end
47
- #
48
- # def search(options = {})
49
- # call(SearchRequest.new(options), SearchResponse)
50
- # end
51
- # end
52
- #
53
2
  class Service
54
- attr_reader :endpoint
55
-
56
- # This constructor should be called from your Service with the endpoint URL
57
- # for the REST service.
58
- def initialize(endpoint)
59
- @endpoint = URI::parse(endpoint)
3
+ def initialize(values={})
4
+ @values = values
60
5
  end
61
6
 
62
- protected
63
-
64
- # Calls the service using a query built from the Request object passed in
65
- # as its first parameter. Once the response comes back from the service,
66
- # the body of the response is used to instantiate the response class, and
67
- # this response object is returned.
68
- def call(request, response_class)
69
- request.valid?
70
- uri = @endpoint.clone
71
- uri.query = query(request).to_s
72
- response = request(uri)
73
- puts "Response:\n#{response.body}\n\n" if $DEBUG
74
- response_class.new(response.body)
7
+ def authenticate(*args)
8
+ @credentials = args
75
9
  end
76
10
 
77
- private
78
-
79
- def default_query
80
- Query.new
81
- end
11
+ class << self
12
+ include Contextable
82
13
 
83
- def query(request)
84
- Query.new(default_query.merge(request.to_query))
85
- end
14
+ def endpoint(url, options={}, &block)
15
+ Endpoint.new(self, url, options, &block)
16
+ end
86
17
 
87
- def request(uri)
88
- puts "Request:\n#{uri.to_s}\n\n" if $DEBUG
89
- http = Net::HTTP.new(uri.host, uri.port)
18
+ def register_action(action) # :nodoc:
19
+ @actions ||= {}
90
20
 
91
- if uri.scheme == 'https'
92
- http.use_ssl = true
93
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE
94
- end
21
+ unless @actions[action.name]
22
+ @actions[action.name] = action.name
95
23
 
96
- http.start do |http|
97
- request = Net::HTTP::Get.new("#{uri.path}?#{uri.query}")
98
- http.request(request)
24
+ define_method(action.name) do |*args|
25
+ action.execute(@values, @credentials, *args)
26
+ end
27
+ else
28
+ raise ArgumentError.new("Duplicate action '#{action.name}'.")
29
+ end
99
30
  end
100
31
  end
101
32
  end
data/lib/relax.rb CHANGED
@@ -1,13 +1,18 @@
1
- $:.unshift(File.dirname(__FILE__)) unless $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
1
+ require 'rubygems'
2
2
 
3
- require 'relax/query'
4
- require 'relax/parsers'
5
- require 'relax/request'
6
- require 'relax/response'
7
- require 'relax/service'
8
- require 'relax/symbolic_hash'
3
+ gem 'relief', '~> 0.0.2'
4
+ require 'relief'
9
5
 
10
- module Relax
11
- class MissingParameter < ArgumentError ; end
12
- class UnrecognizedParser < ArgumentError ; end
6
+ gem 'rest-client', '~> 0.9.2'
7
+ require 'restclient'
8
+
9
+ module Relax # :nodoc:
10
+ autoload :Action, 'relax/action'
11
+ autoload :Context, 'relax/context'
12
+ autoload :Contextable, 'relax/contextable'
13
+ autoload :Endpoint, 'relax/endpoint'
14
+ autoload :Instance, 'relax/instance'
15
+ autoload :Parameter, 'relax/parameter'
16
+ autoload :Performer, 'relax/performer'
17
+ autoload :Service, 'relax/service'
13
18
  end
@@ -0,0 +1,69 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'spec_helper')
2
+
3
+ describe Relax::Endpoint do
4
+ it "provides access to the URL" do
5
+ service = Class.new(Relax::Service)
6
+ endpoint = service.endpoint("http://api.example.com/") { }
7
+ endpoint.url.should == "http://api.example.com/"
8
+ end
9
+
10
+ it "allows contextual defaults to be set" do
11
+ service = Class.new(Relax::Service)
12
+ endpoint = service.endpoint("http://api.example.com/") { }
13
+ endpoint.should respond_to(:defaults)
14
+ end
15
+
16
+ describe "actions" do
17
+ it "actions should check for required values for service defaults" do
18
+ service = Class.new(Relax::Service) do
19
+ defaults { parameter :api_key, :required => true }
20
+ endpoint("http://api.example.com/") { action(:fetch) { } }
21
+ end
22
+
23
+ proc {
24
+ service.new.fetch
25
+ }.should raise_error(ArgumentError, /missing.*api_key/i)
26
+ end
27
+
28
+ it "actions should check for required values for endpoint defaults" do
29
+ service = Class.new(Relax::Service) do
30
+ endpoint("http://api.example.com/") do
31
+ defaults { parameter :operation, :required => true }
32
+ action(:fetch) { }
33
+ end
34
+ end
35
+
36
+ proc {
37
+ service.new.fetch
38
+ }.should raise_error(ArgumentError, /missing.*operation/i)
39
+ end
40
+
41
+ it "actions should check for required values for action parameters" do
42
+ service = Class.new(Relax::Service) do
43
+ endpoint("http://api.example.com/") do
44
+ action(:fetch) { parameter :id, :required => true }
45
+ end
46
+ end
47
+
48
+ proc {
49
+ service.new.fetch
50
+ }.should raise_error(ArgumentError, /missing.*id/i)
51
+ end
52
+ end
53
+
54
+ describe ".action" do
55
+ it "is callable from within an Endpoint" do
56
+ service = Class.new(Relax::Service)
57
+ endpoint = service.endpoint("http://api.example.com/") { }
58
+ endpoint.should respond_to(:action)
59
+ end
60
+
61
+ it "defines an instance method by the same name on the Service" do
62
+ service = Class.new(Relax::Service)
63
+ service.new.should_not respond_to(:fetch)
64
+
65
+ service.endpoint("http://api.example.com/") { action :fetch }
66
+ service.new.should respond_to(:fetch)
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,63 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'spec_helper')
2
+
3
+ describe "an example service's" do
4
+ describe "get_photos action" do
5
+ it "includes :get_photos" do
6
+ Flickr.new.should respond_to(:get_photos)
7
+ end
8
+
9
+ it "requires an API key" do
10
+ proc {
11
+ Flickr.new.get_photos
12
+ }.should raise_error(ArgumentError, /missing.*api_key/i)
13
+ end
14
+
15
+ it "requires a user ID" do
16
+ proc {
17
+ Flickr.new(:api_key => 'secret').get_photos
18
+ }.should raise_error(ArgumentError, /missing.*user_id/i)
19
+ end
20
+
21
+ it "parses the response" do
22
+ flickr = Flickr.new(:api_key => 'secret')
23
+ flickr.get_photos(:user_id => '59532755@N00', :per_page => 3).should == {
24
+ :status => 'ok',
25
+ :photos => {
26
+ :total => '7830',
27
+ :photo => [
28
+ { :ispublic => '1', :isfriend => '0', :owner => '59532755@N00', :isfamily => '0', :secret => '2ebe0307e3', :server => '3562', :farm => '4', :id => '3508500178', :title => 'Rich Kilmer'},
29
+ {:ispublic => '1', :isfriend => '0', :owner => '59532755@N00', :isfamily => '0', :secret => '10b217377b', :server => '3593', :farm => '4', :id => '3508500140', :title => 'Women In Rails'},
30
+ {:ispublic => '1', :isfriend => '0', :owner => '59532755@N00', :isfamily => '0', :secret => '83bc8fbf71', :server => '3620', :farm => '4', :id => '3507688713', :title => 'Obie Fernandez'}
31
+ ],
32
+ :per_page => '3',
33
+ :pages => '2610',
34
+ :page => '1'
35
+ }
36
+ }
37
+ end
38
+ end
39
+
40
+ describe "get_user_by_username action" do
41
+ it "includes :get_user_by_username" do
42
+ Flickr.new.should respond_to(:get_user_by_username)
43
+ end
44
+
45
+ it "requires an API key" do
46
+ proc {
47
+ Flickr.new.get_user_by_username
48
+ }.should raise_error(ArgumentError, /missing.*api_key/i)
49
+ end
50
+
51
+ it "parses the response" do
52
+ flickr = Flickr.new(:api_key => 'secret')
53
+ flickr.get_user_by_username(:username => 'duncandavidson').should == {
54
+ :user => {
55
+ :username => 'duncandavidson',
56
+ :nsid => '59532755@N00',
57
+ :id => '59532755@N00'
58
+ },
59
+ :status => 'ok'
60
+ }
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,21 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'spec_helper')
2
+
3
+ describe Relax::Service do
4
+ it "allows contextual defaults to be set" do
5
+ Relax::Service.should respond_to(:defaults)
6
+ end
7
+
8
+ describe ".endpoint" do
9
+ it "is callable from within a Service" do
10
+ Relax::Service.should respond_to(:endpoint)
11
+ end
12
+
13
+ it "creates a new Endpoint" do
14
+ Relax::Endpoint.should_receive(:new)
15
+
16
+ class Service < Relax::Service
17
+ endpoint "http://api.example.com/"
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,78 @@
1
+ class Flickr < Relax::Service
2
+ defaults do
3
+ parameter :api_key, :required => true
4
+ end
5
+
6
+ endpoint "http://api.flickr.com/services/rest" do
7
+ defaults do
8
+ parameter :method, :required => true
9
+ end
10
+
11
+ action :get_photos do
12
+ set :method, "flickr.people.getPublicPhotos"
13
+ parameter :user_id, :required => true
14
+ parameter :safe_search
15
+ parameter :extras
16
+ parameter :per_page
17
+ parameter :page
18
+
19
+ parser :rsp do
20
+ element :status, :attribute => :stat
21
+
22
+ element :photos do
23
+ element :page, :attribute => true
24
+ element :pages, :attribute => true
25
+ element :per_page, :attribute => :perpage
26
+ element :total, :attribute => true
27
+
28
+ elements :photo do
29
+ element :id, :attribute => true
30
+ element :owner, :attribute => true
31
+ element :secret, :attribute => true
32
+ element :server, :attribute => true
33
+ element :farm, :attribute => true
34
+ element :title, :attribute => true
35
+ element :ispublic, :attribute => true
36
+ element :isfriend, :attribute => true
37
+ element :isfamily, :attribute => true
38
+ end
39
+ end
40
+ end
41
+ end
42
+
43
+ action :get_user_by_username do
44
+ set :method, "flickr.people.findByUsername"
45
+ parameter :username, :required => true
46
+
47
+ parser :rsp do
48
+ element :status, :attribute => :stat
49
+
50
+ element :user do
51
+ element :id, :attribute => true
52
+ element :nsid, :attribute => true
53
+ element :username
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+
60
+ FakeWeb.register_uri(:get, 'http://api.flickr.com/services/rest?api_key=secret&method=flickr.people.findByUsername&username=duncandavidson', :string => <<-RESPONSE)
61
+ <?xml version="1.0" encoding="utf-8" ?>
62
+ <rsp stat="ok">
63
+ <user id="59532755@N00" nsid="59532755@N00">
64
+ <username>duncandavidson</username>
65
+ </user>
66
+ </rsp>
67
+ RESPONSE
68
+
69
+ FakeWeb.register_uri(:get, 'http://api.flickr.com/services/rest?user_id=59532755@N00&per_page=3&method=flickr.people.getPublicPhotos&api_key=secret', :string => <<-RESPONSE)
70
+ <?xml version="1.0" encoding="utf-8" ?>
71
+ <rsp stat="ok">
72
+ <photos page="1" pages="2610" perpage="3" total="7830">
73
+ <photo id="3508500178" owner="59532755@N00" secret="2ebe0307e3" server="3562" farm="4" title="Rich Kilmer" ispublic="1" isfriend="0" isfamily="0" />
74
+ <photo id="3508500140" owner="59532755@N00" secret="10b217377b" server="3593" farm="4" title="Women In Rails" ispublic="1" isfriend="0" isfamily="0" />
75
+ <photo id="3507688713" owner="59532755@N00" secret="83bc8fbf71" server="3620" farm="4" title="Obie Fernandez" ispublic="1" isfriend="0" isfamily="0" />
76
+ </photos>
77
+ </rsp>
78
+ RESPONSE
@@ -0,0 +1,12 @@
1
+ $:.push File.join(File.dirname(__FILE__), '..', 'lib')
2
+
3
+ require 'rubygems'
4
+
5
+ gem 'rspec', '~> 1.2.2'
6
+ require 'spec'
7
+
8
+ gem 'fakeweb', '~> 1.2.2'
9
+ require 'fakeweb'
10
+
11
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'relax')
12
+ require File.join(File.dirname(__FILE__), 'services', 'flickr')
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: relax
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.7
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tyler Hunt
@@ -9,18 +9,58 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-04-27 00:00:00 -04:00
12
+ date: 2009-05-07 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
- name: hpricot
16
+ name: rest-client
17
17
  type: :runtime
18
18
  version_requirement:
19
19
  version_requirements: !ruby/object:Gem::Requirement
20
20
  requirements:
21
- - - ">="
21
+ - - ~>
22
22
  - !ruby/object:Gem::Version
23
- version: "0.6"
23
+ version: 0.9.2
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: nokogiri
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: 1.2.3
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: relief
37
+ type: :runtime
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ version: 0.0.3
44
+ version:
45
+ - !ruby/object:Gem::Dependency
46
+ name: jeweler
47
+ type: :development
48
+ version_requirement:
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: 0.11.0
54
+ version:
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ type: :development
58
+ version_requirement:
59
+ version_requirements: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ~>
62
+ - !ruby/object:Gem::Version
63
+ version: 1.2.2
24
64
  version:
25
65
  description:
26
66
  email: tyler@tylerhunt.com
@@ -29,29 +69,32 @@ executables: []
29
69
  extensions: []
30
70
 
31
71
  extra_rdoc_files:
32
- - README
33
72
  - LICENSE
73
+ - README
34
74
  files:
35
- - lib/relax
36
- - lib/relax/parsers
37
- - lib/relax/parsers/base.rb
38
- - lib/relax/parsers/factory.rb
39
- - lib/relax/parsers/hpricot.rb
40
- - lib/relax/parsers/rexml.rb
41
- - lib/relax/parsers.rb
42
- - lib/relax/query.rb
43
- - lib/relax/request.rb
44
- - lib/relax/response.rb
45
- - lib/relax/service.rb
46
- - lib/relax/symbolic_hash.rb
75
+ - LICENSE
76
+ - Rakefile
77
+ - VERSION.yml
47
78
  - lib/relax.rb
79
+ - lib/relax/action.rb
80
+ - lib/relax/context.rb
81
+ - lib/relax/contextable.rb
82
+ - lib/relax/endpoint.rb
83
+ - lib/relax/instance.rb
84
+ - lib/relax/parameter.rb
85
+ - lib/relax/performer.rb
86
+ - lib/relax/service.rb
87
+ - spec/relax/endpoint_spec.rb
88
+ - spec/relax/integration_spec.rb
89
+ - spec/relax/service_spec.rb
90
+ - spec/services/flickr.rb
91
+ - spec/spec_helper.rb
48
92
  - README
49
- - LICENSE
50
93
  has_rdoc: true
51
- homepage: http://tylerhunt.com/
94
+ homepage: http://github.com/tylerhunt/relax
52
95
  post_install_message:
53
- rdoc_options: []
54
-
96
+ rdoc_options:
97
+ - --charset=UTF-8
55
98
  require_paths:
56
99
  - lib
57
100
  required_ruby_version: !ruby/object:Gem::Requirement
@@ -72,12 +115,10 @@ rubyforge_project: relax
72
115
  rubygems_version: 1.3.1
73
116
  signing_key:
74
117
  specification_version: 2
75
- summary: A simple library for creating REST consumers.
118
+ summary: A flexible library for creating web service consumers.
76
119
  test_files:
77
- - spec/parsers/factory_spec.rb
78
- - spec/parsers/hpricot_spec.rb
79
- - spec/parsers/rexml_spec.rb
80
- - spec/query_spec.rb
81
- - spec/request_spec.rb
82
- - spec/response_spec.rb
83
- - spec/symbolic_hash_spec.rb
120
+ - spec/relax/endpoint_spec.rb
121
+ - spec/relax/integration_spec.rb
122
+ - spec/relax/service_spec.rb
123
+ - spec/services/flickr.rb
124
+ - spec/spec_helper.rb
@@ -1,30 +0,0 @@
1
- module Relax
2
- module Parsers
3
- class Base
4
- attr_reader :parent
5
- attr_reader :parameters
6
-
7
- def initialize(raw, parent)
8
- @parent = parent
9
- @parameters = parent.class.instance_variable_get('@parameters')
10
- parse!
11
- end
12
-
13
- def parse!; end
14
-
15
- def root; end
16
- def is?(name); end
17
- def has?(name); end
18
- def element(name); end
19
- def elements(name); end
20
-
21
- def attribute(element, name); end
22
- def value(value); end
23
- def text_value(value); end
24
- def integer_value(value); end
25
- def float_value(value); end
26
- def date_value(value); end
27
- def time_value(value); end
28
- end
29
- end
30
- end
@@ -1,29 +0,0 @@
1
- module Relax
2
- module Parsers
3
- # Manages the Relax::Parsers in the library.
4
- module Factory
5
- class << self
6
- # Returns the parser class which has been registered for the given
7
- # +name+.
8
- def get(name)
9
- @@parsers ||= {}
10
- @@parsers[name] || raise(UnrecognizedParser, "Given parser name not recognized: #{name.inspect}. Expected one of: #{@@parsers.keys.inspect}")
11
- end
12
-
13
- # Registers a new parser with the factory. The +name+ should be unique,
14
- # but if not, it will override the previously defined parser for the
15
- # given +name+.
16
- def register(name, klass)
17
- @@parsers ||= {}
18
- @@parsers[:default] = klass if @@parsers.empty?
19
- @@parsers[name] = klass
20
- end
21
-
22
- # Removes all registered parsers from the factory.
23
- def clear!
24
- @@parsers = {}
25
- end
26
- end
27
- end
28
- end
29
- end