manveru-innate 2009.02.06
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +1409 -0
- data/COPYING +18 -0
- data/MANIFEST +100 -0
- data/README.md +485 -0
- data/Rakefile +139 -0
- data/example/app/retro_games.rb +57 -0
- data/example/app/whywiki_erb/layout/wiki.html.erb +15 -0
- data/example/app/whywiki_erb/spec/wiki.rb +19 -0
- data/example/app/whywiki_erb/start.rb +45 -0
- data/example/app/whywiki_erb/view/edit.html.erb +6 -0
- data/example/app/whywiki_erb/view/index.html.erb +10 -0
- data/example/custom_middleware.rb +43 -0
- data/example/error_handling.rb +31 -0
- data/example/hello.rb +12 -0
- data/example/howto_spec.rb +60 -0
- data/example/link.rb +35 -0
- data/example/providing_hash.rb +46 -0
- data/example/session.rb +42 -0
- data/innate.gemspec +118 -0
- data/lib/innate.rb +191 -0
- data/lib/innate/action.rb +156 -0
- data/lib/innate/adapter.rb +89 -0
- data/lib/innate/cache.rb +117 -0
- data/lib/innate/cache/api.rb +106 -0
- data/lib/innate/cache/drb.rb +58 -0
- data/lib/innate/cache/file_based.rb +39 -0
- data/lib/innate/cache/marshal.rb +17 -0
- data/lib/innate/cache/memory.rb +22 -0
- data/lib/innate/cache/yaml.rb +17 -0
- data/lib/innate/core_compatibility/basic_object.rb +9 -0
- data/lib/innate/core_compatibility/string.rb +3 -0
- data/lib/innate/current.rb +37 -0
- data/lib/innate/dynamap.rb +81 -0
- data/lib/innate/helper.rb +195 -0
- data/lib/innate/helper/aspect.rb +62 -0
- data/lib/innate/helper/cgi.rb +39 -0
- data/lib/innate/helper/flash.rb +36 -0
- data/lib/innate/helper/link.rb +55 -0
- data/lib/innate/helper/partial.rb +90 -0
- data/lib/innate/helper/redirect.rb +85 -0
- data/lib/innate/helper/send_file.rb +18 -0
- data/lib/innate/log.rb +23 -0
- data/lib/innate/log/color_formatter.rb +43 -0
- data/lib/innate/log/hub.rb +72 -0
- data/lib/innate/mock.rb +49 -0
- data/lib/innate/node.rb +471 -0
- data/lib/innate/options.rb +91 -0
- data/lib/innate/options/dsl.rb +155 -0
- data/lib/innate/request.rb +165 -0
- data/lib/innate/response.rb +18 -0
- data/lib/innate/route.rb +109 -0
- data/lib/innate/session.rb +104 -0
- data/lib/innate/session/flash.rb +94 -0
- data/lib/innate/setup.rb +23 -0
- data/lib/innate/spec.rb +42 -0
- data/lib/innate/state.rb +22 -0
- data/lib/innate/state/accessor.rb +130 -0
- data/lib/innate/state/fiber.rb +68 -0
- data/lib/innate/state/thread.rb +39 -0
- data/lib/innate/traited.rb +20 -0
- data/lib/innate/trinity.rb +22 -0
- data/lib/innate/version.rb +3 -0
- data/lib/innate/view.rb +67 -0
- data/lib/innate/view/erb.rb +17 -0
- data/lib/innate/view/none.rb +9 -0
- data/lib/rack/middleware_compiler.rb +62 -0
- data/lib/rack/reloader.rb +192 -0
- data/spec/example/hello.rb +14 -0
- data/spec/example/link.rb +29 -0
- data/spec/helper.rb +2 -0
- data/spec/innate/cache/common.rb +45 -0
- data/spec/innate/cache/marshal.rb +5 -0
- data/spec/innate/cache/memory.rb +5 -0
- data/spec/innate/cache/yaml.rb +5 -0
- data/spec/innate/dynamap.rb +22 -0
- data/spec/innate/helper.rb +66 -0
- data/spec/innate/helper/aspect.rb +80 -0
- data/spec/innate/helper/cgi.rb +37 -0
- data/spec/innate/helper/flash.rb +148 -0
- data/spec/innate/helper/link.rb +82 -0
- data/spec/innate/helper/partial.rb +66 -0
- data/spec/innate/helper/redirect.rb +148 -0
- data/spec/innate/helper/send_file.rb +21 -0
- data/spec/innate/helper/view/aspect_hello.erb +1 -0
- data/spec/innate/helper/view/locals.erb +1 -0
- data/spec/innate/helper/view/loop.erb +4 -0
- data/spec/innate/helper/view/num.erb +1 -0
- data/spec/innate/helper/view/partial.erb +1 -0
- data/spec/innate/helper/view/recursive.erb +8 -0
- data/spec/innate/mock.rb +84 -0
- data/spec/innate/node.rb +180 -0
- data/spec/innate/node/bar.html +1 -0
- data/spec/innate/node/foo.html.erb +1 -0
- data/spec/innate/node/with_layout.erb +3 -0
- data/spec/innate/options.rb +90 -0
- data/spec/innate/parameter.rb +154 -0
- data/spec/innate/request.rb +73 -0
- data/spec/innate/route.rb +129 -0
- data/spec/innate/session.rb +59 -0
- data/spec/innate/traited.rb +55 -0
- metadata +160 -0
data/lib/innate/node.rb
ADDED
@@ -0,0 +1,471 @@
|
|
1
|
+
module Innate
|
2
|
+
|
3
|
+
# The nervous system of Innate, so you can relax.
|
4
|
+
#
|
5
|
+
# Node may be included into any class to make it a valid responder to
|
6
|
+
# requests.
|
7
|
+
#
|
8
|
+
# The major difference between this and the Ramaze controller is that every
|
9
|
+
# Node acts as a standalone application with its own dispatcher.
|
10
|
+
#
|
11
|
+
# What's also an important difference is the fact that Node is a module, so
|
12
|
+
# we don't have to spend a lot of time designing the perfect subclassing
|
13
|
+
# scheme.
|
14
|
+
#
|
15
|
+
# This makes dispatching more fun, avoids a lot of processing that is done by
|
16
|
+
# Rack anyway and lets you tailor your application down to the last action
|
17
|
+
# exactly the way you want without worrying about side-effects to other
|
18
|
+
# nodes.
|
19
|
+
#
|
20
|
+
# Upon inclusion, it will also include Innate::Trinity and Innate::Helper to
|
21
|
+
# provide you with request/response objects, a session and all the standard
|
22
|
+
# helper methods as well as the ability to simply add other helpers.
|
23
|
+
#
|
24
|
+
# NOTE:
|
25
|
+
# * Although I tried to minimize the amount of code in here there is still
|
26
|
+
# quite a number of methods left in order to do ramaze-style lookups.
|
27
|
+
# Those methods, and all other methods occurring in the ancestors after
|
28
|
+
# Innate::Node will not be considered valid action methods and will be
|
29
|
+
# ignored.
|
30
|
+
# * This also means that method_missing will not see any of the requests
|
31
|
+
# coming in.
|
32
|
+
# * If you want an action to act as a catch-all, use `def index(*args)`.
|
33
|
+
|
34
|
+
module Node
|
35
|
+
include Traited
|
36
|
+
|
37
|
+
HELPERS = [:aspect, :cgi, :flash, :link, :partial, :redirect, :send_file]
|
38
|
+
LIST = Set.new
|
39
|
+
|
40
|
+
trait(:layout => nil, :alias_view => {}, :provide => {},
|
41
|
+
:method_arities => {}, :wrap => [:aspect_wrap])
|
42
|
+
|
43
|
+
# Upon inclusion we make ourselves comfortable.
|
44
|
+
def self.included(obj)
|
45
|
+
obj.__send__(:include, Helper)
|
46
|
+
obj.helper(*HELPERS)
|
47
|
+
|
48
|
+
obj.extend(Trinity, self)
|
49
|
+
|
50
|
+
LIST << obj
|
51
|
+
|
52
|
+
return if obj.provide.any?
|
53
|
+
# provide .html with no interpolation
|
54
|
+
obj.provide(:html => :erb, :yaml => :yaml, :json => :json)
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.setup
|
58
|
+
LIST.each{|node| Innate.map(node.mapping, node) }
|
59
|
+
Log.debug("Mapped Nodes: %p" % DynaMap::MAP)
|
60
|
+
end
|
61
|
+
|
62
|
+
def mapping
|
63
|
+
mapped = Innate.to(self)
|
64
|
+
return mapped if mapped
|
65
|
+
return '/' if Innate::Node::LIST.size == 1
|
66
|
+
"/" << self.name.gsub(/\B[A-Z][^A-Z]/, '_\&').downcase
|
67
|
+
end
|
68
|
+
|
69
|
+
# Shortcut to map or remap this Node
|
70
|
+
|
71
|
+
def map(location)
|
72
|
+
Innate.map(location, self)
|
73
|
+
end
|
74
|
+
|
75
|
+
# This little piece of nasty looking code enables you to provide different
|
76
|
+
# content from a single action.
|
77
|
+
#
|
78
|
+
# Usage:
|
79
|
+
#
|
80
|
+
# class Feeds
|
81
|
+
# include Innate::Node
|
82
|
+
# map '/feed'
|
83
|
+
#
|
84
|
+
# provide :html => :erb, :rss => :erb, :atom => :erb
|
85
|
+
#
|
86
|
+
# def index
|
87
|
+
# @feed = build_some_feed
|
88
|
+
# end
|
89
|
+
# end
|
90
|
+
#
|
91
|
+
# This will do following to these requests:
|
92
|
+
#
|
93
|
+
# /feed # => call Feeds#index with template /view/feed/index.erb
|
94
|
+
# /feed.atom # => call Feeds#index with template /view/feed/index.atom.erb
|
95
|
+
# /feed.rss # => call Feeds#index with template /view/feed/index.rss.erb
|
96
|
+
#
|
97
|
+
# If index.atom.erb isn't available we fall back to /view/feed/index.erb
|
98
|
+
#
|
99
|
+
# So it's really easy to add your own content representation.
|
100
|
+
#
|
101
|
+
# If no matching provider is found for the given extension it will fall
|
102
|
+
# back to the one specified for html.
|
103
|
+
#
|
104
|
+
# The correct templating engine is selected by matching the last extension
|
105
|
+
# of the template itself to the one set in Innate::View.
|
106
|
+
#
|
107
|
+
# If you don't want that your response is passed through a templating
|
108
|
+
# engine, use :none like:
|
109
|
+
#
|
110
|
+
# provide :txt => :none
|
111
|
+
#
|
112
|
+
# So a request to
|
113
|
+
#
|
114
|
+
# /feed.txt # => call Feeds#index with template /view/feed/index.txt.erb
|
115
|
+
#
|
116
|
+
# NOTE: provides also have effect on the chosen layout for the action.
|
117
|
+
#
|
118
|
+
# Given a Node at '/' with `layout('default')`:
|
119
|
+
# /layout/default.erb
|
120
|
+
# /layout/default.rss.erb
|
121
|
+
# /view/index.erb
|
122
|
+
# /view/feed.rss.erb
|
123
|
+
#
|
124
|
+
# /feed.rss will wrap /view/feed.rss.erb in /layout/default.rss.erb
|
125
|
+
# /index will wrap /view/index.erb in /layout/default.erb
|
126
|
+
|
127
|
+
def provide(formats = {})
|
128
|
+
return ancestral_trait[:provide] if formats.empty?
|
129
|
+
|
130
|
+
trait[:provide] ||= {}
|
131
|
+
formats.each{|pr, as| trait[:provide][pr.to_s] = as.to_s }
|
132
|
+
|
133
|
+
ancestral_trait[:provide]
|
134
|
+
end
|
135
|
+
|
136
|
+
# This makes the Node a valid application for Rack.
|
137
|
+
# +env+ is the environment hash passed from the Rack::Handler
|
138
|
+
#
|
139
|
+
# We rely on correct PATH_INFO.
|
140
|
+
#
|
141
|
+
# As defined by the Rack spec, PATH_INFO may be empty if it wants the root
|
142
|
+
# of the application, so we insert '/' to make our dispatcher simple.
|
143
|
+
#
|
144
|
+
# Innate will not rescue any errors for you or do any error handling, this
|
145
|
+
# should be done by an underlying middleware.
|
146
|
+
#
|
147
|
+
# We do however log errors at some vital points in order to provide you
|
148
|
+
# with feedback in your logs.
|
149
|
+
#
|
150
|
+
# NOTE:
|
151
|
+
# * A lot of functionality in here relies on the fact that call is
|
152
|
+
# executed within Innate::STATE.wrap which populates the variables used
|
153
|
+
# by Trinity.
|
154
|
+
# * If you use the Node directly as a middleware make sure that you #use
|
155
|
+
# Innate::Current as a middleware before it.
|
156
|
+
|
157
|
+
def call(env)
|
158
|
+
path = env['PATH_INFO']
|
159
|
+
path << '/' if path.empty?
|
160
|
+
|
161
|
+
response.reset
|
162
|
+
response = try_resolve(path)
|
163
|
+
response['Content-Type'] ||= 'text/html'
|
164
|
+
|
165
|
+
Current.session.flush(response)
|
166
|
+
|
167
|
+
response.finish
|
168
|
+
end
|
169
|
+
|
170
|
+
# Let's try to find some valid action for given +path+.
|
171
|
+
# Otherwise we dispatch to action_not_found
|
172
|
+
|
173
|
+
def try_resolve(path)
|
174
|
+
action = resolve(path)
|
175
|
+
action ? action_found(action) : action_not_found(path)
|
176
|
+
end
|
177
|
+
|
178
|
+
# Executed once an Action has been found.
|
179
|
+
# Reset the Response instance, catch :respond and :redirect.
|
180
|
+
# Action#call has to return a String.
|
181
|
+
|
182
|
+
def action_found(action)
|
183
|
+
result = catch(:respond){ catch(:redirect){ action.call }}
|
184
|
+
|
185
|
+
if result.respond_to?(:finish)
|
186
|
+
return result
|
187
|
+
else
|
188
|
+
Current.response.write(result)
|
189
|
+
return Current.response
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
# The default handler in case no action was found, kind of method_missing.
|
194
|
+
# Must modify the response in order to have any lasting effect.
|
195
|
+
#
|
196
|
+
# Reasoning:
|
197
|
+
# * We are doing this is in order to avoid tons of special error handling
|
198
|
+
# code that would impact runtime and make the overall API more
|
199
|
+
# complicated.
|
200
|
+
# * This cannot be a normal action is that methods defined in Innate::Node
|
201
|
+
# will never be considered for actions.
|
202
|
+
#
|
203
|
+
# To use a normal action with template do following:
|
204
|
+
#
|
205
|
+
# class Hi
|
206
|
+
# include Innate::Node
|
207
|
+
# map '/'
|
208
|
+
#
|
209
|
+
# def action_not_found(path)
|
210
|
+
# return if path == '/not_found'
|
211
|
+
# # No normal action, runs on bare metal
|
212
|
+
# try_resolve('/not_found')
|
213
|
+
# end
|
214
|
+
#
|
215
|
+
# def not_found
|
216
|
+
# # Normal action
|
217
|
+
# "Sorry, I do not exist"
|
218
|
+
# end
|
219
|
+
# end
|
220
|
+
|
221
|
+
def action_not_found(path)
|
222
|
+
response.status = 404
|
223
|
+
response['Content-Type'] = 'text/plain'
|
224
|
+
response.write("Action not found at: %p" % path)
|
225
|
+
|
226
|
+
response
|
227
|
+
end
|
228
|
+
|
229
|
+
# Let's get down to business, first check if we got any wishes regarding
|
230
|
+
# the representation from the client, otherwise we will assume he wants
|
231
|
+
# html.
|
232
|
+
|
233
|
+
def resolve(path)
|
234
|
+
name, wish = find_provide(path)
|
235
|
+
update_method_arities
|
236
|
+
find_action(name, wish)
|
237
|
+
end
|
238
|
+
|
239
|
+
def find_provide(path)
|
240
|
+
name, wish = path, 'html'
|
241
|
+
|
242
|
+
provide.find do |key, value|
|
243
|
+
next unless path =~ /^(.+)\.#{key}$/i
|
244
|
+
name, wish = $1, key
|
245
|
+
end
|
246
|
+
|
247
|
+
return name, wish
|
248
|
+
end
|
249
|
+
|
250
|
+
# Now we're talking Action, we try to find a matching template and method,
|
251
|
+
# if we can't find either we go to the next pattern, otherwise we answer
|
252
|
+
# with an Action with everything we know so far about the demands of the
|
253
|
+
# client.
|
254
|
+
|
255
|
+
def find_action(given_name, wish)
|
256
|
+
patterns_for(given_name) do |name, params|
|
257
|
+
view = find_view(name, wish)
|
258
|
+
method = find_method(name, params)
|
259
|
+
|
260
|
+
next unless view or method
|
261
|
+
|
262
|
+
layout = find_layout(name, wish)
|
263
|
+
|
264
|
+
Action.create(
|
265
|
+
:node => self, :params => params, :wish => wish, :method => method,
|
266
|
+
:view => view, :options => {}, :variables => {}, :layout => layout)
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
# TODO: allow layouts combined of method and view... hairy :)
|
271
|
+
def find_layout(name, wish)
|
272
|
+
return unless found_layout = layout
|
273
|
+
|
274
|
+
if found = to_layout(found_layout, wish)
|
275
|
+
[:layout, found]
|
276
|
+
elsif found = find_view(found_layout, wish)
|
277
|
+
[:view, found]
|
278
|
+
elsif found = find_method(found_layout, [])
|
279
|
+
[:method, found]
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
# I hope this method talks for itself, we check arity if possible, but will
|
284
|
+
# happily dispatch to any method that has default parameters.
|
285
|
+
# If you don't want your method to be responsible for messing up a request
|
286
|
+
# you should think twice about the arguments you specify due to limitations
|
287
|
+
# in Ruby.
|
288
|
+
#
|
289
|
+
# So if you want your method to take only one parameter which may have a
|
290
|
+
# default value following will work fine:
|
291
|
+
#
|
292
|
+
# def index(foo = "bar", *rest)
|
293
|
+
#
|
294
|
+
# But following will respond to /arg1/arg2 and then fail due to ArgumentError:
|
295
|
+
#
|
296
|
+
# def index(foo = "bar")
|
297
|
+
#
|
298
|
+
# Here a glance at how parameters are expressed in arity:
|
299
|
+
#
|
300
|
+
# def index(a) # => 1
|
301
|
+
# def index(a = :a) # => -1
|
302
|
+
# def index(a, *r) # => -2
|
303
|
+
# def index(a = :a, *r) # => -1
|
304
|
+
#
|
305
|
+
# def index(a, b) # => 2
|
306
|
+
# def index(a, b, *r) # => -3
|
307
|
+
# def index(a, b = :b) # => -2
|
308
|
+
# def index(a, b = :b, *r) # => -2
|
309
|
+
#
|
310
|
+
# def index(a = :a, b = :b) # => -1
|
311
|
+
# def index(a = :a, b = :b, *r) # => -1
|
312
|
+
#
|
313
|
+
# NOTE: Once 1.9 is mainstream we can use Method#parameters to do accurate
|
314
|
+
# prediction
|
315
|
+
def find_method(name, params)
|
316
|
+
return unless arity = trait[:method_arities][name]
|
317
|
+
name if arity == params.size or arity < 0
|
318
|
+
end
|
319
|
+
|
320
|
+
# Answer with and set the @method_arities Hash, keys are method names,
|
321
|
+
# values are method arities.
|
322
|
+
#
|
323
|
+
# Usually called from Node::resolve
|
324
|
+
#
|
325
|
+
# NOTE:
|
326
|
+
# * This will be executed once for every request, once we have settled
|
327
|
+
# things down a bit more we can switch to update based on Reloader
|
328
|
+
# hooks and update once on startup.
|
329
|
+
# However, that may cause problems with dynamically created methods, so
|
330
|
+
# let's play it safe for now.
|
331
|
+
#
|
332
|
+
# Example:
|
333
|
+
#
|
334
|
+
# Hi.update_method_arities
|
335
|
+
# # => {'index' => 0, 'foo' => -1, 'bar => 2}
|
336
|
+
def update_method_arities
|
337
|
+
arities = trait[:method_arities] = {}
|
338
|
+
|
339
|
+
exposed = ancestors & Helper::EXPOSE.to_a
|
340
|
+
higher = ancestors.select{|a| a < Innate::Node }
|
341
|
+
|
342
|
+
(higher + exposed).reverse_each do |ancestor|
|
343
|
+
ancestor.public_instance_methods(false).each do |im|
|
344
|
+
arities[im.to_s] = ancestor.instance_method(im).arity
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
arities
|
349
|
+
end
|
350
|
+
|
351
|
+
# Try to find the best template for the given basename and wish.
|
352
|
+
# Also, having extraordinarily much fun with globs.
|
353
|
+
def find_view(file, wish)
|
354
|
+
path = [Innate.options.app.root, Innate.options.app.view, view_root, file]
|
355
|
+
to_template(path, wish)
|
356
|
+
end
|
357
|
+
|
358
|
+
# This is done to make you feel more at home, pass an absolute path or a
|
359
|
+
# path relative to your application root to set it, otherwise you'll get
|
360
|
+
# the current mapping.
|
361
|
+
def view_root(location = nil)
|
362
|
+
return @view_root = location if location
|
363
|
+
@view_root ||= Innate.to(self)
|
364
|
+
end
|
365
|
+
|
366
|
+
def alias_view(to, from)
|
367
|
+
(trait[:alias_view] ||= {})[to] = from
|
368
|
+
end
|
369
|
+
|
370
|
+
# Find the best matching file for the layout, if any.
|
371
|
+
def to_layout(file, wish)
|
372
|
+
path = [Innate.options.app.root, Innate.options.app.layout, file]
|
373
|
+
to_template(path, wish)
|
374
|
+
end
|
375
|
+
|
376
|
+
def to_template(path, wish)
|
377
|
+
return unless path.all?
|
378
|
+
|
379
|
+
path = File.join(*path.map{|pa| pa.to_s })
|
380
|
+
exts = [provide[wish], *provide.keys].flatten.compact.uniq.join(',')
|
381
|
+
found = Dir["#{path}.{#{wish}.,#{wish},}{#{exts},}"].uniq
|
382
|
+
|
383
|
+
if found.size > 1
|
384
|
+
Log.warn("%d views found for %p | %p" % [found.size, path, wish])
|
385
|
+
end
|
386
|
+
|
387
|
+
template = found.first
|
388
|
+
ancestral_trait[:alias_view][template] || template
|
389
|
+
end
|
390
|
+
|
391
|
+
# Set the +name+ of the layout you want, this takes only the basename
|
392
|
+
# without any filename-extension or directory.
|
393
|
+
def layout(name = nil)
|
394
|
+
name ? trait(:layout => name) : ancestral_trait[:layout]
|
395
|
+
end
|
396
|
+
|
397
|
+
# The innate beauty in Nitro, Ramaze, and Innate.
|
398
|
+
#
|
399
|
+
# Will yield the name of the action and parameter for the action method in
|
400
|
+
# order of significance.
|
401
|
+
#
|
402
|
+
# def foo__bar # responds to /foo/bar
|
403
|
+
# def foo(bar) # also responds to /foo/bar
|
404
|
+
#
|
405
|
+
# But foo__bar takes precedence because it's more explicit.
|
406
|
+
#
|
407
|
+
# The last fallback will always be the index action with all of the path
|
408
|
+
# turned into parameters.
|
409
|
+
#
|
410
|
+
# Samples:
|
411
|
+
#
|
412
|
+
# class Foo; include Innate::Node; map '/'; end
|
413
|
+
#
|
414
|
+
# Foo.patterns_for('/'){|action, params| p action => params }
|
415
|
+
# {"index"=>[]}
|
416
|
+
#
|
417
|
+
# Foo.patterns_for('/foo/bar'){|action, params| p action => params }
|
418
|
+
# {"foo__bar"=>[]}
|
419
|
+
# {"foo"=>["bar"]}
|
420
|
+
# {"index"=>["foo", "bar"]}
|
421
|
+
#
|
422
|
+
# Foo.patterns_for('/foo/bar/baz'){|action, params| p action => params }
|
423
|
+
# {"foo__bar__baz"=>[]}
|
424
|
+
# {"foo__bar"=>["baz"]}
|
425
|
+
# {"foo"=>["bar", "baz"]}
|
426
|
+
# {"index"=>["foo", "bar", "baz"]}
|
427
|
+
def patterns_for(path)
|
428
|
+
atoms = path.split('/')
|
429
|
+
atoms.delete('')
|
430
|
+
result = nil
|
431
|
+
|
432
|
+
atoms.size.downto(0) do |len|
|
433
|
+
action = atoms[0...len].join('__')
|
434
|
+
params = atoms[len..-1]
|
435
|
+
action = 'index' if action.empty?
|
436
|
+
|
437
|
+
return result if result = yield(action, params)
|
438
|
+
end
|
439
|
+
|
440
|
+
return nil
|
441
|
+
end
|
442
|
+
|
443
|
+
# This awesome piece of hackery implements action AOP, methods may register
|
444
|
+
# themself in the trait[:wrap] and will be called in left-to-right order,
|
445
|
+
# each being passed the action instance and a block that they have to yield
|
446
|
+
# to continue the chain.
|
447
|
+
#
|
448
|
+
# This enables things like action logging, caching, aspects,
|
449
|
+
# authentication, etc...
|
450
|
+
#
|
451
|
+
# @param [Action] action instance that is being passed to every registered method
|
452
|
+
# @param [Proc] block contains the instructions to call the action method if any
|
453
|
+
# @see Action#render
|
454
|
+
# @author manveru
|
455
|
+
|
456
|
+
def wrap_action_call(action, &block)
|
457
|
+
wrap = ancestral_trait[:wrap]
|
458
|
+
|
459
|
+
head, tail = wrap[0], wrap[1..-1].reverse
|
460
|
+
combined = tail.inject(block){|s,v| lambda{ __send__(v, action, &s) } }
|
461
|
+
__send__(head, action, &combined)
|
462
|
+
end
|
463
|
+
|
464
|
+
# For compatibility with new Kernel#binding behaviour in 1.9
|
465
|
+
#
|
466
|
+
# @return [Binding] binding of the instance being rendered.
|
467
|
+
# @see Action#binding
|
468
|
+
# @author manveru
|
469
|
+
def binding; super; end
|
470
|
+
end
|
471
|
+
end
|