actionpack 0.9.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of actionpack might be problematic. Click here for more details.

Files changed (100) hide show
  1. data/CHANGELOG +604 -0
  2. data/MIT-LICENSE +21 -0
  3. data/README +418 -0
  4. data/RUNNING_UNIT_TESTS +14 -0
  5. data/examples/.htaccess +24 -0
  6. data/examples/address_book/index.rhtml +33 -0
  7. data/examples/address_book/layout.rhtml +8 -0
  8. data/examples/address_book_controller.cgi +9 -0
  9. data/examples/address_book_controller.fcgi +6 -0
  10. data/examples/address_book_controller.rb +52 -0
  11. data/examples/address_book_controller.rbx +4 -0
  12. data/examples/benchmark.rb +52 -0
  13. data/examples/benchmark_with_ar.fcgi +89 -0
  14. data/examples/blog_controller.cgi +53 -0
  15. data/examples/debate/index.rhtml +14 -0
  16. data/examples/debate/new_topic.rhtml +22 -0
  17. data/examples/debate/topic.rhtml +32 -0
  18. data/examples/debate_controller.cgi +57 -0
  19. data/install.rb +93 -0
  20. data/lib/action_controller.rb +47 -0
  21. data/lib/action_controller/assertions/action_pack_assertions.rb +166 -0
  22. data/lib/action_controller/assertions/active_record_assertions.rb +65 -0
  23. data/lib/action_controller/base.rb +626 -0
  24. data/lib/action_controller/benchmarking.rb +49 -0
  25. data/lib/action_controller/cgi_ext/cgi_ext.rb +43 -0
  26. data/lib/action_controller/cgi_ext/cgi_methods.rb +91 -0
  27. data/lib/action_controller/cgi_process.rb +123 -0
  28. data/lib/action_controller/filters.rb +279 -0
  29. data/lib/action_controller/flash.rb +65 -0
  30. data/lib/action_controller/layout.rb +143 -0
  31. data/lib/action_controller/request.rb +92 -0
  32. data/lib/action_controller/rescue.rb +94 -0
  33. data/lib/action_controller/response.rb +15 -0
  34. data/lib/action_controller/scaffolding.rb +183 -0
  35. data/lib/action_controller/session/active_record_store.rb +72 -0
  36. data/lib/action_controller/session/drb_server.rb +9 -0
  37. data/lib/action_controller/session/drb_store.rb +31 -0
  38. data/lib/action_controller/support/class_attribute_accessors.rb +57 -0
  39. data/lib/action_controller/support/class_inheritable_attributes.rb +37 -0
  40. data/lib/action_controller/support/clean_logger.rb +10 -0
  41. data/lib/action_controller/support/cookie_performance_fix.rb +121 -0
  42. data/lib/action_controller/support/inflector.rb +70 -0
  43. data/lib/action_controller/templates/rescues/_request_and_response.rhtml +28 -0
  44. data/lib/action_controller/templates/rescues/diagnostics.rhtml +22 -0
  45. data/lib/action_controller/templates/rescues/layout.rhtml +29 -0
  46. data/lib/action_controller/templates/rescues/missing_template.rhtml +2 -0
  47. data/lib/action_controller/templates/rescues/template_error.rhtml +26 -0
  48. data/lib/action_controller/templates/rescues/unknown_action.rhtml +2 -0
  49. data/lib/action_controller/templates/scaffolds/edit.rhtml +6 -0
  50. data/lib/action_controller/templates/scaffolds/layout.rhtml +29 -0
  51. data/lib/action_controller/templates/scaffolds/list.rhtml +24 -0
  52. data/lib/action_controller/templates/scaffolds/new.rhtml +5 -0
  53. data/lib/action_controller/templates/scaffolds/show.rhtml +9 -0
  54. data/lib/action_controller/test_process.rb +194 -0
  55. data/lib/action_controller/url_rewriter.rb +153 -0
  56. data/lib/action_view.rb +40 -0
  57. data/lib/action_view/base.rb +253 -0
  58. data/lib/action_view/helpers/active_record_helper.rb +171 -0
  59. data/lib/action_view/helpers/date_helper.rb +223 -0
  60. data/lib/action_view/helpers/debug_helper.rb +17 -0
  61. data/lib/action_view/helpers/form_helper.rb +176 -0
  62. data/lib/action_view/helpers/form_options_helper.rb +169 -0
  63. data/lib/action_view/helpers/tag_helper.rb +59 -0
  64. data/lib/action_view/helpers/text_helper.rb +129 -0
  65. data/lib/action_view/helpers/url_helper.rb +72 -0
  66. data/lib/action_view/partials.rb +61 -0
  67. data/lib/action_view/template_error.rb +84 -0
  68. data/lib/action_view/vendor/builder.rb +13 -0
  69. data/lib/action_view/vendor/builder/blankslate.rb +21 -0
  70. data/lib/action_view/vendor/builder/xmlbase.rb +143 -0
  71. data/lib/action_view/vendor/builder/xmlevents.rb +63 -0
  72. data/lib/action_view/vendor/builder/xmlmarkup.rb +288 -0
  73. data/rakefile +105 -0
  74. data/test/abstract_unit.rb +9 -0
  75. data/test/controller/action_pack_assertions_test.rb +295 -0
  76. data/test/controller/active_record_assertions_test.rb +118 -0
  77. data/test/controller/cgi_test.rb +142 -0
  78. data/test/controller/cookie_test.rb +38 -0
  79. data/test/controller/filters_test.rb +159 -0
  80. data/test/controller/flash_test.rb +69 -0
  81. data/test/controller/layout_test.rb +49 -0
  82. data/test/controller/redirect_test.rb +44 -0
  83. data/test/controller/render_test.rb +169 -0
  84. data/test/controller/url_test.rb +318 -0
  85. data/test/fixtures/layouts/builder.rxml +3 -0
  86. data/test/fixtures/layouts/standard.rhtml +1 -0
  87. data/test/fixtures/test/_customer.rhtml +1 -0
  88. data/test/fixtures/test/greeting.rhtml +1 -0
  89. data/test/fixtures/test/hello.rxml +4 -0
  90. data/test/fixtures/test/hello_world.rhtml +1 -0
  91. data/test/fixtures/test/hello_xml_world.rxml +11 -0
  92. data/test/fixtures/test/list.rhtml +1 -0
  93. data/test/template/active_record_helper_test.rb +76 -0
  94. data/test/template/date_helper_test.rb +103 -0
  95. data/test/template/form_helper_test.rb +115 -0
  96. data/test/template/form_options_helper_test.rb +174 -0
  97. data/test/template/tag_helper_test.rb +18 -0
  98. data/test/template/text_helper_test.rb +62 -0
  99. data/test/template/url_helper_test.rb +35 -0
  100. metadata +154 -0
@@ -0,0 +1,49 @@
1
+ require 'benchmark'
2
+
3
+ module ActionController #:nodoc:
4
+ # The benchmarking module times the performance of actions and reports to the logger. If the Active Record
5
+ # package has been included, a separate timing section for database calls will be added as well.
6
+ module Benchmarking #:nodoc:
7
+ def self.append_features(base)
8
+ super
9
+ base.class_eval {
10
+ alias_method :perform_action_without_benchmark, :perform_action
11
+ alias_method :perform_action, :perform_action_with_benchmark
12
+
13
+ alias_method :render_without_benchmark, :render
14
+ alias_method :render, :render_with_benchmark
15
+ }
16
+ end
17
+
18
+ def render_with_benchmark(template_name = "#{controller_name}/#{action_name}", status = "200 OK")
19
+ if logger.nil?
20
+ render_without_benchmark(template_name, status)
21
+ else
22
+ @rendering_runtime = Benchmark::measure{ render_without_benchmark(template_name, status) }.real
23
+ end
24
+ end
25
+
26
+ def perform_action_with_benchmark
27
+ if logger.nil?
28
+ perform_action_without_benchmark
29
+ else
30
+ runtime = [Benchmark::measure{ perform_action_without_benchmark }.real, 0.0001].max
31
+ log_message = "Completed in #{sprintf("%4f", runtime)} (#{(1 / runtime).floor} reqs/sec)"
32
+ log_message << rendering_runtime(runtime) if @rendering_runtime
33
+ log_message << active_record_runtime(runtime) if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
34
+ logger.info(log_message)
35
+ end
36
+ end
37
+
38
+ private
39
+ def rendering_runtime(runtime)
40
+ " | Rendering: #{sprintf("%f", @rendering_runtime)} (#{sprintf("%d", (@rendering_runtime / runtime) * 100)}%)"
41
+ end
42
+
43
+ def active_record_runtime(runtime)
44
+ db_runtime = ActiveRecord::Base.connection.reset_runtime
45
+ db_percentage = (db_runtime / runtime) * 100
46
+ " | DB: #{sprintf("%f", db_runtime)} (#{sprintf("%d", db_percentage)}%)"
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,43 @@
1
+ require 'cgi'
2
+ require 'cgi/session'
3
+ require 'cgi/session/pstore'
4
+ require 'action_controller/cgi_ext/cgi_methods'
5
+
6
+ # Wrapper around the CGIMethods that have been secluded to allow testing without
7
+ # an instatiated CGI object
8
+ class CGI #:nodoc:
9
+ class << self
10
+ alias :escapeHTML_fail_on_nil :escapeHTML
11
+
12
+ def escapeHTML(string)
13
+ escapeHTML_fail_on_nil(string) unless string.nil?
14
+ end
15
+ end
16
+
17
+ # Returns a parameter hash including values from both the request (POST/GET)
18
+ # and the query string with the latter taking precedence.
19
+ def parameters
20
+ request_parameters.update(query_parameters)
21
+ end
22
+
23
+ def query_parameters
24
+ CGIMethods.parse_query_parameters(query_string)
25
+ end
26
+
27
+ def request_parameters
28
+ CGIMethods.parse_request_parameters(params)
29
+ end
30
+
31
+ def redirect(where)
32
+ header({
33
+ "Status" => "302 Moved",
34
+ "location" => "#{where}"
35
+ })
36
+ end
37
+
38
+ def session(parameters = nil)
39
+ parameters = {} if parameters.nil?
40
+ parameters['database_manager'] = CGI::Session::PStore
41
+ CGI::Session.new(self, parameters)
42
+ end
43
+ end
@@ -0,0 +1,91 @@
1
+ require 'cgi'
2
+
3
+ # Static methods for parsing the query and request parameters that can be used in
4
+ # a CGI extension class or testing in isolation.
5
+ class CGIMethods #:nodoc:
6
+ public
7
+ # Returns a hash with the pairs from the query string. The implicit hash construction that is done in
8
+ # parse_request_params is not done here.
9
+ def CGIMethods.parse_query_parameters(query_string)
10
+ parsed_params = {}
11
+
12
+ query_string.split(/[&;]/).each { |p|
13
+ k, v = p.split('=')
14
+
15
+ k = CGI.unescape(k) unless k.nil?
16
+ v = CGI.unescape(v) unless v.nil?
17
+
18
+ if k =~ /(.*)\[\]$/
19
+ if parsed_params.has_key? $1
20
+ parsed_params[$1] << v
21
+ else
22
+ parsed_params[$1] = [v]
23
+ end
24
+ else
25
+ parsed_params[k] = v.nil? ? nil : v
26
+ end
27
+ }
28
+
29
+ return parsed_params
30
+ end
31
+
32
+ # Returns the request (POST/GET) parameters in a parsed form where pairs such as "customer[address][street]" /
33
+ # "Somewhere cool!" are translated into a full hash hierarchy, like
34
+ # { "customer" => { "address" => { "street" => "Somewhere cool!" } } }
35
+ def CGIMethods.parse_request_parameters(params)
36
+ parsed_params = {}
37
+
38
+ for key, value in params
39
+ value = [value] if key =~ /.*\[\]$/
40
+ CGIMethods.build_deep_hash(
41
+ CGIMethods.get_typed_value(value[0]),
42
+ parsed_params,
43
+ CGIMethods.get_levels(key)
44
+ )
45
+ end
46
+
47
+ return parsed_params
48
+ end
49
+
50
+ private
51
+ def CGIMethods.get_typed_value(value)
52
+ if value.respond_to?(:content_type) && !value.content_type.empty?
53
+ # Uploaded file
54
+ value
55
+ elsif value.respond_to?(:read)
56
+ # Value as part of a multipart request
57
+ value.read
58
+ elsif value.class == Array
59
+ value
60
+ else
61
+ # Standard value (not a multipart request)
62
+ value.to_s
63
+ end
64
+ end
65
+
66
+ def CGIMethods.get_levels(key_string)
67
+ return [] if key_string.nil? or key_string.empty?
68
+
69
+ levels = []
70
+ main, existance = /(\w+)(\[)?.?/.match(key_string).captures
71
+ levels << main
72
+
73
+ unless existance.nil?
74
+ hash_part = key_string.sub(/\w+\[/, "")
75
+ hash_part.slice!(-1, 1)
76
+ levels += hash_part.split(/\]\[/)
77
+ end
78
+
79
+ levels
80
+ end
81
+
82
+ def CGIMethods.build_deep_hash(value, hash, levels)
83
+ if levels.length == 0
84
+ value;
85
+ elsif hash.nil?
86
+ { levels.first => CGIMethods.build_deep_hash(value, nil, levels[1..-1]) }
87
+ else
88
+ hash.update({ levels.first => CGIMethods.build_deep_hash(value, hash[levels.first], levels[1..-1]) })
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,123 @@
1
+ require 'action_controller/cgi_ext/cgi_ext'
2
+ require 'action_controller/support/cookie_performance_fix'
3
+ require 'action_controller/session/drb_store'
4
+ require 'action_controller/session/active_record_store'
5
+
6
+ module ActionController #:nodoc:
7
+ class Base
8
+ # Process a request extracted from an CGI object and return a response. Pass false as <tt>session_options</tt> to disable
9
+ # sessions (large performance increase if sessions are not needed). The <tt>session_options</tt> are the same as for CGI::Session:
10
+ #
11
+ # * <tt>:database_manager</tt> - standard options are CGI::Session::FileStore, CGI::Session::MemoryStore, and CGI::Session::PStore
12
+ # (default). Additionally, there is CGI::Session::DRbStore and CGI::Session::ActiveRecordStore. Read more about these in
13
+ # lib/action_controller/session.
14
+ # * <tt>:session_key</tt> - the parameter name used for the session id. Defaults to '_session_id'.
15
+ # * <tt>:session_id</tt> - the session id to use. If not provided, then it is retrieved from the +session_key+ parameter
16
+ # of the request, or automatically generated for a new session.
17
+ # * <tt>:new_session</tt> - if true, force creation of a new session. If not set, a new session is only created if none currently
18
+ # exists. If false, a new session is never created, and if none currently exists and the +session_id+ option is not set,
19
+ # an ArgumentError is raised.
20
+ # * <tt>:session_expires</tt> - the time the current session expires, as a +Time+ object. If not set, the session will continue
21
+ # indefinitely.
22
+ # * <tt>:session_domain</tt> - the hostname domain for which this session is valid. If not set, defaults to the hostname of the
23
+ # server.
24
+ # * <tt>:session_secure</tt> - if +true+, this session will only work over HTTPS.
25
+ # * <tt>:session_path</tt> - the path for which this session applies. Defaults to the directory of the CGI script.
26
+ def self.process_cgi(cgi = CGI.new, session_options = {})
27
+ new.process_cgi(cgi, session_options)
28
+ end
29
+
30
+ def process_cgi(cgi, session_options = {}) #:nodoc:
31
+ process(CgiRequest.new(cgi, session_options), CgiResponse.new(cgi)).out
32
+ end
33
+ end
34
+
35
+ class CgiRequest < AbstractRequest #:nodoc:
36
+ attr_accessor :cgi
37
+
38
+ DEFAULT_SESSION_OPTIONS =
39
+ { "database_manager" => CGI::Session::PStore, "prefix" => "ruby_sess.", "session_path" => "/" }
40
+
41
+ def initialize(cgi, session_options = {})
42
+ @cgi = cgi
43
+ @session_options = session_options
44
+ super()
45
+ end
46
+
47
+ def query_parameters
48
+ @cgi.query_string ? CGIMethods.parse_query_parameters(@cgi.query_string) : {}
49
+ end
50
+
51
+ def request_parameters
52
+ CGIMethods.parse_request_parameters(@cgi.params)
53
+ end
54
+
55
+ def env
56
+ @cgi.send(:env_table)
57
+ end
58
+
59
+ def cookies
60
+ @cgi.cookies.freeze
61
+ end
62
+
63
+ def host
64
+ env["HTTP_X_FORWARDED_HOST"] || @cgi.host.split(":").first
65
+ end
66
+
67
+ def session
68
+ return @session unless @session.nil?
69
+ begin
70
+ @session = (@session_options == false ? {} : CGI::Session.new(@cgi, DEFAULT_SESSION_OPTIONS.merge(@session_options)))
71
+ @session["__valid_session"]
72
+ return @session
73
+ rescue ArgumentError => e
74
+ @session.delete
75
+ raise(
76
+ ActionController::SessionRestoreError,
77
+ "Session contained objects where the class definition wasn't available. " +
78
+ "Remember to require classes for all objects kept in the session. " +
79
+ "The session has been deleted."
80
+ )
81
+ end
82
+ end
83
+
84
+ def reset_session
85
+ @session.delete
86
+ @session = (@session_options == false ? {} : new_session)
87
+ end
88
+
89
+ def method_missing(method_id, *arguments)
90
+ @cgi.send(method_id, *arguments) rescue super
91
+ end
92
+
93
+ private
94
+ def new_session
95
+ CGI::Session.new(@cgi, DEFAULT_SESSION_OPTIONS.merge(@session_options).merge("new_session" => true))
96
+ end
97
+ end
98
+
99
+ class CgiResponse < AbstractResponse #:nodoc:
100
+ def initialize(cgi)
101
+ @cgi = cgi
102
+ super()
103
+ end
104
+
105
+ def out
106
+ convert_content_type!(@headers)
107
+ print @cgi.header(@headers)
108
+ if @body.respond_to?(:call)
109
+ @body.call(self)
110
+ else
111
+ print @body
112
+ end
113
+ end
114
+
115
+ private
116
+ def convert_content_type!(headers)
117
+ if headers["Content-Type"]
118
+ headers["type"] = headers["Content-Type"]
119
+ headers.delete "Content-Type"
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,279 @@
1
+ module ActionController #:nodoc:
2
+ module Filters #:nodoc:
3
+ def self.append_features(base)
4
+ super
5
+ base.extend(ClassMethods)
6
+ base.class_eval { include ActionController::Filters::InstanceMethods }
7
+ end
8
+
9
+ # Filters enable controllers to run shared pre and post processing code for its actions. These filters can be used to do
10
+ # authentication, caching, or auditing before the intended action is performed. Or to do localization or output
11
+ # compression after the action has been performed.
12
+ #
13
+ # Filters have access to the request, response, and all the instance variables set by other filters in the chain
14
+ # or by the action (in the case of after filters). Additionally, it's possible for a pre-processing <tt>before_filter</tt>
15
+ # to halt the processing before the intended action is processed by returning false. This is especially useful for
16
+ # filters like authentication where you're not interested in allowing the action to be performed if the proper
17
+ # credentials are not in order.
18
+ #
19
+ # == Filter inheritance
20
+ #
21
+ # Controller inheritance hierarchies share filters downwards, but subclasses can also add new filters without
22
+ # affecting the superclass. For example:
23
+ #
24
+ # class BankController < ActionController::Base
25
+ # before_filter :audit
26
+ #
27
+ # private
28
+ # def audit
29
+ # # record the action and parameters in an audit log
30
+ # end
31
+ # end
32
+ #
33
+ # class VaultController < BankController
34
+ # before_filter :verify_credentials
35
+ #
36
+ # private
37
+ # def verify_credentials
38
+ # # make sure the user is allowed into the vault
39
+ # end
40
+ # end
41
+ #
42
+ # Now any actions performed on the BankController will have the audit method called before. On the VaultController,
43
+ # first the audit method is called, then the verify_credentials method. If the audit method returns false, then
44
+ # verify_credentials and the intended action is never called.
45
+ #
46
+ # == Filter types
47
+ #
48
+ # A filter can take one of three forms: method reference (symbol), external class, or inline method (proc). The first
49
+ # is the most common and works by referencing a protected or private method somewhere in the inheritance hierarchy of
50
+ # the controller by use of a symbol. In the bank example above, both BankController and VaultController use this form.
51
+ #
52
+ # Using an external class makes for more easily reused generic filters, such as output compression. External filter classes
53
+ # are implemented by having a static +filter+ method on any class and then passing this class to the filter method. Example:
54
+ #
55
+ # class OutputCompressionFilter
56
+ # def self.filter(controller)
57
+ # controller.response.body = compress(controller.response.body)
58
+ # end
59
+ # end
60
+ #
61
+ # class NewspaperController < ActionController::Base
62
+ # after_filter OutputCompressionFilter
63
+ # end
64
+ #
65
+ # The filter method is passed the controller instance and is hence granted access to all aspects of the controller and can
66
+ # manipulate them as it sees fit.
67
+ #
68
+ # The inline method (using a proc) can be used to quickly do something small that doesn't require a lot of explanation.
69
+ # Or just as a quick test. It works like this:
70
+ #
71
+ # class WeblogController < ActionController::Base
72
+ # before_filter { |controller| return false if controller.params["stop_action"] }
73
+ # end
74
+ #
75
+ # As you can see, the block expects to be passed the controller after it has assigned the request to the internal variables.
76
+ # This means that the block has access to both the request and response objects complete with convenience methods for params,
77
+ # session, template, and assigns. Note: The inline method doesn't strictly has to be a block. Any object that responds to call
78
+ # and returns 1 or -1 on arity will do (such as a Proc or an Method object).
79
+ #
80
+ # == Filter chain ordering
81
+ #
82
+ # Using <tt>before_filter</tt> and <tt>after_filter</tt> appends the specified filters to the existing chain. That's usually
83
+ # just fine, but some times you care more about the order in which the filters are executed. When that's the case, you
84
+ # can use <tt>prepend_before_filter</tt> and <tt>prepend_after_filter</tt>. Filters added by these methods will be put at the
85
+ # beginning of their respective chain and executed before the rest. For example:
86
+ #
87
+ # class ShoppingController
88
+ # before_filter :verify_open_shop
89
+ #
90
+ # class CheckoutController
91
+ # prepend_before_filter :ensure_items_in_cart, :ensure_items_in_stock
92
+ #
93
+ # The filter chain for the CheckoutController is now <tt>:ensure_items_in_cart, :ensure_items_in_stock,</tt>
94
+ # <tt>:verify_open_shop</tt>. So if either of the ensure filters return false, we'll never get around to see if the shop
95
+ # is open or not.
96
+ #
97
+ # You may pass multiple filter arguments of each type as well as a filter block.
98
+ # If a block is given, it is treated as the last argument.
99
+ #
100
+ # == Around filters
101
+ #
102
+ # In addition to the individual before and after filters, it's also possible to specify that a single object should handle
103
+ # both the before and after call. That's especially usefuly when you need to keep state active between the before and after,
104
+ # such as the example of a benchmark filter below:
105
+ #
106
+ # class WeblogController < ActionController::Base
107
+ # around_filter BenchmarkingFilter.new
108
+ #
109
+ # # Before this action is performed, BenchmarkingFilter#before(controller) is executed
110
+ # def index
111
+ # end
112
+ # # After this action has been performed, BenchmarkingFilter#after(controller) is executed
113
+ # end
114
+ #
115
+ # class BenchmarkingFilter
116
+ # def initialize
117
+ # @runtime
118
+ # end
119
+ #
120
+ # def before
121
+ # start_timer
122
+ # end
123
+ #
124
+ # def after
125
+ # stop_timer
126
+ # report_result
127
+ # end
128
+ # end
129
+ module ClassMethods
130
+ # The passed <tt>filters</tt> will be appended to the array of filters that's run _before_ actions
131
+ # on this controller are performed.
132
+ def append_before_filter(*filters, &block)
133
+ filters << block if block_given?
134
+ append_filter_to_chain("before", filters)
135
+ end
136
+
137
+ # The passed <tt>filters</tt> will be prepended to the array of filters that's run _before_ actions
138
+ # on this controller are performed.
139
+ def prepend_before_filter(*filters, &block)
140
+ filters << block if block_given?
141
+ prepend_filter_to_chain("before", filters)
142
+ end
143
+
144
+ # Short-hand for append_before_filter since that's the most common of the two.
145
+ alias :before_filter :append_before_filter
146
+
147
+ # The passed <tt>filters</tt> will be appended to the array of filters that's run _after_ actions
148
+ # on this controller are performed.
149
+ def append_after_filter(*filters, &block)
150
+ filters << block if block_given?
151
+ append_filter_to_chain("after", filters)
152
+ end
153
+
154
+ # The passed <tt>filters</tt> will be prepended to the array of filters that's run _after_ actions
155
+ # on this controller are performed.
156
+ def prepend_after_filter(*filters, &block)
157
+ filters << block if block_given?
158
+ prepend_filter_to_chain("after", filters)
159
+ end
160
+
161
+ # Short-hand for append_after_filter since that's the most common of the two.
162
+ alias :after_filter :append_after_filter
163
+
164
+ # The passed <tt>filters</tt> will have their +before+ method appended to the array of filters that's run both before actions
165
+ # on this controller are performed and have their +after+ method prepended to the after actions. The filter objects must all
166
+ # respond to both +before+ and +after+. So if you do append_around_filter A.new, B.new, the callstack will look like:
167
+ #
168
+ # B#before
169
+ # A#before
170
+ # A#after
171
+ # B#after
172
+ def append_around_filter(filters)
173
+ for filter in [filters].flatten
174
+ ensure_filter_responds_to_before_and_after(filter)
175
+ append_before_filter { |c| filter.before(c) }
176
+ prepend_after_filter { |c| filter.after(c) }
177
+ end
178
+ end
179
+
180
+ # The passed <tt>filters</tt> will have their +before+ method prepended to the array of filters that's run both before actions
181
+ # on this controller are performed and have their +after+ method appended to the after actions. The filter objects must all
182
+ # respond to both +before+ and +after+. So if you do prepend_around_filter A.new, B.new, the callstack will look like:
183
+ #
184
+ # A#before
185
+ # B#before
186
+ # B#after
187
+ # A#after
188
+ def prepend_around_filter(filters)
189
+ for filter in [filters].flatten
190
+ ensure_filter_responds_to_before_and_after(filter)
191
+ prepend_before_filter { |c| filter.before(c) }
192
+ append_after_filter { |c| filter.after(c) }
193
+ end
194
+ end
195
+
196
+ # Short-hand for append_around_filter since that's the most common of the two.
197
+ alias :around_filter :append_around_filter
198
+
199
+ # Returns all the before filters for this class and all its ancestors.
200
+ def before_filters #:nodoc:
201
+ read_inheritable_attribute("before_filters")
202
+ end
203
+
204
+ # Returns all the after filters for this class and all its ancestors.
205
+ def after_filters #:nodoc:
206
+ read_inheritable_attribute("after_filters")
207
+ end
208
+
209
+ private
210
+ def append_filter_to_chain(condition, filters)
211
+ write_inheritable_array("#{condition}_filters", filters)
212
+ end
213
+
214
+ def prepend_filter_to_chain(condition, filters)
215
+ write_inheritable_attribute("#{condition}_filters", filters + read_inheritable_attribute("#{condition}_filters"))
216
+ end
217
+
218
+ def ensure_filter_responds_to_before_and_after(filter)
219
+ unless filter.respond_to?(:before) && filter.respond_to?(:after)
220
+ raise ActionControllerError, "Filter object must respond to both before and after"
221
+ end
222
+ end
223
+ end
224
+
225
+ module InstanceMethods # :nodoc:
226
+ def self.append_features(base)
227
+ super
228
+ base.class_eval {
229
+ alias_method :perform_action_without_filters, :perform_action
230
+ alias_method :perform_action, :perform_action_with_filters
231
+ }
232
+ end
233
+
234
+ def perform_action_with_filters
235
+ return if before_action == false
236
+ perform_action_without_filters
237
+ after_action
238
+ end
239
+
240
+ # Calls all the defined before-filter filters, which are added by using "before_filter :method".
241
+ # If any of the filters return false, no more filters will be executed and the action is aborted.
242
+ def before_action #:doc:
243
+ call_filters(self.class.before_filters)
244
+ end
245
+
246
+ # Calls all the defined after-filter filters, which are added by using "after_filter :method".
247
+ # If any of the filters return false, no more filters will be executed.
248
+ def after_action #:doc:
249
+ call_filters(self.class.after_filters)
250
+ end
251
+
252
+ private
253
+ def call_filters(filters)
254
+ filters.each do |filter|
255
+ if Symbol === filter
256
+ if self.send(filter) == false then return false end
257
+ elsif filter_block?(filter)
258
+ if filter.call(self) == false then return false end
259
+ elsif filter_class?(filter)
260
+ if filter.filter(self) == false then return false end
261
+ else
262
+ raise(
263
+ ActionControllerError,
264
+ "Filters need to be either a symbol, proc/method, or class implementing a static filter method"
265
+ )
266
+ end
267
+ end
268
+ end
269
+
270
+ def filter_block?(filter)
271
+ filter.respond_to?("call") && (filter.arity == 1 || filter.arity == -1)
272
+ end
273
+
274
+ def filter_class?(filter)
275
+ filter.respond_to?("filter")
276
+ end
277
+ end
278
+ end
279
+ end