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