rlivsey-voorhees 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Richard Livsey
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,205 @@
1
+ # Voorhees
2
+
3
+ ## Design goals
4
+
5
+ * Be as fast as possible
6
+ * Be simple, yet configurable
7
+ * Include just what you need
8
+ * Don't stomp on object hierarcy (it's a mixin)
9
+ * Lazy load only the objects you need, when you need them
10
+
11
+ ## Example usage
12
+
13
+ class User
14
+ include Voorhees::Resource
15
+
16
+ json_service :list, :path => "/users/find.json"
17
+
18
+ def messages
19
+ json_request(Message) do |r|
20
+ r.path = "/#{self.id}/messages.json"
21
+ end
22
+ end
23
+ end
24
+
25
+ user = User.get(1)
26
+ user.json_attributes => [:id, :login, :email]
27
+ user.raw_json => {:id => 1, :login => 'test', :email => 'bob@example.com'}
28
+ user.login => 'test'
29
+ user.login = 'new login'
30
+ user.login => 'new login'
31
+
32
+ user.messages => [Message, Message, Message, ...]
33
+
34
+ User.list(:filter => 'xxx') => [User, User, User, ...]
35
+ User.list(:blah => false) => raises ParameterRequiredException
36
+ etc...
37
+
38
+ See /examples/ directory for more.
39
+
40
+ ## A bit more in-depth
41
+
42
+ ### Configuration
43
+
44
+ Setup global configuration for requests with Voorhees::Config
45
+ These can all be overridden on individual requests/services
46
+
47
+ Voorhees::Config.setup do |c|
48
+ c[:base_uri] = "http://api.example.com/json"
49
+ c[:required] = [:something]
50
+ c[:defaults] = {:api_version => 2}
51
+ c[:timeout] = 10
52
+ c[:retries] = 3
53
+ end
54
+
55
+ #### System options
56
+
57
+ * logger: set a logger to use for debug messages, defaults to Logger.new(STDOUT) or RAILS_DEFAULT_LOGGER if it's defined
58
+
59
+ #### Request options
60
+
61
+ * base_uri: Prepend all paths with this, usually the domain of the service
62
+ * defaults: A hash of default parameters
63
+ * hierarchy: Define the class hierarchy for nested data - see below for info
64
+ * http_method: The Net::HTTP method to use. One of Net::HTTP::Get (default), Net::HTTP::Post, Net::HTTP::Put or Net::HTTP::Delete
65
+ * parameters: Hash of data to send along with the request, overrides any defaults
66
+ * path: Path to the service. Can be relative if you have a base_uri set.
67
+ * required: Array of required parameters. Raises a Voorhees::ParameterMissingError if a required parameter is not set.
68
+ * retries: Number of times to retry if it fails to load data from the service
69
+ * timeout: Number of seconds to wait for the service to send data
70
+
71
+ ### Services and Requests
72
+
73
+ There are 3 ways to communicate with the service.
74
+
75
+ #### json_service
76
+
77
+ This sets up a class method
78
+
79
+ class User
80
+ include Voorhees::Resource
81
+ json_service :list, :path => "/users.json"
82
+ end
83
+
84
+ User.list(:page => 3) => [User, User, User, ...]
85
+
86
+ #### json_request
87
+
88
+ This is used in instance methods:
89
+
90
+ class User
91
+ include Voorhees::Resource
92
+
93
+ def friends
94
+ json_request do |r|
95
+ r.path => "/friends.json"
96
+ r.parameters => {:user_id => self.id}
97
+ end
98
+ end
99
+ end
100
+
101
+ User.new.friends(:limit => 2) => [User, User]
102
+
103
+ By default it assumes you're getting items of the same class, you can override this like so:
104
+
105
+ def messages
106
+ json_request(Message) do |r|
107
+ r.path => "/messages.json"
108
+ r.parameters => {:user_id => self.id}
109
+ end
110
+ end
111
+
112
+ User.new.messages => [Message, Message, ...]
113
+
114
+ #### Voorhees::Request
115
+
116
+ Both json_service and json_request create Voorhees::Request objects to do their bidding.
117
+
118
+ If you like you can use this yourself directly.
119
+
120
+ This sets up a request identical to the json_request messages example above:
121
+
122
+ request = Voorhees::Request.new(Message)
123
+ request.path = "/messages.json"
124
+ request.parameters => {:user_id => self.id}
125
+
126
+ To perform the HTTP request (returning a Voorhees::Response object):
127
+
128
+ response = request.perform
129
+
130
+ You can now get at the parsed JSON, or convert them to objects:
131
+
132
+ response.json => [{id: 5, subject: "Test", ... }, ...]
133
+ response.to_objects => [Message, Message, Message, ...]
134
+
135
+ ### Object Hierarchies
136
+
137
+ Say you have a service which responds with a list of users in the following format:
138
+
139
+ curl http://example.com/users.json
140
+
141
+ [{
142
+ "email":"bt@example.com",
143
+ "username":"btables",
144
+ "name":"Bobby Tables",
145
+ "id":1,
146
+ "address":{
147
+ "street":"24 Monkey Close",
148
+ "city":"Somesville",
149
+ "country":"Somewhere",
150
+ "coords":{
151
+ "lat":52.9876,
152
+ "lon":12.3456
153
+ }
154
+ }
155
+ }]
156
+
157
+ You can define a service to consume this as follows:
158
+
159
+ class User
160
+ include Voorhees::Resource
161
+ json_service :list, :path => "http://example.com/users.json"
162
+ end
163
+
164
+ Calling User.list will return a list of User instances.
165
+
166
+ users = User.list
167
+ users[0].name => "bt@example.com"
168
+
169
+ However, what about the address? It just returns as a Hash of parsed JSON:
170
+
171
+ users[0].address => {"street":"24 Monkey Close", "city":... }
172
+
173
+ If you have an Address class you'd like to use, you can tell it like so:
174
+
175
+ json_service :list, :path => "http://example.com/users.json",
176
+ :hierarchy => {:address => Address}
177
+
178
+ You can nest hierarchies to an infinite depth like so:
179
+
180
+ json_service :list, :path => "http://example.com/users.json",
181
+ :hierarchy => {:address => [Address, {:coords => LatLon}]}
182
+
183
+ Instead of the class name, you can also just use a symbol:
184
+
185
+ json_service :list, :path => "http://example.com/users.json",
186
+ :hierarchy => {:address => [:address, {:coords => :lat_lon}]}
187
+
188
+ With that we can now do:
189
+
190
+ users = User.list
191
+ users[0].name => "Bobby Tables"
192
+ users[0].address.country => "Somewhere"
193
+ users[0].address.coords.lat => 52.9876
194
+
195
+ ## Thanks
196
+
197
+ The ideas and design came from discussions when refactoring [LVS::JSONService](http://github.com/LVS/JSONService) the original of which was
198
+ developed by [Andy Jeffries](http://github.com/andyjeffries/) for use at LVS
199
+
200
+ Much discussion with [John Cinnamond](http://github.com/jcinnamond)
201
+ and [Jason Lee](http://github.com/jlsync)
202
+
203
+ ## Copyright
204
+
205
+ Copyright (c) 2009 Richard Livsey. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,48 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "voorhees"
8
+ gem.summary = %Q{Library to consume and interract with JSON services}
9
+ gem.email = "richard@livsey.org"
10
+ gem.homepage = "http://github.com/rlivsey/voorhees"
11
+ gem.authors = ["Richard Livsey"]
12
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
13
+ end
14
+
15
+ rescue LoadError
16
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
17
+ end
18
+
19
+ require 'spec/rake/spectask'
20
+ Spec::Rake::SpecTask.new(:spec) do |spec|
21
+ spec.libs << 'lib' << 'spec'
22
+ spec.spec_files = FileList['spec/**/*_spec.rb']
23
+ end
24
+
25
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
26
+ spec.libs << 'lib' << 'spec'
27
+ spec.pattern = 'spec/**/*_spec.rb'
28
+ spec.rcov = true
29
+ end
30
+
31
+
32
+ task :default => :spec
33
+
34
+ require 'rake/rdoctask'
35
+ Rake::RDocTask.new do |rdoc|
36
+ if File.exist?('VERSION.yml')
37
+ config = YAML.load(File.read('VERSION.yml'))
38
+ version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
39
+ else
40
+ version = ""
41
+ end
42
+
43
+ rdoc.rdoc_dir = 'rdoc'
44
+ rdoc.title = "voorhees #{version}"
45
+ rdoc.rdoc_files.include('README*')
46
+ rdoc.rdoc_files.include('lib/**/*.rb')
47
+ end
48
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,85 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+
4
+ require 'rubygems'
5
+ require 'json' # use whatever JSON gem you want, as long as it supports JSON.parse
6
+ require 'active_support' # used for inflector
7
+ require 'voorhees'
8
+ require 'pp'
9
+
10
+ Voorhees::Config.setup do |c|
11
+ c[:base_uri] = "http://twitter.com"
12
+ end
13
+
14
+ class User
15
+ include Voorhees::Resource
16
+
17
+ json_service :get,
18
+ :path => "/users/show.json",
19
+ :hierarchy => {
20
+ :status => :tweet
21
+ }
22
+
23
+ def friends
24
+ json_request do |r|
25
+ r.path = "/statuses/friends.json"
26
+ r.parameters = {:id => self.screen_name}
27
+ r.hierarchy = {
28
+ :status => :tweet
29
+ }
30
+ end
31
+ end
32
+
33
+ end
34
+
35
+ class Tweet
36
+ include Voorhees::Resource
37
+
38
+ json_service :public_timeline,
39
+ :path => "/statuses/public_timeline.json",
40
+ :hierarchy => {
41
+ :user => User # can be a class
42
+ }
43
+
44
+ json_service :users_timeline,
45
+ :path => "/statuses/user_timeline.json",
46
+ :hierarchy => {
47
+ :user => :user # or a symbol
48
+ }
49
+
50
+ end
51
+
52
+ puts "> tweets = Tweet.public_timeline"
53
+ tweets = Tweet.public_timeline
54
+
55
+ puts "> tweets[0].class: #{tweets[0].class}"
56
+ puts "> tweets[0].text: #{tweets[0].text}"
57
+ puts "> tweets[0].user.name: #{tweets[0].user.name}"
58
+
59
+ puts "\n\n"
60
+ puts "> tweets = Tweet.users_timeline(:id => 'rlivsey', :page => 2)"
61
+ tweets = Tweet.users_timeline(:id => 'rlivsey', :page => 2)
62
+
63
+ puts "> tweets[0].text: #{tweets[0].text}"
64
+ puts "> tweets[0].user.name: #{tweets[0].user.name}"
65
+
66
+ puts "\n\n"
67
+
68
+ puts "> rlivsey = User.get(:id => 'rlivsey')"
69
+ rlivsey = User.get(:id => 'rlivsey')
70
+
71
+ puts "> rlivsey.name: #{rlivsey.name}"
72
+ puts "> rlivsey.location: #{rlivsey.location}"
73
+ puts "> rlivsey.status.text: #{rlivsey.status.text}"
74
+ puts "> rlivsey.status.created_at: #{rlivsey.status.created_at}"
75
+
76
+ puts "\n\n"
77
+
78
+ puts "> friends = rlivsey.friends"
79
+ friends = rlivsey.friends
80
+
81
+ puts "> friends[0].class: #{friends[0].class}"
82
+ puts "> friends[0].name: #{friends[0].name}"
83
+ puts "> friends[0].status.text: #{friends[0].status.text}"
84
+
85
+
data/lib/voorhees.rb ADDED
@@ -0,0 +1,5 @@
1
+ require "voorhees/config"
2
+ require "voorhees/exceptions"
3
+ require "voorhees/request"
4
+ require "voorhees/response"
5
+ require "voorhees/resource"
@@ -0,0 +1,86 @@
1
+ require 'logger'
2
+
3
+ module Voorhees
4
+ module Config
5
+ class << self
6
+
7
+ # the configuration hash itself
8
+ def configuration
9
+ @configuration ||= defaults
10
+ end
11
+
12
+ def defaults
13
+ {
14
+ :logger => defined?(RAILS_DEFAULT_LOGGER) ? RAILS_DEFAULT_LOGGER : Logger.new(STDOUT),
15
+ :timeout => 10,
16
+ :retries => 0,
17
+ :http_method => Net::HTTP::Get
18
+ }
19
+ end
20
+
21
+ def [](key)
22
+ configuration[key]
23
+ end
24
+
25
+ def []=(key, val)
26
+ configuration[key] = val
27
+ end
28
+
29
+ # remove an item from the configuration
30
+ def delete(key)
31
+ configuration.delete(key)
32
+ end
33
+
34
+ # Return the value of the key, or the default if doesn't exist
35
+ #
36
+ # ==== Examples
37
+ #
38
+ # Voorhees::Config.fetch(:monkey, false)
39
+ # => false
40
+ #
41
+ def fetch(key, default)
42
+ configuration.fetch(key, default)
43
+ end
44
+
45
+ def to_hash
46
+ configuration
47
+ end
48
+
49
+ # Yields the configuration.
50
+ #
51
+ # ==== Examples
52
+ # Voorhees::Config.use do |config|
53
+ # config[:debug] = true
54
+ # config.something = false
55
+ # end
56
+ #
57
+ def setup
58
+ yield self
59
+ nil
60
+ end
61
+
62
+ def clear
63
+ @configuration = {}
64
+ end
65
+
66
+ def reset
67
+ @configuration = defaults
68
+ end
69
+
70
+ # allow getting and setting properties via Voorhees::Config.xxx
71
+ #
72
+ # ==== Examples
73
+ # Voorhees::Config.debug
74
+ # Voorhees::Config.debug = false
75
+ #
76
+ def method_missing(method, *args)
77
+ if method.to_s[-1,1] == '='
78
+ configuration[method.to_s.tr('=','').to_sym] = *args
79
+ else
80
+ configuration[method]
81
+ end
82
+ end
83
+
84
+ end
85
+ end
86
+ end