roar 0.0.1.alpha1 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
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
+