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.
- data/CHANGELOG +604 -0
- data/MIT-LICENSE +21 -0
- data/README +418 -0
- data/RUNNING_UNIT_TESTS +14 -0
- data/examples/.htaccess +24 -0
- data/examples/address_book/index.rhtml +33 -0
- data/examples/address_book/layout.rhtml +8 -0
- data/examples/address_book_controller.cgi +9 -0
- data/examples/address_book_controller.fcgi +6 -0
- data/examples/address_book_controller.rb +52 -0
- data/examples/address_book_controller.rbx +4 -0
- data/examples/benchmark.rb +52 -0
- data/examples/benchmark_with_ar.fcgi +89 -0
- data/examples/blog_controller.cgi +53 -0
- data/examples/debate/index.rhtml +14 -0
- data/examples/debate/new_topic.rhtml +22 -0
- data/examples/debate/topic.rhtml +32 -0
- data/examples/debate_controller.cgi +57 -0
- data/install.rb +93 -0
- data/lib/action_controller.rb +47 -0
- data/lib/action_controller/assertions/action_pack_assertions.rb +166 -0
- data/lib/action_controller/assertions/active_record_assertions.rb +65 -0
- data/lib/action_controller/base.rb +626 -0
- data/lib/action_controller/benchmarking.rb +49 -0
- data/lib/action_controller/cgi_ext/cgi_ext.rb +43 -0
- data/lib/action_controller/cgi_ext/cgi_methods.rb +91 -0
- data/lib/action_controller/cgi_process.rb +123 -0
- data/lib/action_controller/filters.rb +279 -0
- data/lib/action_controller/flash.rb +65 -0
- data/lib/action_controller/layout.rb +143 -0
- data/lib/action_controller/request.rb +92 -0
- data/lib/action_controller/rescue.rb +94 -0
- data/lib/action_controller/response.rb +15 -0
- data/lib/action_controller/scaffolding.rb +183 -0
- data/lib/action_controller/session/active_record_store.rb +72 -0
- data/lib/action_controller/session/drb_server.rb +9 -0
- data/lib/action_controller/session/drb_store.rb +31 -0
- data/lib/action_controller/support/class_attribute_accessors.rb +57 -0
- data/lib/action_controller/support/class_inheritable_attributes.rb +37 -0
- data/lib/action_controller/support/clean_logger.rb +10 -0
- data/lib/action_controller/support/cookie_performance_fix.rb +121 -0
- data/lib/action_controller/support/inflector.rb +70 -0
- data/lib/action_controller/templates/rescues/_request_and_response.rhtml +28 -0
- data/lib/action_controller/templates/rescues/diagnostics.rhtml +22 -0
- data/lib/action_controller/templates/rescues/layout.rhtml +29 -0
- data/lib/action_controller/templates/rescues/missing_template.rhtml +2 -0
- data/lib/action_controller/templates/rescues/template_error.rhtml +26 -0
- data/lib/action_controller/templates/rescues/unknown_action.rhtml +2 -0
- data/lib/action_controller/templates/scaffolds/edit.rhtml +6 -0
- data/lib/action_controller/templates/scaffolds/layout.rhtml +29 -0
- data/lib/action_controller/templates/scaffolds/list.rhtml +24 -0
- data/lib/action_controller/templates/scaffolds/new.rhtml +5 -0
- data/lib/action_controller/templates/scaffolds/show.rhtml +9 -0
- data/lib/action_controller/test_process.rb +194 -0
- data/lib/action_controller/url_rewriter.rb +153 -0
- data/lib/action_view.rb +40 -0
- data/lib/action_view/base.rb +253 -0
- data/lib/action_view/helpers/active_record_helper.rb +171 -0
- data/lib/action_view/helpers/date_helper.rb +223 -0
- data/lib/action_view/helpers/debug_helper.rb +17 -0
- data/lib/action_view/helpers/form_helper.rb +176 -0
- data/lib/action_view/helpers/form_options_helper.rb +169 -0
- data/lib/action_view/helpers/tag_helper.rb +59 -0
- data/lib/action_view/helpers/text_helper.rb +129 -0
- data/lib/action_view/helpers/url_helper.rb +72 -0
- data/lib/action_view/partials.rb +61 -0
- data/lib/action_view/template_error.rb +84 -0
- data/lib/action_view/vendor/builder.rb +13 -0
- data/lib/action_view/vendor/builder/blankslate.rb +21 -0
- data/lib/action_view/vendor/builder/xmlbase.rb +143 -0
- data/lib/action_view/vendor/builder/xmlevents.rb +63 -0
- data/lib/action_view/vendor/builder/xmlmarkup.rb +288 -0
- data/rakefile +105 -0
- data/test/abstract_unit.rb +9 -0
- data/test/controller/action_pack_assertions_test.rb +295 -0
- data/test/controller/active_record_assertions_test.rb +118 -0
- data/test/controller/cgi_test.rb +142 -0
- data/test/controller/cookie_test.rb +38 -0
- data/test/controller/filters_test.rb +159 -0
- data/test/controller/flash_test.rb +69 -0
- data/test/controller/layout_test.rb +49 -0
- data/test/controller/redirect_test.rb +44 -0
- data/test/controller/render_test.rb +169 -0
- data/test/controller/url_test.rb +318 -0
- data/test/fixtures/layouts/builder.rxml +3 -0
- data/test/fixtures/layouts/standard.rhtml +1 -0
- data/test/fixtures/test/_customer.rhtml +1 -0
- data/test/fixtures/test/greeting.rhtml +1 -0
- data/test/fixtures/test/hello.rxml +4 -0
- data/test/fixtures/test/hello_world.rhtml +1 -0
- data/test/fixtures/test/hello_xml_world.rxml +11 -0
- data/test/fixtures/test/list.rhtml +1 -0
- data/test/template/active_record_helper_test.rb +76 -0
- data/test/template/date_helper_test.rb +103 -0
- data/test/template/form_helper_test.rb +115 -0
- data/test/template/form_options_helper_test.rb +174 -0
- data/test/template/tag_helper_test.rb +18 -0
- data/test/template/text_helper_test.rb +62 -0
- data/test/template/url_helper_test.rb +35 -0
- 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
|