hactor 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,6 +1,61 @@
1
1
  # Hactor
2
2
 
3
- TODO: Write a gem description
3
+ Here's an example of how to use Hactor:
4
+
5
+
6
+ ```ruby
7
+ require 'hactor'
8
+
9
+ class UserListActor
10
+ include Hactor::Actor
11
+
12
+ def on_200(response)
13
+ user_links = response.body.links.select { |link| link.rel == 'ht:user' }
14
+ user_links.each do |link|
15
+ puts "#{link.title} (#{link.href})"
16
+ end
17
+ end
18
+ end
19
+
20
+ class LatestPostActor
21
+ include Hactor::Actor
22
+
23
+ def on_200(response)
24
+ puts response.body.embedded_resources.first.links.all
25
+ end
26
+ end
27
+
28
+ class HomeActor
29
+ include Hactor::Actor
30
+
31
+ def on_200(response)
32
+ response.follow 'ht:users', actor: UserListActor.new
33
+ response.follow 'ht:latest-posts', actor: LatestPostActor.new
34
+ end
35
+ end
36
+
37
+ Hactor.start url: 'http://haltalk.herokuapp.com/', actor: HomeActor.new
38
+ ```
39
+
40
+ The following demonstrates the various things an actor can do with a response:
41
+
42
+ ```ruby
43
+ class SomeActor
44
+ include Hactor::Actor
45
+
46
+ def on_200(response)
47
+ # Follow the rel_name link
48
+ response.follow rel_name, actor: some_actor
49
+ # Expand link's URI template with query_vars and follow that
50
+ response.follow rel_name, expand_with: query_vars , actor: some_actor
51
+ # Traverse the link with some_method, some_headers, and some_body
52
+ response.traverse rel_name, method: some_method, headers: some_headers, body: some_string, actor: some_actor
53
+ # In each case, the response produce will be handled by some_actor
54
+ end
55
+ end
56
+
57
+ Hactor.start url: some_entry_point_url, actor: SomeActor.new
58
+ ```
4
59
 
5
60
  ## Installation
6
61
 
data/example.rb ADDED
@@ -0,0 +1,49 @@
1
+ require 'hactor'
2
+
3
+ def start
4
+ Hactor.start url: 'http://haltalk.herokuapp.com/', actor: HomeActor.new
5
+ end
6
+
7
+ class HomeActor
8
+ include Hactor::Actor
9
+
10
+ def on_200(response)
11
+ name = 'CHANGE ME'
12
+ user_name = 'PLEASE_CHANGE_ME'
13
+
14
+ response.follow 'ht:latest-posts', actor: LatestPostActor.new
15
+
16
+ response.traverse('ht:signup', method: 'POST',
17
+ body: { username: user_name, real_name: name, password: 'blarb' },
18
+ actor: SignedUpActor.new)
19
+
20
+ response.follow('ht:me', expand_with: { name: user_name },
21
+ actor: ->(res) { puts res.properties })
22
+ end
23
+ end
24
+
25
+ class LatestPostActor
26
+ include Hactor::Actor
27
+
28
+ def on_200(response)
29
+ response.embedded_resources.each do |resource|
30
+ puts resource.link('self').href
31
+ puts resource.properties
32
+ end
33
+ end
34
+ end
35
+
36
+ class SignedUpActor
37
+ include Hactor::Actor
38
+
39
+ def on_200(response)
40
+ puts "Created an account"
41
+ end
42
+
43
+ def on_400(response)
44
+ puts "Failed to create account"
45
+ end
46
+ end
47
+
48
+
49
+ start()
data/hactor.gemspec CHANGED
@@ -18,4 +18,5 @@ Gem::Specification.new do |gem|
18
18
  gem.require_paths = ["lib"]
19
19
  gem.add_development_dependency 'rspec'
20
20
  gem.add_dependency 'faraday'
21
+ gem.add_dependency 'addressable'
21
22
  end
data/lib/hactor.rb CHANGED
@@ -1,12 +1,15 @@
1
1
  require 'hactor/version'
2
2
  require 'hactor/http/client'
3
+ require 'hactor/null_actor'
3
4
  require 'hactor/actor'
4
5
 
5
6
  module Hactor
6
7
  def self.start(options)
7
8
  url = options.fetch :url
8
- actor = options.fetch :actor
9
- http_client = options.fetch(:http_client) { Hactor::HTTP::Client.new }
10
- http_client.get(url: url, actor: actor)
9
+
10
+ actor = options[:actor] || NullActor.new
11
+ http_client = options[:http_client] || Hactor::HTTP::Client.new
12
+
13
+ http_client.get(url, actor: actor)
11
14
  end
12
15
  end
@@ -1,22 +1,35 @@
1
- require 'delegate'
2
1
  require 'json'
2
+ require 'delegate'
3
+ require 'forwardable'
3
4
  require 'hactor/hal/resource'
4
5
 
5
6
  module Hactor
6
7
  module HAL
7
- class Document < SimpleDelegator
8
- attr_reader :body, :resource_class
8
+ class Document < Delegator
9
+ extend Forwardable
10
+
11
+ attr_reader :body, :response, :resource_class
12
+
13
+ def_delegators :response, :http_client
9
14
 
10
15
  def initialize(body, options={})
11
- @resource_class = options.fetch(:resource_class) { Resource }
16
+ @response = options.fetch(:response)
17
+ @resource_class = options[:resource_class] || Hactor::HAL::Resource
18
+ body = '{}' if body == 'null'
12
19
  @body = JSON.parse(body)
13
- super(root_resource)
20
+ end
21
+
22
+ def __getobj__
23
+ root_resource
14
24
  end
15
25
 
16
26
  def root_resource
17
- @root_resource ||= resource_class.new(body)
27
+ @root_resource ||= resource_class.new(body, rel: 'self', context: self)
28
+ end
29
+
30
+ def base_url
31
+ response.env[:url]
18
32
  end
19
33
  end
20
34
  end
21
35
  end
22
-
@@ -9,21 +9,25 @@ module Hactor
9
9
  extend Forwardable
10
10
  include Enumerable
11
11
 
12
- attr_reader :hash, :embedded_class, :flat_collection_class
13
- def_delegators :each
12
+ attr_reader :hash, :parent, :embedded_class, :flat_collection_class
14
13
 
15
- def initialize(hash, options={})
14
+ def_delegators :all, :each
15
+
16
+ def initialize(hash, options)
16
17
  #TODO: throw/log parsing error if not hash
17
- @embedded_class = options.fetch(:embedded_class) { Resource }
18
- @flat_collection_class = options.fetch(:flat_collection_class) { FlatCollection }
19
18
  @hash = hash
19
+ @parent = options.fetch(:parent)
20
+ @embedded_class = options[:embedded_class] || Resource
21
+ @flat_collection_class = options[:flat_collection_class] || FlatCollection
20
22
  end
21
23
 
22
24
  def all
23
- @all ||= flat_collection_class.new(hash, item_class: embedded_class)
25
+ @all ||= flat_collection_class.new hash,
26
+ parent: parent,
27
+ item_class: embedded_class
24
28
  end
25
29
 
26
- def find_by_rel(rel)
30
+ def find(rel)
27
31
  all.find(->{ NullResource.new }) { |resource| resource.rel == rel }
28
32
  end
29
33
  end
@@ -1,23 +1,27 @@
1
1
  module Hactor
2
2
  module HAL
3
3
  class FlatCollection < Array
4
- attr_reader :item_class
4
+ attr_reader :parent, :item_class
5
5
 
6
- def initialize(hash, options={})
6
+ def initialize(hash, options)
7
+ @parent = options.fetch(:parent)
7
8
  @item_class = options.fetch(:item_class)
8
9
  super flatten(hash)
9
10
  end
10
11
 
11
12
  private
12
13
  def flatten(hash)
14
+ hash ||= {}
13
15
  arr = []
14
- hash.each do |rel, value|
15
- if value.is_a? Array
16
- arr += value.map { |link| item_class.new(link.merge(rel: rel)) }
17
- else
18
- arr.push item_class.new(value.merge(rel: rel))
19
- end
16
+
17
+ hash.each do |rel, value|
18
+ if value.is_a? Array
19
+ arr += value.map { |obj| item_class.new(obj, rel: rel, context: parent) }
20
+ else
21
+ arr.push item_class.new(value, rel: rel, context: parent)
20
22
  end
23
+ end
24
+
21
25
  arr
22
26
  end
23
27
  end
@@ -1,7 +1,31 @@
1
1
  require 'ostruct'
2
+ require 'addressable/template'
2
3
 
3
4
  module Hactor
4
5
  module HAL
5
- class Link < OpenStruct;end
6
+ class Link < OpenStruct
7
+ attr_reader :rel, :parent_resource, :template_class
8
+
9
+ def initialize(obj, options)
10
+ @href = obj['href']
11
+ @rel = options.fetch(:rel)
12
+ @parent_resource = options.fetch(:context)
13
+ @template_class = options[:template_class] || Addressable::Template
14
+ super(obj)
15
+ end
16
+
17
+ def href(variables=nil)
18
+ if templated?
19
+ variables ||= {}
20
+ template_class.new(@href).expand(variables).to_s
21
+ else
22
+ @href
23
+ end
24
+ end
25
+
26
+ def templated?
27
+ !!templated
28
+ end
29
+ end
6
30
  end
7
31
  end
@@ -1,7 +1,7 @@
1
1
  require 'forwardable'
2
2
  require 'hactor/hal/flat_collection'
3
3
  require 'hactor/hal/link'
4
- #require 'hactor/hal/null_link'
4
+ require 'hactor/hal/null_link'
5
5
 
6
6
  module Hactor
7
7
  module HAL
@@ -9,23 +9,31 @@ module Hactor
9
9
  extend Forwardable
10
10
  include Enumerable
11
11
 
12
- attr_reader :hash, :link_class, :flat_collection_class
12
+ attr_reader :hash, :parent, :link_class, :flat_collection_class
13
13
  def_delegators :all, :each
14
14
 
15
15
  def initialize(hash, options={})
16
16
  #TODO: throw/log parsing error if not hash
17
- @link_class = options.fetch(:link_class) { Link }
18
- @flat_collection_class = options.fetch(:flat_collection_class) { FlatCollection }
19
17
  @hash = hash
18
+ @parent = options.fetch(:parent)
19
+ @link_class = options[:link_class] || Link
20
+ @flat_collection_class = options[:flat_collection_class] || FlatCollection
20
21
  end
21
22
 
22
23
  def all
23
- @all ||= flat_collection_class.new(hash, item_class: link_class)
24
+ @all ||= flat_collection_class.new hash,
25
+ item_class: link_class,
26
+ parent: parent
27
+
24
28
  end
25
29
 
26
- def find_by_rel(rel)
30
+ def find(rel)
27
31
  all.find(->{ NullLink.new }) { |link| link.rel == rel }
28
32
  end
33
+
34
+ def with_rel(rel)
35
+ all.select { |link| link.rel == rel }
36
+ end
29
37
  end
30
38
  end
31
39
  end
@@ -0,0 +1,8 @@
1
+ module Hactor
2
+ module HAL
3
+ class NullLink
4
+ def initialize(obj={}, options={});end
5
+ def href;end
6
+ end
7
+ end
8
+ end
@@ -1,19 +1,33 @@
1
+ require 'forwardable'
1
2
  require 'hactor/hal/link_collection'
2
3
  require 'hactor/hal/embedded_collection'
3
4
 
4
5
  module Hactor
5
6
  module HAL
6
7
  class Resource
8
+ extend Forwardable
9
+
7
10
  RESERVED_PROPERTIES = ['_links', '_embedded']
8
11
 
9
- attr_reader :state, :link_collection_class, :embedded_collection_class
12
+ attr_reader :rel, :state, :http_client, :context
13
+ attr_accessor :link_collection_class, :embedded_collection_class
14
+
15
+ def_delegators :context, :http_client, :base_url
10
16
 
11
- def initialize(state, options={})
12
- @link_collection_class = options.fetch(:link_collection_class) { LinkCollection }
13
- @embedded_collection_class = options.fetch(:embedded_collection_class) { EmbeddedCollection }
17
+ def initialize(state, options)
18
+ @rel = options.fetch(:rel)
19
+ @context = options.fetch(:context)
14
20
  @state = state
15
21
  end
16
22
 
23
+ def follow(rel, options={})
24
+ http_client.follow link(rel), options_in_context(options)
25
+ end
26
+
27
+ def traverse(rel, options={})
28
+ http_client.traverse link(rel), options_in_context(options)
29
+ end
30
+
17
31
  def properties
18
32
  @properties ||= state.reject { |k,v|
19
33
  RESERVED_PROPERTIES.include? k
@@ -21,11 +35,12 @@ module Hactor
21
35
  end
22
36
 
23
37
  def link(rel, options={})
24
- links.find_by_rel(rel)
38
+ links.find(rel)
25
39
  end
26
40
 
27
41
  def links
28
- @links ||= link_collection_class.new(state['_links'])
42
+ @links ||= link_collection_class.new state['_links'],
43
+ parent: self
29
44
  end
30
45
 
31
46
  def embedded_resource(rel)
@@ -33,7 +48,22 @@ module Hactor
33
48
  end
34
49
 
35
50
  def embedded_resources
36
- @embedded_resources ||= embedded_collection_class.new(state['_embedded'])
51
+ @embedded_resources ||= embedded_collection_class.new state['_embedded'],
52
+ parent: self
53
+ end
54
+
55
+ def embedded_collection_class
56
+ @embedded_collection_class ||= EmbeddedCollection
57
+ end
58
+
59
+ def link_collection_class
60
+ @link_collection_class ||= LinkCollection
61
+ end
62
+
63
+ private
64
+
65
+ def options_in_context(options)
66
+ options.merge context_url: context.base_url
37
67
  end
38
68
  end
39
69
  end
@@ -1,33 +1,49 @@
1
- require 'hactor/http/response'
2
1
  require 'faraday'
2
+ require 'hactor/null_actor'
3
+ require 'hactor/http/response'
3
4
 
4
5
  module Hactor
5
6
  module HTTP
6
- class Client < SimpleDelegator
7
+ class Client < Delegator
7
8
  attr_reader :response_class, :backend
8
9
 
9
10
  def initialize(options={})
10
- @response_class = options.fetch(:response_class) { Hactor::HTTP::Response }
11
- @backend = options.fetch(:backend) { Faraday.new }
12
- super(@backend)
11
+ @response_class = options[:response_class] || Hactor::HTTP::Response
12
+ @backend = options[:backend] || Faraday.new
13
13
  end
14
14
 
15
- def follow(link, options = {})
16
- actor = options.fetch(:actor)
15
+ def follow(link, options)
17
16
  context_url = options.fetch(:context_url)
18
17
 
19
- url = context_url.merge(link.href)
20
- get(url: url, actor: actor)
18
+ url = context_url.merge(link.href(options[:expand_with]))
19
+ get(url, options)
21
20
  end
22
21
 
23
- def get(options)
24
- url = options.fetch :url
25
- actor = options.fetch :actor
22
+ def traverse(link, options)
23
+ context_url = options.fetch(:context_url)
24
+ method = options.fetch(:method).to_s.downcase
26
25
 
27
- response = response_class.new(backend.get(url),
28
- http_client: self)
26
+ actor = options[:actor] || Hactor::NullActor.new
27
+ headers = options[:headers]
28
+ body = options[:body]
29
+
30
+ url = context_url.merge link.href(options[:expand_with])
31
+ response = response_class.new backend.send(method, url, body, headers),
32
+ http_client: self
29
33
  actor.call response
30
34
  end
35
+
36
+ def get(url, options)
37
+ actor = options[:actor] || Hactor::NullActor.new
38
+ headers = options[:headers]
39
+ response = response_class.new backend.get(url, nil, headers),
40
+ http_client: self
41
+ actor.call response
42
+ end
43
+
44
+ def __getobj__
45
+ backend
46
+ end
31
47
  end
32
48
  end
33
49
  end
@@ -1,28 +1,35 @@
1
1
  require 'delegate'
2
+ require 'forwardable'
2
3
  require 'hactor/hal/document'
3
4
 
4
5
  module Hactor
5
6
  module HTTP
6
- class Response < SimpleDelegator
7
- attr_reader :request_client, :codec
7
+ class Response < Delegator
8
+ extend Forwardable
9
+
10
+ attr_reader :codec, :response
11
+ attr_accessor :http_client
12
+
13
+ def_delegators :body, :follow,
14
+ :traverse,
15
+ :properties,
16
+ :embedded_resources,
17
+ :links
8
18
 
9
19
  def initialize(response, options={})
10
- @request_client = options.fetch(:http_client)
20
+ @http_client = options.fetch(:http_client)
11
21
  @codec = options.fetch(:codec) { Hactor::HAL::Document }
12
22
  @body = options[:body]
13
- super(response)
23
+ @response = response
14
24
  end
15
25
 
16
- def follow(rel, options={})
17
- actor = options.fetch(:actor) { Hactor::NullActor.new }
18
- client = options.fetch(:http_client) { request_client }
19
-
20
- link = body.link(rel)
21
- client.follow(link, context_url: env[:url], actor: actor)
26
+ def body
27
+ @body ||= codec.new __getobj__.body,
28
+ response: self
22
29
  end
23
30
 
24
- def body
25
- @body ||= codec.new(__getobj__.body)
31
+ def __getobj__
32
+ response
26
33
  end
27
34
  end
28
35
  end
@@ -0,0 +1,7 @@
1
+ module Hactor
2
+ class NullActor
3
+ def call(response)
4
+ raise "Response had no actor"
5
+ end
6
+ end
7
+ end
@@ -1,3 +1,3 @@
1
1
  module Hactor
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
@@ -2,21 +2,31 @@ require 'spec_helper'
2
2
  require 'hactor/hal/document'
3
3
 
4
4
  describe Hactor::HAL::Document do
5
- describe "#root_resource" do
6
- let(:resource_class) { mock }
7
- let(:sentinel) { stub }
8
- let(:body) { '{}' }
9
- let(:doc) do
10
- Hactor::HAL::Document.new(body,
11
- resource_class: resource_class)
12
- end
5
+ let(:resource_class) { mock }
6
+ let(:response) { mock }
7
+ let(:sentinel) { stub }
8
+ let(:body) { '{}' }
9
+
10
+ let(:doc) do
11
+ Hactor::HAL::Document.new body,
12
+ response: response,
13
+ resource_class: resource_class
14
+ end
13
15
 
16
+ describe "#root_resource" do
14
17
  it "returns new Resource object" do
15
18
  resource_class.should_receive(:new)
16
- .with(JSON.parse(body))
19
+ .with(JSON.parse(body), { rel: 'self', context: doc })
17
20
  .and_return(sentinel)
18
21
 
19
22
  doc.root_resource.should == sentinel
20
23
  end
21
24
  end
25
+
26
+ describe "#base_url" do
27
+ it "returns the url via the response object" do
28
+ response.should_receive(:env).and_return url: sentinel
29
+ doc.base_url.should == sentinel
30
+ end
31
+ end
22
32
  end
@@ -11,8 +11,10 @@ describe Hactor::HAL::LinkCollection do
11
11
  let(:flat_collection_class) { mock }
12
12
  let(:flat_collection) { mock }
13
13
  let(:link) { stub }
14
+ let(:parent) { stub }
14
15
  let(:collection) do
15
16
  Hactor::HAL::LinkCollection.new(simple_link_hash,
17
+ parent: parent,
16
18
  link_class: link_class,
17
19
  flat_collection_class: flat_collection_class)
18
20
  end
@@ -20,11 +22,11 @@ describe Hactor::HAL::LinkCollection do
20
22
  describe "#find" do
21
23
  it "finds a link with the supplied rel" do
22
24
  flat_collection_class.should_receive(:new)
23
- .with(simple_link_hash, item_class: link_class)
25
+ .with(simple_link_hash, parent: parent, item_class: link_class)
24
26
  .and_return(flat_collection)
25
27
  flat_collection.should_receive(:find).and_return(link)
26
28
 
27
- collection.find_by_rel(:self).should == link
29
+ collection.find(:self).should == link
28
30
  end
29
31
  end
30
32
  end
@@ -0,0 +1,38 @@
1
+ require 'spec_helper'
2
+ require 'hactor/hal/link'
3
+
4
+ describe Hactor::HAL::Link do
5
+ context "templated link" do
6
+ let(:template_class) { mock('templ_class') }
7
+ let(:template) { mock('template') }
8
+ let(:href) { stub }
9
+
10
+ let(:state) do
11
+ {
12
+ 'href' => href,
13
+ 'templated' => true
14
+ }
15
+ end
16
+
17
+ let(:link) do
18
+ Hactor::HAL::Link.new(state,{
19
+ rel: stub,
20
+ context: stub,
21
+ template_class: template_class
22
+ })
23
+ end
24
+
25
+ describe "#href" do
26
+ it "expands using the variables passed in" do
27
+ sentinel, variables = stub, stub
28
+
29
+ template_class.should_receive(:new).with(href).
30
+ and_return(template)
31
+ template.should_receive(:expand).with(variables).
32
+ and_return(stub(to_s: sentinel))
33
+
34
+ link.href(variables).should == sentinel
35
+ end
36
+ end
37
+ end
38
+ end
@@ -15,17 +15,53 @@ describe Hactor::HAL::Resource do
15
15
  }
16
16
  end
17
17
 
18
- let(:resource) do
19
- Hactor::HAL::Resource.new(json,
20
- link_collection_class: link_collection_class,
21
- embedded_collection_class: embedded_collection_class)
22
- end
23
-
24
18
  let(:link_collection_class) { mock }
25
- let(:embedded_collection_class) { mock }
26
19
  let(:link_collection) { mock }
20
+ let(:embedded_collection_class) { mock }
27
21
  let(:embedded_collection) { mock }
22
+ let(:http_client) { mock }
23
+
28
24
  let(:rel) { stub }
25
+ let(:link) { stub }
26
+ let(:base_url) { stub }
27
+ let(:document) { stub(http_client: http_client, base_url: base_url) }
28
+
29
+ let(:resource) do
30
+ Hactor::HAL::Resource.new json,
31
+ rel: rel,
32
+ context: document
33
+
34
+ end
35
+
36
+ before :each do
37
+ resource.link_collection_class = link_collection_class
38
+ resource.embedded_collection_class = embedded_collection_class
39
+ end
40
+
41
+ context "http transition methods" do
42
+ before :each do
43
+ link_collection_class.should_receive(:new).and_return(link_collection)
44
+ link_collection.should_receive(:find).with(rel).and_return(link)
45
+ end
46
+
47
+ describe "#follow" do
48
+ it "tells the http client to follow the link and also supplies the context URL" do
49
+ http_client.should_receive(:follow).with(link, { context_url: base_url })
50
+
51
+ resource.follow(rel)
52
+ end
53
+ end
54
+
55
+ describe "#traverse" do
56
+ it "tells the http client to traverse the link using the supplied method and also supplies the context URL" do
57
+ method = stub
58
+ http_client.should_receive(:traverse).
59
+ with(link, { method: method, context_url: base_url })
60
+
61
+ resource.traverse(rel, method: method)
62
+ end
63
+ end
64
+ end
29
65
 
30
66
  describe "#properties" do
31
67
  it "returns a Hash containing the json with the reserved properties removed" do
@@ -35,13 +71,13 @@ describe Hactor::HAL::Resource do
35
71
 
36
72
  context "links" do
37
73
  before :each do
38
- link_collection_class.should_receive(:new).with(json['_links']).and_return(link_collection)
74
+ link_collection_class.should_receive(:new).
75
+ with(json['_links'], parent: resource).and_return(link_collection)
39
76
  end
40
77
 
41
78
  describe "#link" do
42
79
  it "finds a link with the given rel" do
43
- link = stub
44
- link_collection.should_receive(:find_by_rel).with(rel).and_return(link)
80
+ link_collection.should_receive(:find).with(rel).and_return(link)
45
81
  resource.link(rel).should == link
46
82
  end
47
83
  end
@@ -55,7 +91,8 @@ describe Hactor::HAL::Resource do
55
91
 
56
92
  context "embedded resources" do
57
93
  before :each do
58
- embedded_collection_class.should_receive(:new).with(json['_embedded']).and_return(embedded_collection)
94
+ embedded_collection_class.should_receive(:new).
95
+ with(json['_embedded'], parent: resource).and_return(embedded_collection)
59
96
  end
60
97
 
61
98
  describe "#embedded_resource" do
@@ -5,48 +5,83 @@ describe Hactor::HTTP::Client do
5
5
  let(:response_class) { mock }
6
6
  let(:backend) { mock }
7
7
  let(:actor) { mock }
8
+ let(:link) { mock }
9
+ let(:context_url) { mock }
10
+
11
+ let(:variables) { stub }
12
+ let(:uri) { stub }
13
+ let(:resolved_uri) { stub }
14
+ let(:response) { stub }
15
+ let(:headers) { stub }
16
+ let(:hactor_response) { stub }
17
+ let(:url) { 'http://example.com/' }
18
+
8
19
  let(:client) do
9
- Hactor::HTTP::Client.new(response_class: response_class,
10
- backend: backend)
20
+ Hactor::HTTP::Client.new response_class: response_class,
21
+ backend: backend
11
22
  end
12
23
 
13
- describe "#get" do
14
- context "a valid URL is supplied" do
15
- let(:url) { 'http://example.com/' }
16
- let(:response) { stub }
17
- let(:hactor_response) { stub }
18
-
19
- it "GETs from URL and passes Response object to the actor" do
20
- backend.should_receive(:get)
21
- .with(url)
22
- .and_return(response)
23
- response_class.should_receive(:new)
24
- .with(response, http_client: client)
25
- .and_return(hactor_response)
26
- actor.should_receive(:call)
27
- .with(hactor_response)
28
-
29
- client.get(url: url, actor: actor)
30
- end
31
- end
24
+ describe "#follow" do
25
+ before :each do
26
+ context_url.should_receive(:merge).with(uri).
27
+ and_return(resolved_uri)
32
28
 
33
- describe "#follow" do
34
- let(:link) { mock }
35
- let(:uri) { stub }
36
- let(:context_url) { mock }
37
- let(:resolved_uri) { stub }
29
+ response_class.should_receive(:new).with(response, http_client: backend).
30
+ and_return(hactor_response)
38
31
 
32
+ actor.should_receive(:call).with(hactor_response)
33
+ end
34
+
35
+ context "plain url (no expand_with in option)" do
39
36
  it "should use the context url to resolve link href" do
40
- Delegator.class_eval { include RSpec::Mocks::Methods }
41
- client.stub!(:get).with(url: resolved_uri, actor: actor)
42
- link.should_receive(:href)
37
+ link.should_receive(:href).with(nil)
43
38
  .and_return(uri)
44
- context_url.should_receive(:merge)
45
- .with(uri)
46
- .and_return(resolved_uri)
39
+ backend.should_receive(:get).with(resolved_uri, nil, headers).
40
+ and_return(response)
47
41
 
48
- client.follow(link, context_url: context_url, actor: actor)
42
+ client.follow link,
43
+ context_url: context_url,
44
+ actor: actor,
45
+ headers: headers
49
46
  end
50
47
  end
48
+
49
+ context "URI template (expand_with in option)" do
50
+ it "should apply the variables to the template and follow resulting URI" do
51
+ link.should_receive(:href).with(variables).and_return(uri)
52
+ backend.should_receive(:get).with(resolved_uri, nil, headers).
53
+ and_return(response)
54
+
55
+ client.follow link,
56
+ context_url: context_url,
57
+ actor: actor,
58
+ expand_with: variables,
59
+ headers: headers
60
+ end
61
+ end
62
+ end
63
+
64
+ describe "#traverse" do
65
+ let(:method) { 'POST' }
66
+ let(:body) { stub 'body' }
67
+
68
+ it "should send a message of the given method to the http backend" do
69
+ link.should_receive(:href).with(variables).and_return(uri)
70
+ context_url.should_receive(:merge).with(uri).
71
+ and_return(resolved_uri)
72
+ backend.should_receive('post').with(resolved_uri, body, headers).
73
+ and_return(response)
74
+ response_class.should_receive(:new).with(response, http_client: backend).
75
+ and_return(hactor_response)
76
+ actor.should_receive(:call).with(hactor_response)
77
+
78
+ client.traverse link,
79
+ context_url: context_url,
80
+ method: method,
81
+ headers: headers,
82
+ body: body,
83
+ actor: actor,
84
+ expand_with: variables
85
+ end
51
86
  end
52
87
  end
@@ -2,16 +2,13 @@ require 'spec_helper'
2
2
  require 'hactor/http/response'
3
3
 
4
4
  describe Hactor::HTTP::Response do
5
- let(:codec) { mock }
6
- let(:body) { mock }
7
- let(:wrapped_response) { mock }
8
- let(:http_client) { mock }
5
+ let(:codec) { mock 'codec' }
6
+ let(:body) { mock 'body' }
7
+ let(:wrapped_response) { mock 'wrapped_response' }
8
+ let(:http_client) { stub }
9
9
 
10
10
  describe "#follow" do
11
11
  let(:rel) { stub }
12
- let(:link) { stub }
13
- let(:actor) { stub }
14
- let(:context_url) { stub }
15
12
  let(:response) do
16
13
  Hactor::HTTP::Response.new(wrapped_response,
17
14
  http_client: http_client,
@@ -19,14 +16,12 @@ describe Hactor::HTTP::Response do
19
16
  body: body)
20
17
  end
21
18
 
22
- it "follows the link with supplied rel" do
23
- body.should_receive(:link).with(rel).and_return(link)
24
- http_client.should_receive(:follow)
25
- .with(link, context_url: context_url, actor: actor)
26
- wrapped_response.should_receive(:env)
27
- .and_return({ url: context_url })
19
+ it "delegates to the body" do
20
+ options = stub
28
21
 
29
- response.follow(rel, actor: actor, http_client: http_client)
22
+ body.should_receive(:follow).with(rel, options)
23
+
24
+ response.follow(rel, options)
30
25
  end
31
26
  end
32
27
 
@@ -41,7 +36,9 @@ describe Hactor::HTTP::Response do
41
36
 
42
37
  it "passes itself into the codec" do
43
38
  wrapped_response.should_receive(:body).and_return(body)
44
- codec.should_receive(:new).with(body).and_return(sentinel)
39
+ codec.should_receive(:new).
40
+ with(body, response: response).
41
+ and_return(sentinel)
45
42
 
46
43
  response.body.should == sentinel
47
44
  end
@@ -0,0 +1,12 @@
1
+ require 'hactor/null_actor'
2
+
3
+ describe Hactor::NullActor do
4
+ let(:response) { mock }
5
+ describe "#call" do
6
+ it "should raise an exception when called" do
7
+ expect {
8
+ subject.call(response)
9
+ }.to raise_exception(RuntimeError)
10
+ end
11
+ end
12
+ end
data/spec/hactor_spec.rb CHANGED
@@ -8,7 +8,7 @@ describe Hactor do
8
8
  let(:actor) { mock }
9
9
  let(:http_client) { mock }
10
10
  it "GETs from the URL and passes a response object to an actor" do
11
- http_client.should_receive(:get).with(url: url, actor: actor)
11
+ http_client.should_receive(:get).with(url, actor: actor)
12
12
  Hactor.start(url: url, actor: actor, http_client: http_client)
13
13
  end
14
14
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hactor
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-11-29 00:00:00.000000000 Z
12
+ date: 2013-01-30 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
@@ -43,6 +43,22 @@ dependencies:
43
43
  - - ! '>='
44
44
  - !ruby/object:Gem::Version
45
45
  version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: addressable
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
46
62
  description: A framework for building hypermedia clients
47
63
  email:
48
64
  - mikekelly321@gmail.com
@@ -56,6 +72,7 @@ files:
56
72
  - LICENSE.txt
57
73
  - README.md
58
74
  - Rakefile
75
+ - example.rb
59
76
  - hactor.gemspec
60
77
  - lib/hactor.rb
61
78
  - lib/hactor/actor.rb
@@ -64,19 +81,22 @@ files:
64
81
  - lib/hactor/hal/flat_collection.rb
65
82
  - lib/hactor/hal/link.rb
66
83
  - lib/hactor/hal/link_collection.rb
84
+ - lib/hactor/hal/null_link.rb
67
85
  - lib/hactor/hal/resource.rb
68
86
  - lib/hactor/http/client.rb
69
87
  - lib/hactor/http/response.rb
88
+ - lib/hactor/null_actor.rb
70
89
  - lib/hactor/version.rb
71
90
  - spec/hactor/hal/document_spec.rb
72
91
  - spec/hactor/hal/embedded_collection_spec.rb
73
92
  - spec/hactor/hal/flat_collection_spec.rb
74
93
  - spec/hactor/hal/link_collection_spec.rb
94
+ - spec/hactor/hal/link_spec.rb
75
95
  - spec/hactor/hal/resource_spec.rb
76
96
  - spec/hactor/http/client_spec.rb
77
97
  - spec/hactor/http/response_spec.rb
98
+ - spec/hactor/null_actor_spec.rb
78
99
  - spec/hactor_spec.rb
79
- - spec/integration.rb
80
100
  - spec/spec_helper.rb
81
101
  homepage: https://github.com/mikekelly/hactor
82
102
  licenses: []
@@ -90,15 +110,21 @@ required_ruby_version: !ruby/object:Gem::Requirement
90
110
  - - ! '>='
91
111
  - !ruby/object:Gem::Version
92
112
  version: '0'
113
+ segments:
114
+ - 0
115
+ hash: -4470173572118963512
93
116
  required_rubygems_version: !ruby/object:Gem::Requirement
94
117
  none: false
95
118
  requirements:
96
119
  - - ! '>='
97
120
  - !ruby/object:Gem::Version
98
121
  version: '0'
122
+ segments:
123
+ - 0
124
+ hash: -4470173572118963512
99
125
  requirements: []
100
126
  rubyforge_project:
101
- rubygems_version: 1.8.24
127
+ rubygems_version: 1.8.23
102
128
  signing_key:
103
129
  specification_version: 3
104
130
  summary: A framework for building hypermedia clients
@@ -107,9 +133,10 @@ test_files:
107
133
  - spec/hactor/hal/embedded_collection_spec.rb
108
134
  - spec/hactor/hal/flat_collection_spec.rb
109
135
  - spec/hactor/hal/link_collection_spec.rb
136
+ - spec/hactor/hal/link_spec.rb
110
137
  - spec/hactor/hal/resource_spec.rb
111
138
  - spec/hactor/http/client_spec.rb
112
139
  - spec/hactor/http/response_spec.rb
140
+ - spec/hactor/null_actor_spec.rb
113
141
  - spec/hactor_spec.rb
114
- - spec/integration.rb
115
142
  - spec/spec_helper.rb
data/spec/integration.rb DELETED
@@ -1,37 +0,0 @@
1
- require 'spec_helper'
2
- require 'hactor'
3
- require 'hactor/actor'
4
-
5
- class UserListActor
6
- include Hactor::Actor
7
-
8
- def on_200(response)
9
- user_links = response.body.links.select { |link| link.rel == 'ht:user' }
10
- user_links.each do |link|
11
- puts "#{link.title} (#{link.href})"
12
- end
13
- end
14
- end
15
-
16
- class LatestPostActor
17
- include Hactor::Actor
18
-
19
- def on_200(response)
20
- puts response.body.embedded_resources.all.first.links.all
21
- end
22
- end
23
-
24
- class HomeActor
25
- include Hactor::Actor
26
-
27
- def on_200(response)
28
- response.follow 'ht:users', actor: UserListActor.new
29
- response.follow 'ht:latest-posts', actor: LatestPostActor.new
30
- end
31
- end
32
-
33
- describe Hactor do
34
- it "should work as expected :)" do
35
- Hactor.start url: 'http://haltalk.herokuapp.com/', actor: HomeActor.new
36
- end
37
- end