roar 0.9.2 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
data/.travis.yml ADDED
@@ -0,0 +1,3 @@
1
+ rvm: 1.9.3
2
+ notifications:
3
+ irc: "irc.freenode.org#cells"
data/CHANGES.markdown CHANGED
@@ -1,27 +1,36 @@
1
- h2. 0.9.2
1
+ ## 0.10.0
2
+
3
+ * Requiring representable-0.1.3.
4
+ * Added JSON-HAL support.
5
+ * Links are no longer rendered when `href` is `nil` or `false`.
6
+ * `Representer.link` class method now accepts either the `rel` value, only, or a hash of link attributes (defined in `Hypermedia::Hyperlink.params`), like `link :rel => :self, :title => "You're good" do..`
7
+ * API CHANGE: `Representer#links` no longer returns the `href` value but the link object. Use it like `object.links[:self].href` to retrieve the URL.
8
+ * `#from_json` won't throw an exception anymore when passed an empty json document.
9
+
10
+ ## 0.9.2
2
11
 
3
12
  * Using representable-1.1.
4
13
 
5
- h2. 0.9.1
14
+ ## 0.9.1
6
15
 
7
16
  * Removed @Representer#to_attributes@ and @#from_attributes@.
8
17
  * Using representable-1.0.1 now.
9
18
 
10
- h2. 0.9.0
19
+ ## 0.9.0
11
20
 
12
21
  * Using representable-0.12.x.
13
22
  * `Representer::Base` is now simply `Representer`.
14
23
  * Removed all the class methods from `HttpVerbs` except for `get`.
15
24
 
16
25
 
17
- h2. 0.8.3
26
+ ## 0.8.3
18
27
 
19
28
  * Maintenance release for representable compat.
20
29
 
21
- h2. 0.8.2
30
+ ## 0.8.2
22
31
 
23
32
  * Removing `restfulie` dependency - we now use `Net::HTTP`.
24
33
 
25
- h2. 0.8.1
34
+ ## 0.8.1
26
35
 
27
36
  * Added the :except and :include options to `#from_*`.
data/README.textile CHANGED
@@ -2,28 +2,14 @@ h1. ROAR
2
2
 
3
3
  _Resource-Oriented Architectures in Ruby._
4
4
 
5
- "Lets make documents suit our models and not models fit to documents."
6
-
7
- Questions? Need help? Free 1st Level Support on irc.freenode.org#roar !
8
-
9
5
  h2. Introduction
10
6
 
11
- REST is about representations. Representations are documents. Documents are pushed back and forth between clients and REST services. Roar focuses on providing object-oriented representations.
12
-
13
- Most REST gems provide a neat HTTP interface, automatic parsing of documents and a more or less convenient way for rendering representations. Roar is different. The central concept of Roar are *representers* - object-oriented documents suitable for parsing _and_ rendering, extendable at runtime and with hypermedia support. The Representer concept is the answer to the missing REST abstraction layer in most frameworks.
14
-
15
-
16
- h2. Features
17
-
18
- * OOP access to documents.
19
- * Parsing _and_ rendering of representations in one place.
20
- * Declaratively define document syntax and semantics.
21
- * Hypermedia support.
22
- * ActiveResource-like client support.
23
- * Useable in both client _and_ server.
24
- * Framework agnostic, runs with sinatra, Rails, webmachine and friends.
7
+ Roar is a framework for parsing and rendering REST documents. Nothing more. With Roar, REST documents - also known as representations - are defined using a new concept called representers. Both syntax and semantics are declared in Ruby modules that can be mixed into your domain models, following clean OOP patterns.
25
8
 
9
+ Roar comes with built-in JSON, JSON::HAL and XML support. It exposes a highly modular architecture and makes it very simple to add new media types and functionality where needed. Additional features include client HTTP support, coercion, client-side caching, awesome hypermedia support and more. Representers feel pretty well in DCI environments, too.
26
10
 
11
+ Roar is completely framework-agnostic and loves being used in web kits like Rails, Webmachine, Sinatra, Padrino, etc. Actually, Roar makes it fun designing real, hypermedia-driven, and resource-oriented systems that will even make Steve sleep happily at night so he finally gets some REST!
12
+
27
13
  h2. Example
28
14
 
29
15
  Say your webshop consists of two completely separated apps. The REST backend, a Sinatra app, serves articles and processes orders. The frontend, being browsed by your clients, is a rich Rails application. It queries the services for articles, renders them nicely and reads or writes orders with REST calls. That being said, the frontend turns out to be a pure REST client.
@@ -307,3 +293,6 @@ Making that system RESTful basically means
307
293
  # Do _not_ let the frontend compute any URLs to further actions.
308
294
  # Showing articles, creating a new order, adding articles to it and finally placing the order - this all requires further URLs. These URLs are embedded as _hypermedia_ in the representations sent by the REST backend.
309
295
 
296
+ h2. Support
297
+
298
+ Questions? Need help? Free 1st Level Support on irc.freenode.org#roar !
@@ -0,0 +1,32 @@
1
+ require "roar/representer/feature/http_verbs"
2
+
3
+ module Roar
4
+ # Automatically add accessors for properties and collections. Also mixes in HttpVerbs.
5
+ module Representer
6
+ module Feature
7
+ module Client
8
+ include HttpVerbs
9
+
10
+ def self.extended(base)
11
+ base.instance_eval do
12
+ representable_attrs.each do |attr|
13
+ next unless attr.instance_of? Representable::Definition # ignore hyperlinks etc for now.
14
+ name = attr.name
15
+
16
+ # TODO: could anyone please make this better?
17
+ instance_eval %Q{
18
+ def #{name}=(v)
19
+ @#{name} = v
20
+ end
21
+
22
+ def #{name}
23
+ @#{name}
24
+ end
25
+ }
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -27,7 +27,7 @@ module Roar
27
27
  # and updates properties accordingly.
28
28
  def post(url, format)
29
29
  # DISCUSS: what if a redirect happens here?
30
- document = http.post_uri(url, serialize(:links => false), format).body
30
+ document = http.post_uri(url, serialize, format).body
31
31
  deserialize(document)
32
32
  end
33
33
 
@@ -1,7 +1,7 @@
1
1
  module Roar
2
2
  module Representer
3
3
  module Feature
4
- # Adds #link to the representer to define hypermedia links in the representation.
4
+ # Define hypermedia links in your representations.
5
5
  #
6
6
  # Example:
7
7
  #
@@ -13,6 +13,12 @@ module Roar
13
13
  # link :self do
14
14
  # "http://orders/#{id}"
15
15
  # end
16
+ #
17
+ # If you want more attributes, just pass a hash to #link.
18
+ #
19
+ # link :rel => :next, :title => "Next, please!" do
20
+ # "http://orders/#{id}"
21
+ # end
16
22
  module Hypermedia
17
23
  def self.included(base)
18
24
  base.extend ClassMethods
@@ -34,14 +40,13 @@ module Roar
34
40
  protected
35
41
  # Setup hypermedia links by invoking their blocks. Usually called by #serialize.
36
42
  def prepare_links!
37
- links_def = find_links_definition or return
38
- hyperlink_class = links_def.sought_type
43
+ links_def = find_links_definition or return
39
44
 
40
- links_def.rel2block.each do |link|
41
- links.update_link(hyperlink_class.new.tap do |hyperlink| # create Hyperlink representer.
42
- hyperlink.rel = link[:rel]
43
- hyperlink.href = run_link_block(link[:block])
44
- end)
45
+ links_def.rel2block.each do |config| # config is [{..}, block]
46
+ options = config.first
47
+ options[:href] = run_link_block(config.last) or next
48
+
49
+ links.update_link(Feature::Hypermedia::Hyperlink.new(options))
45
50
  end
46
51
  end
47
52
 
@@ -56,7 +61,7 @@ module Roar
56
61
 
57
62
  class LinkCollection < Array
58
63
  def [](rel)
59
- link = find { |l| l.rel.to_s == rel.to_s } and return link.href
64
+ link = find { |l| l.rel.to_s == rel.to_s } and return link
60
65
  end
61
66
 
62
67
  # Checks if the link is already contained by querying for its +rel+.
@@ -81,18 +86,23 @@ module Roar
81
86
  #
82
87
  # The block is executed in instance context, so you may call properties or other accessors.
83
88
  # Note that you're free to put decider logic into #link blocks, too.
84
- def link(rel, &block)
85
- unless links = find_links_definition
86
- links = LinksDefinition.new(:links, links_definition_options)
87
- representable_attrs << links
88
- end
89
+ def link(options, &block)
90
+ links = find_links_definition || create_links
89
91
 
90
- links.rel2block << {:rel => rel, :block => block}
92
+ options = {:rel => options} if options.is_a?(Symbol)
93
+ links.rel2block << [options, block]
91
94
  end
92
95
 
93
96
  def find_links_definition
94
97
  representable_attrs.find { |d| d.is_a?(LinksDefinition) }
95
98
  end
99
+
100
+ private
101
+ def create_links
102
+ LinksDefinition.new(*links_definition_options).tap do |links|
103
+ representable_attrs << links
104
+ end
105
+ end
96
106
  end
97
107
 
98
108
 
@@ -102,6 +112,24 @@ module Roar
102
112
  @rel2block ||= []
103
113
  end
104
114
  end
115
+
116
+
117
+ # An abstract hypermedia link with +rel+, +href+ and other attributes.
118
+ # Overwrite the Hyperlink.params method if you need more link attributes.
119
+ class Hyperlink
120
+ def self.params
121
+ [:rel, :href, :media, :title, :hreflang]
122
+ end
123
+
124
+ attr_accessor *params
125
+
126
+ def initialize(options={})
127
+ options.each do |k,v|
128
+ next unless self.class.params.include?(k.to_sym)
129
+ instance_variable_set("@#{k}", v)
130
+ end
131
+ end
132
+ end
105
133
  end
106
134
  end
107
135
  end
@@ -1,4 +1,5 @@
1
1
  require 'roar/representer'
2
+ require 'roar/representer/feature/hypermedia'
2
3
  require 'representable/json'
3
4
 
4
5
  module Roar
@@ -21,7 +22,7 @@ module Roar
21
22
  end
22
23
 
23
24
  def from_json(document, options={})
24
- document ||= "{}" # DISCUSS: provide this for convenience, or better not?
25
+ document = '{}' if document.nil? or document.empty?
25
26
 
26
27
  super
27
28
  end
@@ -38,24 +39,23 @@ module Roar
38
39
 
39
40
 
40
41
  module ClassMethods
41
- def deserialize(json)
42
- from_json(json)
42
+ def deserialize(*args)
43
+ from_json(*args)
43
44
  end
44
45
 
45
46
  # TODO: move to instance method, or remove?
46
47
  def links_definition_options
47
- {:class => Hyperlink , :collection => true}
48
+ [:links, {:class => Feature::Hypermedia::Hyperlink, :extend => HyperlinkRepresenter, :collection => true}]
48
49
  end
49
50
  end
50
51
 
51
52
 
52
- # Encapsulates a hypermedia link.
53
- class Hyperlink
53
+ # Represents a hyperlink in standard roar+json.
54
+ module HyperlinkRepresenter
54
55
  include JSON
55
- attr_accessor :rel, :href
56
-
57
- property :rel
58
- property :href
56
+ Feature::Hypermedia::Hyperlink.params.each do |attr|
57
+ property attr
58
+ end
59
59
  end
60
60
  end
61
61
  end
@@ -0,0 +1,109 @@
1
+ module Roar::Representer
2
+ module JSON
3
+ # Including the JSON::HAL module in your representer will render and parse documents
4
+ # following the HAL specification: http://stateless.co/hal_specification.html
5
+ # Links will be embedded using the +_links+ key, nested resources with the +_embedded+ key.
6
+ #
7
+ # Embedded resources can be specified when calling #property or +collection using the
8
+ # :embedded => true option.
9
+ #
10
+ # Example:
11
+ #
12
+ # module OrderRepresenter
13
+ # include Roar::Representer::JSON::HAL
14
+ #
15
+ # property :id
16
+ # collection :items, :class => Item, :extend => ItemRepresenter, :embedded => true
17
+ #
18
+ # link :self do
19
+ # "http://orders/#{id}"
20
+ # end
21
+ # end
22
+ #
23
+ # Renders to
24
+ #
25
+ # "{\"id\":1,\"_embedded\":{\"items\":[{\"value\":\"Beer\",\"_links\":{\"self\":{\"href\":\"http://items/Beer\"}}}]},\"_links\":{\"self\":{\"href\":\"http://orders/1\"}}}"
26
+ module HAL
27
+ def self.included(base)
28
+ base.class_eval do
29
+ include Roar::Representer::JSON
30
+ include Links # overwrites #links_definition_options.
31
+ extend ClassMethods # overwrites #links_definition_options, again.
32
+ include Resources
33
+ end
34
+ end
35
+
36
+ module Resources
37
+ # Write the property to the +_embedded+ hash when it's a resource.
38
+ def compile_fragment(bin, doc)
39
+ return super unless bin.definition.options[:embedded]
40
+ super(bin, doc[:_embedded] ||= {})
41
+ end
42
+
43
+ def uncompile_fragment(bin, doc)
44
+ return super unless bin.definition.options[:embedded]
45
+ super(bin, doc["_embedded"])
46
+ end
47
+ end
48
+
49
+ module ClassMethods
50
+ def links_definition_options
51
+ super.tap { |options| options[1].merge!({:from => :_links}) }
52
+ end
53
+ end
54
+
55
+ # Including this module in your representer will render and parse your embedded hyperlinks
56
+ # following the HAL specification: http://stateless.co/hal_specification.html
57
+ #
58
+ # module SongRepresenter
59
+ # include Roar::Representer::JSON
60
+ # include Roar::Representer::JSON::HAL::Links
61
+ #
62
+ # link :self { "http://self" }
63
+ # end
64
+ #
65
+ # Renders to
66
+ #
67
+ # {"links":{"self":{"href":"http://self"}}}
68
+ #
69
+ # Note that the HAL::Links module alone doesn't prepend an underscore to +links+. Use the JSON::HAL module for that.
70
+ module Links
71
+ # TODO: allow more attributes besides :href in Hyperlink.
72
+ def self.included(base)
73
+ base.class_eval do
74
+ include Roar::Representer::Feature::Hypermedia
75
+ extend Links::ClassMethods
76
+ end
77
+ end
78
+
79
+
80
+ module LinkCollectionRepresenter
81
+ include JSON
82
+
83
+ def to_hash(*)
84
+ {}.tap do |hash|
85
+ each do |link|
86
+ # TODO: we statically use JSON::HyperlinkRepresenter here.
87
+ hash[link.rel] = link.extend(JSON::HyperlinkRepresenter).to_hash(:except => [:rel])
88
+ end
89
+ end
90
+ end
91
+
92
+ def from_hash(json, *)
93
+ json.each do |k, v|
94
+ self << Feature::Hypermedia::Hyperlink.new(v.merge :rel => k)
95
+ end
96
+ self
97
+ end
98
+ end
99
+
100
+
101
+ module ClassMethods
102
+ def links_definition_options
103
+ super.tap { |options| options[1] = {:class => Feature::Hypermedia::LinkCollection, :extend => LinkCollectionRepresenter} }
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
@@ -1,4 +1,5 @@
1
1
  require 'roar/representer'
2
+ require 'roar/representer/feature/hypermedia'
2
3
  require 'representable/xml'
3
4
 
4
5
  module Roar
@@ -37,7 +38,7 @@ module Roar
37
38
  include Representable::XML::ClassMethods
38
39
 
39
40
  def links_definition_options
40
- {:from => :link, :class => Hyperlink, :collection => true}
41
+ [:links, {:from => :link, :class => Feature::Hypermedia::Hyperlink, :collection => true, :extend => XML::HyperlinkRepresenter}]
41
42
  end
42
43
 
43
44
  # Generic entry-point for parsing.
@@ -46,19 +47,15 @@ module Roar
46
47
  end
47
48
  end
48
49
 
49
-
50
- # Encapsulates a hypermedia <link ...>.
51
- class Hyperlink
52
- # TODO: make XML a module to include in Hyperlink < Base.
53
- attr_accessor :rel, :href
50
+ module HyperlinkRepresenter
54
51
  include XML
55
52
 
56
53
  self.representation_wrap = :link
57
54
 
58
- property :rel, :from => "rel", :attribute => true
59
- property :href, :from => "href", :attribute => true
55
+ Feature::Hypermedia::Hyperlink.params.each do |attr|
56
+ property attr, :attribute => true
57
+ end
60
58
  end
61
-
62
59
  end
63
60
  end
64
61
  end
data/lib/roar/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Roar
2
- VERSION = "0.9.2"
2
+ VERSION = "0.10.0"
3
3
  end
data/roar.gemspec CHANGED
@@ -19,9 +19,11 @@ Gem::Specification.new do |s|
19
19
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
20
  s.require_paths = ["lib"]
21
21
 
22
- s.add_runtime_dependency "representable", "~> 1.1"
22
+ s.add_runtime_dependency "representable", ">= 1.1.3"
23
23
 
24
+ s.add_development_dependency "rake"
24
25
  s.add_development_dependency "test_xml"
25
26
  s.add_development_dependency "minitest", ">= 2.8.1"
26
27
  s.add_development_dependency "sinatra", "~> 1.2.6"
28
+ s.add_development_dependency "sham_rack", "~> 1.3.0"
27
29
  end
@@ -0,0 +1,24 @@
1
+ require 'test_helper'
2
+ require 'roar/representer/feature/client'
3
+
4
+ class ClientTest < MiniTest::Spec
5
+ describe "Client" do
6
+ before do
7
+ @representer = Module.new do
8
+ include Roar::Representer
9
+ property :name
10
+ property :band
11
+ end
12
+
13
+ @song = Object.new.extend(@representer)
14
+ end
15
+
16
+ it "should add accessors" do
17
+ @song.extend Roar::Representer::Feature::Client
18
+ @song.name = "Social Suicide"
19
+ @song.band = "Bad Religion"
20
+ assert_equal "Social Suicide", @song.name
21
+ assert_equal "Bad Religion", @song.band
22
+ end
23
+ end
24
+ end
data/test/fake_server.rb CHANGED
@@ -36,13 +36,13 @@ class FakeServer < Sinatra::Base
36
36
  end
37
37
 
38
38
 
39
- require Dir.pwd + '/order_representers'
39
+ require './test/order_representers'
40
40
  JSON::Order.class_eval do
41
41
  def items_url
42
- "http://localhost:9999/orders/1/items"
42
+ "http://roar.example.com/orders/1/items"
43
43
  end
44
44
  def order_url(order)
45
- "http://localhost:9999/orders/#{order}"
45
+ "http://roar.example.com/orders/#{order}"
46
46
  end
47
47
  def represented
48
48
  1
@@ -72,4 +72,3 @@ class FakeServer < Sinatra::Base
72
72
 
73
73
  end
74
74
 
75
- FakeServer.run! :host => 'localhost', :port => 9999
@@ -0,0 +1,69 @@
1
+ require 'roar/representer/json/hal'
2
+
3
+ class HalJsonTest < MiniTest::Spec
4
+ module SongRepresenter
5
+ include Roar::Representer::JSON
6
+ include Roar::Representer::JSON::HAL::Links
7
+
8
+ link :self do
9
+ "http://self"
10
+ end
11
+
12
+ link :rel => :next, :title => "Hey, @myabc" do
13
+ "http://hit"
14
+ end
15
+ end
16
+
17
+ describe "JSON::HAL::Links" do
18
+ before do
19
+ @song = Object.new.extend(SongRepresenter)
20
+ end
21
+
22
+ it "renders links according to the HAL spec" do
23
+ assert_equal "{\"links\":{\"self\":{\"href\":\"http://self\"},\"next\":{\"href\":\"http://hit\",\"title\":\"Hey, @myabc\"}}}", @song.to_json
24
+ end
25
+
26
+ it "parses incoming JSON links correctly" do
27
+ @song.from_json "{\"links\":{\"self\":{\"href\":\"http://self\",\"title\":\"Hey, @myabc\"}}}"
28
+ assert_equal "http://self", @song.links[:self].href
29
+ assert_equal "Hey, @myabc", @song.links[:self].title
30
+ assert_equal nil, @song.links[:next]
31
+ end
32
+ end
33
+
34
+
35
+ describe "HAL/JSON" do
36
+ before do
37
+ Bla = Module.new do
38
+ include Roar::Representer::JSON::HAL
39
+ property :value
40
+ link :self do
41
+ "http://items/#{value}"
42
+ end
43
+ end
44
+
45
+ @order_rep = Module.new do
46
+ include Roar::Representer::JSON::HAL
47
+ property :id
48
+ collection :items, :class => Item, :extend => Bla, :embedded => true
49
+ link :self do
50
+ "http://orders/#{id}"
51
+ end
52
+ end
53
+
54
+ @order = Order.new(:items => [Item.new(:value => "Beer")], :id => 1).extend(@order_rep)
55
+ end
56
+
57
+ it "render links and embedded resources according to HAL" do
58
+ assert_equal "{\"id\":1,\"_embedded\":{\"items\":[{\"value\":\"Beer\",\"_links\":{\"self\":{\"href\":\"http://items/Beer\"}}}]},\"_links\":{\"self\":{\"href\":\"http://orders/1\"}}}", @order.to_json
59
+ end
60
+
61
+ it "parses links and resources following the mighty HAL" do
62
+ @order.from_json("{\"id\":2,\"_embedded\":{\"items\":[{\"value\":\"Coffee\",\"_links\":{\"self\":{\"href\":\"http://items/Coffee\"}}}]},\"_links\":{\"self\":{\"href\":\"http://orders/2\"}}}")
63
+ assert_equal 2, @order.id
64
+ assert_equal "Coffee", @order.items.first.value
65
+ assert_equal "http://items/Coffee", @order.items.first.links[:self].href
66
+ assert_equal "http://orders/2", @order.links[:self].href
67
+ end
68
+ end
69
+ end
@@ -32,7 +32,7 @@ class HttpVerbsTest < MiniTest::Spec
32
32
  include Roar::Representer::Feature::HttpVerbs
33
33
  attr_accessor :name, :label
34
34
  end
35
- @band = @Band.get("http://localhost:9999/bands/slayer", "application/json")
35
+ @band = @Band.get("http://roar.example.com/bands/slayer", "application/json")
36
36
  assert_equal "Slayer", @band.name
37
37
  assert_equal "Canadian Maple", @band.label
38
38
  end
@@ -40,7 +40,7 @@ class HttpVerbsTest < MiniTest::Spec
40
40
 
41
41
  describe "#get" do
42
42
  it "updates instance with incoming representation" do
43
- @band.get("http://localhost:9999/bands/slayer", "application/json")
43
+ @band.get("http://roar.example.com/bands/slayer", "application/json")
44
44
  assert_equal "Slayer", @band.name
45
45
  assert_equal "Canadian Maple", @band.label
46
46
  end
@@ -51,7 +51,7 @@ class HttpVerbsTest < MiniTest::Spec
51
51
  @band.name = "Strung Out"
52
52
  assert_equal nil, @band.label
53
53
 
54
- @band.post("http://localhost:9999/bands", "application/xml")
54
+ @band.post("http://roar.example.com/bands", "application/xml")
55
55
  assert_equal "Strung Out", @band.name
56
56
  assert_equal "n/a", @band.label
57
57
  end
@@ -61,7 +61,7 @@ class HttpVerbsTest < MiniTest::Spec
61
61
  it "updates instance with incoming representation" do
62
62
  @band.name = "Strung Out"
63
63
  @band.label = "Fat Wreck"
64
- @band.put("http://localhost:9999/bands/strungout", "application/xml")
64
+ @band.put("http://roar.example.com/bands/strungout", "application/xml")
65
65
  assert_equal "Strung Out", @band.name
66
66
  assert_equal "Fat Wreck", @band.label
67
67
  end
@@ -4,6 +4,36 @@ require 'roar/representer/json'
4
4
 
5
5
  class HypermediaTest
6
6
  describe "Hypermedia Feature" do
7
+ describe "Hypermedia.link" do
8
+ before do
9
+ @mod = Module.new do
10
+ include Roar::Representer::JSON
11
+ include Roar::Representer::Feature::Hypermedia
12
+ end
13
+ end
14
+
15
+ it "accepts rel symbol, only" do
16
+ @mod.class_eval do
17
+ link :self do
18
+ "http://self"
19
+ end
20
+ end
21
+
22
+ assert_equal "{\"links\":[{\"rel\":\"self\",\"href\":\"http://self\"}]}", Object.new.extend(@mod).to_json
23
+ end
24
+
25
+ it "accepts any options" do
26
+ @mod.class_eval do
27
+ link :rel => :self, :title => "Hey, @myabc" do
28
+ "http://self"
29
+ end
30
+ end
31
+
32
+ assert_equal "{\"links\":[{\"rel\":\"self\",\"href\":\"http://self\",\"title\":\"Hey, @myabc\"}]}", Object.new.extend(@mod).to_json
33
+ end
34
+ end
35
+
36
+
7
37
  before do
8
38
  @bookmarks = Class.new do
9
39
  include AttributesContructor
@@ -67,7 +97,7 @@ class HypermediaTest
67
97
  attr_accessor :note
68
98
  end
69
99
 
70
- assert_equal "{\"note\":{\"links\":[{\"rel\":\"self\",\"href\":\"http://me\"}]}}", Page.new(note: Note.new).to_json
100
+ assert_equal "{\"note\":{\"links\":[{\"rel\":\"self\",\"href\":\"http://me\"}]}}", Page.new(:note => Note.new).to_json
71
101
  end
72
102
  end
73
103
 
@@ -95,7 +125,7 @@ class HypermediaTest
95
125
  describe "#links" do
96
126
  before do
97
127
  @set = @bookmarks.new
98
- hyper = Roar::Representer::XML::Hyperlink
128
+ hyper = Roar::Representer::Feature::Hypermedia::Hyperlink
99
129
 
100
130
  @set.links = [
101
131
  {:rel => "self", :href => "http://self"},
@@ -115,10 +145,10 @@ class HypermediaTest
115
145
  end
116
146
 
117
147
  describe "#link[]" do
118
- it "provides shorthand accessor for rels" do
119
- assert_equal "http://self", @set.links["self"]
120
- assert_equal "http://self", @set.links[:self]
121
- assert_equal "http://next", @set.links[:next]
148
+ it "returns link object" do
149
+ assert_equal "http://self", @set.links["self"].href
150
+ assert_equal "http://self", @set.links[:self].href
151
+ assert_equal "http://next", @set.links[:next].href
122
152
  assert_equal nil, @set.links[:prev]
123
153
  end
124
154
  end
@@ -171,7 +201,9 @@ class LinkCollectionTest < MiniTest::Spec
171
201
  describe "LinkCollection" do
172
202
  it "provides #update_link" do
173
203
  collection = Roar::Representer::Feature::Hypermedia::LinkCollection.new
174
- link = Roar::Representer::XML::Hyperlink.new(rel: "self", href: "http://self")
204
+ link = Roar::Representer::Feature::Hypermedia::Hyperlink.new
205
+ link.rel = "self"
206
+ link.href = "http://self"
175
207
 
176
208
  collection.update_link(link)
177
209
  assert_equal 1, collection.size
@@ -182,3 +214,27 @@ class LinkCollectionTest < MiniTest::Spec
182
214
  end
183
215
  end
184
216
 
217
+ class HyperlinkTest < MiniTest::Spec
218
+ Hyperlink = Roar::Representer::Feature::Hypermedia::Hyperlink
219
+ describe "Hyperlink" do
220
+ before do
221
+ @link = Hyperlink.new(:rel => "self", :href => "http://self")
222
+ end
223
+
224
+ it "accepts string keys in constructor" do
225
+ assert_equal "Hey, @myabc", Hyperlink.new("title" => "Hey, @myabc").title
226
+ end
227
+
228
+ it "responds to #media" do
229
+ assert_equal nil, @link.media
230
+ end
231
+
232
+ it "responds to #rel" do
233
+ assert_equal "self", @link.rel
234
+ end
235
+
236
+ it "responds to #href" do
237
+ assert_equal "http://self", @link.href
238
+ end
239
+ end
240
+ end
@@ -30,6 +30,10 @@ class JsonRepresenterTest < MiniTest::Spec
30
30
  it "is aliased by #serialize" do
31
31
  assert_equal '{"id":1}', @order.serialize
32
32
  end
33
+
34
+ it "accepts :include and :except" do
35
+ assert_equal '{}', @order.to_json(:except => [:id])
36
+ end
33
37
  end
34
38
 
35
39
  describe "#from_json" do
@@ -46,6 +50,15 @@ class JsonRepresenterTest < MiniTest::Spec
46
50
  it "works with a nil document" do
47
51
  assert @order.from_json(nil)
48
52
  end
53
+
54
+ it "works with an empty document" do
55
+ assert @order.from_json('')
56
+ end
57
+
58
+ it "accepts :include and :except" do
59
+ @order.from_json('{"id":1}', :except => [:id])
60
+ assert_equal nil, @order.id
61
+ end
49
62
  end
50
63
 
51
64
  describe "JSON.from_json" do
@@ -57,18 +70,27 @@ class JsonRepresenterTest < MiniTest::Spec
57
70
  end
58
71
  end
59
72
 
73
+ # test the generic roar+json HyperlinkRepresenter
60
74
  class JsonHyperlinkRepresenterTest
61
75
  describe "API" do
62
76
  before do
63
- @l = Roar::Representer::JSON::Hyperlink.from_json({:rel => :self, :href => "http://roar.apotomo.de"}.to_json)
77
+ @link = Roar::Representer::Feature::Hypermedia::Hyperlink.new.extend(Roar::Representer::JSON::HyperlinkRepresenter).from_json({:rel => :self, :href => "http://roar.apotomo.de", :media => "web"}.to_json)
64
78
  end
65
79
 
66
80
  it "responds to #rel" do
67
- assert_equal "self", @l.rel
81
+ assert_equal "self", @link.rel
68
82
  end
69
83
 
70
84
  it "responds to #href" do
71
- assert_equal "http://roar.apotomo.de", @l.href
85
+ assert_equal "http://roar.apotomo.de", @link.href
86
+ end
87
+
88
+ it "responds to #media" do
89
+ assert_equal "web", @link.media
90
+ end
91
+
92
+ it "responds to #to_json" do
93
+ assert_equal "{\"rel\":\"self\",\"href\":\"http://roar.apotomo.de\",\"media\":\"web\"}", @link.to_json
72
94
  end
73
95
  end
74
96
  end
@@ -105,6 +127,17 @@ class JsonHypermediaTest
105
127
  it "renders link: correctly in JSON" do
106
128
  assert_equal "{\"id\":1,\"links\":[{\"rel\":\"self\",\"href\":\"http://self\"},{\"rel\":\"next\",\"href\":\"http://next/1\"}]}", @c.new(:id => 1).to_json
107
129
  end
130
+
131
+ it "doesn't render links when empty" do
132
+ assert_equal("{\"links\":[]}", Class.new do
133
+ include Roar::Representer::JSON
134
+ include Roar::Representer::Feature::Hypermedia
135
+
136
+ link :self do nil end
137
+ link :next do false end
138
+ end.new.to_json)
139
+ end
140
+
108
141
  end
109
142
  end
110
143
 
data/test/test_helper.rb CHANGED
@@ -5,7 +5,6 @@ require 'test/unit'
5
5
  require 'minitest/spec'
6
6
 
7
7
  require 'roar/representer'
8
- require 'roar/representer/feature/hypermedia'
9
8
  require 'roar/representer/feature/http_verbs'
10
9
 
11
10
 
@@ -34,3 +33,10 @@ end
34
33
 
35
34
  require "test_xml/mini_test"
36
35
  require "roar/representer/xml"
36
+
37
+ require 'sham_rack'
38
+ require './test/fake_server'
39
+
40
+ ShamRack.at('roar.example.com').rackup do
41
+ run FakeServer
42
+ end
@@ -8,24 +8,24 @@ class TransportTest < MiniTest::Spec
8
8
  end
9
9
 
10
10
  it "#get_uri returns response" do
11
- assert_equal "<method>get</method>", @transport.get_uri("http://localhost:9999/method", "application/xml").body
11
+ assert_equal "<method>get</method>", @transport.get_uri("http://roar.example.com/method", "application/xml").body
12
12
  end
13
13
 
14
14
  it "#post_uri returns response" do
15
- assert_equal "<method>post</method>", @transport.post_uri("http://localhost:9999/method", "booty", "application/xml").body
15
+ assert_equal "<method>post</method>", @transport.post_uri("http://roar.example.com/method", "booty", "application/xml").body
16
16
  end
17
17
 
18
18
  it "#put_uri returns response" do
19
- assert_equal "<method>put</method>", @transport.put_uri("http://localhost:9999/method", "booty", "application/xml").body
19
+ assert_equal "<method>put</method>", @transport.put_uri("http://roar.example.com/method", "booty", "application/xml").body
20
20
  end
21
21
 
22
22
  it "#delete_uri returns response" do
23
- assert_equal "<method>delete</method>", @transport.delete_uri("http://localhost:9999/method", "application/xml").body
23
+ assert_equal "<method>delete</method>", @transport.delete_uri("http://roar.example.com/method", "application/xml").body
24
24
  end
25
25
 
26
26
  # TODO: how to get PATCH into Sinatra?
27
27
  #it "#patch_uri returns Restfulie response" do
28
- # assert_equal "<method>patch</method>", @o.patch_uri("http://localhost:9999/method", "booty", "application/xml").body
28
+ # assert_equal "<method>patch</method>", @o.patch_uri("http://roar.example.com/method", "booty", "application/xml").body
29
29
  #end
30
30
  end
31
31
  end
@@ -18,34 +18,15 @@ class PositionRepresenter
18
18
  end
19
19
 
20
20
 
21
-
22
-
23
- class XMLRepresenterUnitTest < MiniTest::Spec
24
- describe "XmlRepresenter" do
25
- describe "#link" do
26
- class Rapper
27
- include Roar::Representer::XML
28
- include Roar::Representer::Feature::Hypermedia
29
-
30
- link :self
31
- link :next
32
- end
33
-
34
- it "creates a LinksDefinition" do
35
- assert_equal 1, Rapper.representable_attrs.size
36
- assert_equal [{:rel=>:self, :block=>nil}, {:rel=>:next, :block=>nil}], Rapper.representable_attrs.first.rel2block
37
- end
38
- end
39
- end
40
- end
41
-
42
-
43
21
  class XMLRepresenterFunctionalTest < MiniTest::Spec
44
- class GreedyOrder
22
+ class Order
45
23
  include AttributesContructor
46
24
  attr_accessor :id, :items
47
25
  end
48
26
 
27
+ class GreedyOrder < Order
28
+ end
29
+
49
30
  class TestXmlRepresenter
50
31
  include Roar::Representer::XML
51
32
  self.representation_wrap= :order
@@ -58,6 +39,7 @@ class XMLRepresenterFunctionalTest < MiniTest::Spec
58
39
  before do
59
40
  @m = {"id" => "1"}
60
41
  @o = Order.new(@m)
42
+
61
43
  @r = TestXmlRepresenter.new
62
44
  @i = ItemRepresenter.new
63
45
  @i.value = "Beer"
@@ -77,6 +59,10 @@ class XMLRepresenterFunctionalTest < MiniTest::Spec
77
59
  it "is aliased by #serialize" do
78
60
  assert_equal @r.to_xml, @r.serialize
79
61
  end
62
+
63
+ it "accepts :include and :except" do
64
+ assert_equal '<order/>', @r.to_xml(:except => [:id])
65
+ end
80
66
  end
81
67
 
82
68
  describe "#from_xml" do
@@ -89,6 +75,11 @@ class XMLRepresenterFunctionalTest < MiniTest::Spec
89
75
  @order = Order.new.deserialize("<order><id>1</id></order>")
90
76
  assert_equal "1", @order.id
91
77
  end
78
+
79
+ it "accepts :include and :except" do
80
+ @order = Order.new.deserialize("<order><id>1</id></order>", :except => [:id])
81
+ assert_equal nil, @order.id
82
+ end
92
83
  end
93
84
 
94
85
 
@@ -195,20 +186,23 @@ end
195
186
  class XmlHyperlinkRepresenterTest < MiniTest::Spec
196
187
  describe "API" do
197
188
  before do
198
- @l = Roar::Representer::XML::Hyperlink.from_xml(%{<link rel="self" href="http://roar.apotomo.de"/>})
189
+ @link = Roar::Representer::Feature::Hypermedia::Hyperlink.new.extend(Roar::Representer::XML::HyperlinkRepresenter).from_xml(%{<link rel="self" href="http://roar.apotomo.de" media="web"/>})
199
190
  end
200
191
 
201
- it "responds to #to_xml" do
202
- assert_xml_equal %{<link rel=\"self\" href=\"http://roar.apotomo.de\"/>}, @l.to_xml
192
+ it "responds to #rel" do
193
+ assert_equal "self", @link.rel
203
194
  end
204
195
 
196
+ it "responds to #href" do
197
+ assert_equal "http://roar.apotomo.de", @link.href
198
+ end
205
199
 
206
- it "responds to #rel" do
207
- assert_equal "self", @l.rel
200
+ it "responds to #media" do
201
+ assert_equal "web", @link.media
208
202
  end
209
203
 
210
- it "responds to #href" do
211
- assert_equal "http://roar.apotomo.de", @l.href
204
+ it "responds to #to_xml" do
205
+ assert_xml_equal %{<link rel=\"self\" href=\"http://roar.apotomo.de\" media="web"/>}, @link.to_xml
212
206
  end
213
207
  end
214
208
  end
metadata CHANGED
@@ -1,90 +1,92 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: roar
3
- version: !ruby/object:Gem::Version
4
- prerelease: false
5
- segments:
6
- - 0
7
- - 9
8
- - 2
9
- version: 0.9.2
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.10.0
5
+ prerelease:
10
6
  platform: ruby
11
- authors:
7
+ authors:
12
8
  - Nick Sutterer
13
9
  autorequire:
14
10
  bindir: bin
15
11
  cert_chain: []
16
-
17
- date: 2012-02-06 00:00:00 +01:00
18
- default_executable:
19
- dependencies:
20
- - !ruby/object:Gem::Dependency
12
+ date: 2012-03-22 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
21
15
  name: representable
22
- prerelease: false
23
- requirement: &id001 !ruby/object:Gem::Requirement
16
+ requirement: &82244490 !ruby/object:Gem::Requirement
24
17
  none: false
25
- requirements:
26
- - - ~>
27
- - !ruby/object:Gem::Version
28
- segments:
29
- - 1
30
- - 1
31
- version: "1.1"
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 1.1.3
32
22
  type: :runtime
33
- version_requirements: *id001
34
- - !ruby/object:Gem::Dependency
35
- name: test_xml
36
23
  prerelease: false
37
- requirement: &id002 !ruby/object:Gem::Requirement
24
+ version_requirements: *82244490
25
+ - !ruby/object:Gem::Dependency
26
+ name: rake
27
+ requirement: &82244190 !ruby/object:Gem::Requirement
38
28
  none: false
39
- requirements:
40
- - - ">="
41
- - !ruby/object:Gem::Version
42
- segments:
43
- - 0
44
- version: "0"
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
45
33
  type: :development
46
- version_requirements: *id002
47
- - !ruby/object:Gem::Dependency
48
- name: minitest
49
34
  prerelease: false
50
- requirement: &id003 !ruby/object:Gem::Requirement
35
+ version_requirements: *82244190
36
+ - !ruby/object:Gem::Dependency
37
+ name: test_xml
38
+ requirement: &82243710 !ruby/object:Gem::Requirement
51
39
  none: false
52
- requirements:
53
- - - ">="
54
- - !ruby/object:Gem::Version
55
- segments:
56
- - 2
57
- - 8
58
- - 1
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *82243710
47
+ - !ruby/object:Gem::Dependency
48
+ name: minitest
49
+ requirement: &82243040 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
59
54
  version: 2.8.1
60
55
  type: :development
61
- version_requirements: *id003
62
- - !ruby/object:Gem::Dependency
63
- name: sinatra
64
56
  prerelease: false
65
- requirement: &id004 !ruby/object:Gem::Requirement
57
+ version_requirements: *82243040
58
+ - !ruby/object:Gem::Dependency
59
+ name: sinatra
60
+ requirement: &82242340 !ruby/object:Gem::Requirement
66
61
  none: false
67
- requirements:
62
+ requirements:
68
63
  - - ~>
69
- - !ruby/object:Gem::Version
70
- segments:
71
- - 1
72
- - 2
73
- - 6
64
+ - !ruby/object:Gem::Version
74
65
  version: 1.2.6
75
66
  type: :development
76
- version_requirements: *id004
77
- description: Streamlines the development of RESTful, resource-oriented architectures in Ruby.
78
- email:
67
+ prerelease: false
68
+ version_requirements: *82242340
69
+ - !ruby/object:Gem::Dependency
70
+ name: sham_rack
71
+ requirement: &82241770 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ~>
75
+ - !ruby/object:Gem::Version
76
+ version: 1.3.0
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: *82241770
80
+ description: Streamlines the development of RESTful, resource-oriented architectures
81
+ in Ruby.
82
+ email:
79
83
  - apotonick@gmail.com
80
84
  executables: []
81
-
82
85
  extensions: []
83
-
84
86
  extra_rdoc_files: []
85
-
86
- files:
87
+ files:
87
88
  - .gitignore
89
+ - .travis.yml
88
90
  - CHANGES.markdown
89
91
  - Gemfile
90
92
  - README.textile
@@ -93,14 +95,17 @@ files:
93
95
  - lib/roar.rb
94
96
  - lib/roar/rails.rb
95
97
  - lib/roar/representer.rb
98
+ - lib/roar/representer/feature/client.rb
96
99
  - lib/roar/representer/feature/http_verbs.rb
97
100
  - lib/roar/representer/feature/hypermedia.rb
98
101
  - lib/roar/representer/feature/transport.rb
99
102
  - lib/roar/representer/json.rb
103
+ - lib/roar/representer/json/hal.rb
100
104
  - lib/roar/representer/xml.rb
101
105
  - lib/roar/version.rb
102
106
  - roar.gemspec
103
107
  - test/Gemfile
108
+ - test/client_test.rb
104
109
  - test/dummy/Rakefile
105
110
  - test/dummy/app/controllers/albums_controller.rb
106
111
  - test/dummy/app/controllers/application_controller.rb
@@ -137,6 +142,7 @@ files:
137
142
  - test/dummy/tmp/app/cells/blog/post/latest.html.erb
138
143
  - test/dummy/tmp/app/cells/blog/post_cell.rb
139
144
  - test/fake_server.rb
145
+ - test/hal_json_test.rb
140
146
  - test/http_verbs_feature_test.rb
141
147
  - test/hypermedia_feature_test.rb
142
148
  - test/integration_test.rb
@@ -148,37 +154,28 @@ files:
148
154
  - test/test_helper.rb
149
155
  - test/transport_test.rb
150
156
  - test/xml_representer_test.rb
151
- has_rdoc: true
152
157
  homepage: http://rubygems.org/gems/roar
153
158
  licenses: []
154
-
155
159
  post_install_message:
156
160
  rdoc_options: []
157
-
158
- require_paths:
161
+ require_paths:
159
162
  - lib
160
- required_ruby_version: !ruby/object:Gem::Requirement
163
+ required_ruby_version: !ruby/object:Gem::Requirement
161
164
  none: false
162
- requirements:
163
- - - ">="
164
- - !ruby/object:Gem::Version
165
- segments:
166
- - 0
167
- version: "0"
168
- required_rubygems_version: !ruby/object:Gem::Requirement
165
+ requirements:
166
+ - - ! '>='
167
+ - !ruby/object:Gem::Version
168
+ version: '0'
169
+ required_rubygems_version: !ruby/object:Gem::Requirement
169
170
  none: false
170
- requirements:
171
- - - ">="
172
- - !ruby/object:Gem::Version
173
- segments:
174
- - 0
175
- version: "0"
171
+ requirements:
172
+ - - ! '>='
173
+ - !ruby/object:Gem::Version
174
+ version: '0'
176
175
  requirements: []
177
-
178
176
  rubyforge_project: roar
179
- rubygems_version: 1.3.7
177
+ rubygems_version: 1.8.10
180
178
  signing_key:
181
179
  specification_version: 3
182
180
  summary: Resource-oriented architectures in Ruby.
183
181
  test_files: []
184
-