actionpack 2.1.0 → 2.1.1

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 (60) hide show
  1. data/CHANGELOG +17 -0
  2. data/Rakefile +10 -6
  3. data/lib/action_controller.rb +0 -0
  4. data/lib/action_controller/assertions/response_assertions.rb +1 -1
  5. data/lib/action_controller/assertions/selector_assertions.rb +26 -46
  6. data/lib/action_controller/base.rb +8 -4
  7. data/lib/action_controller/dispatcher.rb +1 -1
  8. data/lib/action_controller/filters.rb +194 -195
  9. data/lib/action_controller/polymorphic_routes.rb +25 -12
  10. data/lib/action_controller/record_identifier.rb +20 -13
  11. data/lib/action_controller/request.rb +9 -6
  12. data/lib/action_controller/request_profiler.rb +0 -0
  13. data/lib/action_controller/response.rb +0 -0
  14. data/lib/action_controller/routing.rb +5 -1
  15. data/lib/action_controller/routing/builder.rb +1 -2
  16. data/lib/action_controller/routing/segments.rb +1 -1
  17. data/lib/action_controller/templates/rescues/layout.erb +1 -1
  18. data/lib/action_controller/test_process.rb +4 -2
  19. data/lib/action_controller/vendor/html-scanner/html/document.rb +1 -1
  20. data/lib/action_controller/verification.rb +1 -1
  21. data/lib/action_pack/version.rb +1 -1
  22. data/lib/action_view/base.rb +7 -3
  23. data/lib/action_view/helpers/asset_tag_helper.rb +14 -11
  24. data/lib/action_view/helpers/date_helper.rb +3 -3
  25. data/lib/action_view/helpers/form_helper.rb +5 -1
  26. data/lib/action_view/helpers/form_options_helper.rb +2 -2
  27. data/lib/action_view/helpers/form_tag_helper.rb +5 -3
  28. data/lib/action_view/helpers/javascript_helper.rb +4 -4
  29. data/lib/action_view/helpers/prototype_helper.rb +7 -7
  30. data/lib/action_view/helpers/tag_helper.rb +4 -3
  31. data/lib/action_view/helpers/text_helper.rb +1 -1
  32. data/lib/action_view/helpers/url_helper.rb +3 -5
  33. data/lib/action_view/partial_template.rb +1 -1
  34. data/test/controller/action_pack_assertions_test.rb +24 -5
  35. data/test/controller/assert_select_test.rb +6 -1
  36. data/test/controller/base_test.rb +37 -1
  37. data/test/controller/cgi_test.rb +0 -0
  38. data/test/controller/dispatcher_test.rb +2 -2
  39. data/test/controller/html-scanner/document_test.rb +25 -0
  40. data/test/controller/integration_upload_test.rb +1 -1
  41. data/test/controller/new_render_test.rb +22 -3
  42. data/test/controller/polymorphic_routes_test.rb +33 -0
  43. data/test/controller/redirect_test.rb +0 -0
  44. data/test/controller/render_test.rb +1 -1
  45. data/test/controller/request_test.rb +6 -0
  46. data/test/controller/resources_test.rb +15 -17
  47. data/test/controller/routing_test.rb +22 -2
  48. data/test/controller/session/cookie_store_test.rb +0 -0
  49. data/test/controller/test_test.rb +11 -2
  50. data/test/controller/verification_test.rb +34 -17
  51. data/test/fixtures/test/render_file_from_template.html.erb +1 -0
  52. data/test/template/date_helper_test.rb +59 -8
  53. data/test/template/deprecated_erb_variable_test.rb +9 -0
  54. data/test/template/form_options_helper_test.rb +505 -514
  55. data/test/template/form_tag_helper_test.rb +13 -0
  56. data/test/template/javascript_helper_test.rb +7 -4
  57. data/test/template/prototype_helper_test.rb +14 -6
  58. data/test/template/text_helper_test.rb +1 -0
  59. data/test/template/url_helper_test.rb +11 -1
  60. metadata +8 -4
@@ -48,6 +48,9 @@ module ActionController
48
48
  #
49
49
  # # calls post_url(post)
50
50
  # polymorphic_url(post) # => "http://example.com/posts/1"
51
+ # polymorphic_url([blog, post]) # => "http://example.com/blogs/1/posts/1"
52
+ # polymorphic_url([:admin, blog, post]) # => "http://example.com/admin/blogs/1/posts/1"
53
+ # polymorphic_url([user, :blog, post]) # => "http://example.com/users/1/blog/posts/1"
51
54
  #
52
55
  # ==== Options
53
56
  #
@@ -83,8 +86,6 @@ module ActionController
83
86
  else [ record_or_hash_or_array ]
84
87
  end
85
88
 
86
- args << format if format
87
-
88
89
  inflection =
89
90
  case
90
91
  when options[:action].to_s == "new"
@@ -96,6 +97,9 @@ module ActionController
96
97
  else
97
98
  :singular
98
99
  end
100
+
101
+ args.delete_if {|arg| arg.is_a?(Symbol) || arg.is_a?(String)}
102
+ args << format if format
99
103
 
100
104
  named_route = build_named_route_call(record_or_hash_or_array, namespace, inflection, options)
101
105
  send!(named_route, *args)
@@ -136,11 +140,19 @@ module ActionController
136
140
  else
137
141
  record = records.pop
138
142
  route = records.inject("") do |string, parent|
139
- string << "#{RecordIdentifier.send!("singular_class_name", parent)}_"
143
+ if parent.is_a?(Symbol) || parent.is_a?(String)
144
+ string << "#{parent}_"
145
+ else
146
+ string << "#{RecordIdentifier.send!("singular_class_name", parent)}_"
147
+ end
140
148
  end
141
149
  end
142
150
 
143
- route << "#{RecordIdentifier.send!("#{inflection}_class_name", record)}_"
151
+ if record.is_a?(Symbol) || record.is_a?(String)
152
+ route << "#{record}_"
153
+ else
154
+ route << "#{RecordIdentifier.send!("#{inflection}_class_name", record)}_"
155
+ end
144
156
 
145
157
  action_prefix(options) + namespace + route + routing_type(options).to_s
146
158
  end
@@ -163,16 +175,17 @@ module ActionController
163
175
  end
164
176
  end
165
177
 
178
+ # Remove the first symbols from the array and return the url prefix
179
+ # implied by those symbols.
166
180
  def extract_namespace(record_or_hash_or_array)
167
- returning "" do |namespace|
168
- if record_or_hash_or_array.is_a?(Array)
169
- record_or_hash_or_array.delete_if do |record_or_namespace|
170
- if record_or_namespace.is_a?(String) || record_or_namespace.is_a?(Symbol)
171
- namespace << "#{record_or_namespace}_"
172
- end
173
- end
174
- end
181
+ return "" unless record_or_hash_or_array.is_a?(Array)
182
+
183
+ namespace_keys = []
184
+ while (key = record_or_hash_or_array.first) && key.is_a?(String) || key.is_a?(Symbol)
185
+ namespace_keys << record_or_hash_or_array.shift
175
186
  end
187
+
188
+ namespace_keys.map {|k| "#{k}_"}.join
176
189
  end
177
190
  end
178
191
  end
@@ -31,18 +31,21 @@ module ActionController
31
31
  module RecordIdentifier
32
32
  extend self
33
33
 
34
+ JOIN = '_'.freeze
35
+ NEW = 'new'.freeze
36
+
34
37
  # Returns plural/singular for a record or class. Example:
35
38
  #
36
39
  # partial_path(post) # => "posts/post"
37
40
  # partial_path(Person) # => "people/person"
38
41
  # partial_path(Person, "admin/games") # => "admin/people/person"
39
42
  def partial_path(record_or_class, controller_path = nil)
40
- klass = class_from_record_or_class(record_or_class)
43
+ name = model_name_from_record_or_class(record_or_class)
41
44
 
42
45
  if controller_path && controller_path.include?("/")
43
- "#{File.dirname(controller_path)}/#{klass.name.tableize}/#{klass.name.demodulize.underscore}"
46
+ "#{File.dirname(controller_path)}/#{name.partial_path}"
44
47
  else
45
- "#{klass.name.tableize}/#{klass.name.demodulize.underscore}"
48
+ name.partial_path
46
49
  end
47
50
  end
48
51
 
@@ -56,21 +59,25 @@ module ActionController
56
59
  # dom_class(post, :edit) # => "edit_post"
57
60
  # dom_class(Person, :edit) # => "edit_person"
58
61
  def dom_class(record_or_class, prefix = nil)
59
- [ prefix, singular_class_name(record_or_class) ].compact * '_'
62
+ singular = singular_class_name(record_or_class)
63
+ prefix ? "#{prefix}#{JOIN}#{singular}" : singular
60
64
  end
61
65
 
62
66
  # The DOM id convention is to use the singular form of an object or class with the id following an underscore.
63
67
  # If no id is found, prefix with "new_" instead. Examples:
64
68
  #
65
- # dom_id(Post.new(:id => 45)) # => "post_45"
69
+ # dom_id(Post.find(45)) # => "post_45"
66
70
  # dom_id(Post.new) # => "new_post"
67
71
  #
68
72
  # If you need to address multiple instances of the same class in the same view, you can prefix the dom_id:
69
73
  #
70
- # dom_id(Post.new(:id => 45), :edit) # => "edit_post_45"
74
+ # dom_id(Post.find(45), :edit) # => "edit_post_45"
71
75
  def dom_id(record, prefix = nil)
72
- prefix ||= 'new' unless record.id
73
- [ prefix, singular_class_name(record), record.id ].compact * '_'
76
+ if record_id = record.id
77
+ "#{dom_class(record, prefix)}#{JOIN}#{record_id}"
78
+ else
79
+ dom_class(record, prefix || NEW)
80
+ end
74
81
  end
75
82
 
76
83
  # Returns the plural class name of a record or class. Examples:
@@ -78,7 +85,7 @@ module ActionController
78
85
  # plural_class_name(post) # => "posts"
79
86
  # plural_class_name(Highrise::Person) # => "highrise_people"
80
87
  def plural_class_name(record_or_class)
81
- singular_class_name(record_or_class).pluralize
88
+ model_name_from_record_or_class(record_or_class).plural
82
89
  end
83
90
 
84
91
  # Returns the singular class name of a record or class. Examples:
@@ -86,12 +93,12 @@ module ActionController
86
93
  # singular_class_name(post) # => "post"
87
94
  # singular_class_name(Highrise::Person) # => "highrise_person"
88
95
  def singular_class_name(record_or_class)
89
- class_from_record_or_class(record_or_class).name.underscore.tr('/', '_')
96
+ model_name_from_record_or_class(record_or_class).singular
90
97
  end
91
98
 
92
99
  private
93
- def class_from_record_or_class(record_or_class)
94
- record_or_class.is_a?(Class) ? record_or_class : record_or_class.class
100
+ def model_name_from_record_or_class(record_or_class)
101
+ (record_or_class.is_a?(Class) ? record_or_class : record_or_class.class).model_name
95
102
  end
96
103
  end
97
- end
104
+ end
@@ -134,14 +134,17 @@ module ActionController
134
134
  # REMOTE_ADDR is a proxy. HTTP_X_FORWARDED_FOR may be a comma-
135
135
  # delimited list in the case of multiple chained proxies; the last
136
136
  # address which is not trusted is the originating IP.
137
-
138
137
  def remote_ip
139
- if TRUSTED_PROXIES !~ @env['REMOTE_ADDR']
140
- return @env['REMOTE_ADDR']
138
+ remote_addr_list = @env['REMOTE_ADDR'] && @env['REMOTE_ADDR'].split(',').collect(&:strip)
139
+
140
+ unless remote_addr_list.blank?
141
+ not_trusted_addrs = remote_addr_list.reject {|addr| addr =~ TRUSTED_PROXIES}
142
+ return not_trusted_addrs.first unless not_trusted_addrs.empty?
141
143
  end
144
+ remote_ips = @env['HTTP_X_FORWARDED_FOR'] && @env['HTTP_X_FORWARDED_FOR'].split(',')
142
145
 
143
146
  if @env.include? 'HTTP_CLIENT_IP'
144
- if @env.include? 'HTTP_X_FORWARDED_FOR'
147
+ if remote_ips && !remote_ips.include?(@env['HTTP_CLIENT_IP'])
145
148
  # We don't know which came from the proxy, and which from the user
146
149
  raise ActionControllerError.new(<<EOM)
147
150
  IP spoofing attack?!
@@ -149,11 +152,11 @@ HTTP_CLIENT_IP=#{@env['HTTP_CLIENT_IP'].inspect}
149
152
  HTTP_X_FORWARDED_FOR=#{@env['HTTP_X_FORWARDED_FOR'].inspect}
150
153
  EOM
151
154
  end
155
+
152
156
  return @env['HTTP_CLIENT_IP']
153
157
  end
154
158
 
155
- if @env.include? 'HTTP_X_FORWARDED_FOR' then
156
- remote_ips = @env['HTTP_X_FORWARDED_FOR'].split(',')
159
+ if remote_ips
157
160
  while remote_ips.size > 1 && TRUSTED_PROXIES =~ remote_ips.last.strip
158
161
  remote_ips.pop
159
162
  end
File without changes
File without changes
@@ -88,6 +88,10 @@ module ActionController
88
88
  #
89
89
  # map.connect ':controller/:action/:id', :action => 'show', :defaults => { :page => 'Dashboard' }
90
90
  #
91
+ # Note: The default routes, as provided by the Rails generator, make all actions in every
92
+ # controller accessible via GET requests. You should consider removing them or commenting
93
+ # them out if you're using named routes and resources.
94
+ #
91
95
  # == Named routes
92
96
  #
93
97
  # Routes can be named with the syntax <tt>map.name_of_route options</tt>,
@@ -369,7 +373,7 @@ module ActionController
369
373
 
370
374
  Routes = RouteSet.new
371
375
 
372
- ::Inflector.module_eval do
376
+ ActiveSupport::Inflector.module_eval do
373
377
  # Ensures that routes are reloaded when Rails inflections are updated.
374
378
  def inflections_with_route_reloading(&block)
375
379
  returning(inflections_without_route_reloading(&block)) {
@@ -67,10 +67,9 @@ module ActionController
67
67
  options = options.dup
68
68
 
69
69
  if options[:namespace]
70
- options[:controller] = "#{options[:path_prefix]}/#{options[:controller]}"
70
+ options[:controller] = "#{options.delete(:namespace).sub(/\/$/, '')}/#{options[:controller]}"
71
71
  options.delete(:path_prefix)
72
72
  options.delete(:name_prefix)
73
- options.delete(:namespace)
74
73
  end
75
74
 
76
75
  requirements = (options.delete(:requirements) || {}).dup
@@ -249,7 +249,7 @@ module ActionController
249
249
  end
250
250
 
251
251
  def extract_value
252
- "#{local_name} = hash[:#{key}] && hash[:#{key}].collect { |path_component| URI.escape(path_component.to_param, ActionController::Routing::Segment::UNSAFE_PCHAR) }.to_param #{"|| #{default.inspect}" if default}"
252
+ "#{local_name} = hash[:#{key}] && Array(hash[:#{key}]).collect { |path_component| URI.escape(path_component.to_param, ActionController::Routing::Segment::UNSAFE_PCHAR) }.to_param #{"|| #{default.inspect}" if default}"
253
253
  end
254
254
 
255
255
  def default
@@ -1,4 +1,4 @@
1
- <html>
1
+ <html xmlns="http://www.w3.org/1999/xhtml">
2
2
  <head>
3
3
  <title>Action Controller: Exception caught</title>
4
4
  <style>
@@ -171,7 +171,7 @@ module ActionController #:nodoc:
171
171
 
172
172
  # Was the response successful?
173
173
  def success?
174
- response_code == 200
174
+ (200..299).include?(response_code)
175
175
  end
176
176
 
177
177
  # Was the URL not found?
@@ -333,7 +333,7 @@ module ActionController #:nodoc:
333
333
  attr_reader :original_filename
334
334
 
335
335
  # The content type of the "uploaded" file
336
- attr_reader :content_type
336
+ attr_accessor :content_type
337
337
 
338
338
  def initialize(path, content_type = Mime::TEXT, binary = false)
339
339
  raise "#{path} file does not exist" unless File.exist?(path)
@@ -413,6 +413,8 @@ module ActionController #:nodoc:
413
413
  get(@response.redirected_to.delete(:action), @response.redirected_to.stringify_keys)
414
414
  end
415
415
 
416
+ deprecate :follow_redirect => "If you wish to follow redirects, you should use integration tests"
417
+
416
418
  def assigns(key = nil)
417
419
  if key.nil?
418
420
  @response.template.assigns
@@ -17,7 +17,7 @@ module HTML #:nodoc:
17
17
  @root = Node.new(nil)
18
18
  node_stack = [ @root ]
19
19
  while token = tokenizer.next
20
- node = Node.parse(node_stack.last, tokenizer.line, tokenizer.position, token)
20
+ node = Node.parse(node_stack.last, tokenizer.line, tokenizer.position, token, strict)
21
21
 
22
22
  node_stack.last.children << node unless node.tag? && node.closing == :close
23
23
  if node.tag?
@@ -116,7 +116,7 @@ module ActionController #:nodoc:
116
116
  end
117
117
 
118
118
  def apply_redirect_to(redirect_to_option) # :nodoc:
119
- redirect_to_option.is_a?(Symbol) ? self.send!(redirect_to_option) : redirect_to_option
119
+ (redirect_to_option.is_a?(Symbol) && redirect_to_option != :back) ? self.send!(redirect_to_option) : redirect_to_option
120
120
  end
121
121
 
122
122
  def apply_remaining_actions(options) # :nodoc:
@@ -2,7 +2,7 @@ module ActionPack #:nodoc:
2
2
  module VERSION #:nodoc:
3
3
  MAJOR = 2
4
4
  MINOR = 1
5
- TINY = 0
5
+ TINY = 1
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].join('.')
8
8
  end
@@ -178,10 +178,13 @@ module ActionView #:nodoc:
178
178
  # that alert()s the caught exception (and then re-raises it).
179
179
  @@debug_rjs = false
180
180
  cattr_accessor :debug_rjs
181
-
181
+
182
182
  @@erb_variable = '_erbout'
183
183
  cattr_accessor :erb_variable
184
-
184
+ class << self
185
+ deprecate :erb_variable= => 'The erb variable will no longer be configurable. Use the concat helper method instead of appending to it directly.'
186
+ end
187
+
185
188
  attr_internal :request
186
189
 
187
190
  delegate :request_forgery_protection_token, :template, :params, :session, :cookies, :response, :headers,
@@ -253,6 +256,7 @@ If you are rendering a subtemplate, you must now use controller-like partial syn
253
256
  elsif options == :update
254
257
  update_page(&block)
255
258
  elsif options.is_a?(Hash)
259
+ use_full_path = options[:use_full_path]
256
260
  options = options.reverse_merge(:locals => {}, :use_full_path => true)
257
261
 
258
262
  if partial_layout = options.delete(:layout)
@@ -266,7 +270,7 @@ If you are rendering a subtemplate, you must now use controller-like partial syn
266
270
  end
267
271
  end
268
272
  elsif options[:file]
269
- render_file(options[:file], options[:use_full_path], options[:locals])
273
+ render_file(options[:file], use_full_path || false, options[:locals])
270
274
  elsif options[:partial] && options[:collection]
271
275
  render_partial_collection(options[:partial], options[:collection], options[:spacer_template], options[:locals])
272
276
  elsif options[:partial]
@@ -485,21 +485,24 @@ module ActionView
485
485
  source = "#{@controller.request.relative_url_root}#{source}"
486
486
  end
487
487
  end
488
- source = rewrite_asset_path(source)
489
488
 
490
- if include_host
491
- host = compute_asset_host(source)
489
+ rewrite_asset_path(source)
490
+ end
491
+ end
492
492
 
493
- if has_request && !host.blank? && host !~ %r{^[-a-z]+://}
494
- host = "#{@controller.request.protocol}#{host}"
495
- end
493
+ source = ActionView::Base.computed_public_paths[cache_key]
496
494
 
497
- "#{host}#{source}"
498
- else
499
- source
500
- end
501
- end
495
+ if include_host && source !~ %r{^[-a-z]+://}
496
+ host = compute_asset_host(source)
497
+
498
+ if has_request && !host.blank? && host !~ %r{^[-a-z]+://}
499
+ host = "#{@controller.request.protocol}#{host}"
502
500
  end
501
+
502
+ "#{host}#{source}"
503
+ else
504
+ source
505
+ end
503
506
  end
504
507
 
505
508
  # Pick an asset host for this source. Returns +nil+ if no host is set,
@@ -696,15 +696,15 @@ module ActionView
696
696
 
697
697
  class FormBuilder
698
698
  def date_select(method, options = {}, html_options = {})
699
- @template.date_select(@object_name, method, options.merge(:object => @object))
699
+ @template.date_select(@object_name, method, options.merge(:object => @object), html_options)
700
700
  end
701
701
 
702
702
  def time_select(method, options = {}, html_options = {})
703
- @template.time_select(@object_name, method, options.merge(:object => @object))
703
+ @template.time_select(@object_name, method, options.merge(:object => @object), html_options)
704
704
  end
705
705
 
706
706
  def datetime_select(method, options = {}, html_options = {})
707
- @template.datetime_select(@object_name, method, options.merge(:object => @object))
707
+ @template.datetime_select(@object_name, method, options.merge(:object => @object), html_options)
708
708
  end
709
709
  end
710
710
  end
@@ -601,7 +601,11 @@ module ActionView
601
601
  end
602
602
 
603
603
  def object
604
- @object || (@template_object.instance_variable_get("@#{@object_name}") rescue nil)
604
+ @object || @template_object.instance_variable_get("@#{@object_name}")
605
+ rescue NameError
606
+ # As @object_name may contain the nested syntax (item[subobject]) we
607
+ # need to fallback to nil.
608
+ nil
605
609
  end
606
610
 
607
611
  def value(object)
@@ -304,7 +304,7 @@ module ActionView
304
304
  #
305
305
  # NOTE: Only the option tags are returned, you have to wrap this call in
306
306
  # a regular HTML select tag.
307
- def time_zone_options_for_select(selected = nil, priority_zones = nil, model = TimeZone)
307
+ def time_zone_options_for_select(selected = nil, priority_zones = nil, model = ::ActiveSupport::TimeZone)
308
308
  zone_options = ""
309
309
 
310
310
  zones = model.all
@@ -417,7 +417,7 @@ module ActionView
417
417
  value = value(object)
418
418
  content_tag("select",
419
419
  add_options(
420
- time_zone_options_for_select(value || options[:default], priority_zones, options[:model] || TimeZone),
420
+ time_zone_options_for_select(value || options[:default], priority_zones, options[:model] || ActiveSupport::TimeZone),
421
421
  options, value
422
422
  ), html_options
423
423
  )
@@ -129,7 +129,7 @@ module ActionView
129
129
  # label_tag 'name', nil, :class => 'small_label'
130
130
  # # => <label for="name" class="small_label">Name</label>
131
131
  def label_tag(name, text = nil, options = {})
132
- content_tag :label, text || name.humanize, { "for" => name }.update(options.stringify_keys)
132
+ content_tag :label, text || name.to_s.humanize, { "for" => name }.update(options.stringify_keys)
133
133
  end
134
134
 
135
135
  # Creates a hidden form input field used to transmit data that would be lost due to HTTP's statelessness or
@@ -348,11 +348,13 @@ module ActionView
348
348
  options.stringify_keys!
349
349
 
350
350
  if disable_with = options.delete("disable_with")
351
+ disable_with = "this.value='#{disable_with}'"
352
+ disable_with << ";#{options.delete('onclick')}" if options['onclick']
353
+
351
354
  options["onclick"] = [
352
355
  "this.setAttribute('originalValue', this.value)",
353
356
  "this.disabled=true",
354
- "this.value='#{disable_with}'",
355
- "#{options["onclick"]}",
357
+ disable_with,
356
358
  "result = (this.form.onsubmit ? (this.form.onsubmit() ? this.form.submit() : false) : this.form.submit())",
357
359
  "if (result == false) { this.value = this.getAttribute('originalValue'); this.disabled = false }",
358
360
  "return result;",