hyperclient 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rvmrc CHANGED
@@ -1 +1 @@
1
- rvm ruby-1.9.2-p180-patched@hyperclient --create
1
+ rvm use --create 1.9.3@hyperclient
data/.travis.yml ADDED
@@ -0,0 +1,2 @@
1
+ rvm:
2
+ - 1.9.3
data/.yardopts ADDED
@@ -0,0 +1,8 @@
1
+ --title "Hyperclient"
2
+ --readme README.md
3
+ --protected
4
+ --private
5
+ --plugin tomdoc
6
+ lib
7
+ -
8
+ [A-Z]*.*
data/Gemfile CHANGED
@@ -1,4 +1,12 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- # Specify your gem's dependencies in hyperclient.gemspec
4
3
  gemspec
4
+
5
+ gem 'rake'
6
+ gem 'guard'
7
+ gem 'guard-minitest'
8
+ gem 'pry'
9
+
10
+ gem 'redcarpet'
11
+ gem 'yard', '~> 0.7.5'
12
+ gem 'yard-tomdoc', git: 'git://github.com/rubyworks/yard-tomdoc'
data/Guardfile ADDED
@@ -0,0 +1,6 @@
1
+ guard 'minitest' do
2
+ watch(%r|^test/(.*)_test\.rb|)
3
+ watch(%r|^lib/(.*)([^/]+)\.rb|) { |m| "test/#{m[1]}#{m[2]}_test.rb" }
4
+ watch(%r|^(.*)([^/]+)\.rb|) { |m| "test/#{m[1]}#{m[2]}_test.rb" }
5
+ watch(%r|^test/test_helper\.rb|) { "test" }
6
+ end
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2012 Mike Subelsky
1
+ Copyright (c) 2012 Oriol Gual
2
2
 
3
3
  MIT License
4
4
 
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2012 Codegram Technologies
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/Rakefile CHANGED
@@ -1,2 +1,28 @@
1
1
  #!/usr/bin/env rake
2
- require "bundler/gem_tasks"
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+
8
+ require 'yard'
9
+ YARD::Config.load_plugin('yard-tomdoc')
10
+ YARD::Rake::YardocTask.new do |t|
11
+ t.files = ['lib/**/*.rb']
12
+ t.options = %w(-r README.md)
13
+ end
14
+
15
+
16
+ Bundler::GemHelper.install_tasks
17
+
18
+ require 'rake/testtask'
19
+
20
+ Rake::TestTask.new(:test) do |t|
21
+ t.libs << 'lib'
22
+ t.libs << 'test'
23
+ t.pattern = 'test/**/*_test.rb'
24
+ t.verbose = false
25
+ end
26
+
27
+
28
+ task :default => :test
data/Readme.md ADDED
@@ -0,0 +1,145 @@
1
+ # Hyperclient [![Build Status](https://secure.travis-ci.org/codegram/hyperclient.png)](http://travis-ci.org/codegram/hyperclient) [![Dependency Status](https://gemnasium.com/codegram/hyperclient.png)](http://gemnasium.com/codegram/hyperclient)
2
+
3
+ Hyperclient is a Ruby Hypermedia API client.
4
+
5
+ ## Documentation
6
+
7
+ [Hyperclient on documentup][documentup]
8
+
9
+ ## Usage
10
+
11
+ Example API client:
12
+
13
+ ````ruby
14
+ class MyAPIClient
15
+ include Hyperclient
16
+
17
+ entry_point 'http://myapp.com/api'
18
+ auth :digest, 'user', 'password'
19
+ end
20
+ ````
21
+
22
+ [More examples][examples]
23
+
24
+ ## HAL
25
+
26
+ Hyperclient only works with JSON HAL friendly APIs. [Learn about JSON HAL][hal].
27
+
28
+ ## Resources
29
+
30
+ Hyperclient will try to fetch and discover the resources from your API.
31
+
32
+ ### Links
33
+
34
+ Accessing the links for a given resource is quite straightforward:
35
+
36
+ ````ruby
37
+ api = MyAPIClient.new
38
+ api.links.posts_categories
39
+ # => #<Resource @name="posts_categories" ...>
40
+ ````
41
+
42
+ You can also iterate between all the links:
43
+
44
+ ````ruby
45
+ api.links.each do |link|
46
+ puts link.name, link.url
47
+ end
48
+ ````
49
+
50
+ Actually, you can call any [Enumerable][enumerable] method :D
51
+
52
+ If a Resource doesn't have friendly name you can always access it as a Hash:
53
+
54
+ ````ruby
55
+ api = MyAPIClient.new
56
+ api.links['http://myapi.org/rels/post_categories']
57
+ ````
58
+
59
+ ### Embedded resources
60
+
61
+ Accessing embedded resources is similar to accessing links:
62
+
63
+ ````ruby
64
+ api = MyAPIClient.new
65
+ api.resources.posts
66
+ # => #<Resource @name="posts" ...>
67
+ ````
68
+
69
+ And you can also iterate between them:
70
+
71
+ ````ruby
72
+ api.resources.each do |resource|
73
+ puts resource.name, resource.url
74
+ end
75
+ ````
76
+
77
+ You can even chain different calls (this also applies for links):
78
+
79
+ ````ruby
80
+ api.resources.posts.first.links.author
81
+ # => #<Resource @name="author" ...>
82
+ ````
83
+
84
+ ### Attributes
85
+
86
+ Not only you might have links and embedded resources in a Resource, but also
87
+ its attributes:
88
+
89
+ ````ruby
90
+ api.resources.posts.first.attributes
91
+ # => {title: 'Linting the hell out of your Ruby classes with Pelusa',
92
+ teaser: 'Gain new insights about your code thanks to static analysis',
93
+ body: '...' }
94
+ ````
95
+
96
+ ### HTTP
97
+
98
+ OK, navigating an API is really cool, but you may want to actually do something
99
+ with it, right?
100
+
101
+ Hyperclient uses [HTTParty][httparty] under the hood to perform HTTP calls. You can
102
+ call any valid HTTP method on any Resource:
103
+
104
+ ````ruby
105
+ post = api.resources.posts.first
106
+ post.get
107
+ post.head
108
+ post.put({title: 'New title'})
109
+ post.delete
110
+ post.options
111
+
112
+ posts = api.resources.posts
113
+ posts.post({title: "I'm a blogger!", body: 'Wohoo!!'})
114
+ ````
115
+
116
+ ## TODO
117
+
118
+ * Resource permissions: Using the `Allow` header Hyperclient should be able to
119
+ restrict the allowed method on a given `Resource`.
120
+
121
+
122
+ ## Contributing
123
+
124
+ * [List of hyperclient contributors][contributors]
125
+
126
+ * Fork the project.
127
+ * Make your feature addition or bug fix.
128
+ * Add specs for it. This is important so we don't break it in a future
129
+ version unintentionally.
130
+ * Commit, do not mess with rakefile, version, or history.
131
+ If you want to have your own version, that is fine but bump version
132
+ in a commit by itself I can ignore when I pull.
133
+ * Send me a pull request. Bonus points for topic branches.
134
+
135
+ ## License
136
+
137
+ MIT License. Copyright 2012 [Codegram Technologies][codegram]
138
+
139
+ [hal]: http://stateless.co/hal_specification.html
140
+ [contributors]: https://github.com/codegram/hyperclient/contributors
141
+ [codegram]: http://codegram.com
142
+ [documentup]: http://codegram.github.com/hyperclient
143
+ [httparty]: http://github.com/jnunemaker/httparty
144
+ [examples]: http://github.com/codegram/hyperclient/tree/master/examples
145
+ [enumerable]: http://ruby-doc.org/core-1.9.3/Enumerable.html
@@ -0,0 +1,59 @@
1
+ require 'hyperclient'
2
+
3
+ class HalShop
4
+ include Hyperclient
5
+
6
+ entry_point 'http://hal-shop.heroku.com'
7
+ end
8
+
9
+ def print_resources(resources)
10
+ resources.each do |resource|
11
+ if resource.is_a?(Array)
12
+ print_resources(resource)
13
+ else
14
+ puts %{Found "#{resource.name}" at "#{resource.url}" }
15
+ end
16
+ end
17
+ end
18
+
19
+ def print_attributes(attributes)
20
+ puts "-----------------------------"
21
+ attributes.each do |attribute, value|
22
+ puts %{#{attribute}: #{value}}
23
+ end
24
+ end
25
+
26
+ api = HalShop.new
27
+
28
+ puts "Let's inspect the API:"
29
+ puts "\n"
30
+
31
+ puts 'Links from the entry point:'
32
+
33
+ print_resources(api.links)
34
+
35
+ puts
36
+ puts 'Resources at the entry point:'
37
+ print_resources(api.resources)
38
+
39
+ puts
40
+ puts "Let's see what stats we have:"
41
+ print_attributes(api.resources.stats.attributes)
42
+
43
+ products = api.links["http://hal-shop.heroku.com/rels/products"].reload
44
+
45
+ puts
46
+ puts "And what's the inventory of products?"
47
+ puts products.attributes['inventory_size']
48
+
49
+ puts
50
+ puts 'What resources does products have?'
51
+ print_resources(products.resources.products)
52
+
53
+ puts
54
+ puts 'And links?'
55
+ print_resources(products.links)
56
+
57
+ puts
58
+ puts 'Attributes of the first product?'
59
+ print_attributes(products.resources.products.first.attributes)
data/hyperclient.gemspec CHANGED
@@ -2,16 +2,21 @@
2
2
  require File.expand_path('../lib/hyperclient/version', __FILE__)
3
3
 
4
4
  Gem::Specification.new do |gem|
5
- gem.authors = ["Mike Subelsky"]
6
- gem.email = ["mike@subelsky.com"]
7
- gem.description = %q{Proof of concept for interacting with a hypermedia API}
8
- gem.summary = %q{This client is capable of interacting with hypermedia APIs as described in http://www.subbu.org/blog/2008/09/on-linking-part-1 and elsewhere}
9
- gem.homepage = "https://github.com/subelsky/hyperclient"
10
-
5
+ gem.authors = ["Oriol Gual"]
6
+ gem.email = ["oriol.gual@gmail.com"]
7
+ gem.description = %q{HyperClient is a Ruby Hypermedia API client.}
8
+ gem.summary = %q{}
9
+ gem.homepage = "http://codegram.github.com/hyperclient/"
11
10
  gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
12
11
  gem.files = `git ls-files`.split("\n")
13
12
  gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
14
13
  gem.name = "hyperclient"
15
14
  gem.require_paths = ["lib"]
16
15
  gem.version = Hyperclient::VERSION
16
+
17
+ gem.add_dependency 'httparty'
18
+
19
+ gem.add_development_dependency 'minitest'
20
+ gem.add_development_dependency 'turn'
21
+ gem.add_development_dependency 'webmock'
17
22
  end
data/lib/hyperclient.rb CHANGED
@@ -1,5 +1,74 @@
1
- require "hyperclient/version"
2
-
1
+ # Public: The Hyperclient module has various methods to so you can setup your
2
+ # API client.
3
+ #
4
+ # Examples
5
+ #
6
+ # class MyAPI
7
+ # extend Hyperclient
8
+ #
9
+ # entry_point 'http://api.myapp.com'
10
+ # end
11
+ #
3
12
  module Hyperclient
4
- # Your code goes here...
13
+ # Internal: Extend the parent class with our class methods.
14
+ def self.included(base)
15
+ base.send :extend, ClassMethods
16
+ end
17
+
18
+ # Public: Initializes the API with the entry point.
19
+ def entry
20
+ @entry ||= Resource.new('/', resource_options)
21
+ end
22
+
23
+ # Internal: Delegate the method to the API if it exists.
24
+ #
25
+ # This way we can call our API client with the resources name instead of
26
+ # having to add the methods to it.
27
+ def method_missing(method, *args, &block)
28
+ if entry.respond_to?(method)
29
+ entry.send(method, *args, &block)
30
+ else
31
+ super
32
+ end
33
+ end
34
+
35
+ module ClassMethods
36
+ # Public: Set the entry point of your API.
37
+ #
38
+ # Returns nothing.
39
+ def entry_point(url)
40
+ Resource.entry_point = url
41
+ end
42
+
43
+ # Public: Sets the authentication options for your API client.
44
+ #
45
+ # type - A String or Symbol with the authentication method. Can be either
46
+ # :basic or :digest.
47
+ # user - A String with the user.
48
+ # password - A String with the password.
49
+ #
50
+ # Returns nothing.
51
+ def auth(type, user, password)
52
+ http_options({auth: {type: type, credentials: [user, password]}})
53
+ end
54
+
55
+ # Public: Returns a Hash with the HTTP options that will be used to
56
+ # initialize Hyperclient::HTTP.
57
+ def http_options(options = {})
58
+ @@http_options ||= {}
59
+ @@http_options.merge!(options)
60
+
61
+ {http: @@http_options}
62
+ end
63
+ end
64
+
65
+ private
66
+ # Internal: Returns a Hash with the options to initialize the entry point
67
+ # Resource.
68
+ def resource_options
69
+ {name: 'Entry point'}.merge(self.class.http_options)
70
+ end
5
71
  end
72
+
73
+ require 'hyperclient/resource'
74
+ require "hyperclient/version"
@@ -0,0 +1,63 @@
1
+ module Hyperclient
2
+ # Public: Discovers resources from an HTTP response.
3
+ class Discoverer
4
+ # Include goodness of Enumerable.
5
+ include Enumerable
6
+
7
+ # Public: Initializes a Discoverer.
8
+ #
9
+ # response - A Hash representing some resources.
10
+ def initialize(response)
11
+ @response = response
12
+ end
13
+
14
+ # Public: Fetch a Resource with the given name. It is useful when
15
+ # resources don't have a friendly name and you can't call a method on the
16
+ # Discoverer.
17
+ #
18
+ # name - A String representing the resource name.
19
+ #
20
+ # Returns a Resource
21
+ def [](name)
22
+ resources[name.to_s]
23
+ end
24
+
25
+ # Public: Iterates over the discovered resources so one can navigate easily
26
+ # between them.
27
+ #
28
+ # block - A block to pass to each.
29
+ #
30
+ # Returns an Enumerable.
31
+ def each(&block)
32
+ resources.values.each(&block)
33
+ end
34
+
35
+ # Public: Returns a Resource with the name of the method when exists.
36
+ def method_missing(method, *args, &block)
37
+ resources.fetch(method.to_s) { super }
38
+ end
39
+
40
+ private
41
+ # Internal: Returns a Hash with the resources of the response.
42
+ def resources
43
+ return {} unless @response.respond_to?(:inject)
44
+
45
+ @resources ||= @response.inject({}) do |memo, (name, response)|
46
+ next memo if name == 'self'
47
+ memo.update(name => build_resource(response, name))
48
+ end
49
+ end
50
+
51
+ # Internal: Returns a Resource (or a collection of Resources).
52
+ #
53
+ # response - A Hash representing the resource response.
54
+ # name - An optional String with the name of the resource.
55
+ def build_resource(response, name = nil)
56
+ return response.map(&method(:build_resource)) if response.is_a?(Array)
57
+
58
+ Resource.new(response.delete('href'), {response: response, name: name})
59
+ end
60
+ end
61
+ end
62
+
63
+ require 'hyperclient/resource'