rubyrest 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG ADDED
@@ -0,0 +1,6 @@
1
+ *0.0.1*
2
+
3
+ * Initial release with very basic CRUD facilities
4
+ * Hello sample application included
5
+
6
+
data/COPYING ADDED
@@ -0,0 +1,18 @@
1
+ Copyright (c) 2007 Pedro Gutierrez
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to
5
+ deal in the Software without restriction, including without limitation the
6
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
7
+ sell copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
16
+ THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,44 @@
1
+ == Ruby-on-Rest: Simple REST Framework for Ruby
2
+
3
+ Ruby-on-Rest (rubyrest) provides a simple framework to help you expose
4
+ your business objects as web resources.
5
+
6
+ Ruby-on-Rest provides an programming model and a security framework that
7
+ lets you create new REST services without too much effort.
8
+
9
+ == Resources
10
+
11
+ * {Project Documentation}[http://rubyrest.rubyforge.org]
12
+ * {Project Page at Rubyforge}[http://rubyforge.org/projects/rubyrest]
13
+ * {Project Page at Google Code}[http://code.google.com/p/rubyrest]
14
+
15
+ To check out the source code:
16
+
17
+ svn checkout http://rubyrest.rubyforge.org/svn/trunk
18
+
19
+ === Contact
20
+
21
+ 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
+
23
+ == Installation
24
+
25
+ sudo gem install rubyrest
26
+
27
+ == Getting Started
28
+
29
+ === Learning by example
30
+
31
+ Please have a look at the examples provided, they are simple enough to let you grasp how rubyrest works.
32
+
33
+ === Starting and stopping the service
34
+
35
+ Ruby-on-Rest provides a shell command. Open your console, and type the following:
36
+
37
+ rubyrest start|stop <my_service> [<my_service_module>]
38
+
39
+ === Configuring your service
40
+
41
+ Ruby-on-Rest will try to load a file name <my_service>.rb which must provide access to
42
+ the service configuration, service and model.
43
+
44
+
data/Rakefile ADDED
@@ -0,0 +1,106 @@
1
+ require 'rake'
2
+ require 'rake/clean'
3
+ require 'rake/gempackagetask'
4
+ require 'rake/rdoctask'
5
+ require 'fileutils'
6
+ include FileUtils
7
+
8
+ NAME = "rubyrest"
9
+ VERS = "0.0.1"
10
+ CLEAN.include ['**/.*.sw?', 'pkg/*', '.config', 'doc/*', 'coverage/*']
11
+ RDOC_OPTS = ['--quiet', '--title', "Ruby-on-Rest: A simple REST framework for Ruby",
12
+ "--opname", "index.html",
13
+ "--line-numbers",
14
+ "--main", "README",
15
+ "--inline-source"]
16
+
17
+ desc "Packages up Ruby-on-Rest."
18
+ task :default => [:package]
19
+ task :package => [:clean]
20
+
21
+ task :doc => [:rdoc]
22
+
23
+ Rake::RDocTask.new do |rdoc|
24
+ rdoc.rdoc_dir = 'doc/rdoc'
25
+ rdoc.options += RDOC_OPTS
26
+ rdoc.main = "README"
27
+ rdoc.title = "Ruby-on-Rest Documentation"
28
+ rdoc.rdoc_files.add ['README', 'COPYING', 'lib/rubyrest.rb', 'lib/rubyrest/**/*.rb', 'examples/**/*.rb' ]
29
+ end
30
+
31
+ spec = Gem::Specification.new do |s|
32
+ s.name = NAME
33
+ s.version = VERS
34
+ s.platform = Gem::Platform::RUBY
35
+ s.has_rdoc = true
36
+ s.extra_rdoc_files = ["README", "CHANGELOG", "COPYING", 'examples/hello.rb' ]
37
+ s.rdoc_options += RDOC_OPTS + [ '--exclude', 'lib/rubyrest.rb', '--include', 'examples/*.rb' ]
38
+ s.summary = "REST framework for Ruby."
39
+ s.description = s.summary
40
+ s.author = "Pedro Gutierrez"
41
+ s.email = 'pedro.gutierrrez@netcourrier.com'
42
+ s.homepage = 'http://rubyrest.rubyforge.org'
43
+ s.executables = ['rubyrest']
44
+
45
+ s.add_dependency('metaid')
46
+ s.required_ruby_version = '>= 1.8.2'
47
+
48
+ s.files = %w(COPYING README Rakefile) + Dir.glob("{bin,doc,lib}/**/*")
49
+
50
+ s.require_path = "lib"
51
+ s.bindir = "bin"
52
+ end
53
+
54
+ Rake::GemPackageTask.new(spec) do |p|
55
+ p.need_tar = true
56
+ p.gem_spec = spec
57
+ end
58
+
59
+ task :install do
60
+ sh %{rake package}
61
+ sh %{sudo gem install pkg/#{NAME}-#{VERS}}
62
+ end
63
+
64
+ task :uninstall => [:clean] do
65
+ sh %{sudo gem uninstall #{NAME}}
66
+ end
67
+
68
+ desc 'Update docs and upload to rubyforge.org'
69
+ task :doc_rforge do
70
+ sh %{rake doc}
71
+ sh %{scp -r doc/rdoc/* sicozu@rubyforge.org:/var/www/gforge-projects/rubyrest}
72
+ end
73
+
74
+
75
+ desc 'Make a release in Rubyforge'
76
+ task :release => [:clean, :package, :doc_rforge ] do
77
+ # create a svn tag
78
+ sh %{svn copy svn+ssh://rubyforge.org/var/svn/rubyrest/trunk svn+ssh://rubyforge.org/var/svn/rubyrest/tags/#{VERS}}
79
+ # upload the gem
80
+ sh %{scp pkg/#{NAME}-#{VERS}.gem sicozu@rubyforge.org:/var/www/gforge-projects/rubyrest/}
81
+ end
82
+
83
+ require 'spec/rake/spectask'
84
+
85
+ desc "Run specs with coverage"
86
+ Spec::Rake::SpecTask.new('spec') do |t|
87
+ t.spec_files = FileList['spec/*_spec.rb']
88
+ t.rcov = true
89
+ end
90
+
91
+ ##############################################################################
92
+ # Statistics
93
+ ##############################################################################
94
+
95
+ STATS_DIRECTORIES = [
96
+ %w(Code lib/),
97
+ %w(Spec spec/)
98
+ ].collect { |name, dir| [ name, "./#{dir}" ] }.select { |name, dir| File.directory?(dir) }
99
+
100
+ desc "Report code statistics (KLOCs, etc) from the application"
101
+ task :stats do
102
+ require 'extra/stats'
103
+ verbose = true
104
+ CodeStatistics.new(*STATS_DIRECTORIES).to_s
105
+ end
106
+
data/bin/rubyrest ADDED
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # RubyRest $Id:$
4
+ #
5
+ # UNIX Command Line.
6
+ require 'rubygems'
7
+ require 'rubyrest'
8
+
9
+
10
+ module RubyRest
11
+
12
+
13
+ class RubyRestBoot
14
+ include RubyRest::Tools
15
+
16
+ def initialize( option, service, prefix = nil )
17
+
18
+ if option == nil or service == nil or !self.respond_to?( option )
19
+ help_and_exit
20
+ return
21
+ end
22
+
23
+ @option = option
24
+ @service = service
25
+ @service_prefix = prefix
26
+
27
+ #begin
28
+ self.method( @option ).call
29
+ #rescue => e
30
+ # puts "ERROR #{e.message}"
31
+ #end
32
+
33
+ end
34
+
35
+ def help_and_exit
36
+ puts "Usage: rubyrest start|stop <your_service> "
37
+ puts "Ruby-on-Rest: Simple REST for Ruby."
38
+ puts
39
+ puts "Examples:"
40
+ puts " rubyrest start grape"
41
+ puts " rubyrest stop grape"
42
+ puts
43
+ puts "For more information see http://rubyrest.rubyforge.org"
44
+ end
45
+
46
+ def start
47
+ service_module = to_module_name( @service_prefix, @service )
48
+ require @service
49
+ puts "=> Starting service '#{@service}' in module '#{service_module}'"
50
+ config = to_class( service_module, "config" ).new
51
+ config[ :servicemodule ] = service_module
52
+ RubyRest::Engine.new( config ).start
53
+ puts "=> Service #{@service} running!"
54
+ end
55
+
56
+
57
+ def stop
58
+ puts "=> Stopping service #{@service}..."
59
+
60
+
61
+ puts "=> Service #{@service} stopped!"
62
+ end
63
+
64
+ end
65
+ end
66
+
67
+ RubyRest::RubyRestBoot.new( ARGV[0], ARGV[1], ARGV[2] )
data/examples/hello.rb ADDED
@@ -0,0 +1,57 @@
1
+ # Ruby-on-Rest HelloWorld application
2
+ #
3
+ #
4
+ #
5
+ module Acme
6
+
7
+ module Hello
8
+
9
+ # The configuration for the Acme::Hello service.
10
+ # This is a simple service that does not need database
11
+ # connectivity.
12
+ class Config < RubyRest::SimpleConfig
13
+
14
+ # Inits the internal hash of
15
+ # configuration options. This is the minimal expression
16
+ # of a Ruby-on-Rest configuration hash.
17
+ def initialize
18
+ @hash = {
19
+ :servicemodel => [ "welcomeservice" ],
20
+ :serviceport => 9001
21
+ }
22
+ end
23
+
24
+ end
25
+
26
+ # The domain service that actually implements
27
+ # 'business' logic. In this case, it's just going to say "Hello!"
28
+ # by returning a HelloBean object
29
+ class Welcomeservice
30
+
31
+ # Returns a simple object that says hello
32
+ # This is the method invoked by RubyRest::CRUDServlet
33
+ # when it receives a GET request with no particular resource id
34
+ def self.rest_retrieve( principal )
35
+ HelloBean.new
36
+ end
37
+
38
+ end
39
+
40
+ # The object that encapsulates the so sophisticated
41
+ # message. By including RubyRest::Atom::Entry, we get some convenience
42
+ # methods that help to provide an atom entry representation
43
+ # of the bean, without too much effort.
44
+ class HelloBean
45
+ include RubyRest::Atom::DummyEntry
46
+
47
+ # The hello message, included as a 'message'
48
+ # node inside an Atom Entry content.
49
+ def atom_content( builder )
50
+ builder.message "Hello!"
51
+ end
52
+ end
53
+
54
+ end
55
+
56
+ end
57
+
@@ -0,0 +1,204 @@
1
+ # Simple ATOM Feed Generator, used by REST
2
+ # Servlets as the representation of resources returned
3
+ # to the client.
4
+ #
5
+ #
6
+ # $Id:$
7
+ module RubyRest
8
+
9
+ module Atom
10
+ include RubyRest::Tools
11
+
12
+ NAMESPACES = {
13
+ "xmlns" => "http://www.w3.org/2005/Atom",
14
+ "xmlns:moodisland" => "http://www.moodisland.com/ns#"
15
+ }
16
+
17
+ ATOM_TYPE = "application/atom+xml".freeze
18
+ ATOMSERV_TYPE = "application/atomserv+xml".freeze
19
+ HTML_TYPE = "text/html".freeze
20
+ WORKSPACE_METHOD = "dashboard".freeze
21
+ MODULEID = "Ruby-on-Rest (http://rubyrest.rubyforge.org)".freeze
22
+
23
+ #
24
+ # Formats the response as a Atom feed, Atom Entry or
25
+ # Atom Service Document
26
+ def format_response( request, response )
27
+
28
+ builder = Builder::XmlMarkup.new( :target => response.body )
29
+ builder.instruct!
30
+
31
+ if @service_method == "dashboard"
32
+ response[ "content-type" ] = ATOMSERV_TYPE
33
+ build_service_document( @result, builder, request.request_uri )
34
+ else
35
+ response[ "content-type" ] = ATOM_TYPE
36
+ if @result.respond_to? "each"
37
+ build_feed( @result, builder, request.request_uri, request.path, @method )
38
+ else build_entry( @result, builder, request.request_uri ) end
39
+ end
40
+ end
41
+
42
+
43
+ # Builds an Atom Service Document. This is a representation of the
44
+ # user's dashboard or initial workspace.
45
+ def build_service_document( workspace, builder, uri )
46
+ builder.service( NAMESPACES ){
47
+ builder.workspace {
48
+ builder.title "Dashboard"
49
+ workspace.each { |w|
50
+ builder.collection( { "href" => "#{uri}#{w}" } ) {
51
+ builder.title "#{w}_all"
52
+ builder.accept "entry"
53
+ }
54
+ }
55
+ }
56
+ }
57
+ end
58
+
59
+ # Builds an Atom Feed representation of the specified collection
60
+ # of entries
61
+ #
62
+ def build_feed( entries, builder, uri, path, title )
63
+ builder.feed( NAMESPACES ) {
64
+ builder.id uri
65
+ builder.link( { "rel" => "self", "href" => path, "type" => ATOM_TYPE } )
66
+ builder.link( { "rel" => "alternate", "href" => uri, "type" => HTML_TYPE } )
67
+ builder.title title
68
+ builder.updated format_atom_date( Time.now )
69
+ entries.each { |object| build_entry( object, builder, uri ) }
70
+ }
71
+ end
72
+
73
+ # Builds an Atom Entry representation of the specified
74
+ # object.
75
+ #
76
+ # The object is supposed to implement the following mandatory methods:
77
+ # atom_id, atom_title, atom_author, atom_updated, atom_summary
78
+ #
79
+ # The object can implement the following optionnal methods:
80
+ # atom_related, atom_content
81
+ #
82
+ def build_entry( object, builder, uri )
83
+
84
+ entry_link = uri
85
+ entry_link = "#{uri}/#{object.atom_id}" if @id == nil
86
+
87
+ builder.entry( NAMESPACES ) {
88
+ builder.title object.atom_title
89
+ builder.author { builder.name object.atom_author }
90
+ builder.updated format_atom_date( object.atom_updated )
91
+ builder.id entry_link
92
+ builder.summary object.atom_summary
93
+ builder.link( "rel" => "alternate", "href" => entry_link )
94
+
95
+ if object.respond_to? :atom_related
96
+ object.atom_related.each{ |related|
97
+ builder.link( "rel" => "related", "href" => "#{entry_link}/#{related}", "title" => "#{related}", "type" => ATOM_TYPE )
98
+ }
99
+ end
100
+
101
+ builder.moodisland :content do
102
+ object.atom_content( builder ) if object.respond_to? :atom_content
103
+ end
104
+ }
105
+
106
+ end
107
+
108
+ # This module provides some default, arbitrary implementations
109
+ # for methods required by RubyRest in order to
110
+ # provide an Atom Entry representation out of a domain object.
111
+ #
112
+ # Developpers can choose whether to use this implementation
113
+ # or to provide their own.
114
+ module Entry
115
+
116
+ # Returns the Atom Entry title
117
+ def atom_title
118
+ self.name
119
+ end
120
+
121
+ # Returns the Atom Entry Summary. Synonym of atom_title
122
+ def atom_summary
123
+ atom_title
124
+ end
125
+
126
+ # Returns the generated token
127
+ def atom_id
128
+ self.id
129
+ end
130
+
131
+ # Returns Time.now
132
+ def atom_updated
133
+ self.updated
134
+ end
135
+
136
+ # Not a very relevant buy necessary information
137
+ def atom_author
138
+ self.createdby
139
+ end
140
+
141
+ end
142
+
143
+
144
+ # This module provides a failsafe implementation for
145
+ # methods required by RubyRest in order to
146
+ # provide an Atom Entry representation out of a domain object.
147
+ #
148
+ # Developpers should rather use RubyRest::Atom::Entry or
149
+ # provide their own.
150
+ module DummyEntry
151
+
152
+ # Returns the object's class name
153
+ def atom_title
154
+ self.class.name
155
+ end
156
+
157
+ # Returns the Atom Entry Summary.
158
+ # Synonym of atom_title
159
+ def atom_summary
160
+ atom_title
161
+ end
162
+
163
+ # Generates an id from the current time value
164
+ def atom_id
165
+ Time.now.to_i
166
+ end
167
+
168
+ # Returns Time.now
169
+ def atom_updated
170
+ Time.now
171
+ end
172
+
173
+ # Overrides the implementation provided
174
+ # by RubyRest::Atom::Entry
175
+ def atom_author
176
+ MODULEID
177
+ end
178
+
179
+ end
180
+
181
+ # Very simple module that adds some binding facilities
182
+ #
183
+ module EntryBinder
184
+
185
+ # Binds a property against the matching
186
+ # child element in the specified atom entry xml source.
187
+ # The base node to look into is /entry/content
188
+ def atom_bind( xml, property, mandatory = true )
189
+ location = "/entry/content/#{property}"
190
+ node = REXML::XPath.first( xml, location )
191
+ if node == nil
192
+ raise "no node found for location #{location}" if mandatory == true
193
+ return
194
+ end
195
+ if self.respond_to?( "#{property}=" )
196
+ self.method( "#{property}=").call( node.text )
197
+ else self[ property.intern ] = node.text end
198
+
199
+ end
200
+
201
+ end
202
+
203
+ end
204
+ end
@@ -0,0 +1,78 @@
1
+ # RubyRest: $Id:$
2
+ #
3
+ #
4
+ #
5
+ module RubyRest
6
+
7
+ # This class is meant to be subclassed with customized
8
+ # behaviour that will be invoked by the engine at startup.
9
+ # Subclasses can provide connectivity to popular database
10
+ # frameworks such as ActiveRecord, Sequel or Og.
11
+ #
12
+ # Example:
13
+ #
14
+ # module MyService
15
+ # class Config < RubyRest::SimpleConfig
16
+ # def initialize
17
+ # @hash = {
18
+ # ... my config here ..
19
+ # }
20
+ # end
21
+ # end
22
+ # end
23
+ #
24
+ #
25
+ class SimpleConfig
26
+ include RubyRest::Tools
27
+
28
+ # Overrides the specified configuration option
29
+ # with its new value
30
+ def []=( name, value )
31
+ @hash[ name ] = value
32
+ end
33
+
34
+ # Returns a given configuration option value
35
+ def [](name)
36
+ raise error( 000, name ) if !has( name )
37
+ return @hash[ name ]
38
+ end
39
+
40
+ # Returns true if the specified name matches
41
+ # a valid configuration option
42
+ def has( name )
43
+ @hash[ name ] != nil
44
+ end
45
+
46
+ # Dumps all the configuration options
47
+ def to_s
48
+ @hash.each{ |k,v| puts "#{k} = #{v}" }
49
+ end
50
+
51
+ end
52
+
53
+ # Specialization of SimpleConfig that provides
54
+ # hooks for database initialization during startup
55
+ class DatabaseConfig < SimpleConfig
56
+
57
+ # Connects and returns a database instance.
58
+ #
59
+ # This method
60
+ # will delegate to a more specialized method, according to the
61
+ # :adapter config option
62
+ def connect_to_database
63
+ adapter_method = "#{@hash[:dbadapter]}_connect"
64
+ error( 004, self, adapter_method ) if !self.respond_to?( adapter_method )
65
+ @db = self.method( adapter_method ).call
66
+ error( 005, self, adapter_method ) if @db == nil
67
+ end
68
+
69
+ # This method must be implemented in subclasses
70
+ # Intentionnaly left empty
71
+ def init_database
72
+
73
+ end
74
+
75
+ end
76
+
77
+
78
+ end
@@ -0,0 +1,77 @@
1
+ # RubyRest: $Id:$
2
+ #
3
+ #
4
+ #
5
+
6
+ module RubyRest
7
+
8
+ # Objects of this class take a configuration as argument
9
+ # then launch a new server instance.
10
+ class Engine
11
+
12
+ # Enables external objects to read the
13
+ # engine configuration
14
+ attr_reader :config
15
+
16
+ # Creates a new RubyRest engine, for the specified
17
+ # configuration
18
+ def initialize( config )
19
+ @config = config
20
+ end
21
+
22
+ # Starts the engine.
23
+ # The following operations are accomplished:
24
+ #
25
+ # 1. establish a connection to the database
26
+ # 2. initialize the database schema
27
+ # 3. load initial data into the database
28
+ #
29
+ def start
30
+ configure_database if @config.has( :dbadapter )
31
+ start_server
32
+ end
33
+
34
+ # Configures the database connectivity
35
+ def configure_database
36
+ @config.connect_to_database
37
+ @config.setup_persistence
38
+ if @config[ :dbdestroy ] == true
39
+ @config.init_schema
40
+ @config.load_initial_data
41
+ end
42
+ end
43
+
44
+ # Configures and returns a new web server
45
+ # instance. For the moment, only WEBrick instances
46
+ # are supported
47
+ def start_server
48
+ server = RubyRest::Server.new( @config )
49
+ [ "INT", "TERM" ].each { |signal|
50
+ trap( signal ) { server.shutdown }
51
+ }
52
+ server.mount "/", CRUDServlet
53
+ server.start
54
+ end
55
+
56
+ end
57
+
58
+
59
+ # Adds some extra functionnality to the actual server
60
+ # implementation.
61
+ #
62
+ class Server < WEBrick::HTTPServer
63
+
64
+ # the configuration options
65
+ attr_accessor :rubyrest
66
+
67
+ # Creates a new WEBrick instance with the specified
68
+ # configuration
69
+ def initialize( config )
70
+ super( :Port => config[ :serviceport ] )
71
+ @rubyrest = config
72
+ end
73
+
74
+ end
75
+
76
+
77
+ end
@@ -0,0 +1,235 @@
1
+ # WEBrick servlet that implements a simple mapping between
2
+ # HTTP methods and request paths into business methods, using some
3
+ # conventions.
4
+ #
5
+ # Developpers should only subclass this servlet, and implement
6
+ # standard methods that return a xml content.
7
+ #
8
+ # $Id:$
9
+ module RubyRest
10
+
11
+ # This is the servlet that actually exposes a REST API
12
+ # by translating HTTP requests into 'generic' or 'custom' service
13
+ # methods.
14
+ #
15
+ class RESTServlet < WEBrick::HTTPServlet::AbstractServlet
16
+ include RubyRest::Atom, RubyRest::Tools
17
+
18
+ # Injects some RubyRest configuration options
19
+ # from the server to the servlet
20
+ def initialize( server, *options )
21
+ super( server, options )
22
+ @servicemodule = server.rubyrest[ :servicemodule ]
23
+
24
+ if server.rubyrest.has( :authmodel )
25
+ @authmodel = server.rubyrest[ :authmodel ]
26
+ end
27
+
28
+ if server.rubyrest.has( :dashboard )
29
+ @dashboard = server.rubyrest[ :dashboard ]
30
+ else @dashboard = server.rubyrest[ :servicemodel ][0] end
31
+
32
+ end
33
+
34
+ # Inspects the request, and resolve the parameters
35
+ # The format of a request is the following:
36
+ #
37
+ # /:model/:id/:property
38
+ #
39
+ def resolve_params( request )
40
+ params = request.path.split( "/" )
41
+ @http_method = request.request_method
42
+ @token = request[ 'token']
43
+ @model = params[1]
44
+ @id = params[2]
45
+ @property = params[3]
46
+ @body = REXML::Document.new( request.body ) if request.body != nil
47
+ end
48
+
49
+ def resolve_custom_method
50
+ if @property != nil
51
+ @custom_method = "#{@model}_#{@property}"
52
+ else
53
+ @custom_method = WORKSPACE_METHOD
54
+ @custom_method = "#{@model}_#{@generic_method}" if @model != nil
55
+ end
56
+ end
57
+
58
+ def resolve_service_method
59
+ resolve_custom_method
60
+ return @custom_method if self.respond_to? @custom_method
61
+ return @generic_method
62
+ end
63
+
64
+ def dispatch( request, response )
65
+
66
+ @service_method = resolve_service_method
67
+ check_security( request ) if @authmodel != nil
68
+ @result = self.method( @service_method ).call( request )
69
+
70
+ if @result.respond_to? "unauthorized" and @result.unauthorized == true
71
+ raise WEBrick::HTTPStatus::Unauthorized
72
+ else
73
+ response.status = @success_code
74
+ format_response( request, response ) if @result != nil
75
+ raise WEBrick::HTTPStatus::OK
76
+ end
77
+ end
78
+
79
+
80
+ def resolve_get_method
81
+ if @property != nil
82
+ @generic_method = :retrieve_related
83
+ else
84
+ if @id != nil
85
+ @generic_method = :show
86
+ else @generic_method = :retrieve end
87
+ end
88
+ end
89
+
90
+ def do_GET( request, response )
91
+ resolve_params( request )
92
+ @success_code = 200
93
+ resolve_get_method
94
+ dispatch( request, response )
95
+ end
96
+
97
+ def do_POST( request, response )
98
+ resolve_params( request )
99
+ incompatible_path( request ) if @id != nil
100
+ @generic_method = :create
101
+ @success_code = 201
102
+ dispatch( request, response )
103
+ end
104
+
105
+ def do_PUT( request, response )
106
+ resolve_params( request )
107
+ incompatible_path( request ) if @id == nil
108
+ @generic_method = :update
109
+ @success_code = 200
110
+ dispatch( request, response )
111
+ end
112
+
113
+ def do_DELETE( request, response )
114
+ resolve_params( request )
115
+ incompatible_path( request ) if @id == nil
116
+ @generic_method = :delete
117
+ @success_code = 200
118
+ dispatch( request, response )
119
+ end
120
+
121
+ # Raises an error stating that the current
122
+ # http method is not compatible with the requested path.
123
+ def incompatible_path( request )
124
+ error( 100, @http_method, request.path )
125
+ end
126
+
127
+ # Custom service method that authenticates a username/password pair
128
+ # found in the request body. The authentication is left to the class defined by the
129
+ # :authmodel configuration option.
130
+ #
131
+ # Developpers can provide their own implementation, however it is recommended to
132
+ # subclass the class *Credentials*
133
+ def credentials_create( request )
134
+ auth_class = to_class( @servicemodule, @authmodel )
135
+ auth = auth_class.new
136
+ auth_class.rest_bind( auth, @body )
137
+ auth = auth_class.authenticate( auth )
138
+ raise WEBrick::HTTPStatus::Unauthorized if auth == nil
139
+ return auth
140
+ end
141
+
142
+ ANONYMOUS_ACCESS = "POST/credentials"
143
+
144
+ # Defines whether the request is allowed to be processed
145
+ # without the need of a security token
146
+ #
147
+ def anonymous_access
148
+ "#{@http_method}/#{@model}" == ANONYMOUS_ACCESS
149
+ end
150
+
151
+ # Checks that a token is present in the request
152
+ # exception if doing a POST on a credentials property
153
+ def check_security( request )
154
+ auth_class = to_class( @servicemodule , @authmodel )
155
+ if @token == nil and !anonymous_access
156
+ raise WEBrick::HTTPStatus::Unauthorized
157
+ end
158
+ if @token != nil
159
+ @principal = auth_class.validate( @token )
160
+ raise WEBrick::HTTPStatus::Unauthorized if @principal == nil
161
+ end
162
+ end
163
+
164
+ # Returns the model defined by the configuration
165
+ # option :dashboard
166
+ #
167
+ def dashboard( request )
168
+ [ @dashboard ]
169
+ end
170
+
171
+ end
172
+
173
+
174
+
175
+ # Generic servlet, that implements the Moodisland Grape API
176
+ # This layer returns domain objects that can be included
177
+ # in atom feeds as entries.
178
+ #
179
+ class CRUDServlet < RESTServlet
180
+
181
+ # Creates and saves a new object. The object is then
182
+ # returned
183
+ def create( request )
184
+ clazz = to_class( @servicemodule, @model )
185
+ object = clazz.rest_create( @principal )
186
+ clazz.rest_bind( object, @body )
187
+ clazz.rest_save( object, @principal )
188
+ end
189
+
190
+ # Retrieves a list of objects
191
+ #
192
+ def retrieve( request )
193
+ clazz = to_class( @servicemodule, @model )
194
+ clazz.rest_retrieve( @principal )
195
+ end
196
+
197
+ # Retrieve a list of related objects
198
+ #
199
+ def retrieve_related( request )
200
+ clazz = to_class( @servicemodule, @model )
201
+ service_method = "rest_#{@property}"
202
+ error( 500, clazz.name, service_method ) if !clazz.respond_to?( service_method )
203
+ clazz.method( service_method ).call( @id, @principal )
204
+ end
205
+
206
+ # Retrieves a single object
207
+ #
208
+ def show( request )
209
+ clazz = to_class( @servicemodule, @model )
210
+ single = clazz.rest_single( @id, @principal )
211
+ raise error( 200, @model, @id ) if single == nil
212
+ return single
213
+ end
214
+
215
+ # Deletes a single object
216
+ #
217
+ def delete( request )
218
+ clazz = to_class( @servicemodule, @model )
219
+ clazz.rest_delete( @id, @principal )
220
+ end
221
+
222
+ # Retrieves, updates and saves an existing object.
223
+ # The object is then returned
224
+ #
225
+ def update( request )
226
+ object = show( request )
227
+ clazz = object.class
228
+ clazz.rest_bind( object, @body )
229
+ clazz.rest_save( object, @principal )
230
+ end
231
+
232
+ end
233
+
234
+
235
+ end
@@ -0,0 +1,72 @@
1
+ # RubyRest: $Id:$
2
+ #
3
+ #
4
+ #
5
+
6
+ module RubyRest
7
+
8
+ # This module provides a catalog of errors
9
+ # the application is supposed to throw.
10
+ #
11
+ #
12
+ module Tools
13
+
14
+
15
+ ATOM_DATE_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
16
+
17
+ def nvl( value, default )
18
+ return value if value != nil
19
+ return default
20
+ end
21
+
22
+ def format_atom_date( value )
23
+ nvl( value, Time.now ).strftime( ATOM_DATE_FORMAT )
24
+ end
25
+
26
+ def parse_atom_date( value )
27
+ Date.strptime( value, ATOM_DATE_FORMAT )
28
+ end
29
+
30
+ # Resolves the specified module name and model
31
+ # into a class, and returns it
32
+ def to_class( modulename, model )
33
+ Class.by_name( "#{modulename}::#{model.capitalize}" )
34
+ end
35
+
36
+ # Builds a gem name
37
+ def to_gem_name( moduleprefix, modulename )
38
+ return modulename if moduleprefix == nil
39
+ return "#{moduleprefix}-#{modulename}"
40
+ end
41
+
42
+ # Builds a fully qualified module name
43
+ def to_module_name( moduleprefix, modulename )
44
+ return modulename.capitalize if moduleprefix == nil
45
+ return "#{moduleprefix.capitalize}::#{modulename.capitalize}"
46
+ end
47
+
48
+ # Catalog of error messages, indexed by error
49
+ # number
50
+ ERRORS = {
51
+ 000 => "Missing configuration option",
52
+ 001 => "Unable to connect to database. Missing method",
53
+ 002 => "Unable to load schema. Missing method",
54
+ 003 => "Unable to create table",
55
+ 004 => "Please subclass and override",
56
+ 005 => "Sorry, configuration method did not return a valid database connection",
57
+ 006 => "Class was not property configured with its database connection (Sequel)",
58
+ 100 => "Request path and HTTP method are not compatible",
59
+ 200 => "No resource found for model and id",
60
+ 500 => "No service method found in model class"
61
+ }
62
+
63
+ # Raises a new error. Resolves the specified
64
+ # number into a human readable message
65
+ def error( number, *params )
66
+ raise "\##{number}: #{ERRORS[number]}: #{params.join( ', ') }"
67
+ end
68
+
69
+
70
+ end
71
+
72
+ end
data/lib/rubyrest.rb ADDED
@@ -0,0 +1,24 @@
1
+ # RubyRest: $Id:$
2
+ #
3
+ # Entry point to the framework.
4
+ # Loads all the files under the 'rubyrest' subdirectory
5
+ require "rubygems"
6
+ require "extensions/all"
7
+ require "builder"
8
+ require "rexml/document"
9
+ require "webrick"
10
+ require "atom"
11
+
12
+ dir = File.join( File.dirname( __FILE__ ), 'rubyrest' )
13
+
14
+ require File.join( dir, "tools" )
15
+ require File.join( dir, "atom" )
16
+ require File.join( dir, "servlets" )
17
+ require File.join( dir, "config" )
18
+ require File.join( dir, "engine" )
19
+
20
+ module RubyRest #:nodoc:
21
+ class << self
22
+
23
+ end
24
+ end
metadata ADDED
@@ -0,0 +1,82 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.9.1
3
+ specification_version: 1
4
+ name: rubyrest
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.0.1
7
+ date: 2007-03-28 00:00:00 +02:00
8
+ summary: REST framework for Ruby.
9
+ require_paths:
10
+ - lib
11
+ email: pedro.gutierrrez@netcourrier.com
12
+ homepage: http://rubyrest.rubyforge.org
13
+ rubyforge_project:
14
+ description: REST framework for Ruby.
15
+ autorequire:
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 1.8.2
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ post_install_message:
29
+ authors:
30
+ - Pedro Gutierrez
31
+ files:
32
+ - COPYING
33
+ - README
34
+ - Rakefile
35
+ - bin/rubyrest
36
+ - doc/rdoc
37
+ - lib/rubyrest
38
+ - lib/rubyrest.rb
39
+ - lib/rubyrest/atom.rb
40
+ - lib/rubyrest/config.rb
41
+ - lib/rubyrest/engine.rb
42
+ - lib/rubyrest/servlets.rb
43
+ - lib/rubyrest/tools.rb
44
+ - CHANGELOG
45
+ - examples/hello.rb
46
+ test_files: []
47
+
48
+ rdoc_options:
49
+ - --quiet
50
+ - --title
51
+ - "Ruby-on-Rest: A simple REST framework for Ruby"
52
+ - --opname
53
+ - index.html
54
+ - --line-numbers
55
+ - --main
56
+ - README
57
+ - --inline-source
58
+ - --exclude
59
+ - lib/rubyrest.rb
60
+ - --include
61
+ - examples/*.rb
62
+ extra_rdoc_files:
63
+ - README
64
+ - CHANGELOG
65
+ - COPYING
66
+ - examples/hello.rb
67
+ executables:
68
+ - rubyrest
69
+ extensions: []
70
+
71
+ requirements: []
72
+
73
+ dependencies:
74
+ - !ruby/object:Gem::Dependency
75
+ name: metaid
76
+ version_requirement:
77
+ version_requirements: !ruby/object:Gem::Version::Requirement
78
+ requirements:
79
+ - - ">"
80
+ - !ruby/object:Gem::Version
81
+ version: 0.0.0
82
+ version: