hactor 0.0.1 → 0.0.2

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/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