rubyrest 0.0.1

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 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: