reststop 0.5.1 → 0.5.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,450 +1,451 @@
1
- # Right now you'll have to do some weird gymnastics to get this hooked in to a Camping app...
2
- # Something like:
3
- #
4
- # Camping.goes :Blog
5
- #
6
- # module Blog
7
- # include Camping::Session
8
- # include Reststop
9
- #
10
- # Controllers.extend Reststop::Controllers
11
- # end
12
- #
13
- # module Blog::Base
14
- # alias camping_render render
15
- # alias camping_service service
16
- # alias camping_lookup lookup
17
- # include Reststop::Base
18
- # alias service reststop_service
19
- # alias render reststop_render
20
- #
21
- # # Overrides the new Tilt-centric lookup method In camping
22
- # # RESTstop needs to have a first try at looking up the view
23
- # # located in the Views::HTML module.
24
- # def lookup(n)
25
- # T.fetch(n.to_sym) do |k|
26
- # t = Blog::Views::HTML.method_defined?(k) || camping_lookup(n)
27
- # end
28
- # end
29
-
30
- # end
31
- #
32
- # module Blog::Controllers
33
- # extend Reststop::Controllers
34
- # ...
35
- # end
36
- #
37
- # module Blog::Helpers
38
- # alias_method :_R, :R
39
- # remove_method :R
40
- # include Reststop::Helpers
41
- # ...
42
- # end
43
- #
44
- # module Blog::Views
45
- # extend Reststop::Views
46
- # ...
47
- # end
48
- #
49
- # The hope is that this could all get taken care of in a
50
- # `include Reststop` call (via overriding of #extended)
51
- #
52
- # See examples/blog.rb for a working example.
53
- require 'logger'
54
- $LOG = Logger.new(STDOUT)
55
-
56
- module Reststop
57
- module Base
58
- def reststop_service(*a)
59
- if @env['REQUEST_METHOD'] == 'POST' && (input['_method'] == 'put' || input['_method'] == 'delete')
60
- @env['REQUEST_METHOD'] = input._method.upcase
61
- @method = input._method
62
- end
63
- @a0=a[0] if !a.empty?
64
- camping_service(*a)
65
- end
66
-
67
- # Overrides Camping's render method to add the ability to specify a format
68
- # module when rendering a view.
69
- #
70
- # The format can also be specified in other ways (shown in this order
71
- # of precedence):
72
- #
73
- # 1. By providing a second parameter to render()
74
- # (eg: <tt>render(:foo, :HTML)</tt>)
75
- # 2. By setting the @format variable
76
- # 3. By providing a 'format' parameter in the request (i.e. input[:format])
77
- # 4. By adding a file-format extension to the url (e.g. /items.xml or
78
- # /items/2.html).
79
- #
80
- # For example, you could have:
81
- #
82
- # module Foobar::Views
83
- #
84
- # module HTML
85
- # def foo
86
- # # ... render some HTML content
87
- # end
88
- # end
89
- #
90
- # module RSS
91
- # def foo
92
- # # ... render some RSS content
93
- # end
94
- # end
95
- #
96
- # end
97
- #
98
- # Then in your controller, you would call render() like this:
99
- #
100
- # render(:foo, :HTML) # render the HTML version of foo
101
- #
102
- # or
103
- #
104
- # render(:foo, :RSS) # render the RSS version of foo
105
- #
106
- # or
107
- #
108
- # @format = :RSS
109
- # render(:foo) # render the RSS version of foo
110
- #
111
- # or
112
- #
113
- # # url is /foobar/1?format=RSS
114
- # render(:foo) # render the RSS version of foo
115
- #
116
- # or
117
- #
118
- # # url is /foobar/1.rss
119
- # render(:foo) # render the RSS version of foo
120
- #
121
- # If no format is specified, render() will behave like it normally does in
122
- # Camping, by looking for a matching view method directly
123
- # in the Views module.
124
- #
125
- # You can also specify a default format module by calling
126
- # <tt>default_format</tt> after the format module definition.
127
- # For example:
128
- #
129
- # module Foobar::Views
130
- # module HTML
131
- # # ... etc.
132
- # end
133
- # default_format :HTML
134
- # end
135
- #
136
- def reststop_render(action, format = nil)
137
- format = nil unless format.is_a? Symbol
138
-
139
- app_name = self.class.name.split("::").first # @techarch : get the name of the app
140
- format ||= @format
141
-
142
- if format.nil?
143
- begin
144
- ct = CONTENT_TYPE
145
- rescue NameError
146
- ct = 'text/html'
147
- end
148
-
149
- @headers['Content-Type'] ||= ct
150
- basic_render(action) # @techarch
151
- else
152
- mab = (app_name + '::Mab').constantize # @techarch : get the Mab class
153
- m = mab.new({}, self) # @techarch : instantiate Mab
154
- mod = (app_name + "::Views::#{format.to_s}").constantize # @techarch : get the right Views format class
155
-
156
- m.extend mod
157
-
158
- begin
159
- ct = mod::CONTENT_TYPE
160
- rescue NameError
161
- ct = "text/#{format.to_s.downcase}"
162
- end
163
- @headers['Content-Type'] = ct
164
-
165
- s = m.capture{m.send(action)}
166
- s = m.capture{send(:layout){s}} if /^_/!~@method.to_s and m.respond_to?(:layout) # @techarch : replaced a[0] by @method (not 100% sure that's right though)
167
- s
168
- end
169
- end
170
-
171
- # Performs a basic camping rendering (without use of a layout)
172
- # This method was added since the addition of Tilt support in camping
173
- # is assuming layout.
174
- def basic_render(action)
175
- app_name = self.class.name.split("::").first # @techarch : get the name of the app
176
- mab = (app_name + '::Mab').constantize # @techarch : get the Mab class
177
- m = mab.new({}, self) # @techarch : instantiate Mab
178
- s = m.capture{m.send(action)}
179
- end
180
- end
181
-
182
-
183
- module Views
184
- # Call this inside your Views module to set a default format.
185
- #
186
- # For example:
187
- #
188
- # module Foobar::Views
189
- # module HTML
190
- # # ... etc.
191
- # end
192
- # default_format :XML
193
- # end
194
- def default_format(m)
195
- mod = "#{self}::#{m.to_s}".constantize
196
- mab = self.to_s.gsub('::Views','::Mab').constantize # @techarch : get the Mab class
197
- mab.class_eval{include mod}
198
- end
199
- end
200
-
201
- module Helpers
202
- # Overrides Camping's routing helper to make it possible to route RESTful resources.
203
- #
204
- # Some usage examples:
205
- #
206
- # R(Kittens) # /kittens
207
- # R(Kittens, 'new') # /kittens/new
208
- # R(Kittens, 1, 'meow') # /kittens/1/meow
209
- # R(@kitten) # /kittens/1
210
- # R(@kitten, 'meow') # /kittens/1/meow
211
- # R(Kittens, 'list', :colour => 'black') # /kittens/list?colour=black
212
- #
213
- # The current output format is retained, so if the current <tt>@format</tt> is <tt>:XML</tt>,
214
- # the URL will be /kittens/1.xml rather than /kittens/1.
215
- #
216
- # Note that your controller names might not be loaded if you're calling <tt>R</tt> inside a
217
- # view module. In that case you should use the fully qualified name (i.e. Myapp::Controllers::Kittens)
218
- # or include the Controllers module into your view module.
219
- def R(c, *g)
220
-
221
- cl = c.class.name.split("::").last.pluralize
222
- app_name = c.class.name.split("::").first
223
- ctrl_cl = app_name + '::Controllers' # @techarch : get to the Controllers using the current app
224
- ctrl = (app_name != 'Class') ? ctrl_cl.constantize : Controllers
225
-
226
- if ctrl.constants.include?(cl) #@techarch updated to use new cl variable
227
- path = "/#{cl.underscore}/#{c.id}"
228
- path << ".#{@format.to_s.downcase}" if @format
229
- path << "/#{g.shift}" unless g.empty?
230
- self / path
231
- elsif c.respond_to?(:restful?) && c.restful?
232
- base = c.name.split("::").last.underscore
233
- id_or_action = g.shift
234
- if id_or_action.to_s =~ /\d+/ #@techarch needed a to_s after id_or_action to allow pattern matching
235
- id = id_or_action
236
- action = g.shift
237
- else
238
- action = id_or_action
239
- end
240
-
241
- path = "/#{base}"
242
- path << "/#{id}" if id
243
- path << "/#{action}" if action
244
- path << ".#{@format.to_s.downcase}" if @format
245
-
246
- #@techarch substituted U for u=Rack::Utils
247
- u=Rack::Utils
248
- path << "?#{g.collect{|a|a.collect{|k,v| u.escape(k)+"="+u.escape(v)}.join("&")}.join("&")}" unless g.empty? # FIXME: undefined behaviour if there are multiple arguments left
249
- return path
250
- else
251
- _R(c, *g)
252
- end
253
- end # def R
254
- end # module Helpers
255
-
256
- module Controllers
257
- def self.determine_format(input, env) #:nodoc:
258
- if input[:format] && !input[:format].empty?
259
- input[:format].upcase.intern
260
- elsif env['PATH_INFO'] =~ /\.([a-z]+)$/
261
- $~[1].upcase.intern
262
- end
263
- end
264
-
265
- # Calling <tt>REST "<resource name>"</tt> creates a controller with the
266
- # appropriate routes and maps your REST methods to standard
267
- # Camping controller mehods. This is meant to be used in your Controllers
268
- # module in place of <tt>R <routes></tt>.
269
- #
270
- # Your REST class should define the following methods:
271
- #
272
- # * create
273
- # * read(id)
274
- # * update(id)
275
- # * destroy(id)
276
- # * list
277
- #
278
- # Routes will be automatically created based on the resource name fed to the
279
- # REST method. <b>Your class must have the same (but CamelCaps'ed)
280
- # name as the resource name.</b> So if your resource name is 'kittens',
281
- # your controller class must be Kittens.
282
- #
283
- # For example:
284
- #
285
- # module Foobar::Controllers
286
- # class Kittens < REST 'kittens'
287
- # # POST /kittens
288
- # def create
289
- # end
290
- #
291
- # # GET /kittens/(\d+)
292
- # def read(id)
293
- # end
294
- #
295
- # # PUT /kittens/(\d+)
296
- # def update(id)
297
- # end
298
- #
299
- # # DELETE /kittens/(\d+)
300
- # def destroy(id)
301
- # end
302
- #
303
- # # GET /kittens
304
- # def list
305
- # end
306
- # end
307
- # end
308
- #
309
- # Custom actions are also possible. For example, to implement a 'meow'
310
- # action simply add a 'meow' method to the above controller:
311
- #
312
- # # POST/GET/PUT/DELETE /kittens/meow
313
- # # POST/GET/PUT/DELETE /kittens/(\d+)/meow
314
- # def meow(id)
315
- # end
316
- #
317
- # Note that a custom action will respond to all four HTTP methods
318
- # (POST/GET/PUT/DELETE).
319
- #
320
- # Optionally, you can specify a <tt>:prefix</tt> key that will prepend the
321
- # given string to the routes. For example, the following will create all
322
- # of the above routes, prefixed with "/pets"
323
- # (i.e. <tt>POST '/pets/kittens'</tt>, <tt>GET '/pets/kittens/(\d+)'</tt>,
324
- # etc.):
325
- #
326
- # module Foobar::Controllers
327
- # class Items < REST 'kittens', :prefix => '/pets'
328
- # # ...
329
- # end
330
- # end
331
- #
332
- # Format-based routing similar to that in ActiveResource is also implemented.
333
- # For example, to get a list of kittens in XML format, place a
334
- # <tt>GET</tt> call to <tt>/kittens.xml</tt>.
335
- # See the documentation for the render() method for more info.
336
- #
337
- def REST(r, options = {})
338
- crud = R "#{options[:prefix]}/#{r}/([0-9a-zA-Z]+)/([a-z_]+)(?:\.[a-z]+)?",
339
- "#{options[:prefix]}/#{r}/([0-9a-zA-Z]+)(?:\.[a-z]+)?",
340
- "#{options[:prefix]}/#{r}/([a-z_]+)(?:\.[a-z]+)?",
341
- "#{options[:prefix]}/#{r}(?:\.[a-z]+)?"
342
-
343
- crud.module_eval do
344
- meta_def(:restful?){true}
345
-
346
- $LOG.debug("Creating RESTful controller for #{r.inspect} using Reststop #{'pull version number here'}") if $LOG
347
-
348
- def get(id_or_custom_action = nil, custom_action = nil) # :nodoc:
349
- id = input['id'] if input['id']
350
-
351
- custom_action = input['action'] if input['action']
352
-
353
- if self.methods.include? id_or_custom_action
354
- custom_action ||= id_or_custom_action
355
- id ||= nil
356
- else
357
- id ||= id_or_custom_action
358
- end
359
-
360
- id = id.to_i if id && id =~ /^[0-9]+$/
361
-
362
- @format = Reststop::Controllers.determine_format(input, @env)
363
-
364
- begin
365
- if id.nil? && input['id'].nil?
366
- custom_action ? send(custom_action) : list
367
- else
368
- custom_action ? send(custom_action, id || input['id']) : read(id || input['id'])
369
- end
370
- rescue NoMethodError => e
371
- # FIXME: this is probably not a good way to do this, but we need to somehow differentiate
372
- # between 'no such route' vs. other NoMethodErrors
373
- if e.message =~ /no such method/
374
- return no_method(e)
375
- else
376
- raise e
377
- end
378
- rescue ActiveRecord::RecordNotFound => e
379
- return not_found(e)
380
- end
381
- end
382
-
383
-
384
- def post(custom_action = nil) # :nodoc:
385
- @format = Reststop::Controllers.determine_format(input, @env)
386
- custom_action ? send(custom_action) : create
387
- end
388
-
389
- def put(id, custom_action = nil) # :nodoc:
390
- id = id.to_i if id =~ /^[0-9]+$/
391
- @format = Reststop::Controllers.determine_format(input, @env)
392
- custom_action ? send(custom_action, id || input['id']) : update(id || input['id'])
393
- end
394
-
395
- def delete(id, custom_action = nil) # :nodoc:
396
- id = id.to_i if id =~ /^[0-9]+$/
397
- @format = Reststop::Controllers.determine_format(input, @env)
398
- custom_action ? send(custom_action, id || input['id']) : destroy(id || input['id'])
399
- end
400
-
401
- private
402
- def _error(message, status_code = 500, e = nil)
403
- @status = status_code
404
- @message = message
405
- begin
406
- render "error_#{status_code}".intern
407
- rescue NoMethodError
408
- if @format.to_s == 'XML'
409
- "<error code='#{status_code}'>#{@message}</error>"
410
- else
411
- out = "<strong>#{@message}</strong>"
412
- out += "<pre style='color: #bbb'><strong>#{e.class}: #{e}</strong>\n#{e.backtrace.join("\n")}</pre>" if e
413
- out
414
- end
415
- end
416
- end
417
-
418
- def no_method(e)
419
- _error("No controller method responds to this route!", 501, e)
420
- end
421
-
422
- def not_found(e)
423
- _error("Record not found!", 404, e)
424
- end
425
- end
426
- crud
427
- end # def REST
428
- end # module Controllers
429
- end # module Reststop
430
-
431
- module Markaby
432
- class Builder
433
- # Modifies Markaby's 'form' generator so that if a 'method' parameter
434
- # is supplied, a hidden '_method' input is automatically added.
435
- def form(*args, &block)
436
- options = args[0] if args && args[0] && args[0].kind_of?(Hash)
437
- inside = capture &block
438
-
439
- if options && options.has_key?(:method)
440
- inside = input(:type => 'hidden', :name => '_method', :value => options[:method]) +
441
- inside
442
- if options[:method].to_s === 'put' || options[:method].to_s == 'delete'
443
- options[:method] = 'post'
444
- end
445
- end
446
-
447
- tag!(:form, options || args[0]) {inside}
448
- end
449
- end
450
- end
1
+ # Right now you'll have to do some weird gymnastics to get this hooked in to a Camping app...
2
+ # Something like:
3
+ #
4
+ # Camping.goes :Blog
5
+ #
6
+ # module Blog
7
+ # include Camping::Session
8
+ # include Reststop
9
+ #
10
+ # Controllers.extend Reststop::Controllers
11
+ # end
12
+ #
13
+ # module Blog::Base
14
+ # alias camping_render render
15
+ # alias camping_service service
16
+ # alias camping_lookup lookup
17
+ # include Reststop::Base
18
+ # alias service reststop_service
19
+ # alias render reststop_render
20
+ #
21
+ # # Overrides the new Tilt-centric lookup method In camping
22
+ # # RESTstop needs to have a first try at looking up the view
23
+ # # located in the Views::HTML module.
24
+ # def lookup(n)
25
+ # T.fetch(n.to_sym) do |k|
26
+ # t = Blog::Views::HTML.method_defined?(k) || camping_lookup(n)
27
+ # end
28
+ # end
29
+
30
+ # end
31
+ #
32
+ # module Blog::Controllers
33
+ # extend Reststop::Controllers
34
+ # ...
35
+ # end
36
+ #
37
+ # module Blog::Helpers
38
+ # alias_method :_R, :R
39
+ # remove_method :R
40
+ # include Reststop::Helpers
41
+ # ...
42
+ # end
43
+ #
44
+ # module Blog::Views
45
+ # extend Reststop::Views
46
+ # ...
47
+ # end
48
+ #
49
+ # The hope is that this could all get taken care of in a
50
+ # `include Reststop` call (via overriding of #extended)
51
+ #
52
+ # See examples/blog.rb for a working example.
53
+ require 'logger'
54
+ $LOG = Logger.new(STDOUT)
55
+
56
+ module Reststop
57
+ module Base
58
+ def reststop_service(*a)
59
+ if @env['REQUEST_METHOD'] == 'POST' && (input['_method'] == 'put' || input['_method'] == 'delete')
60
+ @env['REQUEST_METHOD'] = input._method.upcase
61
+ @method = input._method
62
+ end
63
+ @a0=a[0] if !a.empty?
64
+ camping_service(*a)
65
+ end
66
+
67
+ # Overrides Camping's render method to add the ability to specify a format
68
+ # module when rendering a view.
69
+ #
70
+ # The format can also be specified in other ways (shown in this order
71
+ # of precedence):
72
+ #
73
+ # 1. By providing a second parameter to render()
74
+ # (eg: <tt>render(:foo, :HTML)</tt>)
75
+ # 2. By setting the @format variable
76
+ # 3. By providing a 'format' parameter in the request (i.e. input[:format])
77
+ # 4. By adding a file-format extension to the url (e.g. /items.xml or
78
+ # /items/2.html).
79
+ #
80
+ # For example, you could have:
81
+ #
82
+ # module Foobar::Views
83
+ #
84
+ # module HTML
85
+ # def foo
86
+ # # ... render some HTML content
87
+ # end
88
+ # end
89
+ #
90
+ # module RSS
91
+ # def foo
92
+ # # ... render some RSS content
93
+ # end
94
+ # end
95
+ #
96
+ # end
97
+ #
98
+ # Then in your controller, you would call render() like this:
99
+ #
100
+ # render(:foo, :HTML) # render the HTML version of foo
101
+ #
102
+ # or
103
+ #
104
+ # render(:foo, :RSS) # render the RSS version of foo
105
+ #
106
+ # or
107
+ #
108
+ # @format = :RSS
109
+ # render(:foo) # render the RSS version of foo
110
+ #
111
+ # or
112
+ #
113
+ # # url is /foobar/1?format=RSS
114
+ # render(:foo) # render the RSS version of foo
115
+ #
116
+ # or
117
+ #
118
+ # # url is /foobar/1.rss
119
+ # render(:foo) # render the RSS version of foo
120
+ #
121
+ # If no format is specified, render() will behave like it normally does in
122
+ # Camping, by looking for a matching view method directly
123
+ # in the Views module.
124
+ #
125
+ # You can also specify a default format module by calling
126
+ # <tt>default_format</tt> after the format module definition.
127
+ # For example:
128
+ #
129
+ # module Foobar::Views
130
+ # module HTML
131
+ # # ... etc.
132
+ # end
133
+ # default_format :HTML
134
+ # end
135
+ #
136
+ def reststop_render(action, format = nil)
137
+ format = nil unless format.is_a? Symbol
138
+
139
+ app_name = self.class.name.split("::").first # @techarch : get the name of the app
140
+ format ||= @format
141
+
142
+ if format.nil?
143
+ begin
144
+ ct = CONTENT_TYPE
145
+ rescue NameError
146
+ ct = 'text/html'
147
+ end
148
+
149
+ @headers['Content-Type'] ||= ct
150
+ basic_render(action) # @techarch
151
+ else
152
+ mab = (app_name + '::Mab').constantize # @techarch : get the Mab class
153
+ m = mab.new({}, self) # @techarch : instantiate Mab
154
+ mod = (app_name + "::Views::#{format.to_s}").constantize # @techarch : get the right Views format class
155
+
156
+ m.extend mod
157
+
158
+ begin
159
+ ct = mod::CONTENT_TYPE
160
+ rescue NameError
161
+ ct = "#{format == :HTML ? 'text' : 'application'}/#{format.to_s.downcase}" #@techarch - used to be text/***
162
+ end
163
+ @headers['Content-Type'] = ct
164
+
165
+ s = m.capture{m.send(action)}
166
+ s = m.capture{send(:layout){s}} if /^_/!~@method.to_s and m.respond_to?(:layout) # @techarch : replaced a[0] by @method (not 100% sure that's right though)
167
+ s
168
+ end
169
+ end
170
+
171
+ # Performs a basic camping rendering (without use of a layout)
172
+ # This method was added since the addition of Tilt support in camping
173
+ # is assuming layout.
174
+ def basic_render(action)
175
+ app_name = self.class.name.split("::").first # @techarch : get the name of the app
176
+ mab = (app_name + '::Mab').constantize # @techarch : get the Mab class
177
+ m = mab.new({}, self) # @techarch : instantiate Mab
178
+ s = m.capture{m.send(action)}
179
+ s = m.capture{send(:layout){s}} if /^_/!~@method.to_s and m.respond_to?(:layout) # @techarch : replaced a[0] by @method (not 100% sure that's right though)
180
+ end
181
+ end
182
+
183
+
184
+ module Views
185
+ # Call this inside your Views module to set a default format.
186
+ #
187
+ # For example:
188
+ #
189
+ # module Foobar::Views
190
+ # module HTML
191
+ # # ... etc.
192
+ # end
193
+ # default_format :XML
194
+ # end
195
+ def default_format(m)
196
+ mod = "#{self}::#{m.to_s}".constantize
197
+ mab = self.to_s.gsub('::Views','::Mab').constantize # @techarch : get the Mab class
198
+ mab.class_eval{include mod}
199
+ end
200
+ end
201
+
202
+ module Helpers
203
+ # Overrides Camping's routing helper to make it possible to route RESTful resources.
204
+ #
205
+ # Some usage examples:
206
+ #
207
+ # R(Kittens) # /kittens
208
+ # R(Kittens, 'new') # /kittens/new
209
+ # R(Kittens, 1, 'meow') # /kittens/1/meow
210
+ # R(@kitten) # /kittens/1
211
+ # R(@kitten, 'meow') # /kittens/1/meow
212
+ # R(Kittens, 'list', :colour => 'black') # /kittens/list?colour=black
213
+ #
214
+ # The current output format is retained, so if the current <tt>@format</tt> is <tt>:XML</tt>,
215
+ # the URL will be /kittens/1.xml rather than /kittens/1.
216
+ #
217
+ # Note that your controller names might not be loaded if you're calling <tt>R</tt> inside a
218
+ # view module. In that case you should use the fully qualified name (i.e. Myapp::Controllers::Kittens)
219
+ # or include the Controllers module into your view module.
220
+ def R(c, *g)
221
+
222
+ cl = c.class.name.split("::").last.pluralize
223
+ app_name = c.class.name.split("::").first
224
+ ctrl_cl = app_name + '::Controllers' # @techarch : get to the Controllers using the current app
225
+ ctrl = (app_name != 'Class') ? ctrl_cl.constantize : Controllers
226
+
227
+ if ctrl.constants.include?(cl) #@techarch updated to use new cl variable
228
+ path = "/#{cl.underscore}/#{c.id}"
229
+ path << ".#{@format.to_s.downcase}" if @format
230
+ path << "/#{g.shift}" unless g.empty?
231
+ self / path
232
+ elsif c.respond_to?(:restful?) && c.restful?
233
+ base = c.name.split("::").last.underscore
234
+ id_or_action = g.shift
235
+ if id_or_action.to_s =~ /\d+/ #@techarch needed a to_s after id_or_action to allow pattern matching
236
+ id = id_or_action
237
+ action = g.shift
238
+ else
239
+ action = id_or_action
240
+ end
241
+
242
+ path = "/#{base}"
243
+ path << "/#{id}" if id
244
+ path << "/#{action}" if action
245
+ path << ".#{@format.to_s.downcase}" if @format
246
+
247
+ #@techarch substituted U for u=Rack::Utils
248
+ u=Rack::Utils
249
+ path << "?#{g.collect{|a|a.collect{|k,v| u.escape(k)+"="+u.escape(v)}.join("&")}.join("&")}" unless g.empty? # FIXME: undefined behaviour if there are multiple arguments left
250
+ return path
251
+ else
252
+ _R(c, *g)
253
+ end
254
+ end # def R
255
+ end # module Helpers
256
+
257
+ module Controllers
258
+ def self.determine_format(input, env) #:nodoc:
259
+ if input[:format] && !input[:format].empty?
260
+ input[:format].upcase.intern
261
+ elsif env['PATH_INFO'] =~ /\.([a-z]+)$/
262
+ $~[1].upcase.intern
263
+ end
264
+ end
265
+
266
+ # Calling <tt>REST "<resource name>"</tt> creates a controller with the
267
+ # appropriate routes and maps your REST methods to standard
268
+ # Camping controller mehods. This is meant to be used in your Controllers
269
+ # module in place of <tt>R <routes></tt>.
270
+ #
271
+ # Your REST class should define the following methods:
272
+ #
273
+ # * create
274
+ # * read(id)
275
+ # * update(id)
276
+ # * destroy(id)
277
+ # * list
278
+ #
279
+ # Routes will be automatically created based on the resource name fed to the
280
+ # REST method. <b>Your class must have the same (but CamelCaps'ed)
281
+ # name as the resource name.</b> So if your resource name is 'kittens',
282
+ # your controller class must be Kittens.
283
+ #
284
+ # For example:
285
+ #
286
+ # module Foobar::Controllers
287
+ # class Kittens < REST 'kittens'
288
+ # # POST /kittens
289
+ # def create
290
+ # end
291
+ #
292
+ # # GET /kittens/(\d+)
293
+ # def read(id)
294
+ # end
295
+ #
296
+ # # PUT /kittens/(\d+)
297
+ # def update(id)
298
+ # end
299
+ #
300
+ # # DELETE /kittens/(\d+)
301
+ # def destroy(id)
302
+ # end
303
+ #
304
+ # # GET /kittens
305
+ # def list
306
+ # end
307
+ # end
308
+ # end
309
+ #
310
+ # Custom actions are also possible. For example, to implement a 'meow'
311
+ # action simply add a 'meow' method to the above controller:
312
+ #
313
+ # # POST/GET/PUT/DELETE /kittens/meow
314
+ # # POST/GET/PUT/DELETE /kittens/(\d+)/meow
315
+ # def meow(id)
316
+ # end
317
+ #
318
+ # Note that a custom action will respond to all four HTTP methods
319
+ # (POST/GET/PUT/DELETE).
320
+ #
321
+ # Optionally, you can specify a <tt>:prefix</tt> key that will prepend the
322
+ # given string to the routes. For example, the following will create all
323
+ # of the above routes, prefixed with "/pets"
324
+ # (i.e. <tt>POST '/pets/kittens'</tt>, <tt>GET '/pets/kittens/(\d+)'</tt>,
325
+ # etc.):
326
+ #
327
+ # module Foobar::Controllers
328
+ # class Items < REST 'kittens', :prefix => '/pets'
329
+ # # ...
330
+ # end
331
+ # end
332
+ #
333
+ # Format-based routing similar to that in ActiveResource is also implemented.
334
+ # For example, to get a list of kittens in XML format, place a
335
+ # <tt>GET</tt> call to <tt>/kittens.xml</tt>.
336
+ # See the documentation for the render() method for more info.
337
+ #
338
+ def REST(r, options = {})
339
+ crud = R "#{options[:prefix]}/#{r}/([0-9a-zA-Z]+)/([a-z_]+)(?:\.[a-z]+)?",
340
+ "#{options[:prefix]}/#{r}/([0-9a-zA-Z]+)(?:\.[a-z]+)?",
341
+ "#{options[:prefix]}/#{r}/([a-z_]+)(?:\.[a-z]+)?",
342
+ "#{options[:prefix]}/#{r}(?:\.[a-z]+)?"
343
+
344
+ crud.module_eval do
345
+ meta_def(:restful?){true}
346
+
347
+ $LOG.debug("Creating RESTful controller for #{r.inspect} using Reststop #{'pull version number here'}") if $LOG
348
+
349
+ def get(id_or_custom_action = nil, custom_action = nil) # :nodoc:
350
+ id = input['id'] if input['id']
351
+
352
+ custom_action = input['action'] if input['action']
353
+
354
+ if self.methods.include? id_or_custom_action
355
+ custom_action ||= id_or_custom_action
356
+ id ||= nil
357
+ else
358
+ id ||= id_or_custom_action
359
+ end
360
+
361
+ id = id.to_i if id && id =~ /^[0-9]+$/
362
+
363
+ @format = Reststop::Controllers.determine_format(input, @env)
364
+
365
+ begin
366
+ if id.nil? && input['id'].nil?
367
+ custom_action ? send(custom_action) : list
368
+ else
369
+ custom_action ? send(custom_action, id || input['id']) : read(id || input['id'])
370
+ end
371
+ rescue NoMethodError => e
372
+ # FIXME: this is probably not a good way to do this, but we need to somehow differentiate
373
+ # between 'no such route' vs. other NoMethodErrors
374
+ if e.message =~ /no such method/
375
+ return no_method(e)
376
+ else
377
+ raise e
378
+ end
379
+ rescue ActiveRecord::RecordNotFound => e
380
+ return not_found(e)
381
+ end
382
+ end
383
+
384
+
385
+ def post(custom_action = nil) # :nodoc:
386
+ @format = Reststop::Controllers.determine_format(input, @env)
387
+ custom_action ? send(custom_action) : create
388
+ end
389
+
390
+ def put(id, custom_action = nil) # :nodoc:
391
+ id = id.to_i if id =~ /^[0-9]+$/
392
+ @format = Reststop::Controllers.determine_format(input, @env)
393
+ custom_action ? send(custom_action, id || input['id']) : update(id || input['id'])
394
+ end
395
+
396
+ def delete(id, custom_action = nil) # :nodoc:
397
+ id = id.to_i if id =~ /^[0-9]+$/
398
+ @format = Reststop::Controllers.determine_format(input, @env)
399
+ custom_action ? send(custom_action, id || input['id']) : destroy(id || input['id'])
400
+ end
401
+
402
+ private
403
+ def _error(message, status_code = 500, e = nil)
404
+ @status = status_code
405
+ @message = message
406
+ begin
407
+ render "error_#{status_code}".intern
408
+ rescue NoMethodError
409
+ if @format.to_s == 'XML'
410
+ "<error code='#{status_code}'>#{@message}</error>"
411
+ else
412
+ out = "<strong>#{@message}</strong>"
413
+ out += "<pre style='color: #bbb'><strong>#{e.class}: #{e}</strong>\n#{e.backtrace.join("\n")}</pre>" if e
414
+ out
415
+ end
416
+ end
417
+ end
418
+
419
+ def no_method(e)
420
+ _error("No controller method responds to this route!", 501, e)
421
+ end
422
+
423
+ def not_found(e)
424
+ _error("Record not found!", 404, e)
425
+ end
426
+ end
427
+ crud
428
+ end # def REST
429
+ end # module Controllers
430
+ end # module Reststop
431
+
432
+ module Markaby
433
+ class Builder
434
+ # Modifies Markaby's 'form' generator so that if a 'method' parameter
435
+ # is supplied, a hidden '_method' input is automatically added.
436
+ def form(*args, &block)
437
+ options = args[0] if args && args[0] && args[0].kind_of?(Hash)
438
+ inside = capture &block
439
+
440
+ if options && options.has_key?(:method)
441
+ inside = input(:type => 'hidden', :name => '_method', :value => options[:method]) +
442
+ inside
443
+ if options[:method].to_s === 'put' || options[:method].to_s == 'delete'
444
+ options[:method] = 'post'
445
+ end
446
+ end
447
+
448
+ tag!(:form, options || args[0]) {inside}
449
+ end
450
+ end
451
+ end