roar 0.9.2 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.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
-