roar 0.8.1 → 0.8.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,8 @@
1
+ h2. 0.8.2
2
+
3
+ * Removing `restfulie` dependency - we now use `Net::HTTP`.
4
+
5
+
6
+ h2. 0.8.1
7
+
8
+ * Added the :except and :include options to `#from_*`.
data/Gemfile CHANGED
@@ -4,9 +4,3 @@ source "http://rubygems.org"
4
4
  gemspec
5
5
 
6
6
  #gem "representable", :path => "../representable"
7
- #gem "test_xml", :path => "/home/nick/projects/test_xml" #"~> 0.1.0"
8
-
9
- group :test do
10
- gem "rails", "~> 3.1.0"
11
- gem "sqlite3"
12
- end
@@ -1,28 +1,27 @@
1
1
  h1. ROAR
2
2
 
3
- _Streamlines the development of RESTful, Resource-Oriented Architectures in Ruby._
3
+ _Resource-Oriented Architectures in Ruby._
4
4
 
5
+ "Lets make documents suit our models and not models fit to documents."
5
6
 
6
- Lets make documents suit our models and not models fit to documents
7
7
 
8
8
  h2. Introduction
9
9
 
10
- Roar is a framework for developing distributed applications while using hypermedia as key for application workflow.
10
+ 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.
11
11
 
12
- REST is an architectural style for distributed systems. However, many implementations forget about the _distributed_ part of REST and simply map CRUD operations to HTTP verbs in a monolithic application.
12
+ 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.
13
13
 
14
14
 
15
15
  h2. Features
16
16
 
17
- * Roar worries about incoming and outgoing *representation documents*.
18
- * Representers let you declaratively *define your representations*.
19
- * Representations now are *OOP instances* with accessors, methods and behaviour.
20
- * Both *rendering and parsing* representations is handled by representers which keeps knowledge in one place.
21
- * Features as *hypermedia* support and *HTTP* methods can be mixed in dynamically.
22
- * Representers are packagable in gems for *distribution to services and clients*.
23
- * *Framework agnostic*, runs with Sinatra, Rails & Co.
24
- * Extra support for *Rails*.
25
- * Makes *testing distributed REST systems* as easy as possible.
17
+ * OOP access to documents.
18
+ * Parsing _and_ rendering of representations in one place.
19
+ * Declaratively define document syntax and semantics.
20
+ * Hypermedia support.
21
+ * ActiveResource-like client support.
22
+ * Useable in both client _and_ server.
23
+ * Framework agnostic, runs with sinatra, Rails, webmachine and friends.
24
+
26
25
 
27
26
  h2. Example
28
27
 
@@ -64,17 +63,16 @@ h2. Representers
64
63
 
65
64
  To render a representational document, the backend service has to define a representer.
66
65
 
67
- <pre>module JSON
68
- class Article
69
- include Roar::Representer::JSON
70
-
71
- property :title
72
- property :id
73
-
74
- link :self do
75
- article_url(represented)
76
- end
77
- end
66
+ <pre>
67
+ class Article
68
+ include Roar::Representer::JSON
69
+
70
+ property :title
71
+ property :id
72
+
73
+ link :self do
74
+ article_url(represented)
75
+ end
78
76
  end
79
77
  </pre>
80
78
 
@@ -85,18 +83,10 @@ h3. Rendering Representations in the Service
85
83
 
86
84
  In order to *render* an actual document, the backend service would have to do a few steps: creating a representer, filling in data, and then serialize it.
87
85
 
88
- <pre>JSON::Article.new(
89
- :title => "Lonestar",
90
- :id => 666).
91
- serialize # => "{\"article\":{\"id\":666, ...
92
- </pre>
93
-
94
- Using the @ModelRepresenting@ feature module we can take a shortcut.
95
-
96
- <pre>@beer = Article.find_by_title("Lonestar")
97
-
98
- JSON::Article.from_model(@beer).
99
- serialize # => "{\"article\":{\"id\":666, ...
86
+ <pre>Article.new(
87
+ title: "Lonestar",
88
+ id: 666).
89
+ to_json # => "{\"article\":{\"id\":666, ...
100
90
  </pre>
101
91
 
102
92
  Articles itself are useless, so they may be placed into orders. This is the next example.
@@ -128,23 +118,22 @@ What if we wanted to check an existing order? We'd @GET http://orders/1@, right?
128
118
 
129
119
  Since orders may contain a composition of articles, how would the order service define its representer?
130
120
 
131
- <pre>module JSON
132
- class Order
133
- include Roar::Representer::JSON
134
-
135
- property :id
136
- property :client_id
137
-
138
- collection :articles, :as => Article
139
-
140
- link :self do
141
- order_url(represented)
142
- end
143
-
144
- link :items do
145
- items_url
146
- end
121
+ <pre>
122
+ class Order
123
+ include Roar::Representer::JSON
124
+
125
+ property :id
126
+ property :client_id
127
+
128
+ collection :articles, :as => Article
129
+
130
+ link :self do
131
+ order_url(represented)
147
132
  end
133
+
134
+ link :items do
135
+ items_url
136
+ end
148
137
  end
149
138
  </pre>
150
139
 
@@ -159,21 +148,11 @@ If we were to implement an endpoint for creating new orders, we'd allow POST to
159
148
 
160
149
  <pre>
161
150
  post "/orders" do
162
- incoming = JSON::Order.deserialize(request.body.string)
151
+ incoming = Order.deserialize(request.body.string)
163
152
  puts incoming.to_attributes #=> {:client_id => 815}
164
153
  </pre>
165
154
 
166
- Again, the @ModelRepresenting@ module comes in handy for creating a new database record.
167
-
168
- <pre>
169
- post "/orders" do
170
- # ...
171
- @order = Order.create(incoming.to_nested_attributes)
172
-
173
- JSON::Order.for_model(@order).serialize
174
- </pre>
175
-
176
- Look how the @#to_nested_attributes@ method helps extracting data from the incoming document and, again, @#serialize@ returns the freshly created order's representation. Roar's representers are truely working in both directions, rendering and parsing and thus prevent you from redundant knowledge sharing.
155
+ Look how the @#to_attributes@ method helps extracting data from the incoming document and, again, @#to_json@ returns the freshly created order's representation. Roar's representers are truely working in both directions, rendering and parsing and thus prevent you from redundant knowledge sharing.
177
156
 
178
157
 
179
158
  h2. Representers in the Client
@@ -189,7 +168,7 @@ To create a new order, the frontend needs to POST to the REST backend. Here's ho
189
168
 
190
169
 
191
170
  <pre>
192
- order = JSON::Order.new(:client_id => current_user.id)
171
+ order = Order.new(:client_id => current_user.id)
193
172
  order.post!("http://orders/")
194
173
  </pre>
195
174
 
@@ -239,7 +218,7 @@ h3. Using Hypermedia
239
218
  Let the frontend add the delicious "Lonestar" beer to our order, now!
240
219
 
241
220
  <pre>
242
- beer = JSON::Article.new(:title => "Lonestar Beer")
221
+ beer = Article.new(:title => "Lonestar Beer")
243
222
  beer.post(order.links[:items])
244
223
  </pre>
245
224
 
@@ -280,7 +259,7 @@ What if the ordering API is going a different way? What if we had to place artic
280
259
  Here's what could happen in the frontend.
281
260
 
282
261
  <pre>
283
- beer = JSON::Article.new(:title => "Lonestar Beer")
262
+ beer = Article.new(:title => "Lonestar Beer")
284
263
  order.items << beer
285
264
  order.post!(order.links[:self])
286
265
  </pre>
@@ -0,0 +1 @@
1
+ * Add proxies, so nested models can be lazy-loaded.
@@ -1,4 +1,4 @@
1
- require 'roar/client/transport'
1
+ require 'roar/representer/feature/transport'
2
2
 
3
3
  module Roar
4
4
  # Gives HTTP-power to representers where those can automatically serialize, send, process and deserialize HTTP-requests.
@@ -11,7 +11,7 @@ module Roar
11
11
 
12
12
 
13
13
  module ClassMethods
14
- include Client::Transport
14
+ include Transport
15
15
 
16
16
  def get(url, format) # TODO: test me!
17
17
  #url = resource_base + variable_path.to_s
@@ -0,0 +1,43 @@
1
+ require "net/http"
2
+ require "uri"
3
+
4
+ module Roar
5
+ module Representer
6
+ module Feature
7
+ # Implements the HTTP verbs with Net::HTTP.
8
+ module Transport
9
+ # TODO: generically handle return codes/let Restfulie do it.
10
+ def get_uri(uri, as)
11
+ do_request(Net::HTTP::Get, uri, as)
12
+ end
13
+
14
+ def post_uri(uri, body, as)
15
+ do_request(Net::HTTP::Post, uri, as)
16
+ end
17
+
18
+ def put_uri(uri, body, as)
19
+ do_request(Net::HTTP::Put, uri, as)
20
+ end
21
+
22
+ def patch_uri(uri, body, as)
23
+ do_request(Net::HTTP::Patch, uri, as)
24
+ end
25
+
26
+ def delete_uri(uri, as)
27
+ do_request(Net::HTTP::Delete, uri, as)
28
+ end
29
+
30
+ private
31
+ def do_request(what, uri, as, body="")
32
+ # DISCUSS: can this be made easier?
33
+ uri = URI.parse(uri)
34
+ http = Net::HTTP.new(uri.host, uri.port)
35
+ req = what.new(uri.request_uri)
36
+ req.content_type = as
37
+ req.body = body if body
38
+ http.request(req)
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -1,3 +1,3 @@
1
1
  module Roar
2
- VERSION = "0.8.1"
2
+ VERSION = "0.8.2"
3
3
  end
@@ -20,11 +20,9 @@ Gem::Specification.new do |s|
20
20
  s.require_paths = ["lib"]
21
21
 
22
22
  s.add_runtime_dependency "representable", "~> 0.9.2"
23
- s.add_runtime_dependency "restfulie", "~> 1.0.0"
24
- s.add_runtime_dependency "hooks", "~> 0.1.4"
23
+ s.add_runtime_dependency "hooks", "~> 0.1.4"
25
24
 
26
25
  s.add_development_dependency "test_xml"
27
26
  s.add_development_dependency "minitest", "~> 1.6.0"
28
27
  s.add_development_dependency "sinatra", "~> 1.2.6"
29
- s.add_development_dependency "sinatra-reloader", "~> 0.5.0"
30
28
  end
@@ -1,28 +1,28 @@
1
1
  require 'test_helper'
2
- require 'roar/client/transport'
2
+ require 'roar/representer/feature/transport'
3
3
 
4
4
  class TransportTest < MiniTest::Spec
5
5
  describe "Transport" do
6
6
  before do
7
7
  @klass = Class.new(Object) do
8
- include Roar::Client::Transport
8
+ include Roar::Representer::Feature::Transport
9
9
  end
10
10
  @o = @klass.new
11
11
  end
12
12
 
13
- it "#get_uri returns Restfulie response" do
13
+ it "#get_uri returns response" do
14
14
  assert_equal "<method>get</method>", @o.get_uri("http://localhost:9999/method", "application/xml").body
15
15
  end
16
16
 
17
- it "#post_uri returns Restfulie response" do
17
+ it "#post_uri returns response" do
18
18
  assert_equal "<method>post</method>", @o.post_uri("http://localhost:9999/method", "booty", "application/xml").body
19
19
  end
20
20
 
21
- it "#put_uri returns Restfulie response" do
21
+ it "#put_uri returns response" do
22
22
  assert_equal "<method>put</method>", @o.put_uri("http://localhost:9999/method", "booty", "application/xml").body
23
23
  end
24
24
 
25
- it "#delete_uri returns Restfulie response" do
25
+ it "#delete_uri returns response" do
26
26
  assert_equal "<method>delete</method>", @o.delete_uri("http://localhost:9999/method", "application/xml").body
27
27
  end
28
28
 
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 8
8
- - 1
9
- version: 0.8.1
8
+ - 2
9
+ version: 0.8.2
10
10
  platform: ruby
11
11
  authors:
12
12
  - Nick Sutterer
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2011-11-25 00:00:00 +01:00
17
+ date: 2011-11-27 00:00:00 +01:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -32,25 +32,10 @@ dependencies:
32
32
  version: 0.9.2
33
33
  type: :runtime
34
34
  version_requirements: *id001
35
- - !ruby/object:Gem::Dependency
36
- name: restfulie
37
- prerelease: false
38
- requirement: &id002 !ruby/object:Gem::Requirement
39
- none: false
40
- requirements:
41
- - - ~>
42
- - !ruby/object:Gem::Version
43
- segments:
44
- - 1
45
- - 0
46
- - 0
47
- version: 1.0.0
48
- type: :runtime
49
- version_requirements: *id002
50
35
  - !ruby/object:Gem::Dependency
51
36
  name: hooks
52
37
  prerelease: false
53
- requirement: &id003 !ruby/object:Gem::Requirement
38
+ requirement: &id002 !ruby/object:Gem::Requirement
54
39
  none: false
55
40
  requirements:
56
41
  - - ~>
@@ -61,11 +46,11 @@ dependencies:
61
46
  - 4
62
47
  version: 0.1.4
63
48
  type: :runtime
64
- version_requirements: *id003
49
+ version_requirements: *id002
65
50
  - !ruby/object:Gem::Dependency
66
51
  name: test_xml
67
52
  prerelease: false
68
- requirement: &id004 !ruby/object:Gem::Requirement
53
+ requirement: &id003 !ruby/object:Gem::Requirement
69
54
  none: false
70
55
  requirements:
71
56
  - - ">="
@@ -74,11 +59,11 @@ dependencies:
74
59
  - 0
75
60
  version: "0"
76
61
  type: :development
77
- version_requirements: *id004
62
+ version_requirements: *id003
78
63
  - !ruby/object:Gem::Dependency
79
64
  name: minitest
80
65
  prerelease: false
81
- requirement: &id005 !ruby/object:Gem::Requirement
66
+ requirement: &id004 !ruby/object:Gem::Requirement
82
67
  none: false
83
68
  requirements:
84
69
  - - ~>
@@ -89,11 +74,11 @@ dependencies:
89
74
  - 0
90
75
  version: 1.6.0
91
76
  type: :development
92
- version_requirements: *id005
77
+ version_requirements: *id004
93
78
  - !ruby/object:Gem::Dependency
94
79
  name: sinatra
95
80
  prerelease: false
96
- requirement: &id006 !ruby/object:Gem::Requirement
81
+ requirement: &id005 !ruby/object:Gem::Requirement
97
82
  none: false
98
83
  requirements:
99
84
  - - ~>
@@ -104,22 +89,7 @@ dependencies:
104
89
  - 6
105
90
  version: 1.2.6
106
91
  type: :development
107
- version_requirements: *id006
108
- - !ruby/object:Gem::Dependency
109
- name: sinatra-reloader
110
- prerelease: false
111
- requirement: &id007 !ruby/object:Gem::Requirement
112
- none: false
113
- requirements:
114
- - - ~>
115
- - !ruby/object:Gem::Version
116
- segments:
117
- - 0
118
- - 5
119
- - 0
120
- version: 0.5.0
121
- type: :development
122
- version_requirements: *id007
92
+ version_requirements: *id005
123
93
  description: Streamlines the development of RESTful, resource-oriented architectures in Ruby.
124
94
  email:
125
95
  - apotonick@gmail.com
@@ -131,13 +101,12 @@ extra_rdoc_files: []
131
101
 
132
102
  files:
133
103
  - .gitignore
104
+ - CHANGES.markdown
134
105
  - Gemfile
135
106
  - README.textile
136
107
  - Rakefile
108
+ - TODO.markdown
137
109
  - lib/roar.rb
138
- - lib/roar/client/entity_proxy.rb
139
- - lib/roar/client/proxy.rb
140
- - lib/roar/client/transport.rb
141
110
  - lib/roar/rails.rb
142
111
  - lib/roar/rails/controller_methods.rb
143
112
  - lib/roar/rails/representer_methods.rb
@@ -146,6 +115,7 @@ files:
146
115
  - lib/roar/representer/feature/http_verbs.rb
147
116
  - lib/roar/representer/feature/hypermedia.rb
148
117
  - lib/roar/representer/feature/model_representing.rb
118
+ - lib/roar/representer/feature/transport.rb
149
119
  - lib/roar/representer/json.rb
150
120
  - lib/roar/representer/xml.rb
151
121
  - lib/roar/version.rb
@@ -1,58 +0,0 @@
1
- require "roar/model"
2
- require "roar/representer/xml"
3
- require "active_support/core_ext/module/attr_internal"
4
- require "roar/client/proxy"
5
-
6
- module Roar
7
- module Client
8
- # Wraps associated objects, they can be retrieved with #finalize!
9
- # Used e.g. in Representer::Xml.has_proxied.
10
- class EntityProxy
11
- # FIXME: where to move me? i do Representable and i use Transport. however, i'm only for clients.
12
- include Model
13
- include Representer::Xml # FIXME: why does EntityProxy know about xml? get this from Representable or so.
14
- extend Proxy
15
-
16
- attr_internal :proxied_resource
17
-
18
-
19
-
20
- class << self
21
- attr_accessor :options
22
-
23
- def class_for(options)
24
- Class.new(self).tap { |k| k.options = options }
25
- end
26
-
27
- def model_name
28
- options[:class].model_name # proxy!
29
- end
30
-
31
- def from_attributes(attrs) # FIXME: move to Representable or so.
32
- new(attrs)
33
- end
34
- end
35
-
36
- # Get the actual proxied resource.
37
- def finalize!(*)
38
- # TODO: move to class.
39
- # DISCUSS: how to compute uri? what if NO uri passed? exception?
40
- self.proxied_resource = self.class.get_model(original_attributes["uri"], self.class.options[:class])
41
- end
42
-
43
- def attributes
44
- # DISCUSS: delegate all unknown methods to the proxied object?
45
- proxied_resource.attributes # proxy!
46
- end
47
-
48
- def attributes_for_xml(*options)
49
- original_attributes
50
- end
51
-
52
- private
53
- def original_attributes
54
- @attributes
55
- end
56
- end
57
- end
58
- end
@@ -1,14 +0,0 @@
1
- module Roar
2
- module Client
3
-
4
- module Proxy
5
- include Transport
6
-
7
- def get_model(uri, klass) # DISCUSS: not directly used in models. USED in EntityProxy.
8
- body = get_uri(uri).body
9
- klass.from_xml(body) # DISCUSS: knows about DE-serialization and representation-type!
10
- end
11
- end
12
- end
13
-
14
- end
@@ -1,29 +0,0 @@
1
- require "restfulie"
2
-
3
- module Roar
4
- module Client
5
- # Implements the HTTP verbs by abstracting Restfulie.
6
- module Transport
7
- # TODO: generically handle return codes/let Restfulie do it.
8
- def get_uri(uri, as)
9
- Restfulie.at(uri).accepts(as).get # TODO: debugging/logging here.
10
- end
11
-
12
- def post_uri(uri, body, as)
13
- Restfulie.at(uri).as(as).post(body)
14
- end
15
-
16
- def put_uri(uri, body, as)
17
- Restfulie.at(uri).as(as).put(body)
18
- end
19
-
20
- def patch_uri(uri, body, as)
21
- Restfulie.at(uri).as(as).patch(body)
22
- end
23
-
24
- def delete_uri(uri, as)
25
- Restfulie.at(uri).accepts(as).delete # TODO: debugging/logging here.
26
- end
27
- end
28
- end
29
- end