rubyrest 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +12 -0
- data/README +7 -8
- data/Rakefile +1 -1
- data/bin/rubyrest +1 -0
- data/lib/rubyrest.rb +2 -1
- data/lib/rubyrest/application.rb +164 -152
- data/lib/rubyrest/atom.rb +60 -28
- data/lib/rubyrest/client.rb +56 -27
- data/lib/rubyrest/engine.rb +5 -1
- data/lib/rubyrest/resource.rb +64 -29
- data/lib/rubyrest/webrick.rb +13 -8
- data/lib/rubyrest/yaml.rb +33 -0
- metadata +3 -2
data/CHANGELOG
CHANGED
@@ -1,3 +1,15 @@
|
|
1
|
+
== Release 0.2.0
|
2
|
+
|
3
|
+
* The Resource infrastructure has been almost rewritten from scratch. Ruby-on-Rest applications are now much easier to write.
|
4
|
+
* Atom is now deprecated. The default formatter is now based on Yaml.
|
5
|
+
* Simpler support for Sequel based applications
|
6
|
+
* The Hello sample app now uses RSpec to describe its behaviour and provides a starting point for developing new Ruby-on-Rest applications.
|
7
|
+
|
8
|
+
== Release 0.1.2
|
9
|
+
|
10
|
+
* Sequel:Application now supports SQLite rather than PostgreSQL, plus a better database import filename construction and logging at startup.
|
11
|
+
* The application is now injected into Domain classes, so the domain can access the application's global configuration (Domain must define #app and #app= methods).
|
12
|
+
|
1
13
|
== Release 0.1.1
|
2
14
|
|
3
15
|
* New Application hierarchy introduces SimpleApplication and SecureApplication
|
data/README
CHANGED
@@ -27,7 +27,11 @@ If you have any comments or suggestions please send an email to pedro dot gutier
|
|
27
27
|
|
28
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
29
|
|
30
|
-
* The 'Hello' application can be used as a skeletton for new Ruby-on-Rest applications
|
30
|
+
* The 'Hello' application can be used as a skeletton for new Ruby-on-Rest applications. You can test it by running the specifications:
|
31
|
+
|
32
|
+
spec hello_spec.rb
|
33
|
+
|
34
|
+
and hopefully everything goes green :D
|
31
35
|
|
32
36
|
== Starting the service
|
33
37
|
|
@@ -45,13 +49,8 @@ Ruby-on-Rest can be embedded in Ruby code. This is useful for testing purposes:
|
|
45
49
|
|
46
50
|
config = {
|
47
51
|
|
48
|
-
|
49
|
-
|
50
|
-
"version" => "v1.0",
|
51
|
-
"webserver" => :webrick,
|
52
|
-
"destroy" => false ,
|
53
|
-
"daemon" => true,
|
54
|
-
"http_port" => 10000
|
52
|
+
( ... config options here, see the hello app to get started ...)
|
53
|
+
|
55
54
|
}
|
56
55
|
|
57
56
|
engine = RubyRest::Engine.new( config )
|
data/Rakefile
CHANGED
@@ -6,7 +6,7 @@ require 'fileutils'
|
|
6
6
|
include FileUtils
|
7
7
|
|
8
8
|
NAME = "rubyrest"
|
9
|
-
VERS = "0.
|
9
|
+
VERS = "0.2.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",
|
data/bin/rubyrest
CHANGED
@@ -23,6 +23,7 @@ else
|
|
23
23
|
# deploy specific configuration, and build a hash out of it
|
24
24
|
params = YAML::load_file( "#{service}_#{deploy_mode}.yaml" )
|
25
25
|
params[ "service" ] = service
|
26
|
+
params[ "deploy_mode" ] = deploy_mode
|
26
27
|
|
27
28
|
engine = RubyRest::Engine.new( params )
|
28
29
|
engine.start
|
data/lib/rubyrest.rb
CHANGED
@@ -3,8 +3,8 @@
|
|
3
3
|
# Entry point to the framework.
|
4
4
|
# Loads all the files under the 'rubyrest' subdirectory
|
5
5
|
require "rubygems"
|
6
|
+
require "logger"
|
6
7
|
require "sequel"
|
7
|
-
require "sequel/postgres"
|
8
8
|
require "sequel/sqlite"
|
9
9
|
require "extensions/all"
|
10
10
|
require "rexml/document"
|
@@ -18,4 +18,5 @@ require File.join( dir, "resource" )
|
|
18
18
|
require File.join( dir, "application" )
|
19
19
|
require File.join( dir, "engine" )
|
20
20
|
require File.join( dir, "atom" )
|
21
|
+
require File.join( dir, "yaml" )
|
21
22
|
require File.join( dir, "client" )
|
data/lib/rubyrest/application.rb
CHANGED
@@ -1,43 +1,116 @@
|
|
1
1
|
module RubyRest
|
2
2
|
|
3
|
+
# Logger configuration, can be reused in RubyRest and Non RubyRest applications
|
4
|
+
module ApplicationLogger
|
5
|
+
|
6
|
+
def init_logger
|
7
|
+
case @config[:deploy_mode]
|
8
|
+
when "dev"
|
9
|
+
@logger = Logger.new( STDOUT )
|
10
|
+
@logger.level = Logger::DEBUG
|
11
|
+
when "live"
|
12
|
+
raise "you need to specify a logfile when deploying in LIVE mode" if !@config[:log]
|
13
|
+
@logger = Logger.new( @config[:log], 10, 1024000 )
|
14
|
+
@logger.level = Logger::ERROR
|
15
|
+
else
|
16
|
+
@logger = Logger.new( STDOUT )
|
17
|
+
@logger.level = Logger::DEBUG
|
18
|
+
end
|
19
|
+
@logger.info "log level set to #{@logger.level} [OK]" if @logger.info?
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
|
3
26
|
SYS_FILES = [ "application", "client" ].freeze
|
4
27
|
|
5
28
|
# Base Application class for all applications deployed
|
6
29
|
# with Ruby-on-Rest
|
7
30
|
class AbstractApplication
|
8
|
-
|
31
|
+
include RubyRest::ApplicationLogger
|
9
32
|
|
33
|
+
attr_reader :config, :logger
|
10
34
|
|
11
35
|
# To be overriden by applications that use databases
|
12
36
|
def init_database
|
37
|
+
|
38
|
+
end
|
13
39
|
|
40
|
+
# To be overriden
|
41
|
+
def create_tables
|
42
|
+
|
14
43
|
end
|
15
44
|
|
16
45
|
def initialize( config )
|
17
46
|
@config = config
|
18
|
-
|
19
|
-
@resources_by_path = Hash.new
|
20
|
-
@resources_by_name = Hash.new
|
21
|
-
@resources_by_domain = Hash.new
|
22
|
-
register_domain
|
23
|
-
register_formatters
|
47
|
+
init_logger
|
24
48
|
init_database
|
49
|
+
if @config[:destroy]==true
|
50
|
+
drop_schema
|
51
|
+
create_schema
|
52
|
+
load_initial_data if self.respond_to?( :load_initial_data )
|
53
|
+
end
|
54
|
+
register_resources
|
55
|
+
register_formatters
|
56
|
+
register_services
|
25
57
|
end
|
26
58
|
|
27
|
-
#
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
59
|
+
# Checks the configuration and issues an error if the specified
|
60
|
+
# key was not found
|
61
|
+
def check_config( key )
|
62
|
+
raise "missing configuration key: #{key}" if !@config[key]
|
63
|
+
end
|
64
|
+
|
65
|
+
# Specifies the list of resources to be persisted
|
66
|
+
def self.with_resources *resources
|
67
|
+
@resources = resources
|
68
|
+
end
|
69
|
+
|
70
|
+
# Returns the list of persistent resources
|
71
|
+
def self.resources
|
72
|
+
@resources = [] if !@resources
|
73
|
+
return @resources
|
74
|
+
end
|
75
|
+
|
76
|
+
def register_resources
|
77
|
+
@resources = Hash.new
|
78
|
+
self.class.resources.each{ |name|
|
79
|
+
resource_klass = to_resource_class( name )
|
80
|
+
resource = resource_klass.new( self, name )
|
81
|
+
@resources[resource_klass.mount_point]=resource
|
82
|
+
@resources[name]=resource
|
83
|
+
@logger.info "resource mounted: #{resource} [OK]" if @logger.info?
|
35
84
|
}
|
36
85
|
end
|
37
86
|
|
38
|
-
#
|
39
|
-
def
|
87
|
+
# Registers a single service
|
88
|
+
def register_service( service )
|
89
|
+
module_key = "#{service}_module".intern
|
90
|
+
module_key = :module if @config[module_key] == nil
|
91
|
+
|
92
|
+
host_key = "#{service}_host".intern
|
93
|
+
port_key = "#{service}_port".intern
|
94
|
+
role_key = "#{service}_role".intern
|
40
95
|
|
96
|
+
check_config( role_key )
|
97
|
+
check_config( port_key )
|
98
|
+
|
99
|
+
client_class = to_service_class( @config[module_key], service )
|
100
|
+
@services[@config[role_key].intern] = client_class.new( @config[host_key]||"localhost", @config[port_key] )
|
101
|
+
|
102
|
+
@logger.info "service registered #{service} as #{@config[role_key].intern}" if @logger.info?
|
103
|
+
end
|
104
|
+
|
105
|
+
# Registers all the services configured at startup
|
106
|
+
def register_services
|
107
|
+
@services = Hash.new
|
108
|
+
@config[:services].each{ |service| register_service( service ) } if @config[:services] != nil
|
109
|
+
end
|
110
|
+
|
111
|
+
# Returns a service, by name
|
112
|
+
def service( name )
|
113
|
+
@services.fetch( name )
|
41
114
|
end
|
42
115
|
|
43
116
|
# Returns the description and version
|
@@ -51,65 +124,57 @@ module RubyRest
|
|
51
124
|
mount_point = "/"
|
52
125
|
path_tokens = path.split( "/" )
|
53
126
|
mount_point = "/"+path_tokens[1] if path_tokens.length > 0
|
54
|
-
resource
|
55
|
-
raise "no resource mounted at #{mount_point}" if !resource
|
56
|
-
return resource
|
57
|
-
end
|
58
|
-
|
59
|
-
# Retrieves the Ruby-on-Rest resource descriptor for the specified
|
60
|
-
# model object
|
61
|
-
def resource_by_domain( model )
|
62
|
-
resource = @resources_by_domain[model.class]
|
63
|
-
raise "no resource mounted for domain #{model.class}" if !resource
|
64
|
-
return resource
|
65
|
-
end
|
66
|
-
|
67
|
-
def resource_by_name( name )
|
68
|
-
resource = @resources_by_name[name]
|
69
|
-
raise "no resource mounted for name #{name}" if !resource
|
70
|
-
return resource
|
127
|
+
resource(mount_point)
|
71
128
|
end
|
72
129
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
resource_klass = to_resource_class( name )
|
77
|
-
domain_klass = to_domain_class( name )
|
78
|
-
resource_klass.set_domain domain_klass
|
79
|
-
resource = resource_klass.new( self )
|
80
|
-
@resources_by_path[resource_klass.mount_point] = resource
|
81
|
-
@resources_by_domain[domain_klass]=resource
|
82
|
-
@resources_by_name[name]=resource
|
83
|
-
puts "resource mounted: #{resource} [OK]"
|
130
|
+
def resource( key )
|
131
|
+
raise "no resource found at key: #{key}" if @resources.key?( key ) == false
|
132
|
+
@resources[key]
|
84
133
|
end
|
85
134
|
|
86
135
|
# Returns the resource class name for the given name
|
87
136
|
def to_resource_class( name )
|
88
|
-
Class.by_name( @config[:module].capitalize + "::" + @config[:service].capitalize + "::
|
137
|
+
Class.by_name( @config[:module].to_s.capitalize + "::" + @config[:service].to_s.capitalize + "::" + name.to_s.capitalize )
|
89
138
|
end
|
90
139
|
|
91
|
-
|
92
|
-
|
93
|
-
Class.by_name( @config[:module].capitalize + "::" + @config[:service].capitalize + "::Domain::" + name.to_s.capitalize )
|
140
|
+
def to_service_class( module_name, service_name )
|
141
|
+
Class.by_name( module_name.to_s.capitalize + "::" + service_name.to_s.capitalize + "::Client" )
|
94
142
|
end
|
95
143
|
|
96
144
|
# Register all the formatters supported by the application
|
97
145
|
# For the moment, only the Atom representation of resources
|
98
146
|
# is supported
|
99
147
|
def register_formatters
|
100
|
-
@formatters = {
|
148
|
+
@formatters = {
|
149
|
+
:atom => RubyRest::Atom::Formatter.new( self ),
|
150
|
+
:yaml => RubyRest::Format::Yaml.new( self )
|
151
|
+
}
|
152
|
+
end
|
153
|
+
|
154
|
+
def parse_request( params, request )
|
155
|
+
data = formatter( params ).parse_request( request.body )
|
156
|
+
@logger.info( data ) if @logger.info?
|
157
|
+
return data
|
101
158
|
end
|
102
159
|
|
160
|
+
|
103
161
|
# Resolves the formatter to use in the returned
|
104
162
|
# representation
|
105
163
|
def formatter( params )
|
106
|
-
@formatters[params[:format]]||@formatters[:
|
164
|
+
@formatters[params[:format]]||@formatters[:yaml]
|
107
165
|
end
|
108
166
|
|
109
167
|
def render_model( params, model )
|
110
168
|
formatter( params ).format( model, params )
|
111
169
|
end
|
112
170
|
|
171
|
+
# Binds values found in the specified source in
|
172
|
+
# properties of the specified domain object
|
173
|
+
# Delegates to the formatter
|
174
|
+
def bind( object, params )
|
175
|
+
formatter( params ).bind( object, params[:body], params )
|
176
|
+
end
|
177
|
+
|
113
178
|
# Tells whether the specified model will be rendered
|
114
179
|
# as a feed or entry. To be subclassed
|
115
180
|
def is_a_collection( model )
|
@@ -118,55 +183,15 @@ module RubyRest
|
|
118
183
|
|
119
184
|
# Implement me in subclasses
|
120
185
|
def is_a_service_doc( model )
|
121
|
-
|
186
|
+
model != nil && model.is_a?( RubyRest::Atom::ServiceDocument )
|
122
187
|
end
|
123
188
|
|
124
|
-
|
125
189
|
end
|
126
190
|
|
127
|
-
#
|
128
|
-
class SimpleApplication < RubyRest::AbstractApplication
|
129
|
-
|
130
|
-
# Invoked by the web layer, on a GET request.
|
131
|
-
# Retrieves the collection or resource, and formats the
|
132
|
-
# result as a feed, entry or service document
|
133
|
-
def retrieve( params )
|
134
|
-
res = resource_by_path( params[:path] )
|
135
|
-
render_model( params, res.domain.retrieve( params[:target], params[:property] ) )
|
136
|
-
end
|
137
|
-
|
138
|
-
# Invoked by the web layer, on a POST request.
|
139
|
-
# This method delegates to the resource's service and provides
|
140
|
-
# a fresh new model object populated with the data
|
141
|
-
# found in the request body.
|
142
|
-
def create( params )
|
143
|
-
res = resource_by_path( params[:path] )
|
144
|
-
render_model( params, res.domain.create( params[:body] ) )
|
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_by_path( params[:path] )
|
152
|
-
render_model( params, res.domain.update( params[:target], params[:body] ) )
|
153
|
-
end
|
154
|
-
|
155
|
-
# Invoked by the web layer, on a DELETE request
|
156
|
-
def delete( params )
|
157
|
-
res = resource_by_path( params[:path] )
|
158
|
-
res.domain.delete( params[:target] )
|
159
|
-
end
|
160
|
-
|
161
|
-
|
162
|
-
end
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
# Specialization of a Simple application, that introduces the notion
|
191
|
+
# Specialization of an Abstract application, that introduces the notion
|
167
192
|
# of security services and principals. It overrides the retrieve, create, update
|
168
193
|
# and delete methods and adds some extras like automatic binding.
|
169
|
-
class SecureApplication < RubyRest::
|
194
|
+
class SecureApplication < RubyRest::AbstractApplication
|
170
195
|
|
171
196
|
# Defines whether update and create operations should
|
172
197
|
# automatically load and create new domain model, and automatically
|
@@ -178,25 +203,24 @@ module RubyRest
|
|
178
203
|
# its security service
|
179
204
|
def initialize( config )
|
180
205
|
super( config )
|
181
|
-
register_security
|
206
|
+
register_security if !@config[:disable_security]
|
182
207
|
end
|
183
208
|
|
184
|
-
# Register the security service, if specified in the
|
185
|
-
# application configuration hash
|
186
209
|
def register_security
|
187
|
-
|
188
|
-
|
189
|
-
|
210
|
+
if !@services[:security]
|
211
|
+
raise "no security service was defined in application #{self}"
|
212
|
+
end
|
190
213
|
end
|
191
214
|
|
192
215
|
# Returns the security service, of nil if not configured
|
193
216
|
def security
|
194
|
-
@security
|
217
|
+
@services[:security]
|
195
218
|
end
|
196
219
|
|
197
220
|
# Resolves the principal, by inoking the security service
|
198
221
|
def resolve_principal( params )
|
199
|
-
|
222
|
+
return params if @config[:disable_security]
|
223
|
+
principal = security.principal( params[:authkey] )
|
200
224
|
raise "No principal was found for authentication key: #{params[:authkey]}" if !principal
|
201
225
|
params[:principal]=principal
|
202
226
|
return params
|
@@ -208,13 +232,15 @@ module RubyRest
|
|
208
232
|
def retrieve( params )
|
209
233
|
params = resolve_principal( params )
|
210
234
|
res = resource_by_path( params[:path] )
|
235
|
+
params[:resource]=res
|
236
|
+
|
211
237
|
if params[:target] == nil
|
212
|
-
objects = res.
|
238
|
+
objects = res.list( params[:principal] )
|
213
239
|
else
|
214
240
|
if params[:property] == nil
|
215
|
-
objects = res.
|
241
|
+
objects = res.single( params[:target], params[:principal] )
|
216
242
|
else
|
217
|
-
objects = res.
|
243
|
+
objects = res.list_related( params[:target], params[:property], params[:principal] )
|
218
244
|
end
|
219
245
|
end
|
220
246
|
render_model( params, objects )
|
@@ -227,8 +253,9 @@ module RubyRest
|
|
227
253
|
def create( params )
|
228
254
|
params = resolve_principal( params )
|
229
255
|
res = resource_by_path( params[:path] )
|
230
|
-
params[:
|
231
|
-
|
256
|
+
params[:resource]=res
|
257
|
+
params[:body] = bind( res.new_instance( params[:principal] ), params ) if auto_bind == true
|
258
|
+
object = res.save_new( params[:body], params[:principal] )
|
232
259
|
render_model( params, object )
|
233
260
|
end
|
234
261
|
|
@@ -238,8 +265,9 @@ module RubyRest
|
|
238
265
|
def update( params )
|
239
266
|
params = resolve_principal( params )
|
240
267
|
res = resource_by_path( params[:path] )
|
241
|
-
params[:
|
242
|
-
|
268
|
+
params[:resource]=res
|
269
|
+
params[:body] = bind( res.single( params[:target], params[:principal] ), params ) if auto_bind == true
|
270
|
+
object = res.save_existing( params[:body], params[:principal] )
|
243
271
|
render_model( params, object )
|
244
272
|
end
|
245
273
|
|
@@ -247,7 +275,8 @@ module RubyRest
|
|
247
275
|
def delete( params )
|
248
276
|
params = resolve_principal( params )
|
249
277
|
res = resource_by_path( params[:path] )
|
250
|
-
|
278
|
+
params[:resource]=res
|
279
|
+
res.delete_existing( res.single( params[:target], params[:principal] ), params[:principal] )
|
251
280
|
end
|
252
281
|
|
253
282
|
end
|
@@ -257,59 +286,42 @@ module RubyRest
|
|
257
286
|
# implementing the 'init_database' method
|
258
287
|
class SequelApplication < RubyRest::SecureApplication
|
259
288
|
|
260
|
-
|
261
|
-
def self.with_persistent_resources *resources
|
262
|
-
@persistent = resources
|
263
|
-
end
|
264
|
-
|
265
|
-
# Returns the list of persistent resources
|
266
|
-
def self.persistent_resources
|
267
|
-
@persistent = [] if !@persistent
|
268
|
-
return @persistent
|
269
|
-
end
|
289
|
+
attr_reader :db
|
270
290
|
|
271
|
-
#
|
272
|
-
|
273
|
-
|
291
|
+
# Tells whether the specified model will be rendered
|
292
|
+
# as a feed or entry. To be subclassed
|
293
|
+
def is_a_collection( model )
|
294
|
+
super( model ) || model.is_a?( Sequel::Dataset )
|
274
295
|
end
|
275
296
|
|
276
|
-
# Connects all persistent resources to the database and optionnally
|
277
|
-
# recreates the database schema. For the moment this only
|
278
|
-
# works with PostgreSQL databases
|
279
297
|
def init_database
|
280
|
-
|
281
|
-
|
282
|
-
#@db = Sequel.open "sqlite:///#{to_database_path}"
|
283
|
-
self.class.persistent_resources.each{ |r|
|
284
|
-
resource = resource_by_name( r )
|
285
|
-
resource.domain.db=@db
|
286
|
-
if @config[:destroy]
|
287
|
-
resource.domain.recreate_table
|
288
|
-
end
|
289
|
-
}
|
290
|
-
import_data if @config[:destroy]
|
298
|
+
check_config( :database )
|
299
|
+
@db = Sequel.open( "sqlite:///#{@config[:database]}", :max_connections => 10, :logger => @logger )
|
291
300
|
end
|
292
301
|
|
293
|
-
#
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
302
|
+
# Convenience method that hides that safely
|
303
|
+
# drops a table
|
304
|
+
def drop_table( name )
|
305
|
+
|
306
|
+
begin
|
307
|
+
@db << "DROP TABLE #{name}"
|
308
|
+
rescue => e
|
309
|
+
@logger.warn e.message if @logger.warn?
|
300
310
|
end
|
301
|
-
|
311
|
+
|
302
312
|
end
|
303
313
|
|
304
|
-
#
|
305
|
-
|
306
|
-
|
307
|
-
|
314
|
+
# Convenience method that safely drops a view
|
315
|
+
def drop_view( name )
|
316
|
+
|
317
|
+
begin
|
318
|
+
@db << "DROP VIEW #{name}"
|
319
|
+
rescue => e
|
320
|
+
@logger.warn e.message if @logger.warn?
|
321
|
+
end
|
322
|
+
|
308
323
|
end
|
309
324
|
|
310
|
-
def is_a_service_doc( model )
|
311
|
-
super( model ) || model.is_a?( RubyRest::Atom::ServiceDocument )
|
312
|
-
end
|
313
325
|
|
314
326
|
end
|
315
327
|
|
data/lib/rubyrest/atom.rb
CHANGED
@@ -15,22 +15,14 @@ module RubyRest
|
|
15
15
|
ATOM_DATE_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
|
16
16
|
|
17
17
|
|
18
|
-
|
19
|
-
# Ruby-on-Rest specialization of the REXML document
|
20
|
-
# class. Adds some convenient ways of accessing data from
|
21
|
-
# a Atom document
|
22
|
-
class Document < REXML::Document
|
23
|
-
|
24
|
-
def method_missing( name, *args )
|
25
|
-
get_value( name )
|
26
|
-
end
|
18
|
+
module Extensions
|
27
19
|
|
28
20
|
# Resolves the missing method into a content property
|
29
21
|
# and returns its text value
|
30
22
|
def get_value( name, required=true )
|
31
23
|
location = "/entry/rubyrest:content/#{name}"
|
32
24
|
value = text( location )
|
33
|
-
raise "missing value at location #{location} in #{self.to_s}" if required && !value
|
25
|
+
raise "missing value at location #{location} in #{self.to_s}" if required && (!value || value.length == 0)
|
34
26
|
return value
|
35
27
|
end
|
36
28
|
|
@@ -38,18 +30,27 @@ module RubyRest
|
|
38
30
|
get_text( "/entry/rubyrest:content/#{name}" ).value = value
|
39
31
|
end
|
40
32
|
|
41
|
-
|
42
|
-
|
43
|
-
def id
|
44
|
-
text( "/entry/rubyrest:content/id" )
|
33
|
+
def method_missing( name, *args )
|
34
|
+
get_value( name )
|
45
35
|
end
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
def clone
|
50
|
-
self.class.new self.to_s
|
36
|
+
|
37
|
+
def new_copy
|
38
|
+
REXML::Document.new self.to_s
|
51
39
|
end
|
52
|
-
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
class REXML::Element
|
44
|
+
include RubyRest::Atom::Extensions
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
# Ruby-on-Rest specialization of the REXML document
|
49
|
+
# class. Adds some convenient ways of accessing data from
|
50
|
+
# a Atom document
|
51
|
+
class REXML::Document
|
52
|
+
include RubyRest::Atom::Extensions
|
53
|
+
|
53
54
|
end
|
54
55
|
|
55
56
|
# Main Atom formatter class, composed of more specialized objects
|
@@ -71,6 +72,8 @@ module RubyRest
|
|
71
72
|
@props=Hash.new
|
72
73
|
@props[:simple]=SimpleProperty.new( self )
|
73
74
|
@props[:date]=DateProperty.new( self )
|
75
|
+
@props[:nested]=NestedProperty.new( self )
|
76
|
+
|
74
77
|
end
|
75
78
|
|
76
79
|
# Resolves the specified name into a formatter
|
@@ -106,13 +109,18 @@ module RubyRest
|
|
106
109
|
@parent = parent
|
107
110
|
end
|
108
111
|
|
112
|
+
# Shortcut method
|
113
|
+
def app
|
114
|
+
@parent.app
|
115
|
+
end
|
116
|
+
|
109
117
|
def tag( options )
|
110
118
|
value = options[:tag]||options[:property]
|
111
119
|
return value.to_s
|
112
120
|
end
|
113
121
|
|
114
122
|
def object_value( object, options )
|
115
|
-
object
|
123
|
+
object[ options[:property] ]
|
116
124
|
end
|
117
125
|
|
118
126
|
def xml_value( object, options )
|
@@ -122,7 +130,7 @@ module RubyRest
|
|
122
130
|
end
|
123
131
|
|
124
132
|
def bind( object, options, value )
|
125
|
-
object
|
133
|
+
object[ options[:property] ]=value
|
126
134
|
end
|
127
135
|
|
128
136
|
def parse( object, options, xml )
|
@@ -151,6 +159,7 @@ module RubyRest
|
|
151
159
|
|
152
160
|
def self.format_date( date )
|
153
161
|
date = Time.now if !date
|
162
|
+
date = Time.at( date ) if date.is_a?( Bignum )
|
154
163
|
date.strftime( ATOM_DATE_FORMAT )
|
155
164
|
end
|
156
165
|
|
@@ -158,8 +167,9 @@ module RubyRest
|
|
158
167
|
self.class.format_date( date )
|
159
168
|
end
|
160
169
|
|
161
|
-
def string2date(
|
162
|
-
Date.strptime(
|
170
|
+
def string2date( str )
|
171
|
+
date = Date.strptime( str, ATOM_DATE_FORMAT )
|
172
|
+
Time.gm( date.year, date.month, date.day, 0, 0, 0, 0 ).to_i
|
163
173
|
end
|
164
174
|
|
165
175
|
def value_from_model( options, object )
|
@@ -172,6 +182,28 @@ module RubyRest
|
|
172
182
|
|
173
183
|
end
|
174
184
|
|
185
|
+
|
186
|
+
class NestedProperty < SimpleProperty
|
187
|
+
|
188
|
+
NESTED_PROPERTY_SEPARATOR = '_'.freeze
|
189
|
+
|
190
|
+
def bind( object, options, value )
|
191
|
+
prop = options[:property]
|
192
|
+
tokens = prop.to_s.split( NESTED_PROPERTY_SEPARATOR )
|
193
|
+
property = tokens[0].intern
|
194
|
+
nested_prop = tokens[1]
|
195
|
+
object[property]= app.resource( property ).method( "first_by_#{nested_prop}").call( value )[:id]
|
196
|
+
end
|
197
|
+
|
198
|
+
#def object_value( object, options )
|
199
|
+
# prop = options[:property]
|
200
|
+
# value = object
|
201
|
+
# prop.to_s.split( NESTED_PROPERTY_SEPARATOR ).each{ |token| value = value.send token.to_s }
|
202
|
+
# return value
|
203
|
+
#end
|
204
|
+
|
205
|
+
end
|
206
|
+
|
175
207
|
class DomainFormatter
|
176
208
|
|
177
209
|
# Creates a new formatter, as a child of the
|
@@ -213,17 +245,17 @@ module RubyRest
|
|
213
245
|
end
|
214
246
|
|
215
247
|
def format_entry( object, xml, params )
|
216
|
-
resource =
|
248
|
+
resource = params[:resource]
|
217
249
|
raise "no resource found for entry #{object}" if !resource
|
218
250
|
entry = xml.add_element( "entry", NAMESPACES )
|
219
251
|
entry.add_element( "title" )
|
220
|
-
entry.add_element( "author" ).add_element( "name" ).add_text( object
|
221
|
-
entry.add_element( "updated" ).add_text( DateProperty.format_date( object
|
252
|
+
entry.add_element( "author" ).add_element( "name" ).add_text( object[:modifiedby] )
|
253
|
+
entry.add_element( "updated" ).add_text( DateProperty.format_date( object[:modified] ) )
|
222
254
|
entry.add_element( "id" )
|
223
255
|
entry.add_element( "summary" )
|
224
256
|
|
225
257
|
resource.class.links.each{ |link|
|
226
|
-
render = params[:principal].profile.split( "," ).include?( link[:role].to_s )
|
258
|
+
render = link[:role] == nil || params[:principal].profile.split( "," ).include?( link[:role].to_s )
|
227
259
|
entry.add_element( "link", { "title" => link[:title].to_s, "rel" => link[:rel].to_s} ) if render
|
228
260
|
}
|
229
261
|
|
data/lib/rubyrest/client.rb
CHANGED
@@ -7,7 +7,7 @@ module RubyRest
|
|
7
7
|
# Default client for web services deployed with
|
8
8
|
# Ruby-on-Rest. Translates create, retrieve, update and
|
9
9
|
# delete methods into POST, GET, PUT and DELETE http requests
|
10
|
-
class
|
10
|
+
class Abstract
|
11
11
|
|
12
12
|
# Configures the server name and port
|
13
13
|
def initialize( host, port )
|
@@ -29,7 +29,7 @@ module RubyRest
|
|
29
29
|
path << to_query_string( data ) if data
|
30
30
|
headers = prepare_headers( api_key )
|
31
31
|
rsp = http.get( encode_path( path ), headers )
|
32
|
-
return
|
32
|
+
return hidrate_data( rsp ) if rsp.code.to_i == 200
|
33
33
|
end
|
34
34
|
|
35
35
|
# Converts the specified hash of data into a
|
@@ -38,10 +38,10 @@ module RubyRest
|
|
38
38
|
# the response status code is 201.
|
39
39
|
def create( path, data, api_key = nil )
|
40
40
|
body = nil
|
41
|
-
body =
|
41
|
+
body = serialize_data( data ).to_s if data != nil
|
42
42
|
headers = prepare_headers( api_key )
|
43
43
|
rsp = http.post( encode_path( path ), body, headers )
|
44
|
-
return
|
44
|
+
return hidrate_data( rsp ) if rsp.code.to_i == 201
|
45
45
|
end
|
46
46
|
|
47
47
|
# Converts the specified hash of data into a
|
@@ -50,10 +50,10 @@ module RubyRest
|
|
50
50
|
# the response status code is 200.
|
51
51
|
def update( path, data, api_key = nil)
|
52
52
|
body = nil
|
53
|
-
body =
|
53
|
+
body = serialize_data( data ).to_s if data != nil
|
54
54
|
headers = prepare_headers( api_key )
|
55
55
|
rsp = http.put( encode_path( path ), body, headers )
|
56
|
-
return
|
56
|
+
return hidrate_data( rsp ) if rsp.code.to_i == 200
|
57
57
|
end
|
58
58
|
|
59
59
|
# Converts the specified hash of data into a
|
@@ -63,7 +63,8 @@ module RubyRest
|
|
63
63
|
path << to_query_string( data ) if data
|
64
64
|
headers = prepare_headers( api_key )
|
65
65
|
rsp = http.delete( encode_path( path ), headers )
|
66
|
-
rsp.code.to_i
|
66
|
+
raise "error when deleting" if rsp.code.to_i != 200
|
67
|
+
return nil
|
67
68
|
end
|
68
69
|
|
69
70
|
# Convenience method that returns the
|
@@ -83,42 +84,70 @@ module RubyRest
|
|
83
84
|
def prepare_headers( api_key = nil )
|
84
85
|
headers = { "Content-Type" => "text/xml; charset=utf-8" }
|
85
86
|
headers[ "token" ] = api_key if api_key != nil
|
87
|
+
headers["format"]=@format
|
86
88
|
return headers
|
87
89
|
end
|
88
90
|
|
89
|
-
#
|
90
|
-
#
|
91
|
-
def
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
91
|
+
# Reusable method that creates a query string
|
92
|
+
# with the specified hash of data
|
93
|
+
def to_query_string( data )
|
94
|
+
qs = "?"
|
95
|
+
params.each{ |k,v| qs << "#{k}=#{v}&" }
|
96
|
+
qs.chop!
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
|
102
|
+
class Yaml < Abstract
|
103
|
+
|
104
|
+
def initialize( host, port )
|
105
|
+
super( host, port )
|
106
|
+
@format = "yaml"
|
107
|
+
end
|
108
|
+
|
109
|
+
def hidrate_data( response )
|
110
|
+
YAML::load( response.body ) if response.body
|
111
|
+
end
|
112
|
+
|
113
|
+
def serialize_data( hash )
|
114
|
+
hash.to_yaml
|
96
115
|
end
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
116
|
+
|
117
|
+
end
|
118
|
+
|
119
|
+
|
120
|
+
class Atom < Abstract
|
121
|
+
|
122
|
+
def initialize( host, port )
|
123
|
+
super( host, port )
|
124
|
+
@format = "atom"
|
125
|
+
end
|
126
|
+
|
127
|
+
def hidrate_data( response )
|
101
128
|
begin
|
102
|
-
|
129
|
+
REXML::Document.new( response.body ) if response.body
|
103
130
|
rescue => e
|
104
131
|
puts "unable to parse response body: " + e.message
|
105
132
|
puts "---- response body ----"
|
106
|
-
puts
|
133
|
+
puts response.body
|
107
134
|
puts "-----------------------"
|
108
135
|
return nil
|
109
136
|
end
|
110
137
|
end
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
qs.chop!
|
138
|
+
|
139
|
+
def serialize_data( hash )
|
140
|
+
doc = REXML::Document.new
|
141
|
+
content = doc.add_element( "entry" ).add_element( "rubyrest:content" )
|
142
|
+
hash.each { |name,value| content.add_element( name.to_s ).add_text( value.to_s ) }
|
143
|
+
return doc
|
118
144
|
end
|
145
|
+
|
146
|
+
|
119
147
|
|
120
148
|
end
|
121
149
|
|
150
|
+
|
122
151
|
end
|
123
152
|
|
124
153
|
end
|
data/lib/rubyrest/engine.rb
CHANGED
@@ -12,7 +12,7 @@ module RubyRest
|
|
12
12
|
# the statement +require @service+ works.
|
13
13
|
def initialize( config )
|
14
14
|
params = intern_config( config )
|
15
|
-
|
15
|
+
require_library( params )
|
16
16
|
@app = Class.by_name( params[:module].capitalize + "::" + params[:service].capitalize + "::Application" ).new( params )
|
17
17
|
if params[:daemon ] != nil
|
18
18
|
@daemon = params[:daemon ]
|
@@ -20,6 +20,10 @@ module RubyRest
|
|
20
20
|
@server = Class.by_name( "RubyRest::" + params[:webserver].to_s.capitalize + "::Server" ).new( @app )
|
21
21
|
end
|
22
22
|
|
23
|
+
# Requires the library params
|
24
|
+
def require_library( params )
|
25
|
+
require params[:gem] if params[:gem]
|
26
|
+
end
|
23
27
|
|
24
28
|
# Returns a new hash, with all the keys as symbols
|
25
29
|
def intern_config( hash )
|
data/lib/rubyrest/resource.rb
CHANGED
@@ -1,27 +1,25 @@
|
|
1
1
|
module RubyRest
|
2
|
+
|
3
|
+
module Resource
|
4
|
+
|
2
5
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
+
|
7
|
+
# Base REST Resource class that acts as a wrapper for the actual
|
8
|
+
# business logic, data formatting (response time) and validations (request time)
|
9
|
+
class Base
|
10
|
+
|
11
|
+
attr_reader :app, :name
|
6
12
|
|
7
13
|
# Creates a new resource, under the given application
|
8
|
-
def initialize( app )
|
14
|
+
def initialize( app, name )
|
9
15
|
@app = app
|
16
|
+
@logger = app.logger
|
17
|
+
@name = name
|
10
18
|
end
|
11
19
|
|
12
|
-
#
|
13
|
-
def
|
14
|
-
@
|
15
|
-
end
|
16
|
-
|
17
|
-
# Returns the domain implementation
|
18
|
-
def self.domain
|
19
|
-
@domain
|
20
|
-
end
|
21
|
-
|
22
|
-
# Convenience method
|
23
|
-
def domain
|
24
|
-
self.class.domain
|
20
|
+
# Shortcut method to access other resources
|
21
|
+
def resource( name )
|
22
|
+
@app.resource( name )
|
25
23
|
end
|
26
24
|
|
27
25
|
# Defines the url type the resource is going to
|
@@ -34,6 +32,24 @@ module RubyRest
|
|
34
32
|
@mount_point
|
35
33
|
end
|
36
34
|
|
35
|
+
# Sets the list of properties thqt can be updated
|
36
|
+
def self.with_mutable_props *props
|
37
|
+
@mutable_props = *props
|
38
|
+
end
|
39
|
+
|
40
|
+
# Returns the list of properties thqt can be
|
41
|
+
# updated
|
42
|
+
def self.mutable_props; @mutable_props end
|
43
|
+
|
44
|
+
def self.related opts
|
45
|
+
self.related_resources[opts[:property]] =opts[:resource]
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.related_resources
|
49
|
+
@related_resources = Hash.new if !@related_resources
|
50
|
+
return @related_resources
|
51
|
+
end
|
52
|
+
|
37
53
|
def self.atom modifiers
|
38
54
|
self.props << modifiers
|
39
55
|
end
|
@@ -56,21 +72,40 @@ module RubyRest
|
|
56
72
|
return @links
|
57
73
|
end
|
58
74
|
|
59
|
-
# Updates the specified object with the data found
|
60
|
-
# in the specified xml object, and the rules defined in
|
61
|
-
# the resource parsers
|
62
|
-
def bind( object, xml )
|
63
|
-
self.class.props.each{ |p|
|
64
|
-
@app.formatter(p).property( p ).parse( object, p, xml )
|
65
|
-
}
|
66
|
-
return object
|
67
|
-
end
|
68
|
-
|
69
75
|
# String representation of a resource
|
70
76
|
def to_s
|
71
|
-
"path #{self.class.mount_point},
|
77
|
+
"path #{self.class.mount_point}, class #{self.class}"
|
72
78
|
end
|
73
79
|
|
74
|
-
|
80
|
+
end
|
75
81
|
|
82
|
+
class Sequel < Base
|
83
|
+
|
84
|
+
attr_reader :dataset
|
85
|
+
|
86
|
+
# Creates a new resource, under the given application
|
87
|
+
def initialize( app, name )
|
88
|
+
super( app, name )
|
89
|
+
@dataset = app.db[self.class.table_name]
|
90
|
+
end
|
91
|
+
|
92
|
+
# Provides access to an arbitrary table
|
93
|
+
def table( name )
|
94
|
+
app.db[name]
|
95
|
+
end
|
96
|
+
|
97
|
+
def self.with_table_name table_name
|
98
|
+
@table_name = table_name
|
99
|
+
end
|
100
|
+
|
101
|
+
def self.table_name
|
102
|
+
@table_name
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
|
76
111
|
end
|
data/lib/rubyrest/webrick.rb
CHANGED
@@ -11,7 +11,7 @@ module RubyRest
|
|
11
11
|
# Builds a new Webrick adapter instance
|
12
12
|
# for the given application
|
13
13
|
def initialize( app )
|
14
|
-
super( :Port => app.config[:http_port] )
|
14
|
+
super( :Port => app.config[:http_port], :Logger => app.logger )
|
15
15
|
@app = app
|
16
16
|
servlet = RubyRest::Webrick::Servlet.new( self )
|
17
17
|
mount "/", servlet
|
@@ -49,10 +49,11 @@ module RubyRest
|
|
49
49
|
args = Hash.new
|
50
50
|
url_tokens = decode_path( request.path ).split( "/")
|
51
51
|
args[:authkey] = request["token"]
|
52
|
+
args[:format]= request["format"]
|
52
53
|
args[:target] = url_tokens[2] if url_tokens.length>2
|
53
54
|
args[:property] = url_tokens[3] if url_tokens.length>3
|
54
55
|
args[:path] = request.path
|
55
|
-
args[:body] =
|
56
|
+
args[:body] = @app.parse_request( args, request ) if request.body
|
56
57
|
return args
|
57
58
|
end
|
58
59
|
|
@@ -60,27 +61,27 @@ module RubyRest
|
|
60
61
|
params = args(request )
|
61
62
|
response.status = 200
|
62
63
|
params[:action]=:retrieve
|
63
|
-
|
64
|
+
data = @app.retrieve( params )
|
64
65
|
response["content-type"]=params[:content_type]
|
65
|
-
|
66
|
+
response.body = data
|
66
67
|
end
|
67
68
|
|
68
69
|
def do_POST( request, response )
|
69
70
|
params = args(request )
|
70
71
|
response.status = 201
|
71
72
|
params[:action]=:create
|
72
|
-
|
73
|
+
data = @app.create( params )
|
73
74
|
response["content-type"]=params[:content_type]
|
74
|
-
|
75
|
+
response.body = data
|
75
76
|
end
|
76
77
|
|
77
78
|
def do_PUT( request, response )
|
78
79
|
params = args(request )
|
79
80
|
response.status = 200
|
80
81
|
params[:action]=:update
|
81
|
-
|
82
|
+
data = @app.update( params )
|
82
83
|
response["content-type"]=params[:content_type]
|
83
|
-
|
84
|
+
response.body = data
|
84
85
|
end
|
85
86
|
|
86
87
|
def do_DELETE( request, response )
|
@@ -90,6 +91,10 @@ module RubyRest
|
|
90
91
|
@app.delete( params )
|
91
92
|
end
|
92
93
|
|
94
|
+
def to_s
|
95
|
+
"Ruby-on-Rest WEBrick servlet"
|
96
|
+
end
|
97
|
+
|
93
98
|
end
|
94
99
|
|
95
100
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module RubyRest
|
2
|
+
|
3
|
+
module Format
|
4
|
+
|
5
|
+
class Yaml
|
6
|
+
|
7
|
+
def initialize( app )
|
8
|
+
@app = app
|
9
|
+
end
|
10
|
+
|
11
|
+
def format( model, params )
|
12
|
+
model.to_yaml
|
13
|
+
end
|
14
|
+
|
15
|
+
def parse_request( request )
|
16
|
+
YAML::load( request )
|
17
|
+
end
|
18
|
+
|
19
|
+
# Treats the object and the source
|
20
|
+
# as a hash, and updates the object
|
21
|
+
def bind( object, source, params )
|
22
|
+
# preprocesses the source hash
|
23
|
+
# and updates the domain data
|
24
|
+
resource = params[:resource]
|
25
|
+
resource.process_request_data( source ) if resource.respond_to?( :process_request_data )
|
26
|
+
object.update( source )
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
metadata
CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.1
|
|
3
3
|
specification_version: 1
|
4
4
|
name: rubyrest
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.
|
7
|
-
date: 2007-
|
6
|
+
version: 0.2.0
|
7
|
+
date: 2007-08-17 00:00:00 +02:00
|
8
8
|
summary: REST framework for Ruby.
|
9
9
|
require_paths:
|
10
10
|
- lib
|
@@ -43,6 +43,7 @@ files:
|
|
43
43
|
- lib/rubyrest/engine.rb
|
44
44
|
- lib/rubyrest/resource.rb
|
45
45
|
- lib/rubyrest/webrick.rb
|
46
|
+
- lib/rubyrest/yaml.rb
|
46
47
|
test_files: []
|
47
48
|
|
48
49
|
rdoc_options:
|