reactive 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. data/History.txt +3 -0
  2. data/MIT-LICENSE +21 -0
  3. data/Manifest.txt +60 -0
  4. data/README.txt +130 -0
  5. data/Rakefile +14 -0
  6. data/app_generators/reactive/USAGE +11 -0
  7. data/app_generators/reactive/reactive_generator.rb +160 -0
  8. data/app_generators/reactive/templates/README +130 -0
  9. data/app_generators/reactive/templates/Rakefile +10 -0
  10. data/app_generators/reactive/templates/app/controllers/application_controller.rb +2 -0
  11. data/app_generators/reactive/templates/app/helpers/application_helper.rb +2 -0
  12. data/app_generators/reactive/templates/config/boot.rb +94 -0
  13. data/app_generators/reactive/templates/config/databases/frontbase.yml +28 -0
  14. data/app_generators/reactive/templates/config/databases/mysql.yml +54 -0
  15. data/app_generators/reactive/templates/config/databases/oracle.yml +39 -0
  16. data/app_generators/reactive/templates/config/databases/postgresql.yml +48 -0
  17. data/app_generators/reactive/templates/config/databases/sqlite2.yml +16 -0
  18. data/app_generators/reactive/templates/config/databases/sqlite3.yml +19 -0
  19. data/app_generators/reactive/templates/config/empty.log +0 -0
  20. data/app_generators/reactive/templates/config/environment.rb +11 -0
  21. data/app_generators/reactive/templates/script/destroy +12 -0
  22. data/app_generators/reactive/templates/script/generate +12 -0
  23. data/app_generators/reactive/templates/script/run +5 -0
  24. data/app_generators/reactive/templates/script/win_script.cmd +1 -0
  25. data/bin/reactive +16 -0
  26. data/lib/code_statistics.rb +107 -0
  27. data/lib/controller.rb +23 -0
  28. data/lib/controller/base.rb +442 -0
  29. data/lib/controller/filters.rb +767 -0
  30. data/lib/controller/flash.rb +161 -0
  31. data/lib/controller/helpers.rb +204 -0
  32. data/lib/controller/layout.rb +326 -0
  33. data/lib/dispatcher.rb +46 -0
  34. data/lib/generated_attribute.rb +40 -0
  35. data/lib/initializer.rb +425 -0
  36. data/lib/named_base_generator.rb +92 -0
  37. data/lib/reactive.rb +6 -0
  38. data/lib/request.rb +17 -0
  39. data/lib/source_annotation_extractor.rb +62 -0
  40. data/lib/tasks/annotations.rake +23 -0
  41. data/lib/tasks/databases.rake +347 -0
  42. data/lib/tasks/log.rake +9 -0
  43. data/lib/tasks/misc.rake +5 -0
  44. data/lib/tasks/reactive.rb +16 -0
  45. data/lib/tasks/statistics.rake +17 -0
  46. data/lib/tasks/testing.rake +118 -0
  47. data/lib/version.rb +9 -0
  48. data/lib/view.rb +1 -0
  49. data/lib/view/base.rb +33 -0
  50. data/reactive_generators/model/USAGE +27 -0
  51. data/reactive_generators/model/model_generator.rb +52 -0
  52. data/reactive_generators/model/templates/fixtures.yml +19 -0
  53. data/reactive_generators/model/templates/migration.rb +16 -0
  54. data/reactive_generators/model/templates/model.rb +2 -0
  55. data/reactive_generators/model/templates/unit_test.rb +8 -0
  56. data/reactive_generators/scaffold/USAGE +26 -0
  57. data/reactive_generators/scaffold/scaffold_generator.rb +75 -0
  58. data/reactive_generators/scaffold/templates/controller.rb +51 -0
  59. data/reactive_generators/scaffold/templates/functional_test.rb +49 -0
  60. data/reactive_generators/scaffold/templates/helper.rb +2 -0
  61. metadata +142 -0
@@ -0,0 +1,767 @@
1
+ module Reactive::Controller #:nodoc:
2
+ module Filters #:nodoc:
3
+ def self.included(base)
4
+ base.class_eval do
5
+ extend ClassMethods
6
+ include Reactive::Controller::Filters::InstanceMethods
7
+ end
8
+ end
9
+
10
+ # Filters enable controllers to run shared pre- and post-processing code for its actions. These filters can be used to do
11
+ # authentication, caching, or auditing before the intended action is performed. Or to do localization or output
12
+ # compression after the action has been performed. Filters have access to the request, response, and all the instance
13
+ # variables set by other filters in the chain or by the action (in the case of after filters).
14
+ #
15
+ # == Filter inheritance
16
+ #
17
+ # Controller inheritance hierarchies share filters downwards, but subclasses can also add or skip filters without
18
+ # affecting the superclass. For example:
19
+ #
20
+ # class BankController < Reactive::Controller::Base
21
+ # before_filter :audit
22
+ #
23
+ # private
24
+ # def audit
25
+ # # record the action and parameters in an audit log
26
+ # end
27
+ # end
28
+ #
29
+ # class VaultController < BankController
30
+ # before_filter :verify_credentials
31
+ #
32
+ # private
33
+ # def verify_credentials
34
+ # # make sure the user is allowed into the vault
35
+ # end
36
+ # end
37
+ #
38
+ # Now any actions performed on the BankController will have the audit method called before. On the VaultController,
39
+ # first the audit method is called, then the verify_credentials method. If the audit method renders or redirects, then
40
+ # verify_credentials and the intended action are never called.
41
+ #
42
+ # == Filter types
43
+ #
44
+ # A filter can take one of three forms: method reference (symbol), external class, or inline method (proc). The first
45
+ # is the most common and works by referencing a protected or private method somewhere in the inheritance hierarchy of
46
+ # the controller by use of a symbol. In the bank example above, both BankController and VaultController use this form.
47
+ #
48
+ # Using an external class makes for more easily reused generic filters, such as output compression. External filter classes
49
+ # are implemented by having a static +filter+ method on any class and then passing this class to the filter method. Example:
50
+ #
51
+ # class OutputCompressionFilter
52
+ # def self.filter(controller)
53
+ # controller.response.body = compress(controller.response.body)
54
+ # end
55
+ # end
56
+ #
57
+ # class NewspaperController < Reactive::Controller::Base
58
+ # after_filter OutputCompressionFilter
59
+ # end
60
+ #
61
+ # The filter method is passed the controller instance and is hence granted access to all aspects of the controller and can
62
+ # manipulate them as it sees fit.
63
+ #
64
+ # The inline method (using a proc) can be used to quickly do something small that doesn't require a lot of explanation.
65
+ # Or just as a quick test. It works like this:
66
+ #
67
+ # class WeblogController < Reactive::Controller::Base
68
+ # before_filter { |controller| head(400) if controller.params["stop_action"] }
69
+ # end
70
+ #
71
+ # As you can see, the block expects to be passed the controller after it has assigned the request to the internal variables.
72
+ # This means that the block has access to both the request and response objects complete with convenience methods for params,
73
+ # session, template, and assigns. Note: The inline method doesn't strictly have to be a block; any object that responds to call
74
+ # and returns 1 or -1 on arity will do (such as a Proc or an Method object).
75
+ #
76
+ # Please note that around_filters function a little differently than the normal before and after filters with regard to filter
77
+ # types. Please see the section dedicated to around_filters below.
78
+ #
79
+ # == Filter chain ordering
80
+ #
81
+ # Using <tt>before_filter</tt> and <tt>after_filter</tt> appends the specified filters to the existing chain. That's usually
82
+ # just fine, but some times you care more about the order in which the filters are executed. When that's the case, you
83
+ # can use <tt>prepend_before_filter</tt> and <tt>prepend_after_filter</tt>. Filters added by these methods will be put at the
84
+ # beginning of their respective chain and executed before the rest. For example:
85
+ #
86
+ # class ShoppingController < Reactive::Controller::Base
87
+ # before_filter :verify_open_shop
88
+ #
89
+ # class CheckoutController < ShoppingController
90
+ # prepend_before_filter :ensure_items_in_cart, :ensure_items_in_stock
91
+ #
92
+ # The filter chain for the CheckoutController is now <tt>:ensure_items_in_cart, :ensure_items_in_stock,</tt>
93
+ # <tt>:verify_open_shop</tt>. So if either of the ensure filters renders or redirects, we'll never get around to see if the shop
94
+ # is open or not.
95
+ #
96
+ # You may pass multiple filter arguments of each type as well as a filter block.
97
+ # If a block is given, it is treated as the last argument.
98
+ #
99
+ # == Around filters
100
+ #
101
+ # Around filters wrap an action, executing code both before and after.
102
+ # They may be declared as method references, blocks, or objects responding
103
+ # to #filter or to both #before and #after.
104
+ #
105
+ # To use a method as an around_filter, pass a symbol naming the Ruby method.
106
+ # Yield (or block.call) within the method to run the action.
107
+ #
108
+ # around_filter :catch_exceptions
109
+ #
110
+ # private
111
+ # def catch_exceptions
112
+ # yield
113
+ # rescue => exception
114
+ # logger.debug "Caught exception! #{exception}"
115
+ # raise
116
+ # end
117
+ #
118
+ # To use a block as an around_filter, pass a block taking as args both
119
+ # the controller and the action block. You can't call yield directly from
120
+ # an around_filter block; explicitly call the action block instead:
121
+ #
122
+ # around_filter do |controller, action|
123
+ # logger.debug "before #{controller.action_name}"
124
+ # action.call
125
+ # logger.debug "after #{controller.action_name}"
126
+ # end
127
+ #
128
+ # To use a filter object with around_filter, pass an object responding
129
+ # to :filter or both :before and :after. With a filter method, yield to
130
+ # the block as above:
131
+ #
132
+ # around_filter BenchmarkingFilter
133
+ #
134
+ # class BenchmarkingFilter
135
+ # def self.filter(controller, &block)
136
+ # Benchmark.measure(&block)
137
+ # end
138
+ # end
139
+ #
140
+ # With before and after methods:
141
+ #
142
+ # around_filter Authorizer.new
143
+ #
144
+ # class Authorizer
145
+ # # This will run before the action. Redirecting aborts the action.
146
+ # def before(controller)
147
+ # unless user.authorized?
148
+ # redirect_to(login_url)
149
+ # end
150
+ # end
151
+ #
152
+ # # This will run after the action if and only if before did not render or redirect.
153
+ # def after(controller)
154
+ # end
155
+ # end
156
+ #
157
+ # If the filter has before and after methods, the before method will be
158
+ # called before the action. If before renders or redirects, the filter chain is
159
+ # halted and after will not be run. See Filter Chain Halting below for
160
+ # an example.
161
+ #
162
+ # == Filter chain skipping
163
+ #
164
+ # Declaring a filter on a base class conveniently applies to its subclasses,
165
+ # but sometimes a subclass should skip some of its superclass' filters:
166
+ #
167
+ # class ApplicationController < Reactive::Controller::Base
168
+ # before_filter :authenticate
169
+ # around_filter :catch_exceptions
170
+ # end
171
+ #
172
+ # class WeblogController < ApplicationController
173
+ # # Will run the :authenticate and :catch_exceptions filters.
174
+ # end
175
+ #
176
+ # class SignupController < ApplicationController
177
+ # # Skip :authenticate, run :catch_exceptions.
178
+ # skip_before_filter :authenticate
179
+ # end
180
+ #
181
+ # class ProjectsController < ApplicationController
182
+ # # Skip :catch_exceptions, run :authenticate.
183
+ # skip_filter :catch_exceptions
184
+ # end
185
+ #
186
+ # class ClientsController < ApplicationController
187
+ # # Skip :catch_exceptions and :authenticate unless action is index.
188
+ # skip_filter :catch_exceptions, :authenticate, :except => :index
189
+ # end
190
+ #
191
+ # == Filter conditions
192
+ #
193
+ # Filters may be limited to specific actions by declaring the actions to
194
+ # include or exclude. Both options accept single actions (:only => :index)
195
+ # or arrays of actions (:except => [:foo, :bar]).
196
+ #
197
+ # class Journal < Reactive::Controller::Base
198
+ # # Require authentication for edit and delete.
199
+ # before_filter :authorize, :only => [:edit, :delete]
200
+ #
201
+ # # Passing options to a filter with a block.
202
+ # around_filter(:except => :index) do |controller, action_block|
203
+ # results = Profiler.run(&action_block)
204
+ # controller.response.sub! "</body>", "#{results}</body>"
205
+ # end
206
+ #
207
+ # private
208
+ # def authorize
209
+ # # Redirect to login unless authenticated.
210
+ # end
211
+ # end
212
+ #
213
+ # == Filter Chain Halting
214
+ #
215
+ # <tt>before_filter</tt> and <tt>around_filter</tt> may halt the request
216
+ # before a controller action is run. This is useful, for example, to deny
217
+ # access to unauthenticated users or to redirect from http to https.
218
+ # Simply call render or redirect. After filters will not be executed if the filter
219
+ # chain is halted.
220
+ #
221
+ # Around filters halt the request unless the action block is called.
222
+ # Given these filters
223
+ # after_filter :after
224
+ # around_filter :around
225
+ # before_filter :before
226
+ #
227
+ # The filter chain will look like:
228
+ #
229
+ # ...
230
+ # . \
231
+ # . #around (code before yield)
232
+ # . . \
233
+ # . . #before (actual filter code is run)
234
+ # . . . \
235
+ # . . . execute controller action
236
+ # . . . /
237
+ # . . ...
238
+ # . . /
239
+ # . #around (code after yield)
240
+ # . /
241
+ # #after (actual filter code is run, unless the around filter does not yield)
242
+ #
243
+ # If #around returns before yielding, #after will still not be run. The #before
244
+ # filter and controller action will not be run. If #before renders or redirects,
245
+ # the second half of #around and will still run but #after and the
246
+ # action will not. If #around fails to yield, #after will not be run.
247
+ module ClassMethods
248
+ # The passed <tt>filters</tt> will be appended to the filter_chain and
249
+ # will execute before the action on this controller is performed.
250
+ def append_before_filter(*filters, &block)
251
+ append_filter_to_chain(filters, :before, &block)
252
+ end
253
+
254
+ # The passed <tt>filters</tt> will be prepended to the filter_chain and
255
+ # will execute before the action on this controller is performed.
256
+ def prepend_before_filter(*filters, &block)
257
+ prepend_filter_to_chain(filters, :before, &block)
258
+ end
259
+
260
+ # Shorthand for append_before_filter since it's the most common.
261
+ alias :before_filter :append_before_filter
262
+
263
+ # The passed <tt>filters</tt> will be appended to the array of filters
264
+ # that run _after_ actions on this controller are performed.
265
+ def append_after_filter(*filters, &block)
266
+ append_filter_to_chain(filters, :after, &block)
267
+ end
268
+
269
+ # The passed <tt>filters</tt> will be prepended to the array of filters
270
+ # that run _after_ actions on this controller are performed.
271
+ def prepend_after_filter(*filters, &block)
272
+ prepend_filter_to_chain(filters, :after, &block)
273
+ end
274
+
275
+ # Shorthand for append_after_filter since it's the most common.
276
+ alias :after_filter :append_after_filter
277
+
278
+
279
+ # If you append_around_filter A.new, B.new, the filter chain looks like
280
+ #
281
+ # B#before
282
+ # A#before
283
+ # # run the action
284
+ # A#after
285
+ # B#after
286
+ #
287
+ # With around filters which yield to the action block, #before and #after
288
+ # are the code before and after the yield.
289
+ def append_around_filter(*filters, &block)
290
+ filters, conditions = extract_conditions(filters, &block)
291
+ filters.map { |f| proxy_before_and_after_filter(f) }.each do |filter|
292
+ append_filter_to_chain([filter, conditions])
293
+ end
294
+ end
295
+
296
+ # If you prepend_around_filter A.new, B.new, the filter chain looks like:
297
+ #
298
+ # A#before
299
+ # B#before
300
+ # # run the action
301
+ # B#after
302
+ # A#after
303
+ #
304
+ # With around filters which yield to the action block, #before and #after
305
+ # are the code before and after the yield.
306
+ def prepend_around_filter(*filters, &block)
307
+ filters, conditions = extract_conditions(filters, &block)
308
+ filters.map { |f| proxy_before_and_after_filter(f) }.each do |filter|
309
+ prepend_filter_to_chain([filter, conditions])
310
+ end
311
+ end
312
+
313
+ # Shorthand for append_around_filter since it's the most common.
314
+ alias :around_filter :append_around_filter
315
+
316
+ # Removes the specified filters from the +before+ filter chain. Note that this only works for skipping method-reference
317
+ # filters, not procs. This is especially useful for managing the chain in inheritance hierarchies where only one out
318
+ # of many sub-controllers need a different hierarchy.
319
+ #
320
+ # You can control the actions to skip the filter for with the <tt>:only</tt> and <tt>:except</tt> options,
321
+ # just like when you apply the filters.
322
+ def skip_before_filter(*filters)
323
+ skip_filter_in_chain(*filters, &:before?)
324
+ end
325
+
326
+ # Removes the specified filters from the +after+ filter chain. Note that this only works for skipping method-reference
327
+ # filters, not procs. This is especially useful for managing the chain in inheritance hierarchies where only one out
328
+ # of many sub-controllers need a different hierarchy.
329
+ #
330
+ # You can control the actions to skip the filter for with the <tt>:only</tt> and <tt>:except</tt> options,
331
+ # just like when you apply the filters.
332
+ def skip_after_filter(*filters)
333
+ skip_filter_in_chain(*filters, &:after?)
334
+ end
335
+
336
+ # Removes the specified filters from the filter chain. This only works for method reference (symbol)
337
+ # filters, not procs. This method is different from skip_after_filter and skip_before_filter in that
338
+ # it will match any before, after or yielding around filter.
339
+ #
340
+ # You can control the actions to skip the filter for with the <tt>:only</tt> and <tt>:except</tt> options,
341
+ # just like when you apply the filters.
342
+ def skip_filter(*filters)
343
+ skip_filter_in_chain(*filters)
344
+ end
345
+
346
+ # Returns an array of Filter objects for this controller.
347
+ def filter_chain
348
+ read_inheritable_attribute("filter_chain") || []
349
+ end
350
+
351
+ # Returns all the before filters for this class and all its ancestors.
352
+ # This method returns the actual filter that was assigned in the controller to maintain existing functionality.
353
+ def before_filters #:nodoc:
354
+ filter_chain.select(&:before?).map(&:filter)
355
+ end
356
+
357
+ # Returns all the after filters for this class and all its ancestors.
358
+ # This method returns the actual filter that was assigned in the controller to maintain existing functionality.
359
+ def after_filters #:nodoc:
360
+ filter_chain.select(&:after?).map(&:filter)
361
+ end
362
+
363
+ # Returns a mapping between filters and the actions that may run them.
364
+ def included_actions #:nodoc:
365
+ @included_actions ||= read_inheritable_attribute("included_actions") || {}
366
+ end
367
+
368
+ # Returns a mapping between filters and actions that may not run them.
369
+ def excluded_actions #:nodoc:
370
+ @excluded_actions ||= read_inheritable_attribute("excluded_actions") || {}
371
+ end
372
+
373
+ # Find a filter in the filter_chain where the filter method matches the _filter_ param
374
+ # and (optionally) the passed block evaluates to true (mostly used for testing before?
375
+ # and after? on the filter). Useful for symbol filters.
376
+ #
377
+ # The object of type Filter is passed to the block when yielded, not the filter itself.
378
+ def find_filter(filter, &block) #:nodoc:
379
+ filter_chain.select { |f| f.filter == filter && (!block_given? || yield(f)) }.first
380
+ end
381
+
382
+ # Returns true if the filter is excluded from the given action
383
+ def filter_excluded_from_action?(filter,action) #:nodoc:
384
+ case
385
+ when ia = included_actions[filter]
386
+ !ia.include?(action)
387
+ when ea = excluded_actions[filter]
388
+ ea.include?(action)
389
+ end
390
+ end
391
+
392
+ # Filter class is an abstract base class for all filters. Handles all of the included/excluded actions but
393
+ # contains no logic for calling the actual filters.
394
+ class Filter #:nodoc:
395
+ attr_reader :filter, :included_actions, :excluded_actions
396
+
397
+ def initialize(filter)
398
+ @filter = filter
399
+ end
400
+
401
+ def type
402
+ :around
403
+ end
404
+
405
+ def before?
406
+ type == :before
407
+ end
408
+
409
+ def after?
410
+ type == :after
411
+ end
412
+
413
+ def around?
414
+ type == :around
415
+ end
416
+
417
+ def run(controller)
418
+ raise Reactive::Controller::Error, 'No filter type: Nothing to do here.'
419
+ end
420
+
421
+ def call(controller, &block)
422
+ run(controller)
423
+ end
424
+ end
425
+
426
+ # Abstract base class for filter proxies. FilterProxy objects are meant to mimic the behaviour of the old
427
+ # before_filter and after_filter by moving the logic into the filter itself.
428
+ class FilterProxy < Filter #:nodoc:
429
+ def filter
430
+ @filter.filter
431
+ end
432
+ end
433
+
434
+ class BeforeFilterProxy < FilterProxy #:nodoc:
435
+ def type
436
+ :before
437
+ end
438
+
439
+ def run(controller)
440
+ # only filters returning false are halted.
441
+ @filter.call(controller)
442
+ if controller.send!(:performed?)
443
+ controller.send!(:halt_filter_chain, @filter, :rendered_or_redirected)
444
+ end
445
+ end
446
+
447
+ def call(controller)
448
+ yield unless run(controller)
449
+ end
450
+ end
451
+
452
+ class AfterFilterProxy < FilterProxy #:nodoc:
453
+ def type
454
+ :after
455
+ end
456
+
457
+ def run(controller)
458
+ @filter.call(controller)
459
+ end
460
+
461
+ def call(controller)
462
+ yield
463
+ run(controller)
464
+ end
465
+ end
466
+
467
+ class SymbolFilter < Filter #:nodoc:
468
+ def call(controller, &block)
469
+ controller.send!(@filter, &block)
470
+ end
471
+ end
472
+
473
+ class ProcFilter < Filter #:nodoc:
474
+ def call(controller)
475
+ @filter.call(controller)
476
+ rescue LocalJumpError # a yield from a proc... no no bad dog.
477
+ raise(Reactive::Controller::Error, 'Cannot yield from a Proc type filter. The Proc must take two arguments and execute #call on the second argument.')
478
+ end
479
+ end
480
+
481
+ class ProcWithCallFilter < Filter #:nodoc:
482
+ def call(controller, &block)
483
+ @filter.call(controller, block)
484
+ rescue LocalJumpError # a yield from a proc... no no bad dog.
485
+ raise(Reactive::Controller::Error, 'Cannot yield from a Proc type filter. The Proc must take two arguments and execute #call on the second argument.')
486
+ end
487
+ end
488
+
489
+ class MethodFilter < Filter #:nodoc:
490
+ def call(controller, &block)
491
+ @filter.call(controller, &block)
492
+ end
493
+ end
494
+
495
+ class ClassFilter < Filter #:nodoc:
496
+ def call(controller, &block)
497
+ @filter.filter(controller, &block)
498
+ end
499
+ end
500
+
501
+ class ClassBeforeFilter < Filter #:nodoc:
502
+ def call(controller, &block)
503
+ @filter.before(controller)
504
+ end
505
+ end
506
+
507
+ class ClassAfterFilter < Filter #:nodoc:
508
+ def call(controller, &block)
509
+ @filter.after(controller)
510
+ end
511
+ end
512
+
513
+ protected
514
+ def append_filter_to_chain(filters, filter_type = :around, &block)
515
+ pos = find_filter_append_position(filters, filter_type)
516
+ update_filter_chain(filters, filter_type, pos, &block)
517
+ end
518
+
519
+ def prepend_filter_to_chain(filters, filter_type = :around, &block)
520
+ pos = find_filter_prepend_position(filters, filter_type)
521
+ update_filter_chain(filters, filter_type, pos, &block)
522
+ end
523
+
524
+ def update_filter_chain(filters, filter_type, pos, &block)
525
+ new_filters = create_filters(filters, filter_type, &block)
526
+ new_chain = filter_chain.insert(pos, new_filters).flatten
527
+ write_inheritable_attribute('filter_chain', new_chain)
528
+ end
529
+
530
+ def find_filter_append_position(filters, filter_type)
531
+ # appending an after filter puts it at the end of the call chain
532
+ # before and around filters go before the first after filter in the chain
533
+ unless filter_type == :after
534
+ filter_chain.each_with_index do |f,i|
535
+ return i if f.after?
536
+ end
537
+ end
538
+ return -1
539
+ end
540
+
541
+ def find_filter_prepend_position(filters, filter_type)
542
+ # prepending a before or around filter puts it at the front of the call chain
543
+ # after filters go before the first after filter in the chain
544
+ if filter_type == :after
545
+ filter_chain.each_with_index do |f,i|
546
+ return i if f.after?
547
+ end
548
+ return -1
549
+ end
550
+ return 0
551
+ end
552
+
553
+ def create_filters(filters, filter_type, &block) #:nodoc:
554
+ filters, conditions = extract_conditions(filters, &block)
555
+ filters.map! { |filter| find_or_create_filter(filter, filter_type) }
556
+ update_conditions(filters, conditions)
557
+ filters
558
+ end
559
+
560
+ def find_or_create_filter(filter, filter_type)
561
+ if found_filter = find_filter(filter) { |f| f.type == filter_type }
562
+ found_filter
563
+ else
564
+ f = class_for_filter(filter, filter_type).new(filter)
565
+ # apply proxy to filter if necessary
566
+ case filter_type
567
+ when :before
568
+ BeforeFilterProxy.new(f)
569
+ when :after
570
+ AfterFilterProxy.new(f)
571
+ else
572
+ f
573
+ end
574
+ end
575
+ end
576
+
577
+ # The determination of the filter type was once done at run time.
578
+ # This method is here to extract as much logic from the filter run time as possible
579
+ def class_for_filter(filter, filter_type) #:nodoc:
580
+ case
581
+ when filter.is_a?(Symbol)
582
+ SymbolFilter
583
+ when filter.respond_to?(:call)
584
+ if filter.is_a?(Method)
585
+ MethodFilter
586
+ elsif filter.arity == 1
587
+ ProcFilter
588
+ else
589
+ ProcWithCallFilter
590
+ end
591
+ when filter.respond_to?(:filter)
592
+ ClassFilter
593
+ when filter.respond_to?(:before) && filter_type == :before
594
+ ClassBeforeFilter
595
+ when filter.respond_to?(:after) && filter_type == :after
596
+ ClassAfterFilter
597
+ else
598
+ raise(Reactive::Controller::Error, 'A filter must be a Symbol, Proc, Method, or object responding to filter, after or before.')
599
+ end
600
+ end
601
+
602
+ def extract_conditions(*filters, &block) #:nodoc:
603
+ filters.flatten!
604
+ conditions = filters.extract_options!
605
+ filters << block if block_given?
606
+ return filters, conditions
607
+ end
608
+
609
+ def update_conditions(filters, conditions)
610
+ return if conditions.empty?
611
+ if conditions[:only]
612
+ write_inheritable_hash('included_actions', condition_hash(filters, conditions[:only]))
613
+ elsif conditions[:except]
614
+ write_inheritable_hash('excluded_actions', condition_hash(filters, conditions[:except]))
615
+ end
616
+ end
617
+
618
+ def condition_hash(filters, *actions)
619
+ actions = actions.flatten.map(&:to_s)
620
+ filters.inject({}) { |h,f| h.update( f => (actions.blank? ? nil : actions)) }
621
+ end
622
+
623
+ def skip_filter_in_chain(*filters, &test) #:nodoc:
624
+ filters, conditions = extract_conditions(filters)
625
+ filters.map! { |f| block_given? ? find_filter(f, &test) : find_filter(f) }
626
+ filters.compact!
627
+
628
+ if conditions.empty?
629
+ delete_filters_in_chain(filters)
630
+ else
631
+ remove_actions_from_included_actions!(filters,conditions[:only] || [])
632
+ conditions[:only], conditions[:except] = conditions[:except], conditions[:only]
633
+ update_conditions(filters,conditions)
634
+ end
635
+ end
636
+
637
+ def remove_actions_from_included_actions!(filters,*actions)
638
+ actions = actions.flatten.map(&:to_s)
639
+ updated_hash = filters.inject(read_inheritable_attribute('included_actions')||{}) do |hash,filter|
640
+ ia = (hash[filter] || []) - actions
641
+ ia.empty? ? hash.delete(filter) : hash[filter] = ia
642
+ hash
643
+ end
644
+ write_inheritable_attribute('included_actions', updated_hash)
645
+ end
646
+
647
+ def delete_filters_in_chain(filters) #:nodoc:
648
+ write_inheritable_attribute('filter_chain', filter_chain.reject { |f| filters.include?(f) })
649
+ end
650
+
651
+ def filter_responds_to_before_and_after(filter) #:nodoc:
652
+ filter.respond_to?(:before) && filter.respond_to?(:after)
653
+ end
654
+
655
+ def proxy_before_and_after_filter(filter) #:nodoc:
656
+ return filter unless filter_responds_to_before_and_after(filter)
657
+ Proc.new do |controller, action|
658
+ filter.before(controller)
659
+
660
+ if controller.send!(:performed?)
661
+ controller.send!(:halt_filter_chain, filter, :rendered_or_redirected)
662
+ else
663
+ begin
664
+ action.call
665
+ ensure
666
+ filter.after(controller)
667
+ end
668
+ end
669
+ end
670
+ end
671
+ end
672
+
673
+ module InstanceMethods # :nodoc:
674
+ def self.included(base)
675
+ base.class_eval do
676
+ alias_method_chain :perform_action, :filters
677
+ alias_method_chain :process, :filters
678
+ end
679
+ end
680
+
681
+ protected
682
+
683
+ def process_with_filters(request, response, method = :perform_action, *arguments) #:nodoc:
684
+ @before_filter_chain_aborted = false
685
+ process_without_filters(request, response, method, *arguments)
686
+ end
687
+
688
+ def perform_action_with_filters
689
+ call_filters(self.class.filter_chain, 0, 0)
690
+ end
691
+
692
+ private
693
+
694
+ def call_filters(chain, index, nesting)
695
+ index = run_before_filters(chain, index, nesting)
696
+ aborted = @before_filter_chain_aborted
697
+ perform_action_without_filters unless performed? || aborted
698
+ return index if nesting != 0 || aborted
699
+ run_after_filters(chain, index)
700
+ end
701
+
702
+ def skip_excluded_filters(chain, index)
703
+ while (filter = chain[index]) && self.class.filter_excluded_from_action?(filter, action_name)
704
+ index = index.next
705
+ end
706
+ [filter, index]
707
+ end
708
+
709
+ def run_before_filters(chain, index, nesting)
710
+ while chain[index]
711
+ filter, index = skip_excluded_filters(chain, index)
712
+ break unless filter # end of call chain reached
713
+
714
+ case filter.type
715
+ when :before
716
+ filter.run(self) # invoke before filter
717
+ index = index.next
718
+ break if @before_filter_chain_aborted
719
+ when :around
720
+ yielded = false
721
+
722
+ filter.call(self) do
723
+ yielded = true
724
+ # all remaining before and around filters will be run in this call
725
+ index = call_filters(chain, index.next, nesting.next)
726
+ end
727
+
728
+ halt_filter_chain(filter, :did_not_yield) unless yielded
729
+
730
+ break
731
+ else
732
+ break # no before or around filters left
733
+ end
734
+ end
735
+
736
+ index
737
+ end
738
+
739
+ def run_after_filters(chain, index)
740
+ seen_after_filter = false
741
+
742
+ while chain[index]
743
+ filter, index = skip_excluded_filters(chain, index)
744
+ break unless filter # end of call chain reached
745
+
746
+ case filter.type
747
+ when :after
748
+ seen_after_filter = true
749
+ filter.run(self) # invoke after filter
750
+ else
751
+ # implementation error or someone has mucked with the filter chain
752
+ raise Reactive::Controller::Error, "filter #{filter.inspect} was in the wrong place!" if seen_after_filter
753
+ end
754
+
755
+ index = index.next
756
+ end
757
+
758
+ index.next
759
+ end
760
+
761
+ def halt_filter_chain(filter, reason)
762
+ @before_filter_chain_aborted = true
763
+ logger.info "Filter chain halted as [#{filter.inspect}] #{reason}." if logger
764
+ end
765
+ end
766
+ end
767
+ end