manveru-innate 2009.02.06
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 +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
|