rubyrest 0.0.5 → 0.1.0

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