hyperclient 0.0.8 → 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/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