relax 0.0.7 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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