actionpack 1.12.5 → 1.13.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 (179) hide show
  1. data/CHANGELOG +517 -15
  2. data/MIT-LICENSE +1 -1
  3. data/README +18 -20
  4. data/Rakefile +7 -4
  5. data/examples/address_book_controller.rb +3 -3
  6. data/examples/blog_controller.cgi +3 -3
  7. data/examples/debate_controller.cgi +5 -5
  8. data/lib/action_controller.rb +2 -2
  9. data/lib/action_controller/assertions.rb +73 -311
  10. data/lib/action_controller/{deprecated_assertions.rb → assertions/deprecated_assertions.rb} +32 -8
  11. data/lib/action_controller/assertions/dom_assertions.rb +25 -0
  12. data/lib/action_controller/assertions/model_assertions.rb +12 -0
  13. data/lib/action_controller/assertions/response_assertions.rb +140 -0
  14. data/lib/action_controller/assertions/routing_assertions.rb +82 -0
  15. data/lib/action_controller/assertions/selector_assertions.rb +571 -0
  16. data/lib/action_controller/assertions/tag_assertions.rb +117 -0
  17. data/lib/action_controller/base.rb +334 -163
  18. data/lib/action_controller/benchmarking.rb +3 -6
  19. data/lib/action_controller/caching.rb +83 -22
  20. data/lib/action_controller/cgi_ext/cgi_ext.rb +0 -7
  21. data/lib/action_controller/cgi_ext/cgi_methods.rb +167 -173
  22. data/lib/action_controller/cgi_ext/raw_post_data_fix.rb +43 -22
  23. data/lib/action_controller/cgi_process.rb +50 -27
  24. data/lib/action_controller/components.rb +21 -25
  25. data/lib/action_controller/cookies.rb +10 -9
  26. data/lib/action_controller/{dependencies.rb → deprecated_dependencies.rb} +9 -27
  27. data/lib/action_controller/filters.rb +448 -225
  28. data/lib/action_controller/flash.rb +24 -20
  29. data/lib/action_controller/helpers.rb +2 -5
  30. data/lib/action_controller/integration.rb +40 -16
  31. data/lib/action_controller/layout.rb +11 -8
  32. data/lib/action_controller/macros/auto_complete.rb +3 -2
  33. data/lib/action_controller/macros/in_place_editing.rb +3 -2
  34. data/lib/action_controller/mime_responds.rb +41 -29
  35. data/lib/action_controller/mime_type.rb +68 -10
  36. data/lib/action_controller/pagination.rb +4 -3
  37. data/lib/action_controller/request.rb +22 -14
  38. data/lib/action_controller/rescue.rb +25 -22
  39. data/lib/action_controller/resources.rb +302 -0
  40. data/lib/action_controller/response.rb +20 -2
  41. data/lib/action_controller/response.rb.rej +17 -0
  42. data/lib/action_controller/routing.rb +1165 -567
  43. data/lib/action_controller/scaffolding.rb +30 -31
  44. data/lib/action_controller/session/active_record_store.rb +2 -0
  45. data/lib/action_controller/session/drb_store.rb +4 -0
  46. data/lib/action_controller/session/mem_cache_store.rb +4 -0
  47. data/lib/action_controller/session_management.rb +6 -9
  48. data/lib/action_controller/status_codes.rb +89 -0
  49. data/lib/action_controller/streaming.rb +6 -15
  50. data/lib/action_controller/templates/rescues/_request_and_response.rhtml +5 -5
  51. data/lib/action_controller/templates/rescues/diagnostics.rhtml +2 -2
  52. data/lib/action_controller/templates/rescues/routing_error.rhtml +4 -4
  53. data/lib/action_controller/templates/rescues/template_error.rhtml +1 -1
  54. data/lib/action_controller/templates/scaffolds/list.rhtml +1 -1
  55. data/lib/action_controller/test_process.rb +52 -30
  56. data/lib/action_controller/url_rewriter.rb +63 -29
  57. data/lib/action_controller/vendor/html-scanner/html/document.rb +1 -0
  58. data/lib/action_controller/vendor/html-scanner/html/node.rb +3 -4
  59. data/lib/action_controller/vendor/html-scanner/html/selector.rb +822 -0
  60. data/lib/action_controller/verification.rb +22 -11
  61. data/lib/action_pack.rb +1 -1
  62. data/lib/action_pack/version.rb +2 -2
  63. data/lib/action_view.rb +1 -1
  64. data/lib/action_view/base.rb +46 -43
  65. data/lib/action_view/compiled_templates.rb +1 -1
  66. data/lib/action_view/helpers/active_record_helper.rb +54 -17
  67. data/lib/action_view/helpers/asset_tag_helper.rb +97 -46
  68. data/lib/action_view/helpers/capture_helper.rb +1 -1
  69. data/lib/action_view/helpers/date_helper.rb +258 -136
  70. data/lib/action_view/helpers/debug_helper.rb +1 -1
  71. data/lib/action_view/helpers/deprecated_helper.rb +34 -0
  72. data/lib/action_view/helpers/form_helper.rb +75 -35
  73. data/lib/action_view/helpers/form_options_helper.rb +7 -5
  74. data/lib/action_view/helpers/form_tag_helper.rb +44 -6
  75. data/lib/action_view/helpers/java_script_macros_helper.rb +59 -46
  76. data/lib/action_view/helpers/javascript_helper.rb +71 -10
  77. data/lib/action_view/helpers/javascripts/controls.js +41 -23
  78. data/lib/action_view/helpers/javascripts/dragdrop.js +105 -76
  79. data/lib/action_view/helpers/javascripts/effects.js +293 -163
  80. data/lib/action_view/helpers/javascripts/prototype.js +897 -389
  81. data/lib/action_view/helpers/javascripts/prototype.js.rej +561 -0
  82. data/lib/action_view/helpers/number_helper.rb +111 -65
  83. data/lib/action_view/helpers/prototype_helper.rb +84 -109
  84. data/lib/action_view/helpers/scriptaculous_helper.rb +5 -0
  85. data/lib/action_view/helpers/tag_helper.rb +69 -16
  86. data/lib/action_view/helpers/text_helper.rb +149 -112
  87. data/lib/action_view/helpers/url_helper.rb +200 -107
  88. data/lib/action_view/template_error.rb +66 -42
  89. data/test/abstract_unit.rb +4 -2
  90. data/test/active_record_unit.rb +84 -56
  91. data/test/activerecord/active_record_assertions_test.rb +26 -18
  92. data/test/activerecord/active_record_store_test.rb +4 -36
  93. data/test/activerecord/pagination_test.rb +1 -6
  94. data/test/controller/action_pack_assertions_test.rb +230 -113
  95. data/test/controller/addresses_render_test.rb +2 -6
  96. data/test/controller/assert_select_test.rb +576 -0
  97. data/test/controller/base_test.rb +73 -3
  98. data/test/controller/caching_test.rb +228 -0
  99. data/test/controller/capture_test.rb +12 -10
  100. data/test/controller/cgi_test.rb +89 -12
  101. data/test/controller/components_test.rb +24 -2
  102. data/test/controller/content_type_test.rb +139 -0
  103. data/test/controller/controller_fixtures/app/controllers/admin/user_controller.rb +0 -0
  104. data/test/controller/controller_fixtures/app/controllers/user_controller.rb +0 -0
  105. data/test/controller/controller_fixtures/vendor/plugins/bad_plugin/lib/plugin_controller.rb +0 -0
  106. data/test/controller/cookie_test.rb +33 -25
  107. data/test/controller/deprecated_instance_variables_test.rb +48 -0
  108. data/test/controller/deprecation/deprecated_base_methods_test.rb +60 -0
  109. data/test/controller/fake_controllers.rb +0 -1
  110. data/test/controller/filters_test.rb +301 -16
  111. data/test/controller/flash_test.rb +19 -2
  112. data/test/controller/helper_test.rb +2 -2
  113. data/test/controller/integration_test.rb +154 -0
  114. data/test/controller/layout_test.rb +115 -1
  115. data/test/controller/mime_responds_test.rb +94 -0
  116. data/test/controller/mime_type_test.rb +9 -0
  117. data/test/controller/new_render_test.rb +161 -11
  118. data/test/controller/raw_post_test.rb +52 -15
  119. data/test/controller/redirect_test.rb +27 -14
  120. data/test/controller/render_test.rb +76 -29
  121. data/test/controller/request_test.rb +55 -4
  122. data/test/controller/resources_test.rb +274 -0
  123. data/test/controller/routing_test.rb +1533 -824
  124. data/test/controller/selector_test.rb +628 -0
  125. data/test/controller/send_file_test.rb +9 -1
  126. data/test/controller/session_management_test.rb +51 -0
  127. data/test/controller/test_test.rb +113 -29
  128. data/test/controller/url_rewriter_test.rb +86 -17
  129. data/test/controller/verification_test.rb +19 -17
  130. data/test/controller/webservice_test.rb +0 -7
  131. data/test/fixtures/content_type/render_default_content_types_for_respond_to.rhtml +1 -0
  132. data/test/fixtures/content_type/render_default_for_rhtml.rhtml +1 -0
  133. data/test/fixtures/content_type/render_default_for_rjs.rjs +1 -0
  134. data/test/fixtures/content_type/render_default_for_rxml.rxml +1 -0
  135. data/test/fixtures/deprecated_instance_variables/_cookies_ivar.rhtml +1 -0
  136. data/test/fixtures/deprecated_instance_variables/_cookies_method.rhtml +1 -0
  137. data/test/fixtures/deprecated_instance_variables/_flash_ivar.rhtml +1 -0
  138. data/test/fixtures/deprecated_instance_variables/_flash_method.rhtml +1 -0
  139. data/test/fixtures/deprecated_instance_variables/_headers_ivar.rhtml +1 -0
  140. data/test/fixtures/deprecated_instance_variables/_headers_method.rhtml +1 -0
  141. data/test/fixtures/deprecated_instance_variables/_params_ivar.rhtml +1 -0
  142. data/test/fixtures/deprecated_instance_variables/_params_method.rhtml +1 -0
  143. data/test/fixtures/deprecated_instance_variables/_request_ivar.rhtml +1 -0
  144. data/test/fixtures/deprecated_instance_variables/_request_method.rhtml +1 -0
  145. data/test/fixtures/deprecated_instance_variables/_response_ivar.rhtml +1 -0
  146. data/test/fixtures/deprecated_instance_variables/_response_method.rhtml +1 -0
  147. data/test/fixtures/deprecated_instance_variables/_session_ivar.rhtml +1 -0
  148. data/test/fixtures/deprecated_instance_variables/_session_method.rhtml +1 -0
  149. data/test/fixtures/multipart/binary_file +0 -0
  150. data/test/fixtures/public/javascripts/application.js +1 -0
  151. data/test/fixtures/test/_hello.rxml +1 -0
  152. data/test/fixtures/test/hello_world_container.rxml +3 -0
  153. data/test/fixtures/topic.rb +2 -2
  154. data/test/template/active_record_helper_test.rb +83 -12
  155. data/test/template/asset_tag_helper_test.rb +75 -95
  156. data/test/template/compiled_templates_test.rb +1 -0
  157. data/test/template/date_helper_test.rb +873 -181
  158. data/test/template/deprecated_helper_test.rb +36 -0
  159. data/test/template/deprecated_instance_variables_test.rb +43 -0
  160. data/test/template/form_helper_test.rb +77 -1
  161. data/test/template/form_options_helper_test.rb +4 -0
  162. data/test/template/form_tag_helper_test.rb +66 -2
  163. data/test/template/java_script_macros_helper_test.rb +4 -1
  164. data/test/template/javascript_helper_test.rb +29 -0
  165. data/test/template/number_helper_test.rb +63 -27
  166. data/test/template/prototype_helper_test.rb +77 -34
  167. data/test/template/tag_helper_test.rb +34 -6
  168. data/test/template/text_helper_test.rb +69 -34
  169. data/test/template/url_helper_test.rb +168 -16
  170. data/test/testing_sandbox.rb +7 -22
  171. metadata +66 -20
  172. data/filler.txt +0 -50
  173. data/lib/action_controller/code_generation.rb +0 -235
  174. data/lib/action_controller/vendor/xml_simple.rb +0 -1019
  175. data/test/controller/caching_filestore.rb +0 -74
  176. data/test/fixtures/application_root/app/controllers/a_class_that_contains_a_controller/poorly_placed_controller.rb +0 -7
  177. data/test/fixtures/application_root/app/controllers/module_that_holds_controllers/nested_controller.rb +0 -3
  178. data/test/fixtures/application_root/app/models/a_class_that_contains_a_controller.rb +0 -7
  179. data/test/fixtures/dont_load.rb +0 -3
@@ -69,6 +69,11 @@ module ActionView
69
69
  # containing the values of the ids of elements the sortable consists
70
70
  # of, in the current order.
71
71
  #
72
+ # Important: For this to work, the sortable elements must have id
73
+ # attributes in the form "string_identifier". For example, "item_1". Only
74
+ # the identifier part of the id attribute will be serialized.
75
+ #
76
+ #
72
77
  # You can change the behaviour with various options, see
73
78
  # http://script.aculo.us for more documentation.
74
79
  def sortable_element(element_id, options = {})
@@ -2,39 +2,87 @@ require 'cgi'
2
2
  require 'erb'
3
3
 
4
4
  module ActionView
5
- module Helpers
6
- # This is poor man's Builder for the rare cases where you need to programmatically make tags but can't use Builder.
5
+ module Helpers #:nodoc:
6
+ # Use these methods to generate HTML tags programmatically when you can't use
7
+ # a Builder. By default, they output XHTML compliant tags.
7
8
  module TagHelper
8
9
  include ERB::Util
9
10
 
10
- # Examples:
11
- # * <tt>tag("br") => <br /></tt>
12
- # * <tt>tag("input", { "type" => "text"}) => <input type="text" /></tt>
11
+ # Returns an empty HTML tag of type +name+ which by default is XHTML
12
+ # compliant. Setting +open+ to true will create an open tag compatible
13
+ # with HTML 4.0 and below. Add HTML attributes by passing an attributes
14
+ # hash to +options+. For attributes with no value like (disabled and
15
+ # readonly), give it a value of true in the +options+ hash. You can use
16
+ # symbols or strings for the attribute names.
17
+ #
18
+ # tag("br")
19
+ # # => <br />
20
+ # tag("br", nil, true)
21
+ # # => <br>
22
+ # tag("input", { :type => 'text', :disabled => true })
23
+ # # => <input type="text" disabled="disabled" />
13
24
  def tag(name, options = nil, open = false)
14
- "<#{name}#{tag_options(options.stringify_keys) if options}" + (open ? ">" : " />")
25
+ "<#{name}#{tag_options(options) if options}" + (open ? ">" : " />")
15
26
  end
16
27
 
17
- # Examples:
18
- # * <tt>content_tag("p", "Hello world!") => <p>Hello world!</p></tt>
19
- # * <tt>content_tag("div", content_tag("p", "Hello world!"), "class" => "strong") => </tt>
20
- # <tt><div class="strong"><p>Hello world!</p></div></tt>
21
- def content_tag(name, content, options = nil)
22
- "<#{name}#{tag_options(options.stringify_keys) if options}>#{content}</#{name}>"
28
+ # Returns an HTML block tag of type +name+ surrounding the +content+. Add
29
+ # HTML attributes by passing an attributes hash to +options+. For attributes
30
+ # with no value like (disabled and readonly), give it a value of true in
31
+ # the +options+ hash. You can use symbols or strings for the attribute names.
32
+ #
33
+ # content_tag(:p, "Hello world!")
34
+ # # => <p>Hello world!</p>
35
+ # content_tag(:div, content_tag(:p, "Hello world!"), :class => "strong")
36
+ # # => <div class="strong"><p>Hello world!</p></div>
37
+ # content_tag("select", options, :multiple => true)
38
+ # # => <select multiple="multiple">...options...</select>
39
+ #
40
+ # Instead of passing the content as an argument, you can also use a block
41
+ # in which case, you pass your +options+ as the second parameter.
42
+ #
43
+ # <% content_tag :div, :class => "strong" do -%>
44
+ # Hello world!
45
+ # <% end -%>
46
+ # # => <div class="strong"><p>Hello world!</p></div>
47
+ def content_tag(name, content_or_options_with_block = nil, options = nil, &block)
48
+ if block_given?
49
+ options = content_or_options_with_block if content_or_options_with_block.is_a?(Hash)
50
+ content = capture(&block)
51
+ concat(content_tag_string(name, content, options), block.binding)
52
+ else
53
+ content = content_or_options_with_block
54
+ content_tag_string(name, content, options)
55
+ end
23
56
  end
24
57
 
25
- # Returns a CDATA section for the given +content+. CDATA sections
58
+ # Returns a CDATA section with the given +content+. CDATA sections
26
59
  # are used to escape blocks of text containing characters which would
27
60
  # otherwise be recognized as markup. CDATA sections begin with the string
28
- # <tt>&lt;![CDATA[</tt> and end with (and may not contain) the string
29
- # <tt>]]></tt>.
61
+ # <tt><![CDATA[</tt> and end with (and may not contain) the string <tt>]]></tt>.
62
+ #
63
+ # cdata_section("<hello world>")
64
+ # # => <![CDATA[<hello world>]]>
30
65
  def cdata_section(content)
31
66
  "<![CDATA[#{content}]]>"
32
67
  end
33
68
 
69
+ # Returns the escaped +html+ without affecting existing escaped entities.
70
+ #
71
+ # escape_once("1 > 2 &amp; 3")
72
+ # # => "1 &lt; 2 &amp; 3"
73
+ def escape_once(html)
74
+ fix_double_escape(html_escape(html.to_s))
75
+ end
76
+
34
77
  private
78
+ def content_tag_string(name, content, options)
79
+ tag_options = options ? tag_options(options) : ""
80
+ "<#{name}#{tag_options}>#{content}</#{name}>"
81
+ end
82
+
35
83
  def tag_options(options)
36
84
  cleaned_options = convert_booleans(options.stringify_keys.reject {|key, value| value.nil?})
37
- ' ' + cleaned_options.map {|key, value| %(#{key}="#{html_escape(value.to_s)}")}.sort * ' ' unless cleaned_options.empty?
85
+ ' ' + cleaned_options.map {|key, value| %(#{key}="#{escape_once(value)}")}.sort * ' ' unless cleaned_options.empty?
38
86
  end
39
87
 
40
88
  def convert_booleans(options)
@@ -45,6 +93,11 @@ module ActionView
45
93
  def boolean_attribute(options, attribute)
46
94
  options[attribute] ? options[attribute] = attribute : options.delete(attribute)
47
95
  end
96
+
97
+ # Fix double-escaped entities, such as &amp;amp;, &amp;#123;, etc.
98
+ def fix_double_escape(escaped)
99
+ escaped.gsub(/&amp;([a-z]+|(#\d+));/i) { "&#{$1};" }
100
+ end
48
101
  end
49
102
  end
50
103
  end
@@ -2,65 +2,90 @@ require File.dirname(__FILE__) + '/tag_helper'
2
2
 
3
3
  module ActionView
4
4
  module Helpers #:nodoc:
5
- # Provides a set of methods for working with text strings that can help unburden the level of inline Ruby code in the
6
- # templates. In the example below we iterate over a collection of posts provided to the template and print each title
7
- # after making sure it doesn't run longer than 20 characters:
8
- # <% for post in @posts %>
9
- # Title: <%= truncate(post.title, 20) %>
5
+ # The TextHelper Module provides a set of methods for filtering, formatting
6
+ # and transforming strings that can reduce the amount of inline Ruby code in
7
+ # your views. These helper methods extend ActionView making them callable
8
+ # within your template files as shown in the following example which truncates
9
+ # the title of each post to 10 characters.
10
+ #
11
+ # <% @posts.each do |post| %>
12
+ # # post == 'This is my title'
13
+ # Title: <%= truncate(post.title, 10) %>
10
14
  # <% end %>
15
+ # => Title: This is my...
11
16
  module TextHelper
12
- # The regular puts and print are outlawed in eRuby. It's recommended to use the <%= "hello" %> form instead of print "hello".
13
- # If you absolutely must use a method-based output, you can use concat. It's used like this: <% concat "hello", binding %>. Notice that
14
- # it doesn't have an equal sign in front. Using <%= concat "hello" %> would result in a double hello.
17
+ # The preferred method of outputting text in your views is to use the
18
+ # <%= "text" %> eRuby syntax. The regular _puts_ and _print_ methods
19
+ # do not operate as expected in an eRuby code block. If you absolutely must
20
+ # output text within a code block, you can use the concat method.
21
+ #
22
+ # <% concat "hello", binding %>
23
+ # is equivalent to using:
24
+ # <%= "hello" %>
15
25
  def concat(string, binding)
16
26
  eval("_erbout", binding).concat(string)
17
27
  end
18
28
 
19
- # Truncates +text+ to the length of +length+ and replaces the last three characters with the +truncate_string+
20
- # if the +text+ is longer than +length+.
29
+ # If +text+ is longer than +length+, +text+ will be truncated to the length of
30
+ # +length+ and the last three characters will be replaced with the +truncate_string+.
31
+ #
32
+ # truncate("Once upon a time in a world far far away", 14)
33
+ # => Once upon a...
21
34
  def truncate(text, length = 30, truncate_string = "...")
22
35
  if text.nil? then return end
23
- l = length - truncate_string.length
24
- if $KCODE == "NONE"
25
- text.length > length ? text[0...l] + truncate_string : text
26
- else
27
- chars = text.split(//)
28
- chars.length > length ? chars[0...l].join + truncate_string : text
29
- end
36
+ l = length - truncate_string.chars.length
37
+ text.chars.length > length ? text.chars[0...l] + truncate_string : text
30
38
  end
31
39
 
32
- # Highlights the +phrase+ where it is found in the +text+ by surrounding it like
33
- # <strong class="highlight">I'm a highlight phrase</strong>. The highlighter can be specialized by
34
- # passing +highlighter+ as single-quoted string with \1 where the phrase is supposed to be inserted.
35
- # N.B.: The +phrase+ is sanitized to include only letters, digits, and spaces before use.
40
+ # Highlights +phrase+ everywhere it is found in +text+ by inserting it into
41
+ # a +highlighter+ string. The highlighter can be specialized by passing +highlighter+
42
+ # as a single-quoted string with \1 where the phrase is to be inserted.
43
+ #
44
+ # highlight('You searched for: rails', 'rails')
45
+ # => You searched for: <strong class="highlight">rails</strong>
36
46
  def highlight(text, phrase, highlighter = '<strong class="highlight">\1</strong>')
37
47
  if phrase.blank? then return text end
38
48
  text.gsub(/(#{Regexp.escape(phrase)})/i, highlighter) unless text.nil?
39
49
  end
40
50
 
41
- # Extracts an excerpt from the +text+ surrounding the +phrase+ with a number of characters on each side determined
42
- # by +radius+. If the phrase isn't found, nil is returned. Ex:
43
- # excerpt("hello my world", "my", 3) => "...lo my wo..."
51
+ # Extracts an excerpt from +text+ that matches the first instance of +phrase+.
52
+ # The +radius+ expands the excerpt on each side of +phrase+ by the number of characters
53
+ # defined in +radius+. If the excerpt radius overflows the beginning or end of the +text+,
54
+ # then the +excerpt_string+ will be prepended/appended accordingly. If the +phrase+
55
+ # isn't found, nil is returned.
56
+ #
57
+ # excerpt('This is an example', 'an', 5)
58
+ # => "...s is an examp..."
59
+ #
60
+ # excerpt('This is an example', 'is', 5)
61
+ # => "This is an..."
44
62
  def excerpt(text, phrase, radius = 100, excerpt_string = "...")
45
63
  if text.nil? || phrase.nil? then return end
46
64
  phrase = Regexp.escape(phrase)
47
65
 
48
- if found_pos = text =~ /(#{phrase})/i
66
+ if found_pos = text.chars =~ /(#{phrase})/i
49
67
  start_pos = [ found_pos - radius, 0 ].max
50
- end_pos = [ found_pos + phrase.length + radius, text.length ].min
68
+ end_pos = [ found_pos + phrase.chars.length + radius, text.chars.length ].min
51
69
 
52
70
  prefix = start_pos > 0 ? excerpt_string : ""
53
- postfix = end_pos < text.length ? excerpt_string : ""
71
+ postfix = end_pos < text.chars.length ? excerpt_string : ""
54
72
 
55
- prefix + text[start_pos..end_pos].strip + postfix
73
+ prefix + text.chars[start_pos..end_pos].strip + postfix
56
74
  else
57
75
  nil
58
76
  end
59
77
  end
60
78
 
61
- # Attempts to pluralize the +singular+ word unless +count+ is 1. See source for pluralization rules.
79
+ # Attempts to pluralize the +singular+ word unless +count+ is 1. If +plural+
80
+ # is supplied, it will use that when count is > 1, if the ActiveSupport Inflector
81
+ # is loaded, it will use the Inflector to determine the plural form, otherwise
82
+ # it will just add an 's' to the +singular+ word.
83
+ #
84
+ # pluralize(1, 'person') => 1 person
85
+ # pluralize(2, 'person') => 2 people
86
+ # pluralize(3, 'person', 'users') => 3 users
62
87
  def pluralize(count, singular, plural = nil)
63
- "#{count} " + if count == 1
88
+ "#{count} " + if count == 1 || count == '1'
64
89
  singular
65
90
  elsif plural
66
91
  plural
@@ -71,7 +96,11 @@ module ActionView
71
96
  end
72
97
  end
73
98
 
74
- # Word wrap long lines to line_width.
99
+ # Wraps the +text+ into lines no longer than +line_width+ width. This method
100
+ # breaks on the first whitespace character that does not exceed +line_width+.
101
+ #
102
+ # word_wrap('Once upon a time', 4)
103
+ # => Once\nupon\na\ntime
75
104
  def word_wrap(text, line_width = 80)
76
105
  text.gsub(/\n/, "\n\n").gsub(/(.{1,#{line_width}})(\s+|$)/, "\\1\n").strip
77
106
  end
@@ -79,8 +108,9 @@ module ActionView
79
108
  begin
80
109
  require_library_or_gem "redcloth" unless Object.const_defined?(:RedCloth)
81
110
 
82
- # Returns the text with all the Textile codes turned into HTML-tags.
83
- # <i>This method is only available if RedCloth can be required</i>.
111
+ # Returns the text with all the Textile codes turned into HTML tags.
112
+ # <i>This method is only available if RedCloth[http://whytheluckystiff.net/ruby/redcloth/]
113
+ # is available</i>.
84
114
  def textilize(text)
85
115
  if text.blank?
86
116
  ""
@@ -91,8 +121,10 @@ module ActionView
91
121
  end
92
122
  end
93
123
 
94
- # Returns the text with all the Textile codes turned into HTML-tags, but without the regular bounding <p> tag.
95
- # <i>This method is only available if RedCloth can be required</i>.
124
+ # Returns the text with all the Textile codes turned into HTML tags,
125
+ # but without the bounding <p> tag that RedCloth adds.
126
+ # <i>This method is only available if RedCloth[http://whytheluckystiff.net/ruby/redcloth/]
127
+ # is available</i>.
96
128
  def textilize_without_paragraph(text)
97
129
  textiled = textilize(text)
98
130
  if textiled[0..2] == "<p>" then textiled = textiled[3..-1] end
@@ -106,8 +138,9 @@ module ActionView
106
138
  begin
107
139
  require_library_or_gem "bluecloth" unless Object.const_defined?(:BlueCloth)
108
140
 
109
- # Returns the text with all the Markdown codes turned into HTML-tags.
110
- # <i>This method is only available if BlueCloth can be required</i>.
141
+ # Returns the text with all the Markdown codes turned into HTML tags.
142
+ # <i>This method is only available if BlueCloth[http://www.deveiate.org/projects/BlueCloth]
143
+ # is available</i>.
111
144
  def markdown(text)
112
145
  text.blank? ? "" : BlueCloth.new(text).to_html
113
146
  end
@@ -115,29 +148,30 @@ module ActionView
115
148
  # We can't really help what's not there
116
149
  end
117
150
 
118
- # Returns +text+ transformed into HTML using very simple formatting rules
119
- # Surrounds paragraphs with <tt><p></tt> tags, and converts line breaks into <tt><br/></tt>
120
- # Two consecutive newlines(<tt>\n\n</tt>) are considered as a paragraph, one newline (<tt>\n</tt>) is
121
- # considered a linebreak, three or more consecutive newlines are turned into two newlines
151
+ # Returns +text+ transformed into HTML using simple formatting rules.
152
+ # Two or more consecutive newlines(<tt>\n\n</tt>) are considered as a
153
+ # paragraph and wrapped in <tt><p></tt> tags. One newline (<tt>\n</tt>) is
154
+ # considered as a linebreak and a <tt><br /></tt> tag is appended. This
155
+ # method does not remove the newlines from the +text+.
122
156
  def simple_format(text)
123
- text.gsub!(/(\r\n|\n|\r)/, "\n") # lets make them newlines crossplatform
124
- text.gsub!(/\n\n+/, "\n\n") # zap dupes
125
- text.gsub!(/\n\n/, '</p>\0<p>') # turn two newlines into paragraph
126
- text.gsub!(/([^\n])(\n)([^\n])/, '\1\2<br />\3') # turn single newline into <br />
127
-
128
- content_tag("p", text)
157
+ content_tag 'p', text.to_s.
158
+ gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
159
+ gsub(/\n\n+/, "</p>\n\n<p>"). # 2+ newline -> paragraph
160
+ gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
129
161
  end
130
162
 
131
- # Turns all urls and email addresses into clickable links. The +link+ parameter can limit what should be linked.
132
- # Options are <tt>:all</tt> (default), <tt>:email_addresses</tt>, and <tt>:urls</tt>.
163
+ # Turns all urls and email addresses into clickable links. The +link+ parameter
164
+ # will limit what should be linked. You can add html attributes to the links using
165
+ # +href_options+. Options for +link+ are <tt>:all</tt> (default),
166
+ # <tt>:email_addresses</tt>, and <tt>:urls</tt>.
133
167
  #
134
- # Example:
135
- # auto_link("Go to http://www.rubyonrails.com and say hello to david@loudthinking.com") =>
136
- # Go to <a href="http://www.rubyonrails.com">http://www.rubyonrails.com</a> and
168
+ # auto_link("Go to http://www.rubyonrails.org and say hello to david@loudthinking.com") =>
169
+ # Go to <a href="http://www.rubyonrails.org">http://www.rubyonrails.org</a> and
137
170
  # say hello to <a href="mailto:david@loudthinking.com">david@loudthinking.com</a>
138
171
  #
139
172
  # If a block is given, each url and email address is yielded and the
140
- # result is used as the link text. Example:
173
+ # result is used as the link text.
174
+ #
141
175
  # auto_link(post.body, :all, :target => '_blank') do |text|
142
176
  # truncate(text, 15)
143
177
  # end
@@ -150,9 +184,12 @@ module ActionView
150
184
  end
151
185
  end
152
186
 
153
- # Turns all links into words, like "<a href="something">else</a>" to "else".
187
+ # Strips link tags from +text+ leaving just the link label.
188
+ #
189
+ # strip_links('<a href="http://www.rubyonrails.org">Ruby on Rails</a>')
190
+ # => Ruby on Rails
154
191
  def strip_links(text)
155
- text.gsub(/<a.*>(.*)<\/a>/m, '\1')
192
+ text.gsub(/<a\b.*?>(.*?)<\/a>/mi, '\1')
156
193
  end
157
194
 
158
195
  # Try to require the html-scanner library
@@ -161,22 +198,26 @@ module ActionView
161
198
  require 'html/node'
162
199
  rescue LoadError
163
200
  # if there isn't a copy installed, use the vendor version in
164
- # action controller
201
+ # ActionController
165
202
  $:.unshift File.join(File.dirname(__FILE__), "..", "..",
166
203
  "action_controller", "vendor", "html-scanner")
167
204
  require 'html/tokenizer'
168
205
  require 'html/node'
169
206
  end
170
207
 
171
- VERBOTEN_TAGS = %w(form script) unless defined?(VERBOTEN_TAGS)
208
+ VERBOTEN_TAGS = %w(form script plaintext) unless defined?(VERBOTEN_TAGS)
172
209
  VERBOTEN_ATTRS = /^on/i unless defined?(VERBOTEN_ATTRS)
173
210
 
174
- # Sanitizes the given HTML by making form and script tags into regular
211
+ # Sanitizes the +html+ by converting <form> and <script> tags into regular
175
212
  # text, and removing all "onxxx" attributes (so that arbitrary Javascript
176
- # cannot be executed). Also removes href attributes that start with
177
- # "javascript:".
213
+ # cannot be executed). It also removes href= and src= attributes that start with
214
+ # "javascript:". You can modify what gets sanitized by defining VERBOTEN_TAGS
215
+ # and VERBOTEN_ATTRS before this Module is loaded.
178
216
  #
179
- # Returns the sanitized text.
217
+ # sanitize('<script> do_nasty_stuff() </script>')
218
+ # => &lt;script> do_nasty_stuff() &lt;/script>
219
+ # sanitize('<a href="javascript: sucker();">Click here for $100</a>')
220
+ # => <a>Click here for $100</a>
180
221
  def sanitize(html)
181
222
  # only do this if absolutely necessary
182
223
  if html.index("<")
@@ -192,8 +233,8 @@ module ActionView
192
233
  else
193
234
  if node.closing != :close
194
235
  node.attributes.delete_if { |attr,v| attr =~ VERBOTEN_ATTRS }
195
- if node.attributes["href"] =~ /^javascript:/i
196
- node.attributes.delete "href"
236
+ %w(href src).each do |attr|
237
+ node.attributes.delete attr if node.attributes[attr] =~ /^javascript:/i
197
238
  end
198
239
  end
199
240
  node.to_s
@@ -209,11 +250,11 @@ module ActionView
209
250
  html
210
251
  end
211
252
 
212
- # Strips all HTML tags from the input, including comments. This uses the html-scanner
213
- # tokenizer and so it's HTML parsing ability is limited by that of html-scanner.
214
- #
215
- # Returns the tag free text.
216
- def strip_tags(html)
253
+ # Strips all HTML tags from the +html+, including comments. This uses the
254
+ # html-scanner tokenizer and so its HTML parsing ability is limited by
255
+ # that of html-scanner.
256
+ def strip_tags(html)
257
+ return html if html.blank?
217
258
  if html.index("<")
218
259
  text = ""
219
260
  tokenizer = HTML::Tokenizer.new(html)
@@ -231,32 +272,33 @@ module ActionView
231
272
  end
232
273
  end
233
274
 
234
- # Returns a Cycle object whose to_s value cycles through items of an
235
- # array every time it is called. This can be used to alternate classes
236
- # for table rows:
275
+ # Creates a Cycle object whose _to_s_ method cycles through elements of an
276
+ # array every time it is called. This can be used for example, to alternate
277
+ # classes for table rows:
237
278
  #
238
- # <%- for item in @items do -%>
239
- # <tr class="<%= cycle("even", "odd") %>">
240
- # ... use item ...
279
+ # <% @items.each do |item| %>
280
+ # <tr class="<%= cycle("even", "odd") -%>">
281
+ # <td>item</td>
241
282
  # </tr>
242
- # <%- end -%>
283
+ # <% end %>
243
284
  #
244
- # You can use named cycles to prevent clashes in nested loops. You'll
245
- # have to reset the inner cycle, manually:
285
+ # You can use named cycles to allow nesting in loops. Passing a Hash as
286
+ # the last parameter with a <tt>:name</tt> key will create a named cycle.
287
+ # You can manually reset a cycle by calling reset_cycle and passing the
288
+ # name of the cycle.
246
289
  #
247
- # <%- for item in @items do -%>
290
+ # <% @items.each do |item| %>
248
291
  # <tr class="<%= cycle("even", "odd", :name => "row_class")
249
292
  # <td>
250
- # <%- for value in item.values do -%>
251
- # <span style="color:'<%= cycle("red", "green", "blue"
252
- # :name => "colors") %>'">
253
- # item
293
+ # <% item.values.each do |value| %>
294
+ # <span style="color:<%= cycle("red", "green", "blue", :name => "colors") -%>">
295
+ # value
254
296
  # </span>
255
- # <%- end -%>
256
- # <%- reset_cycle("colors") -%>
297
+ # <% end %>
298
+ # <% reset_cycle("colors") %>
257
299
  # </td>
258
300
  # </tr>
259
- # <%- end -%>
301
+ # <% end %>
260
302
  def cycle(first_value, *values)
261
303
  if (values.last.instance_of? Hash)
262
304
  params = values.pop
@@ -273,12 +315,11 @@ module ActionView
273
315
  return cycle.to_s
274
316
  end
275
317
 
276
- # Resets a cycle so that it starts from the first element in the array
277
- # the next time it is used.
318
+ # Resets a cycle so that it starts from the first element the next time
319
+ # it is called. Pass in +name+ to reset a named cycle.
278
320
  def reset_cycle(name = "default")
279
321
  cycle = get_cycle(name)
280
- return if cycle.nil?
281
- cycle.reset
322
+ cycle.reset unless cycle.nil?
282
323
  end
283
324
 
284
325
  class Cycle #:nodoc:
@@ -305,42 +346,42 @@ module ActionView
305
346
  # guaranteed to be reset every time a page is rendered, so it
306
347
  # uses an instance variable of ActionView::Base.
307
348
  def get_cycle(name)
308
- @_cycles = Hash.new if @_cycles.nil?
349
+ @_cycles = Hash.new unless defined?(@_cycles)
309
350
  return @_cycles[name]
310
351
  end
311
352
 
312
353
  def set_cycle(name, cycle_object)
313
- @_cycles = Hash.new if @_cycles.nil?
354
+ @_cycles = Hash.new unless defined?(@_cycles)
314
355
  @_cycles[name] = cycle_object
315
356
  end
316
357
 
317
- AUTO_LINK_RE = /
318
- ( # leading text
319
- <\w+.*?>| # leading HTML tag, or
320
- [^=!:'"\/]| # leading punctuation, or
321
- ^ # beginning of line
358
+ AUTO_LINK_RE = %r{
359
+ ( # leading text
360
+ <\w+.*?>| # leading HTML tag, or
361
+ [^=!:'"/]| # leading punctuation, or
362
+ ^ # beginning of line
322
363
  )
323
364
  (
324
- (?:http[s]?:\/\/)| # protocol spec, or
325
- (?:www\.) # www.*
365
+ (?:https?://)| # protocol spec, or
366
+ (?:www\.) # www.*
326
367
  )
327
368
  (
328
- ([\w]+:?[=?&\/.-]?)* # url segment
329
- \w+[\/]? # url tail
330
- (?:\#\w*)? # trailing anchor
369
+ [-\w]+ # subdomain or domain
370
+ (?:\.[-\w]+)* # remaining subdomains or domain
371
+ (?::\d+)? # port
372
+ (?:/(?:[~\w%.;-]+)?)* # path
373
+ (?:\?[\w%&=.;-]+)? # query string
374
+ (?:\#\w*)? # trailing anchor
331
375
  )
332
- ([[:punct:]]|\s|<|$) # trailing text
333
- /x unless const_defined?(:AUTO_LINK_RE)
376
+ ([[:punct:]]|\s|<|$) # trailing text
377
+ }x unless const_defined?(:AUTO_LINK_RE)
334
378
 
335
379
  # Turns all urls into clickable links. If a block is given, each url
336
- # is yielded and the result is used as the link text. Example:
337
- # auto_link_urls(post.body, :all, :target => '_blank') do |text|
338
- # truncate(text, 15)
339
- # end
380
+ # is yielded and the result is used as the link text.
340
381
  def auto_link_urls(text, href_options = {})
341
382
  extra_options = tag_options(href_options.stringify_keys) || ""
342
383
  text.gsub(AUTO_LINK_RE) do
343
- all, a, b, c, d = $&, $1, $2, $3, $5
384
+ all, a, b, c, d = $&, $1, $2, $3, $4
344
385
  if a =~ /<a\s/i # don't replace URL's that are already linked
345
386
  all
346
387
  else
@@ -353,10 +394,6 @@ module ActionView
353
394
 
354
395
  # Turns all email addresses into clickable links. If a block is given,
355
396
  # each email is yielded and the result is used as the link text.
356
- # Example:
357
- # auto_link_email_addresses(post.body) do |text|
358
- # truncate(text, 15)
359
- # end
360
397
  def auto_link_email_addresses(text)
361
398
  text.gsub(/([\w\.!#\$%\-+.]+@[A-Za-z0-9\-]+(\.[A-Za-z0-9\-]+)+)/) do
362
399
  text = $1