actionpack 0.9.5 → 1.0.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 (53) hide show
  1. data/CHANGELOG +177 -0
  2. data/README +0 -1
  3. data/install.rb +1 -0
  4. data/lib/action_controller.rb +6 -1
  5. data/lib/action_controller/assertions/active_record_assertions.rb +2 -2
  6. data/lib/action_controller/base.rb +53 -41
  7. data/lib/action_controller/benchmarking.rb +1 -1
  8. data/lib/action_controller/cgi_ext/cgi_methods.rb +14 -16
  9. data/lib/action_controller/cgi_process.rb +16 -6
  10. data/lib/action_controller/cookies.rb +70 -0
  11. data/lib/action_controller/dependencies.rb +106 -0
  12. data/lib/action_controller/helpers.rb +14 -3
  13. data/lib/action_controller/layout.rb +16 -2
  14. data/lib/action_controller/request.rb +17 -7
  15. data/lib/action_controller/rescue.rb +33 -3
  16. data/lib/action_controller/support/class_inheritable_attributes.rb +4 -0
  17. data/lib/action_controller/support/inflector.rb +14 -12
  18. data/lib/action_controller/support/misc.rb +6 -0
  19. data/lib/action_controller/templates/rescues/_request_and_response.rhtml +2 -2
  20. data/lib/action_controller/templates/rescues/diagnostics.rhtml +4 -6
  21. data/lib/action_controller/templates/rescues/template_error.rhtml +1 -9
  22. data/lib/action_controller/templates/scaffolds/edit.rhtml +2 -1
  23. data/lib/action_controller/templates/scaffolds/layout.rhtml +36 -0
  24. data/lib/action_controller/templates/scaffolds/new.rhtml +1 -0
  25. data/lib/action_controller/test_process.rb +27 -8
  26. data/lib/action_controller/url_rewriter.rb +15 -4
  27. data/lib/action_view/base.rb +4 -3
  28. data/lib/action_view/helpers/active_record_helper.rb +29 -15
  29. data/lib/action_view/helpers/date_helper.rb +6 -5
  30. data/lib/action_view/helpers/form_helper.rb +31 -4
  31. data/lib/action_view/helpers/form_options_helper.rb +13 -3
  32. data/lib/action_view/helpers/tag_helper.rb +14 -16
  33. data/lib/action_view/helpers/url_helper.rb +46 -1
  34. data/lib/action_view/partials.rb +8 -1
  35. data/lib/action_view/template_error.rb +10 -3
  36. data/lib/action_view/vendor/builder/blankslate.rb +33 -1
  37. data/lib/action_view/vendor/builder/xmlevents.rb +1 -1
  38. data/lib/action_view/vendor/builder/xmlmarkup.rb +1 -1
  39. data/rakefile +4 -13
  40. data/test/controller/action_pack_assertions_test.rb +39 -1
  41. data/test/controller/active_record_assertions_test.rb +6 -5
  42. data/test/controller/cgi_test.rb +33 -3
  43. data/test/controller/cookie_test.rb +43 -2
  44. data/test/controller/helper_test.rb +1 -1
  45. data/test/controller/render_test.rb +9 -0
  46. data/test/controller/url_test.rb +16 -0
  47. data/test/fixtures/scope/test/modgreet.rhtml +1 -0
  48. data/test/template/active_record_helper_test.rb +34 -8
  49. data/test/template/date_helper_test.rb +164 -20
  50. data/test/template/form_helper_test.rb +12 -0
  51. data/test/template/form_options_helper_test.rb +7 -16
  52. data/test/template/url_helper_test.rb +12 -0
  53. metadata +8 -2
@@ -15,7 +15,7 @@ module ActionController #:nodoc:
15
15
  }
16
16
  end
17
17
 
18
- def render_with_benchmark(template_name = "#{controller_name}/#{action_name}", status = "200 OK")
18
+ def render_with_benchmark(template_name = default_template_name, status = "200 OK")
19
19
  if logger.nil?
20
20
  render_without_benchmark(template_name, status)
21
21
  else
@@ -63,29 +63,27 @@ class CGIMethods #:nodoc:
63
63
  end
64
64
  end
65
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(/\]\[/)
66
+ PARAMS_HASH_RE = /^([^\[]+)(\[.*\])?(.)?.*$/
67
+ def CGIMethods.get_levels(key)
68
+ all, main, bracketed, trailing = PARAMS_HASH_RE.match(key).to_a
69
+ if main.nil?
70
+ []
71
+ elsif trailing
72
+ [key]
73
+ elsif bracketed
74
+ [main] + bracketed.slice(1...-1).split('][')
75
+ else
76
+ [main]
77
77
  end
78
-
79
- levels
80
78
  end
81
-
79
+
82
80
  def CGIMethods.build_deep_hash(value, hash, levels)
83
81
  if levels.length == 0
84
- value;
82
+ value
85
83
  elsif hash.nil?
86
84
  { levels.first => CGIMethods.build_deep_hash(value, nil, levels[1..-1]) }
87
85
  else
88
86
  hash.update({ levels.first => CGIMethods.build_deep_hash(value, hash[levels.first], levels[1..-1]) })
89
87
  end
90
88
  end
91
- end
89
+ end
@@ -36,7 +36,7 @@ module ActionController #:nodoc:
36
36
  attr_accessor :cgi
37
37
 
38
38
  DEFAULT_SESSION_OPTIONS =
39
- { "database_manager" => CGI::Session::PStore, "prefix" => "ruby_sess.", "session_path" => "/" }
39
+ { :database_manager => CGI::Session::PStore, :prefix => "ruby_sess.", :session_path => "/" }
40
40
 
41
41
  def initialize(cgi, session_options = {})
42
42
  @cgi = cgi
@@ -67,7 +67,7 @@ module ActionController #:nodoc:
67
67
  def session
68
68
  return @session unless @session.nil?
69
69
  begin
70
- @session = (@session_options == false ? {} : CGI::Session.new(@cgi, DEFAULT_SESSION_OPTIONS.merge(@session_options)))
70
+ @session = (@session_options == false ? {} : CGI::Session.new(@cgi, session_options_with_string_keys))
71
71
  @session["__valid_session"]
72
72
  return @session
73
73
  rescue ArgumentError => e
@@ -94,6 +94,10 @@ module ActionController #:nodoc:
94
94
  def new_session
95
95
  CGI::Session.new(@cgi, DEFAULT_SESSION_OPTIONS.merge(@session_options).merge("new_session" => true))
96
96
  end
97
+
98
+ def session_options_with_string_keys
99
+ DEFAULT_SESSION_OPTIONS.merge(@session_options).inject({}) { |options, pair| options[pair.first.to_s] = pair.last; options }
100
+ end
97
101
  end
98
102
 
99
103
  class CgiResponse < AbstractResponse #:nodoc:
@@ -105,8 +109,12 @@ module ActionController #:nodoc:
105
109
  def out
106
110
  convert_content_type!(@headers)
107
111
  $stdout.binmode if $stdout.respond_to?(:binmode)
112
+ $stdout.sync = false
108
113
  print @cgi.header(@headers)
109
- if @body.respond_to?(:call)
114
+
115
+ if @cgi.send(:env_table)['REQUEST_METHOD'] == 'HEAD'
116
+ return
117
+ elsif @body.respond_to?(:call)
110
118
  @body.call(self)
111
119
  else
112
120
  print @body
@@ -115,9 +123,11 @@ module ActionController #:nodoc:
115
123
 
116
124
  private
117
125
  def convert_content_type!(headers)
118
- if headers["Content-Type"]
119
- headers["type"] = headers["Content-Type"]
120
- headers.delete "Content-Type"
126
+ %w( Content-Type Content-type content-type ).each do |ct|
127
+ if headers[ct]
128
+ headers["type"] = headers[ct]
129
+ headers.delete(ct)
130
+ end
121
131
  end
122
132
  end
123
133
  end
@@ -0,0 +1,70 @@
1
+ module ActionController #:nodoc:
2
+ # Cookies are read and written through ActionController#cookies. The cookies being read is what was received along with the request,
3
+ # the cookies being written is what will be sent out will the response. Cookies are read by value (so you won't get the cookie object
4
+ # itself back -- just the value it holds). Examples for writting:
5
+ #
6
+ # cookies["user_name"] = "david" # => Will set a simple session cookie
7
+ # cookies["login"] = { :value => "XJ-122", :expires => Time.now + 360} # => Will set a cookie that expires in 1 hour
8
+ #
9
+ # Examples for reading:
10
+ #
11
+ # cookies["user_name"] # => "david"
12
+ # cookies.size # => 2
13
+ #
14
+ # Example for deleting:
15
+ #
16
+ # cookies.delete "user_name"
17
+ #
18
+ # All the option symbols for setting cookies are:
19
+ #
20
+ # * <tt>value</tt> - the cookie's value or list of values (as an array).
21
+ # * <tt>path</tt> - the path for which this cookie applies. Defaults to the root of the application.
22
+ # * <tt>domain</tt> - the domain for which this cookie applies.
23
+ # * <tt>expires</tt> - the time at which this cookie expires, as a +Time+ object.
24
+ # * <tt>secure</tt> - whether this cookie is a secure cookie or not (default to false).
25
+ # Secure cookies are only transmitted to HTTPS servers.
26
+ module Cookies
27
+ # Returns the cookie container, which operates as described above.
28
+ def cookies
29
+ CookieJar.new(self)
30
+ end
31
+ end
32
+
33
+ class CookieJar < Hash #:nodoc:
34
+ def initialize(controller)
35
+ @controller, @cookies = controller, controller.instance_variable_get("@cookies")
36
+ super()
37
+ update(@cookies)
38
+ end
39
+
40
+ # Returns the value of the cookie by +name+ -- or nil if no such cookie exist. You set new cookies using either the cookie method
41
+ # or cookies[]= (for simple name/value cookies without options).
42
+ def [](name)
43
+ @cookies[name.to_s].value.first if @cookies[name.to_s] && @cookies[name.to_s].respond_to?(:value)
44
+ end
45
+
46
+ def []=(name, options)
47
+ if options.is_a?(Hash)
48
+ options = options.inject({}) { |options, pair| options[pair.first.to_s] = pair.last; options }
49
+ options["name"] = name.to_s
50
+ else
51
+ options = { "name" => name, "value" => options }
52
+ end
53
+
54
+ set_cookie(name, options)
55
+ end
56
+
57
+ # Removes the cookie on the client machine by setting the value to an empty string.
58
+ def delete(name)
59
+ set_cookie(name, "name" => name, "value" => "")
60
+ end
61
+
62
+ private
63
+ def set_cookie(name, options) #:doc:
64
+ options["path"] = "/" unless options["path"]
65
+ cookie = CGI::Cookie.new(options)
66
+ @controller.logger.info "Cookie set: #{cookie}" unless @controller.logger.nil?
67
+ @controller.response.headers["cookie"] << cookie
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,106 @@
1
+ unless Object.respond_to?(:require_dependency)
2
+ Object.send(:define_method, :require_dependency) { |file_name| ActionController::Base.require_dependency(file_name) }
3
+ end
4
+
5
+ module ActionController #:nodoc:
6
+ module Dependencies #:nodoc:
7
+ def self.append_features(base)
8
+ super
9
+
10
+ base.class_eval do
11
+ # When turned on (which is default), all dependencies are included using "load". This mean that any change is instant in cached
12
+ # environments like mod_ruby or FastCGI. When set to false, "require" is used, which is faster but requires server restart to
13
+ # be effective.
14
+ @@reload_dependencies = true
15
+ cattr_accessor :reload_dependencies
16
+ end
17
+
18
+ base.class_eval { class << self; alias_method :inherited_without_model, :inherited; end }
19
+
20
+ base.extend(ClassMethods)
21
+ end
22
+
23
+ # Dependencies control what classes are needed for the controller to run its course. This is an alternative to doing explicit
24
+ # +require+ statements that bring a number of benefits. It's more succinct, communicates what type of dependency we're talking about,
25
+ # can trigger special behavior (as in the case of +observer+), and enables Rails to be clever about reloading in cached environments
26
+ # like FCGI. Example:
27
+ #
28
+ # class ApplicationController < ActionController::Base
29
+ # model :account, :company, :person, :project, :category
30
+ # helper :access_control
31
+ # service :notifications, :billings
32
+ # observer :project_change_observer
33
+ # end
34
+ #
35
+ # Please note that a controller like ApplicationController will automatically attempt to require_dependency on a model of its name and a helper
36
+ # of its name. If nothing is found, no error is raised. This is especially useful for concrete controllers like PostController:
37
+ #
38
+ # class PostController < ApplicationController
39
+ # # model :post (already required)
40
+ # # helper :post (already required)
41
+ # end
42
+ module ClassMethods
43
+ # Loads the <tt>file_name</tt> if reload_dependencies is true or requires if it's false.
44
+ def require_dependency(file_name)
45
+ reload_dependencies ? silence_warnings { load("#{file_name}.rb") } : require(file_name)
46
+ end
47
+
48
+ # Specifies a variable number of models that this controller depends on. Models are normally Active Record classes or a similar
49
+ # backend for modelling entity classes.
50
+ def model(*models)
51
+ require_dependencies(:model, models)
52
+ depend_on(:model, models)
53
+ end
54
+
55
+ # Specifies a variable number of services that this controller depends on. Services are normally singletons or factories, like
56
+ # Action Mailer service or a Payment Gateway service.
57
+ def service(*services)
58
+ require_dependencies(:service, services)
59
+ depend_on(:service, services)
60
+ end
61
+
62
+ # Specifies a variable number of observers that are to govern when this controller is handling actions. The observers will
63
+ # automatically have .instance called on them to make them active on assignment.
64
+ def observer(*observers)
65
+ require_dependencies(:observer, observers)
66
+ depend_on(:observer, observers)
67
+ instantiate_observers(observers)
68
+ end
69
+
70
+ # Returns an array of symbols that specify the dependencies on a given layer. For the example at the top, calling
71
+ # <tt>ApplicationController.dependencies_on(:model)</tt> would return <tt>[:account, :company, :person, :project, :category]</tt>
72
+ def dependencies_on(layer)
73
+ read_inheritable_attribute("#{layer}_dependencies")
74
+ end
75
+
76
+ def depend_on(layer, dependencies) #:nodoc:
77
+ write_inheritable_array("#{layer}_dependencies", dependencies)
78
+ end
79
+
80
+ private
81
+ def instantiate_observers(observers)
82
+ observers.flatten.each { |observer| Object.const_get(Inflector.classify(observer.to_s)).instance }
83
+ end
84
+
85
+ def require_dependencies(layer, dependencies)
86
+ dependencies.flatten.each do |dependency|
87
+ begin
88
+ require_dependency(dependency.to_s)
89
+ rescue LoadError
90
+ raise LoadError, "Missing #{layer} #{dependency}.rb"
91
+ end
92
+ end
93
+ end
94
+
95
+ def inherited(child)
96
+ inherited_without_model(child)
97
+ begin
98
+ child.model(child.controller_name)
99
+ child.model(Inflector.singularize(child.controller_name))
100
+ rescue LoadError
101
+ # No neither singular or plural model available for this controller
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
@@ -2,6 +2,7 @@ module ActionController #:nodoc:
2
2
  module Helpers #:nodoc:
3
3
  def self.append_features(base)
4
4
  super
5
+ base.class_eval { class << self; alias_method :inherited_without_helper, :inherited; end }
5
6
  base.extend(ClassMethods)
6
7
  end
7
8
 
@@ -30,7 +31,7 @@ module ActionController #:nodoc:
30
31
  # Makes all the (instance) methods in the helper module available to templates rendered through this controller.
31
32
  # See ActionView::Helpers (link:classes/ActionView/Helpers.html) for more about making your own helper modules
32
33
  # available to the templates.
33
- def add_template_helper(helper_module)
34
+ def add_template_helper(helper_module) #:nodoc:
34
35
  template_class.class_eval "include #{helper_module}"
35
36
  end
36
37
 
@@ -53,9 +54,9 @@ module ActionController #:nodoc:
53
54
  file_name = Inflector.underscore(arg.to_s.downcase) + '_helper'
54
55
  class_name = Inflector.camelize(file_name)
55
56
  begin
56
- require file_name
57
+ require_dependency(file_name)
57
58
  rescue LoadError
58
- raise ArgumentError, "Missing helper file helpers/#{file_name}.rb"
59
+ raise LoadError, "Missing helper file helpers/#{file_name}.rb"
59
60
  end
60
61
  raise ArgumentError, "Missing #{class_name} module in helpers/#{file_name}.rb" unless Object.const_defined?(class_name)
61
62
  add_template_helper(Object.const_get(class_name))
@@ -84,6 +85,16 @@ module ActionController #:nodoc:
84
85
  def helper_attr(*attrs)
85
86
  attrs.flatten.each { |attr| helper_method(attr, "#{attr}=") }
86
87
  end
88
+
89
+ private
90
+ def inherited(child)
91
+ inherited_without_helper(child)
92
+ begin
93
+ child.helper(child.controller_name)
94
+ rescue LoadError
95
+ # No default helper available for this controller
96
+ end
97
+ end
87
98
  end
88
99
  end
89
100
  end
@@ -2,11 +2,14 @@ module ActionController #:nodoc:
2
2
  module Layout #:nodoc:
3
3
  def self.append_features(base)
4
4
  super
5
- base.extend(ClassMethods)
6
5
  base.class_eval do
7
6
  alias_method :render_without_layout, :render
8
7
  alias_method :render, :render_with_layout
8
+ class << self
9
+ alias_method :inherited_without_layout, :inherited
10
+ end
9
11
  end
12
+ base.extend(ClassMethods)
10
13
  end
11
14
 
12
15
  # Layouts reverse the common pattern of including shared headers and footers in many templates to isolate changes in
@@ -119,6 +122,16 @@ module ActionController #:nodoc:
119
122
  def layout(template_name)
120
123
  write_inheritable_attribute "layout", template_name
121
124
  end
125
+
126
+ private
127
+ def inherited(child)
128
+ inherited_without_layout(child)
129
+ child.layout(child.controller_name) unless layout_list.grep(/^#{child.controller_name}\.r(?:xml|html)$/).empty?
130
+ end
131
+
132
+ def layout_list
133
+ Dir.glob("#{template_root}/layouts/*.r{xml,html}").map { |layout| File.basename(layout) }
134
+ end
122
135
  end
123
136
 
124
137
  # Returns the name of the active layout. If the layout was specified as a method reference (through a symbol), this method
@@ -135,7 +148,7 @@ module ActionController #:nodoc:
135
148
  active_layout.include?("/") ? active_layout : "layouts/#{active_layout}" if active_layout
136
149
  end
137
150
 
138
- def render_with_layout(template_name = "#{controller_name}/#{action_name}", status = nil, layout = nil) #:nodoc:
151
+ def render_with_layout(template_name = default_template_name, status = nil, layout = nil) #:nodoc:
139
152
  if layout ||= active_layout
140
153
  add_variables_to_assigns
141
154
  logger.info("Rendering #{template_name} within #{layout}") unless logger.nil?
@@ -145,5 +158,6 @@ module ActionController #:nodoc:
145
158
  render_without_layout(template_name, status)
146
159
  end
147
160
  end
161
+
148
162
  end
149
163
  end
@@ -26,6 +26,10 @@ module ActionController
26
26
  method == :delete
27
27
  end
28
28
 
29
+ def head?
30
+ method == :head
31
+ end
32
+
29
33
  # Determine originating IP address. REMOTE_ADDR is the standard
30
34
  # but will fail if the user is behind a proxy. HTTP_CLIENT_IP and/or
31
35
  # HTTP_X_FORWARDED_FOR are set by proxies so check for these before
@@ -33,15 +37,17 @@ module ActionController
33
37
  # delimited list in the case of multiple chained proxies; the first is
34
38
  # the originating IP.
35
39
  def remote_ip
36
- if env['HTTP_CLIENT_IP']
37
- env['HTTP_CLIENT_IP']
38
- elsif env['HTTP_X_FORWARDED_FOR']
39
- env['HTTP_X_FORWARDED_FOR'].split(',').reject { |ip|
40
+ return env['HTTP_CLIENT_IP'] if env.include? 'HTTP_CLIENT_IP'
41
+
42
+ if env.include? 'HTTP_X_FORWARDED_FOR' then
43
+ remote_ips = env['HTTP_X_FORWARDED_FOR'].split(',').reject do |ip|
40
44
  ip =~ /^unknown$|^(10|172\.16|192\.168)\./i
41
- }.first.strip
42
- else
43
- env['REMOTE_ADDR']
45
+ end
46
+
47
+ return remote_ips.first.strip unless remote_ips.empty?
44
48
  end
49
+
50
+ return env['REMOTE_ADDR']
45
51
  end
46
52
 
47
53
  def request_uri
@@ -52,6 +58,10 @@ module ActionController
52
58
  port == 443 ? "https://" : "http://"
53
59
  end
54
60
 
61
+ def ssl?
62
+ protocol == "https://"
63
+ end
64
+
55
65
  def path
56
66
  request_uri ? request_uri.split("?").first : ""
57
67
  end
@@ -8,12 +8,19 @@ module ActionController #:nodoc:
8
8
  module Rescue
9
9
  def self.append_features(base) #:nodoc:
10
10
  super
11
+ base.extend(ClassMethods)
11
12
  base.class_eval do
12
13
  alias_method :perform_action_without_rescue, :perform_action
13
14
  alias_method :perform_action, :perform_action_with_rescue
14
15
  end
15
16
  end
16
17
 
18
+ module ClassMethods #:nodoc:
19
+ def process_with_exception(request, response, exception)
20
+ new.process(request, response, :rescue_action, exception)
21
+ end
22
+ end
23
+
17
24
  protected
18
25
  # Exception handler called when the performance of an action raises an exception.
19
26
  def rescue_action(exception)
@@ -66,7 +73,31 @@ module ActionController #:nodoc:
66
73
  def perform_action_with_rescue #:nodoc:
67
74
  begin
68
75
  perform_action_without_rescue
69
- rescue Exception => exception
76
+ rescue => exception
77
+ if defined?(Breakpoint) and @params["BP-RETRY"] then
78
+ msg = exception.backtrace.first
79
+ if md = /^(.+?):(\d+)(?::in `(.+)')?$/.match(msg) then
80
+ origin_file, origin_line = md[1], md[2].to_i
81
+
82
+ set_trace_func(lambda do |type, file, line, method, context, klass|
83
+ if file == origin_file and line == origin_line then
84
+ set_trace_func(nil)
85
+ @params["BP-RETRY"] = false
86
+
87
+ callstack = caller
88
+ callstack.slice!(0) if callstack.first["rescue.rb"]
89
+ file, line, method = *callstack.first.match(/^(.+?):(\d+)(?::in `(.*?)')?/).captures
90
+
91
+ message = "Exception at #{file}:#{line}#{" in `#{method}'" if method}."
92
+
93
+ Breakpoint.handle_breakpoint(context, message, file, line)
94
+ end
95
+ end)
96
+
97
+ retry
98
+ end
99
+ end
100
+
70
101
  rescue_action(exception)
71
102
  end
72
103
  end
@@ -87,8 +118,7 @@ module ActionController #:nodoc:
87
118
  end
88
119
 
89
120
  def clean_backtrace(exception)
90
- base_dir = File.expand_path(File.dirname(__FILE__) + "/../../../../")
91
- exception.backtrace.collect { |line| line.gsub(base_dir, "").gsub("/public/../config/environments/../../", "").gsub("/public/../", "") }
121
+ exception.backtrace.collect { |line| Object.const_defined?(:RAILS_ROOT) ? line.gsub(RAILS_ROOT, "") : line }
92
122
  end
93
123
  end
94
124
  end