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.
- data/.gitignore +1 -0
- data/Gemfile +8 -0
- data/README.textile +297 -0
- data/Rakefile +16 -0
- data/lib/roar/client/entity_proxy.rb +58 -0
- data/lib/roar/client/proxy.rb +14 -0
- data/lib/roar/client/transport.rb +29 -0
- data/lib/roar/model.rb +36 -0
- data/lib/roar/model/representable.rb +31 -0
- data/lib/roar/rails.rb +21 -0
- data/lib/roar/rails/controller_methods.rb +71 -0
- data/lib/roar/rails/representer_methods.rb +52 -0
- data/lib/roar/rails/test_case.rb +43 -0
- data/lib/roar/representer.rb +72 -0
- data/lib/roar/representer/feature/http_verbs.rb +63 -0
- data/lib/roar/representer/feature/hypermedia.rb +43 -0
- data/lib/roar/representer/feature/model_representing.rb +88 -0
- data/lib/roar/representer/json.rb +32 -0
- data/lib/roar/representer/xml.rb +43 -0
- data/lib/roar/version.rb +1 -1
- data/roar.gemspec +10 -1
- data/test/Gemfile +6 -0
- data/test/dummy/Rakefile +7 -0
- data/test/dummy/app/controllers/albums_controller.rb +27 -0
- data/test/dummy/app/controllers/application_controller.rb +4 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/models/album.rb +6 -0
- data/test/dummy/app/models/song.rb +2 -0
- data/test/dummy/app/representers/representer/xml/album.rb +19 -0
- data/test/dummy/app/representers/representer/xml/song.rb +9 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/app/views/musician/featured.html.erb +1 -0
- data/test/dummy/app/views/musician/featured_with_block.html.erb +4 -0
- data/test/dummy/app/views/musician/hamlet.html.haml +1 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/config/application.rb +20 -0
- data/test/dummy/config/boot.rb +10 -0
- data/test/dummy/config/database.yml +22 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +16 -0
- data/test/dummy/config/environments/production.rb +46 -0
- data/test/dummy/config/environments/test.rb +32 -0
- data/test/dummy/config/locales/en.yml +5 -0
- data/test/dummy/config/routes.rb +7 -0
- data/test/dummy/db/development.sqlite3 +0 -0
- data/test/dummy/db/migrate/20110514114753_create_albums.rb +14 -0
- data/test/dummy/db/migrate/20110514121228_create_songs.rb +14 -0
- data/test/dummy/db/schema.rb +29 -0
- data/test/dummy/db/test.sqlite3 +0 -0
- data/test/dummy/module - (2011-05-14 15:26:19) +5 -0
- data/test/dummy/public/404.html +26 -0
- data/test/dummy/public/422.html +26 -0
- data/test/dummy/public/500.html +26 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/script/rails +6 -0
- data/test/dummy/tmp/app/cells/blog/post/latest.html.erb +7 -0
- data/test/dummy/tmp/app/cells/blog/post_cell.rb +7 -0
- data/test/fake_server.rb +80 -0
- data/test/http_verbs_test.rb +46 -0
- data/test/hypermedia_test.rb +35 -0
- data/test/integration_test.rb +122 -0
- data/test/json_representer_test.rb +101 -0
- data/test/model_representing_test.rb +121 -0
- data/test/model_test.rb +50 -0
- data/test/order_representers.rb +34 -0
- data/test/proxy_test.rb +89 -0
- data/test/rails/controller_methods_test.rb +147 -0
- data/test/rails/rails_representer_methods_test.rb +32 -0
- data/test/representable_test.rb +49 -0
- data/test/representer_test.rb +25 -0
- data/test/ruby_representation_test.rb +144 -0
- data/test/test_helper.rb +45 -0
- data/test/test_helper_test.rb +59 -0
- data/test/transport_test.rb +34 -0
- data/test/xml_hypermedia_test.rb +47 -0
- data/test/xml_representer_test.rb +238 -0
- metadata +181 -13
data/.gitignore
CHANGED
data/Gemfile
CHANGED
@@ -2,3 +2,11 @@ source "http://rubygems.org"
|
|
2
2
|
|
3
3
|
# Specify your gem's dependencies in roar.gemspec
|
4
4
|
gemspec
|
5
|
+
|
6
|
+
#gem "representable", :path => "/home/nick/projects/representable"
|
7
|
+
#gem "test_xml", :path => "/home/nick/projects/test_xml" #"~> 0.1.0"
|
8
|
+
|
9
|
+
group :test do
|
10
|
+
gem "rails", "~> 3.0.0"
|
11
|
+
gem "sqlite3"
|
12
|
+
end
|
data/README.textile
ADDED
@@ -0,0 +1,297 @@
|
|
1
|
+
h1. ROAR
|
2
|
+
|
3
|
+
_Streamlines the development of RESTful, Resource-Oriented Architectures in Ruby._
|
4
|
+
|
5
|
+
|
6
|
+
h2. Introduction
|
7
|
+
|
8
|
+
Roar is a framework for developing distributed applications while using hypermedia as key for application workflow.
|
9
|
+
|
10
|
+
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.
|
11
|
+
|
12
|
+
|
13
|
+
h2. Features
|
14
|
+
|
15
|
+
* Roar worries about incoming and outgoing *representation documents*.
|
16
|
+
* Representers let you declaratively *define your representations*.
|
17
|
+
* Representations now are *OOP instances* with accessors, methods and behaviour.
|
18
|
+
* Both *rendering and parsing* representations is handled by representers which keeps knowledge in one place.
|
19
|
+
* Features as *hypermedia* support and *HTTP* methods can be mixed in dynamically.
|
20
|
+
* Representers are packagable in gems for *distribution to services and clients*.
|
21
|
+
* *Framework agnostic*, runs with Sinatra, Rails & Co.
|
22
|
+
* Extra support for *Rails*.
|
23
|
+
* Makes *testing distributed REST systems* as easy as possible.
|
24
|
+
|
25
|
+
h2. Example
|
26
|
+
|
27
|
+
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.
|
28
|
+
|
29
|
+
|
30
|
+
h2. Representations
|
31
|
+
|
32
|
+
Representations are the pivotal elements of REST. Work in a REST system means working with representations, which can be put down to parsing or extracting representations and rendering the like.
|
33
|
+
|
34
|
+
Roar makes it easy to render and parse representations of resources after defining the formats.
|
35
|
+
|
36
|
+
|
37
|
+
h3. Creating Representations
|
38
|
+
|
39
|
+
Why not GET a particular article, what about a good beer?
|
40
|
+
|
41
|
+
@GET http://articles/lonestarbeer@
|
42
|
+
|
43
|
+
It's cheap and it's good. The response of a GET is a representation of the requested resource. A *representation* is always a *document*. In this example, it's a bit of JSON.
|
44
|
+
|
45
|
+
pre. { "article": {
|
46
|
+
"title": "Lonestar Beer",
|
47
|
+
"id": 4711,
|
48
|
+
"links":[
|
49
|
+
{ "rel": "self",
|
50
|
+
"href": "http://articles/lonestarbeer"}
|
51
|
+
]}
|
52
|
+
}
|
53
|
+
|
54
|
+
p. In addition to boring article data, there's a _link_ embedded in the document. This is *hypermedia*, yeah! We will learn more about that shortly.
|
55
|
+
|
56
|
+
So, how did the service render that JSON document? It could use an ERB template, @#to_json@, or maybe another gem. The document could also be created by a *representer*.
|
57
|
+
|
58
|
+
Representers are the key ingredience in Roar, so let's check them out!
|
59
|
+
|
60
|
+
|
61
|
+
h2. Representers
|
62
|
+
|
63
|
+
To render a representational document, the backend service has to define a representer.
|
64
|
+
|
65
|
+
<pre>module JSON
|
66
|
+
class Article < Roar::Representer::JSON
|
67
|
+
property :title
|
68
|
+
property :id
|
69
|
+
|
70
|
+
link :self do
|
71
|
+
article_url(represented)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
</pre>
|
76
|
+
|
77
|
+
Hooray, we can define plain properties and embedd links easily - and we can even use URL helpers (in Rails). There's even more, nesting, collections, but more on that later!
|
78
|
+
|
79
|
+
|
80
|
+
h3. Rendering Representations in the Service
|
81
|
+
|
82
|
+
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.
|
83
|
+
|
84
|
+
<pre>JSON::Article.new(
|
85
|
+
:title => "Lonestar",
|
86
|
+
:id => 666).
|
87
|
+
serialize # => "{\"article\":{\"id\":666, ...
|
88
|
+
</pre>
|
89
|
+
|
90
|
+
Using the @ModelRepresenting@ feature module we can take a shortcut.
|
91
|
+
|
92
|
+
<pre>@beer = Article.find_by_title("Lonestar")
|
93
|
+
|
94
|
+
JSON::Article.from_model(@beer).
|
95
|
+
serialize # => "{\"article\":{\"id\":666, ...
|
96
|
+
</pre>
|
97
|
+
|
98
|
+
Articles itself are useless, so they may be placed into orders. This is the next example.
|
99
|
+
|
100
|
+
|
101
|
+
h3. Nesting Representations
|
102
|
+
|
103
|
+
What if we wanted to check an existing order? We'd @GET http://orders/1@, right?
|
104
|
+
|
105
|
+
<pre>{ "order": {
|
106
|
+
"id": 1,
|
107
|
+
"client_id": "815",
|
108
|
+
"articles": [
|
109
|
+
{"title": "Lonestar Beer",
|
110
|
+
"id": 666,
|
111
|
+
"links":[
|
112
|
+
{ "rel": "self",
|
113
|
+
"href": "http://articles/lonestarbeer"}
|
114
|
+
]}
|
115
|
+
],
|
116
|
+
"links":[
|
117
|
+
{ "rel": "self",
|
118
|
+
"href": "http://orders/1"},
|
119
|
+
{ "rel": "items",
|
120
|
+
"href": "http://orders/1/items"}
|
121
|
+
]}
|
122
|
+
}
|
123
|
+
</pre>
|
124
|
+
|
125
|
+
Since orders may contain a composition of articles, how would the order service define its representer?
|
126
|
+
|
127
|
+
<pre>module JSON
|
128
|
+
class Order < Roar::Representer::JSON
|
129
|
+
property :id
|
130
|
+
property :client_id
|
131
|
+
|
132
|
+
collection :articles, :as => Article
|
133
|
+
|
134
|
+
link :self do
|
135
|
+
order_url(represented)
|
136
|
+
end
|
137
|
+
|
138
|
+
link :items do
|
139
|
+
items_url
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
</pre>
|
144
|
+
|
145
|
+
The declarative @#collection@ method lets us define compositions of representers.
|
146
|
+
|
147
|
+
|
148
|
+
h3. Parsing Documents in the Service
|
149
|
+
|
150
|
+
Rendering stuff is easy: Representers allow defining the layout and serializing documents for us. However, representers can do more. They work _bi-directional_ in terms of rendering outgoing _and_ parsing incoming representation documents.
|
151
|
+
|
152
|
+
If we were to implement an endpoint for creating new orders, we'd allow POST to @http://orders/@. Let's explore the service code for parsing and creation.
|
153
|
+
|
154
|
+
<pre>
|
155
|
+
post "/orders" do
|
156
|
+
incoming = JSON::Order.deserialize(request.body.string)
|
157
|
+
puts incoming.to_attributes #=> {:client_id => 815}
|
158
|
+
</pre>
|
159
|
+
|
160
|
+
Again, the @ModelRepresenting@ module comes in handy for creating a new database record.
|
161
|
+
|
162
|
+
<pre>
|
163
|
+
post "/orders" do
|
164
|
+
# ...
|
165
|
+
@order = Order.create(incoming.to_nested_attributes)
|
166
|
+
|
167
|
+
JSON::Order.for_model(@order).serialize
|
168
|
+
</pre>
|
169
|
+
|
170
|
+
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.
|
171
|
+
|
172
|
+
|
173
|
+
h2. Representers in the Client
|
174
|
+
|
175
|
+
The new representer abstraction layer seems complex and irritating first, where you used @params[]@ and @#to_json@ is a new OOP instance now. But... the cool thing is: You can package representers in gems and distribute them to your client layer as well. In our example, the web frontend can take advantage of the representers, too.
|
176
|
+
|
177
|
+
|
178
|
+
h3. Using HTTP
|
179
|
+
|
180
|
+
Communication between REST clients and services happens via HTTP - clients request, services respond. There are plenty of great gems helping out, namely Restfulie, HTTParty, etc. Representers in Roar provide support for HTTP as well, given you mix in the @HTTPVerbs@ feature module!
|
181
|
+
|
182
|
+
To create a new order, the frontend needs to POST to the REST backend. Here's how this could happen using a representer on HTTP.
|
183
|
+
|
184
|
+
|
185
|
+
<pre>
|
186
|
+
order = JSON::Order.new(:client_id => current_user.id)
|
187
|
+
order.post!("http://orders/")
|
188
|
+
</pre>
|
189
|
+
|
190
|
+
A couple of noteworthy steps happen here.
|
191
|
+
|
192
|
+
# Using the constructor a blank order document is created.
|
193
|
+
# Initial values like the client's id are passed as arguments and placed in the document.
|
194
|
+
# The filled-out document is POSTed to the given URL.
|
195
|
+
# The backend service creates an actual order record and sends back the representation.
|
196
|
+
# In the @#post!@ call, the returned document is parsed and attributes in the representer instance are updated accordingly,
|
197
|
+
|
198
|
+
After the HTTP roundtrip, the order instance keeps all the information we need for proceeding the ordering workflow.
|
199
|
+
|
200
|
+
<pre>
|
201
|
+
order.id #=> 42
|
202
|
+
</pre>
|
203
|
+
|
204
|
+
h3. Discovering Hypermedia
|
205
|
+
|
206
|
+
Now that we got a fresh order, let's place some items! The system's API allows adding articles to an existing order by POSTing articles to a specific resource. This endpoint is propagated in the order document using *hypermedia*.
|
207
|
+
|
208
|
+
Where and what is this hypermedia?
|
209
|
+
|
210
|
+
First, check the JSON document we get back from the POST.
|
211
|
+
|
212
|
+
<pre>{ "order": {
|
213
|
+
"id": 42,
|
214
|
+
"client_id": 1337,
|
215
|
+
"articles": [],
|
216
|
+
"links":[
|
217
|
+
{ "rel": "self",
|
218
|
+
"href": "http://orders/42"},
|
219
|
+
{ "rel": "items",
|
220
|
+
"href": "http://orders/42/items"}
|
221
|
+
]}
|
222
|
+
}
|
223
|
+
</pre>
|
224
|
+
|
225
|
+
Two hypermedia links are embedded in this representation, both feature a @rel@ attribute for defining a link semantic - a "meaning" - and a @href@ attribute for a network address. Isn't that great?
|
226
|
+
|
227
|
+
* The @self@ link refers to the actual resource. It's a REST best practice and representations should always refer to their resource address.
|
228
|
+
* The @items@ link is what we want. The address @http://orders/42/items@ is what we have to refer to when adding articles to this order. Why? Cause we decided that!
|
229
|
+
|
230
|
+
|
231
|
+
h3. Using Hypermedia
|
232
|
+
|
233
|
+
Let the frontend add the delicious "Lonestar" beer to our order, now!
|
234
|
+
|
235
|
+
<pre>
|
236
|
+
beer = JSON::Article.new(:title => "Lonestar Beer")
|
237
|
+
beer.post(order.links[:items])
|
238
|
+
</pre>
|
239
|
+
|
240
|
+
That's all we need to do.
|
241
|
+
|
242
|
+
# First, we create an appropriate article representation.
|
243
|
+
# Then, the @#links@ method helps extracting the @items@ link URL from the order document.
|
244
|
+
# A simple POST to the respective address places the item in the order.
|
245
|
+
|
246
|
+
The @order@ instance in the frontend is now stale - it doesn't contain articles, yet, since it is still the document from the first post to @http://orders/@.
|
247
|
+
|
248
|
+
<pre>
|
249
|
+
order.items #=> []
|
250
|
+
</pre>
|
251
|
+
|
252
|
+
To update attributes, a GET is needed.
|
253
|
+
|
254
|
+
<pre>
|
255
|
+
order.get!(order.links[:self])
|
256
|
+
</pre>
|
257
|
+
|
258
|
+
Again, we use hypermedia to retrieve the order's URL. And now, the added article is included in the order.
|
259
|
+
|
260
|
+
[*Note:* If this looks clumsy - It's just the raw API for representers. You might be interested in the upcoming DSL that simplifys frequent workflows as updating a representer.]
|
261
|
+
|
262
|
+
<pre>
|
263
|
+
order.to_attributes #=> {:id => 42, :client_id => 1337,
|
264
|
+
:articles => [{:title => "Lonestar Beer", :id => 666}]}
|
265
|
+
</pre>
|
266
|
+
|
267
|
+
This is cool, we used REST representers and hypermedia to create an order and fill it with articles. It's time for a beer, isn't it?
|
268
|
+
|
269
|
+
|
270
|
+
h3. Using Accessors
|
271
|
+
|
272
|
+
What if the ordering API is going a different way? What if we had to place articles into the order document ourselves, and then PUT this representation to @http://orders/42@? No problem with representers!
|
273
|
+
|
274
|
+
Here's what could happen in the frontend.
|
275
|
+
|
276
|
+
<pre>
|
277
|
+
beer = JSON::Article.new(:title => "Lonestar Beer")
|
278
|
+
order.items << beer
|
279
|
+
order.post!(order.links[:self])
|
280
|
+
</pre>
|
281
|
+
|
282
|
+
This was dead simple since representations can be composed of different documents in Roar.
|
283
|
+
|
284
|
+
|
285
|
+
h2. Current Status
|
286
|
+
|
287
|
+
Please note that Roar is still in conception, the API might change as well as concepts do.
|
288
|
+
|
289
|
+
|
290
|
+
h2. What is REST about?
|
291
|
+
|
292
|
+
Making that system RESTful basically means
|
293
|
+
|
294
|
+
# The frontend knows one _single entry point_ URL to the REST services. This is @http://orders@.
|
295
|
+
# Do _not_ let the frontend compute any URLs to further actions.
|
296
|
+
# 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.
|
297
|
+
|
data/Rakefile
CHANGED
@@ -1,2 +1,18 @@
|
|
1
1
|
require 'bundler'
|
2
2
|
Bundler::GemHelper.install_tasks
|
3
|
+
|
4
|
+
require 'rake/testtask'
|
5
|
+
|
6
|
+
task :default => [:test, :testrails]
|
7
|
+
|
8
|
+
Rake::TestTask.new(:test) do |test|
|
9
|
+
test.libs << 'test'
|
10
|
+
test.test_files = FileList['test/xml_representer_test.rb', 'test/model_representing_test.rb', 'test/representer_test.rb', 'test/transport_test.rb', 'test/http_verbs_test.rb', 'test/integration_test.rb', 'test/json_representer_test.rb', 'test/xml_hypermedia_test.rb', 'test/hypermedia_test.rb']
|
11
|
+
test.verbose = true
|
12
|
+
end
|
13
|
+
|
14
|
+
Rake::TestTask.new(:testrails) do |test|
|
15
|
+
test.libs << 'test'
|
16
|
+
test.test_files = FileList['test/rails/*_test.rb']
|
17
|
+
test.verbose = true
|
18
|
+
end
|
@@ -0,0 +1,58 @@
|
|
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
|
@@ -0,0 +1,14 @@
|
|
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
|
@@ -0,0 +1,29 @@
|
|
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
|
data/lib/roar/model.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
module Roar
|
2
|
+
# Basic methods needed to implement the ActiveModel API. Gives your model +#attributes+ and +model_name+.
|
3
|
+
# Include this for quickly converting an object to a ROAR-compatible monster.
|
4
|
+
#
|
5
|
+
# #DISCUSS: are Models used both on client- and server-side? I'd say hell yeah.
|
6
|
+
module Model
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
def model_name
|
11
|
+
ActiveSupport::Inflector.underscore(self) # We don't use AM::Naming for now.
|
12
|
+
end
|
13
|
+
|
14
|
+
def accessors(*names)
|
15
|
+
names.each do |name|
|
16
|
+
class_eval %Q{
|
17
|
+
def #{name}=(v)
|
18
|
+
attributes["#{name}"] = v
|
19
|
+
end
|
20
|
+
|
21
|
+
def #{name}
|
22
|
+
attributes["#{name}"]
|
23
|
+
end
|
24
|
+
}
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
attr_accessor :attributes
|
31
|
+
|
32
|
+
def initialize(attributes={})
|
33
|
+
@attributes = attributes
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|