roar 0.0.1.alpha1 → 0.8.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.
Files changed (77) hide show
  1. data/.gitignore +1 -0
  2. data/Gemfile +8 -0
  3. data/README.textile +297 -0
  4. data/Rakefile +16 -0
  5. data/lib/roar/client/entity_proxy.rb +58 -0
  6. data/lib/roar/client/proxy.rb +14 -0
  7. data/lib/roar/client/transport.rb +29 -0
  8. data/lib/roar/model.rb +36 -0
  9. data/lib/roar/model/representable.rb +31 -0
  10. data/lib/roar/rails.rb +21 -0
  11. data/lib/roar/rails/controller_methods.rb +71 -0
  12. data/lib/roar/rails/representer_methods.rb +52 -0
  13. data/lib/roar/rails/test_case.rb +43 -0
  14. data/lib/roar/representer.rb +72 -0
  15. data/lib/roar/representer/feature/http_verbs.rb +63 -0
  16. data/lib/roar/representer/feature/hypermedia.rb +43 -0
  17. data/lib/roar/representer/feature/model_representing.rb +88 -0
  18. data/lib/roar/representer/json.rb +32 -0
  19. data/lib/roar/representer/xml.rb +43 -0
  20. data/lib/roar/version.rb +1 -1
  21. data/roar.gemspec +10 -1
  22. data/test/Gemfile +6 -0
  23. data/test/dummy/Rakefile +7 -0
  24. data/test/dummy/app/controllers/albums_controller.rb +27 -0
  25. data/test/dummy/app/controllers/application_controller.rb +4 -0
  26. data/test/dummy/app/helpers/application_helper.rb +2 -0
  27. data/test/dummy/app/models/album.rb +6 -0
  28. data/test/dummy/app/models/song.rb +2 -0
  29. data/test/dummy/app/representers/representer/xml/album.rb +19 -0
  30. data/test/dummy/app/representers/representer/xml/song.rb +9 -0
  31. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  32. data/test/dummy/app/views/musician/featured.html.erb +1 -0
  33. data/test/dummy/app/views/musician/featured_with_block.html.erb +4 -0
  34. data/test/dummy/app/views/musician/hamlet.html.haml +1 -0
  35. data/test/dummy/config.ru +4 -0
  36. data/test/dummy/config/application.rb +20 -0
  37. data/test/dummy/config/boot.rb +10 -0
  38. data/test/dummy/config/database.yml +22 -0
  39. data/test/dummy/config/environment.rb +5 -0
  40. data/test/dummy/config/environments/development.rb +16 -0
  41. data/test/dummy/config/environments/production.rb +46 -0
  42. data/test/dummy/config/environments/test.rb +32 -0
  43. data/test/dummy/config/locales/en.yml +5 -0
  44. data/test/dummy/config/routes.rb +7 -0
  45. data/test/dummy/db/development.sqlite3 +0 -0
  46. data/test/dummy/db/migrate/20110514114753_create_albums.rb +14 -0
  47. data/test/dummy/db/migrate/20110514121228_create_songs.rb +14 -0
  48. data/test/dummy/db/schema.rb +29 -0
  49. data/test/dummy/db/test.sqlite3 +0 -0
  50. data/test/dummy/module - (2011-05-14 15:26:19) +5 -0
  51. data/test/dummy/public/404.html +26 -0
  52. data/test/dummy/public/422.html +26 -0
  53. data/test/dummy/public/500.html +26 -0
  54. data/test/dummy/public/favicon.ico +0 -0
  55. data/test/dummy/script/rails +6 -0
  56. data/test/dummy/tmp/app/cells/blog/post/latest.html.erb +7 -0
  57. data/test/dummy/tmp/app/cells/blog/post_cell.rb +7 -0
  58. data/test/fake_server.rb +80 -0
  59. data/test/http_verbs_test.rb +46 -0
  60. data/test/hypermedia_test.rb +35 -0
  61. data/test/integration_test.rb +122 -0
  62. data/test/json_representer_test.rb +101 -0
  63. data/test/model_representing_test.rb +121 -0
  64. data/test/model_test.rb +50 -0
  65. data/test/order_representers.rb +34 -0
  66. data/test/proxy_test.rb +89 -0
  67. data/test/rails/controller_methods_test.rb +147 -0
  68. data/test/rails/rails_representer_methods_test.rb +32 -0
  69. data/test/representable_test.rb +49 -0
  70. data/test/representer_test.rb +25 -0
  71. data/test/ruby_representation_test.rb +144 -0
  72. data/test/test_helper.rb +45 -0
  73. data/test/test_helper_test.rb +59 -0
  74. data/test/transport_test.rb +34 -0
  75. data/test/xml_hypermedia_test.rb +47 -0
  76. data/test/xml_representer_test.rb +238 -0
  77. metadata +181 -13
@@ -0,0 +1,29 @@
1
+ # This file is auto-generated from the current state of the database. Instead
2
+ # of editing this file, please use the migrations feature of Active Record to
3
+ # incrementally modify your database, and then regenerate this schema definition.
4
+ #
5
+ # Note that this schema.rb definition is the authoritative source for your
6
+ # database schema. If you need to create the application database on another
7
+ # system, you should be using db:schema:load, not running all the migrations
8
+ # from scratch. The latter is a flawed and unsustainable approach (the more migrations
9
+ # you'll amass, the slower it'll run and the greater likelihood for issues).
10
+ #
11
+ # It's strongly recommended to check this file into your version control system.
12
+
13
+ ActiveRecord::Schema.define(:version => 20110514121228) do
14
+
15
+ create_table "albums", :force => true do |t|
16
+ t.string "title"
17
+ t.string "year"
18
+ t.datetime "created_at"
19
+ t.datetime "updated_at"
20
+ end
21
+
22
+ create_table "songs", :force => true do |t|
23
+ t.string "title"
24
+ t.integer "album_id"
25
+ t.datetime "created_at"
26
+ t.datetime "updated_at"
27
+ end
28
+
29
+ end
File without changes
@@ -0,0 +1,5 @@
1
+ module Representer
2
+ module CSV
3
+ class AlbumRepresenter; end
4
+ end
5
+ end
@@ -0,0 +1,26 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>The page you were looking for doesn't exist (404)</title>
5
+ <style type="text/css">
6
+ body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
7
+ div.dialog {
8
+ width: 25em;
9
+ padding: 0 4em;
10
+ margin: 4em auto 0 auto;
11
+ border: 1px solid #ccc;
12
+ border-right-color: #999;
13
+ border-bottom-color: #999;
14
+ }
15
+ h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
16
+ </style>
17
+ </head>
18
+
19
+ <body>
20
+ <!-- This file lives in public/404.html -->
21
+ <div class="dialog">
22
+ <h1>The page you were looking for doesn't exist.</h1>
23
+ <p>You may have mistyped the address or the page may have moved.</p>
24
+ </div>
25
+ </body>
26
+ </html>
@@ -0,0 +1,26 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>The change you wanted was rejected (422)</title>
5
+ <style type="text/css">
6
+ body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
7
+ div.dialog {
8
+ width: 25em;
9
+ padding: 0 4em;
10
+ margin: 4em auto 0 auto;
11
+ border: 1px solid #ccc;
12
+ border-right-color: #999;
13
+ border-bottom-color: #999;
14
+ }
15
+ h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
16
+ </style>
17
+ </head>
18
+
19
+ <body>
20
+ <!-- This file lives in public/422.html -->
21
+ <div class="dialog">
22
+ <h1>The change you wanted was rejected.</h1>
23
+ <p>Maybe you tried to change something you didn't have access to.</p>
24
+ </div>
25
+ </body>
26
+ </html>
@@ -0,0 +1,26 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>We're sorry, but something went wrong (500)</title>
5
+ <style type="text/css">
6
+ body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
7
+ div.dialog {
8
+ width: 25em;
9
+ padding: 0 4em;
10
+ margin: 4em auto 0 auto;
11
+ border: 1px solid #ccc;
12
+ border-right-color: #999;
13
+ border-bottom-color: #999;
14
+ }
15
+ h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
16
+ </style>
17
+ </head>
18
+
19
+ <body>
20
+ <!-- This file lives in public/500.html -->
21
+ <div class="dialog">
22
+ <h1>We're sorry, but something went wrong.</h1>
23
+ <p>We've been notified about this issue and we'll take a look at it shortly.</p>
24
+ </div>
25
+ </body>
26
+ </html>
File without changes
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
3
+
4
+ APP_PATH = File.expand_path('../../config/application', __FILE__)
5
+ require File.expand_path('../../config/boot', __FILE__)
6
+ require 'rails/commands'
@@ -0,0 +1,7 @@
1
+ <h1>
2
+ Blog::Post#latest
3
+ </h1>
4
+
5
+ <p>
6
+ Find me in app/cells/blog/post/latest.html.erb
7
+ </p>
@@ -0,0 +1,7 @@
1
+ class Blog::PostCell < Cell::Rails
2
+
3
+ def latest
4
+ render
5
+ end
6
+
7
+ end
@@ -0,0 +1,80 @@
1
+ require "bundler/setup"
2
+ require 'sinatra/base'
3
+ require 'sinatra/reloader'
4
+
5
+
6
+ class FakeServer < Sinatra::Base
7
+ get "/method" do
8
+ "<method>get</method>"
9
+ end
10
+
11
+ post "/method" do
12
+ "<method>post</method>"
13
+ end
14
+
15
+ put "/method" do
16
+ "<method>put</method>"
17
+ end
18
+
19
+ delete "/method" do
20
+ "<method>delete</method>"
21
+ end
22
+
23
+ #patch "/method" do
24
+ # "<method>patch</method>"
25
+ #end
26
+
27
+ post "/band" do
28
+ if request.content_type =~ /xml/
29
+ %{<band><label>n/a</label><name>Strung Out</name>
30
+ <link href="http://search" rel="search" />
31
+ <link href="http://band/strungout" rel="self" />
32
+ </band>}
33
+ else
34
+ '{"band": {"label": "n/a", "name": "Strung Out", "links": [{"href":"http://search", "rel": "search"}, {"href":"http://band/strungout", "rel": "self"}]}}'
35
+ end
36
+ end
37
+
38
+ put "/band/strungout" do
39
+ %{<band><label>Fat Wreck</label><name>Strung Out</name></band>}
40
+ end
41
+
42
+
43
+
44
+ require Dir.pwd + '/order_representers'
45
+ JSON::Order.class_eval do
46
+ def items_url
47
+ "http://localhost:9999/orders/1/items"
48
+ end
49
+ def order_url(order)
50
+ "http://localhost:9999/orders/#{order}"
51
+ end
52
+ def represented
53
+ 1
54
+ end
55
+
56
+ end
57
+
58
+
59
+ post "/orders" do
60
+ incoming = JSON::Order.deserialize(request.body.string)
61
+ # create new record
62
+
63
+ # render new record
64
+
65
+ JSON::Order.from_attributes(incoming.to_attributes).serialize
66
+ end
67
+
68
+ post "/orders/1/items" do
69
+ incoming = JSON::Item.deserialize(request.body.string)
70
+
71
+ JSON::Item.from_attributes(incoming.to_attributes).serialize
72
+ end
73
+
74
+ get "/orders/1" do
75
+ JSON::Order.new(:client_id => 1, :items => [JSON::Item.new(:article_id => "666-S", :amount => 1)]).serialize
76
+ end
77
+
78
+ end
79
+
80
+ FakeServer.run! :host => 'localhost', :port => 9999
@@ -0,0 +1,46 @@
1
+ require 'test_helper'
2
+ require 'roar/representer/feature/http_verbs'
3
+
4
+ class HttpVerbsTest < MiniTest::Spec
5
+ class BandRepresenter < Roar::Representer::XML
6
+ self.representation_name = :band
7
+
8
+ property :name
9
+ property :label
10
+
11
+ include Roar::Representer::Feature::HttpVerbs
12
+ end
13
+
14
+ describe "HttpVerbs" do
15
+ before do
16
+ @r = BandRepresenter.new
17
+ end
18
+
19
+ # TODO: assert that Restfulie#post receives the correct document.
20
+
21
+ it "#post deserializes the incoming representation and returns it" do
22
+ @r.name = "Strung Out"
23
+ rep = @r.post("http://localhost:9999/band", "application/xml")
24
+ assert_equal "Strung Out", rep.name
25
+ assert_equal "n/a", rep.label
26
+ end
27
+
28
+ it "#post! deserializes the incoming representation and replaces attributes" do
29
+ @r.name = "Strung Out"
30
+ assert_equal nil, @r.label
31
+ @r.post!("http://localhost:9999/band", "application/xml")
32
+ assert_equal "Strung Out", @r.name
33
+ assert_equal "n/a", @r.label
34
+ end
35
+
36
+
37
+
38
+ it "#put deserializes the incoming representation and returns it" do
39
+ @r.name = "Strung Out"
40
+ @r.label = "Fat Wreck"
41
+ rep = @r.put("http://localhost:9999/band/strungout", "application/xml")
42
+ assert_equal "Strung Out", rep.name
43
+ assert_equal "Fat Wreck", rep.label
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,35 @@
1
+ require 'test_helper'
2
+ require 'roar/representer/feature/hypermedia'
3
+
4
+ class HypermediaTest
5
+ describe "Hypermedia" do
6
+ class Bookmarks
7
+ include Roar::Representer::Feature::Hypermedia
8
+ end
9
+
10
+ before do
11
+ Hyperlink = Roar::Representer::XML::Hyperlink # TODO: move to abstract module.
12
+ @b = Bookmarks.new
13
+ @b.links = [Hyperlink.from_attributes({"rel" => "self", "href" => "http://self"}), Hyperlink.from_attributes({"rel" => "next", "href" => "http://next"})]
14
+ end
15
+
16
+ describe "#links" do
17
+ it "returns links" do
18
+ assert_kind_of Roar::Representer::Feature::Hypermedia::LinkCollection, @b.links
19
+ assert_equal 2, @b.links.size
20
+ end
21
+
22
+ it "works with empty links set" do
23
+ assert_equal nil, Bookmarks.new.links # default empty array doesn't make sense.
24
+ end
25
+ end
26
+
27
+
28
+ it "responds to links #[]" do
29
+ assert_equal "http://self", @b.links["self"]
30
+ assert_equal "http://self", @b.links[:self]
31
+ assert_equal "http://next", @b.links[:next]
32
+ assert_equal nil, @b.links[:prev]
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,122 @@
1
+ require 'test_helper'
2
+
3
+ require 'roar/representer/xml'
4
+ require 'roar/representer/json'
5
+
6
+ require 'roar/representer/feature/http_verbs'
7
+ require 'roar/representer/feature/hypermedia'
8
+
9
+ class RepresenterIntegrationTest < MiniTest::Spec
10
+ module XML
11
+ class BandRepresenter < Roar::Representer::XML
12
+ self.representation_name = :band
13
+
14
+ property :name
15
+ property :label
16
+
17
+ include Roar::Representer::Feature::HttpVerbs
18
+
19
+
20
+ link :search do
21
+ search_url
22
+ end
23
+
24
+ link :self do
25
+ order_url(represented)
26
+ end
27
+ end
28
+ end
29
+
30
+ # TODO: inherit properly.
31
+ module JSON
32
+ class BandRepresenter < Roar::Representer::JSON
33
+ self.representation_name = :band
34
+
35
+ property :name
36
+ property :label
37
+
38
+ include Roar::Representer::Feature::HttpVerbs
39
+ include Roar::Representer::Feature::Hypermedia
40
+
41
+
42
+ link :search do
43
+ search_url
44
+ end
45
+
46
+ link :self do
47
+ order_url(represented)
48
+ end
49
+ end
50
+ end
51
+
52
+
53
+ require 'order_representers'
54
+ describe "Representer as client" do
55
+ describe "JSON" do
56
+ it "allows a POST workflow" do
57
+ # create representation with initial values:
58
+ @r = JSON::BandRepresenter.new(:name => "Bigwig")
59
+ assert_equal "Bigwig", @r.name
60
+
61
+ @r = @r.post("http://localhost:9999/band", "application/band+json")
62
+ assert_equal "n/a", @r.label
63
+
64
+ # check HATEOAS:
65
+ #@r.extend Roar::Representer::Feature::Hypermedia
66
+ assert_equal "http://search", @r.links[:search]
67
+ assert_equal "http://band/strungout", @r.links[:self]
68
+ end
69
+
70
+ # TODO: implement me.
71
+ it "allows an ordering workflow" do
72
+ # create representation with initial values:
73
+ @o = ::JSON::Order.new(:client_id => 1)
74
+ assert_equal 1, @o.client_id
75
+
76
+ @o.post!("http://localhost:9999/orders", "application/order+json")
77
+ # check HATEOAS:
78
+ #@r.extend Roar::Representer::Feature::Hypermedia
79
+
80
+ assert_equal "http://localhost:9999/orders/1/items", @o.links[:items]
81
+ assert_equal "http://localhost:9999/orders/1", @o.links[:self]
82
+
83
+
84
+ # manually POST item:
85
+ @i = ::JSON::Item.new(:article_id => "666-S", :amount => 1)
86
+ @i.post!(@o.links[:items], "application/item+json")
87
+ @o.get!(@o.links[:self], "application/order+json")
88
+
89
+ # check if item is included in order:
90
+ assert_equal 1, @o.items.size
91
+ assert_equal @i.to_attributes, @o.items.first.to_attributes
92
+
93
+
94
+ ###@@o.delete!(@o.links[:self])
95
+
96
+ # use the DSL to add items:
97
+ #@o.links[:items].post(:article_id => "666-S", :amount => 1)
98
+
99
+ #@o.items.post(:article_id => "666-S", :amount => 1)
100
+
101
+ end
102
+ end
103
+
104
+ describe "XML" do
105
+ it "allows a POST workflow" do
106
+ # create representation with initial values:
107
+ @r = XML::BandRepresenter.new(:name => "Bigwig")
108
+ assert_equal "Bigwig", @r.name
109
+
110
+ @r = @r.post("http://localhost:9999/band", "application/band+xml")
111
+ assert_equal "n/a", @r.label
112
+
113
+ # check HATEOAS:
114
+ #@r.extend Roar::Representer::Feature::Hypermedia
115
+ assert_equal "http://search", @r.links[:search]
116
+ assert_equal "http://band/strungout", @r.links[:self]
117
+ end
118
+ end
119
+
120
+
121
+ end
122
+ end
@@ -0,0 +1,101 @@
1
+ require 'test_helper'
2
+
3
+ require "test_xml/mini_test"
4
+ require "roar/representer/json"
5
+
6
+ class JsonRepresenterFunctionalTest < MiniTest::Spec
7
+ class OrderRepresenter < Roar::Representer::JSON
8
+ self.representation_name= :order
9
+ representable_property :id
10
+ end
11
+
12
+
13
+ describe "JsonRepresenter" do
14
+ before do
15
+ @r = OrderRepresenter.new
16
+ end
17
+
18
+ describe "#to_json" do
19
+ it "#serialize returns the serialized model" do
20
+ @r.id = 1
21
+ assert_equal '{"order":{"id":1}}', @r.serialize
22
+ end
23
+
24
+
25
+ it ".from_xml returns the deserialized model" do
26
+ @m = OrderRepresenter.deserialize('{"order": {"id":1}}')
27
+ assert_equal 1, @m.id
28
+ end
29
+
30
+ it ".from_xml still works with nil" do
31
+ assert OrderRepresenter.deserialize(nil)
32
+ end
33
+
34
+ end
35
+ end
36
+
37
+ end
38
+
39
+ class JsonHyperlinkRepresenterTest
40
+ describe "API" do
41
+ before do
42
+ @l = Roar::Representer::JSON::Hyperlink.from_json({:link => {:rel => :self, :href => "http://roar.apotomo.de"}}.to_json)
43
+ end
44
+
45
+ it "responds to #representation_name" do
46
+ assert_equal :link, @l.class.representation_name
47
+ end
48
+
49
+
50
+ it "responds to #rel" do
51
+ assert_equal "self", @l.rel
52
+ end
53
+
54
+ it "responds to #href" do
55
+ assert_equal "http://roar.apotomo.de", @l.href
56
+ end
57
+ end
58
+ end
59
+
60
+ class JsonHypermediaTest
61
+ describe "Hypermedia API" do
62
+ before do
63
+ @c = Class.new(Roar::Representer::JSON) do
64
+ include Roar::Representer::Feature::Hypermedia
65
+
66
+ self.representation_name= :order
67
+
68
+ representable_property :id
69
+
70
+ link :self do "http://self" end
71
+ link :next do "http://next/#{id}" end
72
+ end
73
+
74
+ @r = @c.new
75
+ end
76
+
77
+ it "responds to #links" do
78
+ assert_equal nil, @r.links
79
+ end
80
+
81
+ it "computes links in #from_attributes" do
82
+ @r = @c.from_attributes({"id" => 1})
83
+ assert_equal 2, @r.links.size
84
+ assert_equal({"rel"=>:self, "href"=>"http://self"}, @r.links.first.to_attributes)
85
+ assert_equal({"rel"=>:next, "href"=>"http://next/1"}, @r.links.last.to_attributes)
86
+ end
87
+
88
+ it "extracts links from JSON" do
89
+ @r = @c.deserialize({:order => {:links => [{:rel => "self", :href => "http://self"}]}}.to_json)
90
+
91
+ assert_equal 1, @r.links.size
92
+ assert_equal({"rel"=>"self", "href"=>"http://self"}, @r.links.first.to_attributes)
93
+ end
94
+
95
+ it "renders link: correctly in JSON" do
96
+ assert_equal "{\"order\":{\"id\":1,\"links\":[{\"rel\":\"self\",\"href\":\"http://self\"},{\"rel\":\"next\",\"href\":\"http://next/1\"}]}}", @c.from_attributes({"id" => 1}).serialize
97
+ end
98
+
99
+ end
100
+ end
101
+