rubyrest 0.0.5 → 0.1.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/CHANGELOG +9 -5
- data/README +18 -23
- data/Rakefile +4 -4
- data/lib/rubyrest/application.rb +221 -0
- data/lib/rubyrest/atom.rb +247 -223
- data/lib/rubyrest/client.rb +19 -29
- data/lib/rubyrest/engine.rb +34 -83
- data/lib/rubyrest/resource.rb +87 -0
- data/lib/rubyrest/webrick.rb +92 -0
- data/lib/rubyrest.rb +10 -15
- metadata +5 -9
- data/examples/hello.rb +0 -57
- data/lib/rubyrest/config.rb +0 -80
- data/lib/rubyrest/servlets.rb +0 -233
- data/lib/rubyrest/tools.rb +0 -72
data/CHANGELOG
CHANGED
@@ -1,9 +1,13 @@
|
|
1
|
-
==
|
1
|
+
== Release 0.1.0
|
2
2
|
|
3
|
-
*
|
4
|
-
*
|
5
|
-
*
|
6
|
-
*
|
3
|
+
* A rewritten from scratch architecture and implementation that offers: pluggability, better object orientation, thread safety and backwards incompatibility (sorry for this one!)
|
4
|
+
* A clear separation between the REST Resource, the Model and the Service
|
5
|
+
* A new domain specific language of declarative rules for Atom data binding and formatting
|
6
|
+
* Built-in Sequel (http://sequel.rubyforge.org) and PostgreSQL application support
|
7
|
+
|
8
|
+
== Release 0.0.6
|
9
|
+
|
10
|
+
* In Atom Entry related links, the #atom_related method must now return an array of hashes with the following keys: title (instead of model), type. A new type is now supported: 'action'.
|
7
11
|
|
8
12
|
== Release 0.0.5
|
9
13
|
|
data/README
CHANGED
@@ -8,15 +8,14 @@ lets you create new REST services without too much effort.
|
|
8
8
|
|
9
9
|
== Resources
|
10
10
|
|
11
|
-
* {Project Documentation}[http://rubyrest.rubyforge.org]
|
12
11
|
* {Project Page at Rubyforge}[http://rubyforge.org/projects/rubyrest]
|
13
|
-
* {
|
12
|
+
* {Developer blog}[http://www.moodisland.com]
|
14
13
|
|
15
14
|
To check out the source code:
|
16
15
|
|
17
16
|
svn checkout svn://rubyforge.org/var/svn/rubyrest/trunk
|
18
17
|
|
19
|
-
|
18
|
+
== Contact
|
20
19
|
|
21
20
|
If you have any comments or suggestions please send an email to pedro dot gutierrez at netcourrier dot com and I'll get back to you.
|
22
21
|
|
@@ -24,13 +23,13 @@ If you have any comments or suggestions please send an email to pedro dot gutier
|
|
24
23
|
|
25
24
|
sudo gem install rubyrest
|
26
25
|
|
27
|
-
== Getting Started
|
28
|
-
|
29
26
|
=== Learning by example
|
30
27
|
|
31
|
-
|
28
|
+
Maybe the easiest way to learn how Ruby-on-Rest works is to take a look at the following examples, included in the source distribution:
|
29
|
+
|
30
|
+
* The 'Hello' application can be used as a skeletton for new Ruby-on-Rest applications
|
32
31
|
|
33
|
-
|
32
|
+
== Starting the service
|
34
33
|
|
35
34
|
Ruby-on-Rest provides a shell command. Open your console, and type the following:
|
36
35
|
|
@@ -40,15 +39,20 @@ Ruby-on-Rest provides a shell command. Open your console, and type the following
|
|
40
39
|
This will first look at a YAML file called <my_service>_dev.yaml or <my_service>_live.yaml under the
|
41
40
|
current path.
|
42
41
|
|
43
|
-
Ruby-on-Rest
|
42
|
+
== Embedding the Ruby-on-Rest engine
|
43
|
+
|
44
|
+
Ruby-on-Rest can be embedded in Ruby code. This is useful for testing purposes:
|
44
45
|
|
45
46
|
config = {
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
47
|
+
|
48
|
+
"module" => "acme",
|
49
|
+
"service" => "hello",
|
50
|
+
"version" => "v1.0",
|
51
|
+
"webserver" => :webrick,
|
52
|
+
"destroy" => false ,
|
53
|
+
"daemon" => true,
|
54
|
+
"http_port" => 10000
|
55
|
+
}
|
52
56
|
|
53
57
|
engine = RubyRest::Engine.new( config )
|
54
58
|
engine.start
|
@@ -56,12 +60,3 @@ Ruby-on-Rest can also be embedded in ruby code. This is useful for testing purpo
|
|
56
60
|
# do some http calls here
|
57
61
|
# ...
|
58
62
|
engine.stop
|
59
|
-
|
60
|
-
|
61
|
-
=== Configuring your service
|
62
|
-
|
63
|
-
Ruby-on-Rest will try to load a file name <my_service>.rb which provides the implementation of the service.
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
data/Rakefile
CHANGED
@@ -6,7 +6,7 @@ require 'fileutils'
|
|
6
6
|
include FileUtils
|
7
7
|
|
8
8
|
NAME = "rubyrest"
|
9
|
-
VERS = "0.0
|
9
|
+
VERS = "0.1.0"
|
10
10
|
CLEAN.include ['**/.*.sw?', 'pkg/*', '.config', 'doc/*', 'coverage/*']
|
11
11
|
RDOC_OPTS = ['--quiet', '--title', "Ruby-on-Rest: A simple REST framework for Ruby",
|
12
12
|
"--opname", "index.html",
|
@@ -25,7 +25,7 @@ Rake::RDocTask.new do |rdoc|
|
|
25
25
|
rdoc.options += RDOC_OPTS
|
26
26
|
rdoc.main = "README"
|
27
27
|
rdoc.title = "Ruby-on-Rest Documentation"
|
28
|
-
rdoc.rdoc_files.add ['README', 'COPYING', "CHANGELOG", 'lib/rubyrest.rb', 'lib/rubyrest/**/*.rb'
|
28
|
+
rdoc.rdoc_files.add ['README', 'COPYING', "CHANGELOG", 'lib/rubyrest.rb', 'lib/rubyrest/**/*.rb' ]
|
29
29
|
end
|
30
30
|
|
31
31
|
spec = Gem::Specification.new do |s|
|
@@ -33,8 +33,8 @@ spec = Gem::Specification.new do |s|
|
|
33
33
|
s.version = VERS
|
34
34
|
s.platform = Gem::Platform::RUBY
|
35
35
|
s.has_rdoc = true
|
36
|
-
s.extra_rdoc_files = ["README", "CHANGELOG", "COPYING"
|
37
|
-
s.rdoc_options += RDOC_OPTS + [ '--exclude', 'lib/rubyrest.rb'
|
36
|
+
s.extra_rdoc_files = ["README", "CHANGELOG", "COPYING" ]
|
37
|
+
s.rdoc_options += RDOC_OPTS + [ '--exclude', 'lib/rubyrest.rb' ]
|
38
38
|
s.summary = "REST framework for Ruby."
|
39
39
|
s.description = s.summary
|
40
40
|
s.author = "Pedro Gutierrez"
|
@@ -0,0 +1,221 @@
|
|
1
|
+
module RubyRest
|
2
|
+
|
3
|
+
# Base Application class for all applications deployed
|
4
|
+
# with Ruby-on-Rest
|
5
|
+
class Application
|
6
|
+
attr_reader :config
|
7
|
+
|
8
|
+
# Defines whether update and create operations should
|
9
|
+
# automatically load and create new domain model, and automatically
|
10
|
+
# bind values from the request body document. Can be disabled
|
11
|
+
# in subclasses.
|
12
|
+
def auto_bind; true end
|
13
|
+
|
14
|
+
# To be overriden by applications that use databases
|
15
|
+
def init_database
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize( config )
|
20
|
+
@config = config
|
21
|
+
setup
|
22
|
+
register_model
|
23
|
+
register_resources
|
24
|
+
register_formatters
|
25
|
+
init_database
|
26
|
+
end
|
27
|
+
|
28
|
+
# Loads all the domain files provided by the developer
|
29
|
+
def register_model
|
30
|
+
path_to_model = @doc_base + "/domain"
|
31
|
+
Dir.foreach( path_to_model ) { |filename|
|
32
|
+
filename = File.basename( filename, ".rb" )
|
33
|
+
if !(File.basename( filename )[0] == ?.) && !FileTest.directory?( filename )
|
34
|
+
require path_to_model + "/" + filename
|
35
|
+
end
|
36
|
+
}
|
37
|
+
end
|
38
|
+
|
39
|
+
# Loads all the resources configured under the 'resources'
|
40
|
+
# folder relative to the application doc base
|
41
|
+
def register_resources
|
42
|
+
@resources_by_path = Hash.new
|
43
|
+
@resources_by_model = Hash.new
|
44
|
+
path_to_resources = @doc_base + "/resources"
|
45
|
+
|
46
|
+
Dir.foreach( @doc_base + "/resources" ) { |filename|
|
47
|
+
if !(File.basename( filename )[0] == ?.) && !FileTest.directory?( filename )
|
48
|
+
filename = File.basename( filename, ".rb" )
|
49
|
+
require path_to_resources + "/" + filename
|
50
|
+
register_resource( to_resource_class( filename ).new( self ) )
|
51
|
+
end
|
52
|
+
}
|
53
|
+
end
|
54
|
+
|
55
|
+
# Please override me if needed
|
56
|
+
def setup
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
# Returns the description and version
|
61
|
+
def to_s
|
62
|
+
@config[:service] + " " + @config[:version]
|
63
|
+
end
|
64
|
+
|
65
|
+
# Resolves the Ruby-on-Rest resource descriptor for the
|
66
|
+
# specified request path
|
67
|
+
def resource_for_path( path )
|
68
|
+
mount_point = "/"
|
69
|
+
path_tokens = path.split( "/" )
|
70
|
+
mount_point = "/"+path_tokens[1] if path_tokens.length > 0
|
71
|
+
resource = @resources_by_path[mount_point]
|
72
|
+
raise "no resource mounted at #{mount_point}" if !resource
|
73
|
+
return resource
|
74
|
+
end
|
75
|
+
|
76
|
+
# Retrieves the Ruby-on-Rest resource descriptor for the specified
|
77
|
+
# model object
|
78
|
+
def resource_for_model( model )
|
79
|
+
resource = @resources_by_model[model.class]
|
80
|
+
raise "no resource mounted for model #{model.class}" if !resource
|
81
|
+
return resource
|
82
|
+
end
|
83
|
+
|
84
|
+
# Registers a new resource instance
|
85
|
+
def register_resource( resource )
|
86
|
+
@resources_by_path[resource.class.mount_point] = resource
|
87
|
+
@resources_by_model[resource.model]=resource.class
|
88
|
+
puts "resource mounted: #{resource}"
|
89
|
+
end
|
90
|
+
|
91
|
+
# Returns the class name for the given resource name
|
92
|
+
def to_resource_class( name )
|
93
|
+
Class.by_name( @config[:module].capitalize + "::" + @config[:service].capitalize + "::" + name.to_s.capitalize )
|
94
|
+
end
|
95
|
+
|
96
|
+
# Register all the formatters supported by the application
|
97
|
+
# For the moment, only the Atom representation of resources
|
98
|
+
# is supported
|
99
|
+
def register_formatters
|
100
|
+
@formatters = { :atom => RubyRest::Atom::Formatter.new( self ) }
|
101
|
+
end
|
102
|
+
|
103
|
+
# Resolves the formatter to use in the returned
|
104
|
+
# representation
|
105
|
+
def formatter( params )
|
106
|
+
@formatters[params[:format]]||@formatters[:atom]
|
107
|
+
end
|
108
|
+
|
109
|
+
# Invoked by the web layer, on a DELETE request
|
110
|
+
def delete( params )
|
111
|
+
res = resource_for_path( params[:path] )
|
112
|
+
res.service.delete( res.service.load( params[:target], params[:principal] ), params[:principal] )
|
113
|
+
end
|
114
|
+
|
115
|
+
def render_model( params, model )
|
116
|
+
formatter( params ).format( model, params )
|
117
|
+
end
|
118
|
+
|
119
|
+
# Invoked by the web layer, on a GET request.
|
120
|
+
# Retrieves the collection or resource, and formats the
|
121
|
+
# result as a feed, entry or service document
|
122
|
+
def retrieve( params )
|
123
|
+
res = resource_for_path( params[:path] )
|
124
|
+
if params[:target] == nil
|
125
|
+
objects = res.service.list( params[:principal] )
|
126
|
+
else
|
127
|
+
if params[:property] == nil
|
128
|
+
objects = res.service.load( params[:target], params[:principal] )
|
129
|
+
else
|
130
|
+
objects = res.service.list_related( params[:target], params[:property], params[:principal] )
|
131
|
+
end
|
132
|
+
end
|
133
|
+
render_model( params, objects )
|
134
|
+
end
|
135
|
+
|
136
|
+
# Invoked by the web layer, on a POST request.
|
137
|
+
# This method delegates to the resource's service and provides
|
138
|
+
# a fresh new model object populated with the data
|
139
|
+
# found in the request body.
|
140
|
+
def create( params )
|
141
|
+
res = resource_for_path( params[:path] )
|
142
|
+
params[:body] = res.bind( res.service.create( params[:principal] ), params[:body] ) if auto_bind == true
|
143
|
+
object = res.service.save_new( params[:body], params[:principal] )
|
144
|
+
render_model( params, object )
|
145
|
+
end
|
146
|
+
|
147
|
+
# Invoked by the web layer, on a PUT request
|
148
|
+
# This method delegates to the resource's service and provides an existing model
|
149
|
+
# object, loaded and populated with the data found in the request body.
|
150
|
+
def update( params )
|
151
|
+
res = resource_for_path( params[:path] )
|
152
|
+
params[:body] = res.bind( res.service.load( params[:target], params[:principal] ), params[:body] ) if auto_bind == true
|
153
|
+
object = res.service.save_existing( params[:target], params[:body], params[:principal] )
|
154
|
+
render_model( params, object )
|
155
|
+
end
|
156
|
+
|
157
|
+
# Tells whether the specified model will be rendered
|
158
|
+
# as a feed or entry. To be subclassed
|
159
|
+
def is_a_collection( model )
|
160
|
+
model == nil || model.is_a?( Array )
|
161
|
+
end
|
162
|
+
|
163
|
+
# Implement me in subclasses
|
164
|
+
def is_a_service_doc( model )
|
165
|
+
|
166
|
+
end
|
167
|
+
|
168
|
+
end
|
169
|
+
|
170
|
+
|
171
|
+
# Specialization of a basic application, that provides support for
|
172
|
+
# Sequel (http://sequel.rubyforge.org) configuration at startup by
|
173
|
+
# implementing the 'init_database' method
|
174
|
+
class SequelApplication < RubyRest::Application
|
175
|
+
|
176
|
+
# Specifies the list of resources to be persisted
|
177
|
+
def self.with_persistent_resources *resources
|
178
|
+
@persistent = resources
|
179
|
+
end
|
180
|
+
|
181
|
+
# Returns the list of persistent resources
|
182
|
+
def self.persistent_resources
|
183
|
+
@persistent = [] if !@persistent
|
184
|
+
return @persistent
|
185
|
+
end
|
186
|
+
|
187
|
+
# Connects all persistent resources to the database and optionnally
|
188
|
+
# recreates the database schema. For the moment this only
|
189
|
+
# works with PostgreSQL databases
|
190
|
+
def init_database
|
191
|
+
@db = Sequel::Postgres::Database.new( @config )
|
192
|
+
self.class.persistent_resources.each{ |r|
|
193
|
+
to_resource_class( r ).model.db=@db
|
194
|
+
}
|
195
|
+
if @config[:destroy]
|
196
|
+
self.class.persistent_resources.each{ |r|
|
197
|
+
to_resource_class( r ).model.recreate_table
|
198
|
+
}
|
199
|
+
puts "importing initial data..."
|
200
|
+
import_data
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
# PLease override this in subclasses
|
205
|
+
def import_data
|
206
|
+
|
207
|
+
end
|
208
|
+
|
209
|
+
# Tells whether the specified model will be rendered
|
210
|
+
# as a feed or entry. To be subclassed
|
211
|
+
def is_a_collection( model )
|
212
|
+
super( model ) || model.is_a?( Sequel::Dataset )
|
213
|
+
end
|
214
|
+
|
215
|
+
def is_a_service_doc( model )
|
216
|
+
super( model ) || model.is_a?( RubyRest::Atom::ServiceDocument )
|
217
|
+
end
|
218
|
+
|
219
|
+
end
|
220
|
+
|
221
|
+
end
|