hyperclient 0.0.8 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -9,5 +9,6 @@ gem 'guard-minitest'
9
9
  gem 'pry'
10
10
 
11
11
  gem 'redcarpet'
12
- gem 'yard', '~> 0.7.5'
12
+ gem 'yard', '~> 0.8'
13
13
  gem 'yard-tomdoc', git: 'git://github.com/rubyworks/yard-tomdoc'
14
+ gem 'simplecov', require: false
data/Readme.md CHANGED
@@ -14,13 +14,12 @@ Hyperclient is a Ruby Hypermedia API client written in Ruby.
14
14
  Example API client:
15
15
 
16
16
  ````ruby
17
- class MyAPIClient
18
- include Hyperclient
17
+ options = {}
18
+ options[:auth] = {type: :digest, user:, 'user', password: 'password'}
19
+ options[:headers] = {'accept-encoding' => 'deflate, gzip'}
20
+ options[:debug] = true
19
21
 
20
- entry_point 'http://myapp.com/api'
21
- auth :digest, 'user', 'password'
22
- http_options headers: {'accept-encoding' => 'deflate, gzip'}, debug: true
23
- end
22
+ api = Hyperclient::EntryPoint.new('http://myapp.com/api', options)
24
23
  ````
25
24
 
26
25
  [More examples][examples]
@@ -38,16 +37,15 @@ Hyperclient will try to fetch and discover the resources from your API.
38
37
  Accessing the links for a given resource is quite straightforward:
39
38
 
40
39
  ````ruby
41
- api = MyAPIClient.new
42
40
  api.links.posts_categories
43
- # => #<Resource @name="posts_categories" ...>
41
+ # => #<Resource ...>
44
42
  ````
45
43
 
46
44
  You can also iterate between all the links:
47
45
 
48
46
  ````ruby
49
- api.links.each do |link|
50
- puts link.name, link.url
47
+ api.links.each do |name, link|
48
+ puts name, link.url
51
49
  end
52
50
  ````
53
51
 
@@ -56,7 +54,6 @@ Actually, you can call any [Enumerable][enumerable] method :D
56
54
  If a Resource doesn't have friendly name you can always access it as a Hash:
57
55
 
58
56
  ````ruby
59
- api = MyAPIClient.new
60
57
  api.links['http://myapi.org/rels/post_categories']
61
58
  ````
62
59
 
@@ -65,24 +62,21 @@ api.links['http://myapi.org/rels/post_categories']
65
62
  Accessing embedded resources is similar to accessing links:
66
63
 
67
64
  ````ruby
68
- api = MyAPIClient.new
69
- api.resources.posts
70
- # => #<Resource @name="posts" ...>
65
+ api.embedded.posts
71
66
  ````
72
67
 
73
68
  And you can also iterate between them:
74
69
 
75
70
  ````ruby
76
- api.resources.each do |resource|
77
- puts resource.name, resource.url
71
+ api.embedded.each do |name, resource|
72
+ puts name, resource.attributes
78
73
  end
79
74
  ````
80
75
 
81
76
  You can even chain different calls (this also applies for links):
82
77
 
83
78
  ````ruby
84
- api.resources.posts.first.links.author
85
- # => #<Resource @name="author" ...>
79
+ api.embedded.posts.first.links.author
86
80
  ````
87
81
 
88
82
  ### Attributes
@@ -91,7 +85,7 @@ Not only you might have links and embedded resources in a Resource, but also
91
85
  its attributes:
92
86
 
93
87
  ````ruby
94
- api.resources.posts.first.attributes
88
+ api.embedded.posts.first.attributes
95
89
  # => {title: 'Linting the hell out of your Ruby classes with Pelusa',
96
90
  teaser: 'Gain new insights about your code thanks to static analysis',
97
91
  body: '...' }
@@ -106,14 +100,14 @@ Hyperclient uses [HTTParty][httparty] under the hood to perform HTTP calls. You
106
100
  call any valid HTTP method on any Resource:
107
101
 
108
102
  ````ruby
109
- post = api.resources.posts.first
103
+ post = api.embedded.posts.first
110
104
  post.get
111
105
  post.head
112
106
  post.put({title: 'New title'})
113
107
  post.delete
114
108
  post.options
115
109
 
116
- posts = api.resources.posts
110
+ posts = api.links.posts
117
111
  posts.post({title: "I'm a blogger!", body: 'Wohoo!!'})
118
112
  ````
119
113
 
@@ -3,42 +3,54 @@ require 'hyperclient'
3
3
  class Cyberscore
4
4
  include Hyperclient
5
5
 
6
- entry_point 'http://cs-api.heroku.com/api/'
7
-
8
6
  def news
9
- links.feeds.links.submissions.embedded.news
7
+ client.links.submissions.embedded.news
10
8
  end
11
9
 
12
10
  def submissions
13
- links.feeds.links.submissions.embedded.submissions
11
+ client.links..submissions.embedded.submissions
14
12
  end
15
13
 
16
14
  def games
17
- links.feeds.links.games.embedded.games
15
+ client.links.games.embedded.games
18
16
  end
19
17
 
20
18
  def add_game(name)
21
- links.feeds.links.submissions.post({name: name})
19
+ client.links.submissions.post({name: name})
22
20
  end
23
21
 
24
22
  def motd
25
- attributes['motd']
23
+ client.attributes.motd
24
+ end
25
+
26
+ def method_missing(method, *args, &block)
27
+ if client.respond_to?(method)
28
+ client.send(method, *args, &block)
29
+ else
30
+ super
31
+ end
32
+ end
33
+
34
+ private
35
+ def client
36
+ @client ||= Hyperclient::EntryPoint.new 'http://cs-api.heroku.com/api',
37
+ {debug: false, headers: {'content-type' => 'application/json'}}
26
38
  end
27
39
  end
28
40
 
29
- def print_resources(resources)
30
- resources.each do |resource|
31
- if resource.is_a?(Array)
32
- print_resources(resource)
41
+ def print_links(links)
42
+ links.each do |name, link|
43
+ if link.is_a?(Array)
44
+ print_links(link)
33
45
  else
34
- puts %{Found "#{resource.name}" at "#{resource.url}" }
46
+ puts %{Found "#{name}" at "#{link.url}" }
35
47
  end
36
48
  end
37
49
  end
38
50
 
39
51
  def print_games(games)
40
52
  games.each do |game|
41
- puts %{Found "#{game.attributes['name']}" at "#{game.url}" }
53
+ puts %{Found "#{game.attributes['name']}" }
42
54
  end
43
55
  end
44
56
 
@@ -49,15 +61,15 @@ puts "Let's inspect the API:"
49
61
  puts "\n"
50
62
 
51
63
  puts 'Links from the entry point:'
52
- print_resources(api.links)
64
+ print_links(api.links)
53
65
  puts "\n"
54
66
 
55
67
  puts 'How is the server feeling today?'
56
68
  puts api.motd
57
69
  puts "\n"
58
70
 
59
- puts "Let's read the feeds:"
60
- print_resources(api.links.feeds.links)
71
+ puts "Let's read the news:"
72
+ print_links(api.links.news.links)
61
73
  puts "\n"
62
74
 
63
75
  puts "I like games!"
data/examples/hal_shop.rb CHANGED
@@ -1,18 +1,12 @@
1
1
  require 'hyperclient'
2
-
3
- class HalShop
4
- include Hyperclient
5
-
6
- entry_point 'http://hal-shop.heroku.com'
7
- http_options debug: true
8
- end
2
+ require 'pp'
9
3
 
10
4
  def print_resources(resources)
11
- resources.each do |resource|
12
- if resource.is_a?(Array)
13
- print_resources(resource)
14
- else
15
- puts %{Found "#{resource.name}" at "#{resource.url}" }
5
+ resources.each do |name, resource|
6
+ begin
7
+ puts %{Found #{name} at #{resource.url}}
8
+ rescue
9
+ puts %{Found #{name}}
16
10
  end
17
11
  end
18
12
  end
@@ -24,7 +18,7 @@ def print_attributes(attributes)
24
18
  end
25
19
  end
26
20
 
27
- api = HalShop.new
21
+ api = Hyperclient::EntryPoint.new 'http://hal-shop.heroku.com'
28
22
 
29
23
  puts "Let's inspect the API:"
30
24
  puts "\n"
@@ -33,25 +27,24 @@ puts 'Links from the entry point:'
33
27
 
34
28
  print_resources(api.links)
35
29
 
36
- puts
37
- puts 'Resources at the entry point:'
38
- print_resources(api.embedded)
39
-
40
30
  puts
41
31
  puts "Let's see what stats we have:"
42
32
  print_attributes(api.embedded.stats.attributes)
43
33
 
44
- products = api.links["http://hal-shop.heroku.com/rels/products"].reload
34
+ products = api.links["http://hal-shop.heroku.com/rels/products"].resource
45
35
 
46
36
  puts
47
37
  puts "And what's the inventory of products?"
48
38
  puts products.attributes['inventory_size']
49
39
 
50
- puts
51
- puts 'What resources does products have?'
52
- print_resources(products.embedded.products)
53
-
54
40
  puts
41
+ puts 'What embedded resources does products have?'
42
+ products.embedded.products.each do |product|
43
+ puts 'Product:'
44
+ print_attributes(product.attributes)
45
+ puts
46
+ end
47
+
55
48
  puts 'And links?'
56
49
  print_resources(products.links)
57
50
 
data/hyperclient.gemspec CHANGED
@@ -15,8 +15,10 @@ Gem::Specification.new do |gem|
15
15
  gem.version = Hyperclient::VERSION
16
16
 
17
17
  gem.add_dependency 'httparty'
18
+ gem.add_dependency 'uri_template'
18
19
 
19
20
  gem.add_development_dependency 'minitest'
20
21
  gem.add_development_dependency 'turn'
21
22
  gem.add_development_dependency 'webmock'
23
+ gem.add_development_dependency 'mocha'
22
24
  end
data/lib/hyperclient.rb CHANGED
@@ -1,89 +1,7 @@
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
1
+ # Public: Hyperclient namespace.
11
2
  #
12
3
  module Hyperclient
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
- # url - A block to pass the API url.
39
- #
40
- # Returns nothing.
41
- def entry_point(&url)
42
- Resource.entry_point = url.call
43
- end
44
-
45
- # Public: Sets the authentication options for your API client.
46
- #
47
- # options - A block used to pass authentication options. Needed data is:
48
- # type - A String or Symbol with the authentication method. Can be
49
- # either :basic or :digest.
50
- # user - A String with the user.
51
- # password - A String with the password.
52
- #
53
- # Example:
54
- # auth{ {type: :digest, user: 'user', password: 'secret'} }
55
- #
56
- # Returns nothing.
57
- def auth(&options)
58
- options = options.call
59
- http_options({auth: {type: options[:type], credentials: [options[:user], options[:password]]}})
60
- end
61
-
62
- # Public: Sets the HTTP options that will be used to initialize
63
- # Hyperclient::HTTP.
64
- #
65
- # options - A Hash with options to pass to HTTP.
66
- #
67
- # Example:
68
- #
69
- # http_options headers: {'accept-encoding' => 'deflate, gzip'}
70
- #
71
- # Returns a Hash.
72
- def http_options(options = {})
73
- @@http_options ||= {}
74
- @@http_options.merge!(options)
75
-
76
- {http: @@http_options}
77
- end
78
- end
79
-
80
- private
81
- # Internal: Returns a Hash with the options to initialize the entry point
82
- # Resource.
83
- def resource_options
84
- {name: 'Entry point'}.merge(self.class.http_options)
85
- end
86
4
  end
87
5
 
88
- require 'hyperclient/resource'
6
+ require 'hyperclient/entry_point'
89
7
  require "hyperclient/version"
@@ -0,0 +1,20 @@
1
+ require 'hyperclient/collection'
2
+
3
+ module Hyperclient
4
+ # Public: A wrapper class to easily acces the attributes in a Resource.
5
+ #
6
+ # Examples
7
+ #
8
+ # resource.attributes['title']
9
+ # resource.attributes.title
10
+ #
11
+ class Attributes < Collection
12
+ # Public: Initializes the Attributes of a Resource.
13
+ #
14
+ # representation - The hash with the HAL representation of the Resource.
15
+ #
16
+ def initialize(representation)
17
+ @collection = representation.delete_if {|key, value| key =~ /^_/}
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,53 @@
1
+ module Hyperclient
2
+ # Public: A helper class to wrapp a collection of elements and provide
3
+ # Hash-like access or via a method call.
4
+ #
5
+ # Examples
6
+ #
7
+ # collection['value']
8
+ # collection.value
9
+ #
10
+ class Collection
11
+ include Enumerable
12
+
13
+ # Public: Initializes the Collection.
14
+ #
15
+ # collection - The Hash to be wrapped.
16
+ def initialize(collection)
17
+ @collection = collection
18
+ end
19
+
20
+ # Public: Each implementation to allow the class to use the Enumerable
21
+ # benefits.
22
+ #
23
+ # Returns an Enumerator.
24
+ def each(&block)
25
+ @collection.each(&block)
26
+ end
27
+
28
+ # Public: Provides Hash-like access to the collection.
29
+ #
30
+ # name - A String or Symbol of the value to get from the collection.
31
+ #
32
+ # Returns an Object.
33
+ def [](name)
34
+ @collection[name.to_s]
35
+ end
36
+
37
+ # Public: Provides method access to the collection values.
38
+ #
39
+ # It allows accessing a value as `collection.name` instead of
40
+ # `collection['name']`
41
+ #
42
+ # Returns an Object.
43
+ def method_missing(method_name, *args, &block)
44
+ @collection.fetch(method_name.to_s) { super }
45
+ end
46
+
47
+ # Internal: Accessory method to allow the collection respond to the
48
+ # methods that will hit method_missing.
49
+ def respond_to_missing?(method_name, include_private = false)
50
+ @collection.include?(method_name.to_s)
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,49 @@
1
+ require 'hyperclient/link'
2
+
3
+ module Hyperclient
4
+ # Public: The EntryPoint is the main public API for Hyperclient. It is used to
5
+ # initialize an API client and setup the configuration.
6
+ #
7
+ # Examples
8
+ #
9
+ # options = {}
10
+ # options[:headers] = {'accept-encoding' => 'deflate, gzip'}
11
+ # options[:auth] = {type: 'digest', user: 'foo', password: 'secret'}
12
+ # options[:debug] = true
13
+ #
14
+ # client = Hyperclient::EntryPoint.new('http://my.api.org', options)
15
+ #
16
+ class EntryPoint
17
+
18
+ # Public: Returns the Hash with the configuration.
19
+ attr_accessor :config
20
+
21
+ # Public: Initializes an EntryPoint.
22
+ #
23
+ # url - A String with the entry point of your API.
24
+ # config - The Hash options used to setup the HTTP client (default: {})
25
+ # See HTTP for more documentation.
26
+ def initialize(url, config = {})
27
+ @config = config.update(base_uri: url)
28
+ @entry = Link.new({'href' => url}, self).resource
29
+ end
30
+
31
+ # Internal: Delegate the method to the entry point Resource if it exists.
32
+ #
33
+ # This way we can call our API client with the resources name instead of
34
+ # having to add the methods to it.
35
+ def method_missing(method, *args, &block)
36
+ if @entry.respond_to?(method)
37
+ @entry.send(method, *args, &block)
38
+ else
39
+ super
40
+ end
41
+ end
42
+
43
+ # Internal: Accessory method to allow the entry point respond to the
44
+ # methods that will hit method_missing.
45
+ def respond_to_missing?(method, include_private = false)
46
+ @entry.respond_to?(method.to_s)
47
+ end
48
+ end
49
+ end