diff options
-rw-r--r-- | actionpack/CHANGELOG.md | 7 | ||||
-rw-r--r-- | actionpack/lib/action_view/helpers/text_helper.rb | 34 | ||||
-rw-r--r-- | actionpack/test/template/text_helper_test.rb | 67 | ||||
-rw-r--r-- | activesupport/lib/active_support/core_ext/string/filters.rb | 2 | ||||
-rw-r--r-- | activesupport/test/core_ext/string_ext_test.rb | 4 | ||||
-rw-r--r-- | guides/source/initialization.textile | 96 | ||||
-rw-r--r-- | railties/lib/rails/application.rb | 2 | ||||
-rw-r--r-- | railties/test/railties/engine_test.rb | 4 |
8 files changed, 125 insertions, 91 deletions
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index 920858d8c0..ba66b525bc 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,5 +1,12 @@ ## Rails 4.0.0 (unreleased) ## +* `truncate` now always returns an escaped HTMl-safe string. The option `:escape` can be used as + false to not escape the result. + + *Li Ellis Gallardo + Rafael Mendonça França* + +* `truncate` now accepts a block to show extra content when the text is truncated. *Li Ellis Gallardo* + * Add `week_field`, `week_field_tag`, `month_field`, `month_field_tag`, `datetime_local_field`, `datetime_local_field_tag`, `datetime_field` and `datetime_field_tag` helpers. *Carlos Galdino* diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb index 8cd7cf0052..0cc0d069ea 100644 --- a/actionpack/lib/action_view/helpers/text_helper.rb +++ b/actionpack/lib/action_view/helpers/text_helper.rb @@ -62,9 +62,11 @@ module ActionView # # Pass a <tt>:separator</tt> to truncate +text+ at a natural break. # - # The result is not marked as HTML-safe, so will be subject to the default escaping when - # used in views, unless wrapped by <tt>raw()</tt>. Care should be taken if +text+ contains HTML tags - # or entities, because truncation may produce invalid HTML (such as unbalanced or incomplete tags). + # Pass a block if you want to show extra content when the text is truncated. + # + # The result is marked as HTML-safe, but it is escaped by default, unless <tt>:escape</tt> is + # +false+. Care should be taken if +text+ contains HTML tags or entities, because truncation + # may produce invalid HTML (such as unbalanced or incomplete tags). # # truncate("Once upon a time in a world far far away") # # => "Once upon a time in a world..." @@ -80,8 +82,18 @@ module ActionView # # truncate("<p>Once upon a time in a world far far away</p>") # # => "<p>Once upon a time in a wo..." - def truncate(text, options = {}) - text.truncate(options.fetch(:length, 30), options) if text + # + # truncate("Once upon a time in a world far far away") { link_to "Continue", "#" } + # # => "Once upon a time in a wo...<a href="#">Continue</a>" + def truncate(text, options = {}, &block) + if text + length = options.fetch(:length, 30) + + content = text.truncate(length, options) + content = options[:escape] == false ? content.html_safe : ERB::Util.html_escape(content) + content << capture(&block) if block_given? && text.length > length + content + end end # Highlights one or more +phrases+ everywhere in +text+ by inserting it into @@ -102,7 +114,7 @@ module ActionView # # => You searched for: <a href="search?q=rails">rails</a> def highlight(text, phrases, options = {}) highlighter = options.fetch(:highlighter, '<mark>\1</mark>') - + text = sanitize(text) if options.fetch(:sanitize, true) if text.blank? || phrases.blank? text @@ -165,12 +177,12 @@ module ActionView # pluralize(0, 'person') # # => 0 people def pluralize(count, singular, plural = nil) - word = if (count == 1 || count =~ /^1(\.0+)?$/) - singular + word = if (count == 1 || count =~ /^1(\.0+)?$/) + singular else plural || singular.pluralize end - + "#{count || 0} #{word}" end @@ -215,7 +227,7 @@ module ActionView # # simple_format(my_text) # # => "<p>Here is some basic text...\n<br />...with a line break.</p>" - # + # # simple_format(my_text, {}, :wrapper_tag => "div") # # => "<div>Here is some basic text...\n<br />...with a line break.</div>" # @@ -231,7 +243,7 @@ module ActionView # # => "<p><span>I'm allowed!</span> It's true.</p>" def simple_format(text, html_options = {}, options = {}) wrapper_tag = options.fetch(:wrapper_tag, :p) - + text = sanitize(text) if options.fetch(:sanitize, true) paragraphs = split_paragraphs(text) diff --git a/actionpack/test/template/text_helper_test.rb b/actionpack/test/template/text_helper_test.rb index f58e474759..a3ab091c6c 100644 --- a/actionpack/test/template/text_helper_test.rb +++ b/actionpack/test/template/text_helper_test.rb @@ -60,14 +60,14 @@ class TextHelperTest < ActionView::TestCase simple_format(text) assert_equal text_clone, text end - + def test_simple_format_does_not_modify_the_html_options_hash options = { :class => "foobar"} passed_options = options.dup simple_format("some text", passed_options) assert_equal options, passed_options end - + def test_simple_format_does_not_modify_the_options_hash options = { :wrapper_tag => :div, :sanitize => false } passed_options = options.dup @@ -75,19 +75,11 @@ class TextHelperTest < ActionView::TestCase assert_equal options, passed_options end - def test_truncate_should_not_be_html_safe - assert !truncate("Hello World!", :length => 12).html_safe? - end - def test_truncate assert_equal "Hello World!", truncate("Hello World!", :length => 12) assert_equal "Hello Wor...", truncate("Hello World!!", :length => 12) end - def test_truncate_should_not_escape_input - assert_equal "Hello <sc...", truncate("Hello <script>code!</script>World!!", :length => 12) - end - def test_truncate_should_use_default_length_of_30 str = "This is a string that will go longer then the default truncate length of 30" assert_equal str[0...27] + "...", truncate(str) @@ -106,7 +98,7 @@ class TextHelperTest < ActionView::TestCase assert_equal "\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 ...".force_encoding('UTF-8'), truncate("\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 \354\225\204\353\235\274\353\246\254\354\230\244".force_encoding('UTF-8'), :length => 10) end - + def test_truncate_does_not_modify_the_options_hash options = { :length => 10 } passed_options = options.dup @@ -114,6 +106,53 @@ class TextHelperTest < ActionView::TestCase assert_equal options, passed_options end + def test_truncate_with_link_options + assert_equal "Here's a long test and I...<a href=\"#\">Continue</a>", + truncate("Here's a long test and I need a continue to read link", :length => 27) { link_to 'Continue', '#' } + end + + def test_truncate_should_be_html_safe + assert truncate("Hello World!", :length => 12).html_safe? + end + + def test_truncate_should_escape_the_input + assert_equal "Hello <sc...", truncate("Hello <script>code!</script>World!!", :length => 12) + end + + def test_truncate_should_not_escape_the_input_with_escape_false + assert_equal "Hello <sc...", truncate("Hello <script>code!</script>World!!", :length => 12, :escape => false) + end + + def test_truncate_with_escape_false_should_be_html_safe + truncated = truncate("Hello <script>code!</script>World!!", :length => 12, :escape => false) + assert truncated.html_safe? + end + + def test_truncate_with_block_should_be_html_safe + truncated = truncate("Here's a long test and I need a continue to read link", :length => 27) { link_to 'Continue', '#' } + assert truncated.html_safe? + end + + def test_truncate_with_block_should_escape_the_input + assert_equal "<script>code!</script>He...<a href=\"#\">Continue</a>", + truncate("<script>code!</script>Here's a long test and I need a continue to read link", :length => 27) { link_to 'Continue', '#' } + end + + def test_truncate_with_block_should_not_escape_the_input_with_escape_false + assert_equal "<script>code!</script>He...<a href=\"#\">Continue</a>", + truncate("<script>code!</script>Here's a long test and I need a continue to read link", :length => 27, :escape => false) { link_to 'Continue', '#' } + end + + def test_truncate_with_block_with_escape_false_should_be_html_safe + truncated = truncate("<script>code!</script>Here's a long test and I need a continue to read link", :length => 27, :escape => false) { link_to 'Continue', '#' } + assert truncated.html_safe? + end + + def test_truncate_with_block_should_escape_the_block + assert_equal "Here's a long test and I...<script>alert('foo');</script>", + truncate("Here's a long test and I need a continue to read link", :length => 27) { "<script>alert('foo');</script>" } + end + def test_highlight_should_be_html_safe assert highlight("This is a beautiful morning", "beautiful").html_safe? end @@ -203,7 +242,7 @@ class TextHelperTest < ActionView::TestCase highlight("<div>abc div</div>", "div", :highlighter => '<b>\1</b>') ) end - + def test_highlight_does_not_modify_the_options_hash options = { :highlighter => '<b>\1</b>', :sanitize => false } passed_options = options.dup @@ -256,7 +295,7 @@ class TextHelperTest < ActionView::TestCase def test_excerpt_with_utf8 assert_equal("...\357\254\203ciency could not be...".force_encoding('UTF-8'), excerpt("That's why e\357\254\203ciency could not be helped".force_encoding('UTF-8'), 'could', :radius => 8)) end - + def test_excerpt_does_not_modify_the_options_hash options = { :omission => "[...]",:radius => 5 } passed_options = options.dup @@ -271,7 +310,7 @@ class TextHelperTest < ActionView::TestCase def test_word_wrap_with_extra_newlines assert_equal("my very very\nvery long\nstring\n\nwith another\nline", word_wrap("my very very very long string\n\nwith another line", :line_width => 15)) end - + def test_word_wrap_does_not_modify_the_options_hash options = { :line_width => 15 } passed_options = options.dup diff --git a/activesupport/lib/active_support/core_ext/string/filters.rb b/activesupport/lib/active_support/core_ext/string/filters.rb index 2478f42290..70f2dcb562 100644 --- a/activesupport/lib/active_support/core_ext/string/filters.rb +++ b/activesupport/lib/active_support/core_ext/string/filters.rb @@ -33,7 +33,7 @@ class String # # => "Once upon a time in a..." # # The last characters will be replaced with the <tt>:omission</tt> string (defaults to "...") - # for a total length not exceeding <tt>:length</tt>: + # for a total length not exceeding <tt>length</tt>: # # 'And they found that many people were sleeping better.'.truncate(25, :omission => '... (continued)') # # => "And they f... (continued)" diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb index 8437ef1347..e5b774425e 100644 --- a/activesupport/test/core_ext/string_ext_test.rb +++ b/activesupport/test/core_ext/string_ext_test.rb @@ -290,6 +290,10 @@ class StringInflectionsTest < ActiveSupport::TestCase "\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 \354\225\204\353\235\274\353\246\254\354\230\244".force_encoding('UTF-8').truncate(10) end + def test_truncate_should_not_be_html_safe + assert !"Hello World!".truncate(12).html_safe? + end + def test_constantize run_constantize_tests_on do |string| string.constantize diff --git a/guides/source/initialization.textile b/guides/source/initialization.textile index 913ff24290..48d4373afe 100644 --- a/guides/source/initialization.textile +++ b/guides/source/initialization.textile @@ -57,7 +57,7 @@ else end </ruby> -The +rbconfig+ file from the Ruby standard library provides us with the +RbConfig+ class which contains detailed information about the Ruby environment, including how Ruby was compiled. We can see thisin use in +railties/lib/rails/script_rails_loader+. +The +rbconfig+ file from the Ruby standard library provides us with the +RbConfig+ class which contains detailed information about the Ruby environment, including how Ruby was compiled. We can see this in use in +railties/lib/rails/script_rails_loader+. <ruby> require 'pathname' @@ -157,11 +157,11 @@ The gems that a Rails 4 application depends on are as follows: TODO: change these when the Rails 4 release is near. * abstract (1.0.0) -* actionmailer (3.1.0.beta) -* actionpack (3.1.0.beta) -* activemodel (3.1.0.beta) -* activerecord (3.1.0.beta) -* activesupport (3.1.0.beta) +* actionmailer (4.0.0.beta) +* actionpack (4.0.0.beta) +* activemodel (4.0.0.beta) +* activerecord (4.0.0.beta) +* activesupport (4.0.0.beta) * arel (2.0.7) * builder (3.0.0) * bundler (1.0.6) @@ -174,8 +174,8 @@ TODO: change these when the Rails 4 release is near. * rack-cache (0.5.3) * rack-mount (0.6.13) * rack-test (0.5.6) -* rails (3.1.0.beta) -* railties (3.1.0.beta) +* rails (4.0.0.beta) +* railties (4.0.0.beta) * rake (0.8.7) * sqlite3-ruby (1.3.2) * thor (0.14.6) @@ -191,6 +191,7 @@ ARGV << '--help' if ARGV.empty? aliases = { "g" => "generate", + "d" => "destroy", "c" => "console", "s" => "server", "db" => "dbconsole", @@ -579,28 +580,6 @@ this time to the +Array+ and +Hash+ classes. This file defines an +extract_options!+ method which Rails uses to extract options from parameters. -<ruby> -class Array - # Extracts options from a set of arguments. Removes and returns the - # last - # element in the array if it's a hash, otherwise returns a blank hash. - # - # def options(*args) - # args.extract_options! - # end - # - # options(1, 2) # => {} - # options(1, 2, :a => :b) # => {:a=>:b} - def extract_options! - if last.is_a?(Hash) && last.extractable_options? - pop - else - {} - end - end -end -</ruby> - h4. +railties/lib/rails/application.rb+ The next file required by +railties/lib/rails.rb+ is +application.rb+. @@ -612,8 +591,7 @@ Before the +Rails::Application+ class is defined however, +rails/engine+ is also loaded, which is responsible for handling the behavior and definitions of Rails engines. -TIP: You can read more about engines in the "Getting Started with Engines":engines.html -guide. +TIP: You can read more about engines in the "Getting Started with Engines":engines.html guide. Among other things, Rails Engine is also responsible for loading the Railtie class. @@ -678,7 +656,7 @@ h4. +activesupport/lib/active_support/deprecation/proxy_wrappers.rb+ +proxy_wrappers.rb+ defines deprecation wrappers for methods, instance variables and constants. Previously, this was used for the +RAILS_ENV+ and +RAILS_ROOT+ constants for 3.0 but since then these constants have been removed. The deprecation message that would be raised from these would be something like: <plain> - BadConstant is deprecated! Use GoodConstant instead. +BadConstant is deprecated! Use GoodConstant instead. </plain> h4. +active_support/ordered_options+ @@ -689,7 +667,7 @@ The next file required is +active_support/core_ext/hash/deep_dup+ which is cover h4. +active_support/core_ext/object+ -This file is responsible for requiring many more core extensions: +This file is responsible for requiring many more Active Support core extensions: <ruby> require 'active_support/core_ext/object/acts_like' @@ -947,7 +925,7 @@ The +initializers_chain+ method referenced in the +initializers_for+ method is d <ruby> def initializers_chain initializers = Collection.new - ancestors.reverse_each do | klass | + ancestors.reverse_each do |klass| next unless klass.respond_to?(:initializers) initializers = initializers + klass.initializers end @@ -1010,46 +988,35 @@ This file defines the +ActiveSupport::Railtie+ constant which like the +I18n::Ra Then this Railtie sets up three more initializers: -* +active_support.initialize_whiny_nils+ * +active_support.deprecation_behavior+ * +active_support.initialize_time_zone+ +* +active_support.set_configs+ We will cover what each of these initializers do when they run. Once the +active_support/railtie+ file has finished loading the next file required from +railties/lib/rails.rb+ is the +action_dispatch/railtie+. -h4. +activesupport/lib/action_dispatch/railtie.rb+ +h4. +actionpack/lib/action_dispatch/railtie.rb+ This file defines the +ActionDispatch::Railtie+ class, but not before requiring +action_dispatch+. -h4. +activesupport/lib/action_dispatch.rb+ - -This file attempts to locate the +active_support+ and +active_model+ libraries by looking a couple of directories back from the current file and then adds the +active_support+ and +active_model+ +lib+ directories to the load path, but only if they aren't already, which they are. - -<ruby> -activesupport_path = File.expand_path('../../../activesupport/lib', __FILE__) -$:.unshift(activesupport_path) if File.directory?(activesupport_path) && !$:.include?(activesupport_path) - -activemodel_path = File.expand_path('../../../activemodel/lib', __FILE__) -$:.unshift(activemodel_path) if File.directory?(activemodel_path) && !$:.include?(activemodel_path) -</ruby> - -In effect, these lines only define the +activesupport_path+ and +activemodel_path+ variables and nothing more. +h4. +actionpack/lib/action_dispatch.rb+ -The next two requires in this file are already done, so they are not run: +This file starts off with the following requires: <ruby> require 'active_support' require 'active_support/dependencies/autoload' +require 'active_support/core_ext/module/attribute_accessors' </ruby> -The following require is to +action_pack+ (+activesupport/lib/action_pack.rb+) which has a 22-line copyright notice at the top of it and ends in a simple require to +action_pack/version+. This file, like other +version.rb+ files before it, defines the +ActionPack::VERSION+ constant: +The following require is to +action_pack+ (+actionpack/lib/action_pack.rb+) which contains a simple require to +action_pack/version+. This file, like other +version.rb+ files before it, defines the +ActionPack::VERSION+ constant: <ruby> module ActionPack module VERSION #:nodoc: - MAJOR = 3 - MINOR = 1 + MAJOR = 4 + MINOR = 0 TINY = 0 PRE = "beta" @@ -1067,8 +1034,8 @@ This file makes a require to +active_model/version+ which defines the version fo <ruby> module ActiveModel module VERSION #:nodoc: - MAJOR = 3 - MINOR = 1 + MAJOR = 4 + MINOR = 0 TINY = 0 PRE = "beta" @@ -1105,7 +1072,7 @@ Once it has finished loading, the +I18n.load_path+ method is used to add the +ac The loading of this file finishes the loading of +active_model+ and so we go back to +action_dispatch+. -h4. Back to +activesupport/lib/action_dispatch.rb+ +h4. Back to +actionpack/lib/action_dispatch.rb+ The remainder of this file requires the +rack+ file from the Rack gem which defines the +Rack+ module. After +rack+, there's autoloads defined for the +Rack+, +ActionDispatch+, +ActionDispatch::Http+, +ActionDispatch::Session+. A new method called +autoload_under+ is used here, and this simply prefixes the files where the modules are autoloaded from with the path specified. For example here: @@ -1119,7 +1086,7 @@ The +Assertions+ module is in the +action_dispatch/testing+ folder rather than s Finally, this file defines a top-level autoload, the +Mime+ constant. -h4. Back to +activesupport/lib/action_dispatch/railtie.rb+ +h4. Back to +actionpack/lib/action_dispatch/railtie.rb+ After +action_dispatch+ is required in this file, the +ActionDispatch::Railtie+ class is defined and is yet another class that inherits from +Rails::Railtie+. This class defines some initial configuration option defaults for +config.action_dispatch+ before setting up a single initializer called +action_dispatch.configure+. @@ -1141,22 +1108,21 @@ h4. +activerecord/lib/active_record.rb+ This file begins by detecting if the +lib+ directories of +active_support+ and +active_model+ are not in the load path and if they aren't then adds them. As we saw back in +action_dispatch.rb+, these directories are already there. -The first three requires have already been done by other files and so aren't loaded here, but the 4th require, the one to +arel+ will require the file provided by the Arel gem, which defines the +Arel+ module. +The first couple of requires have already been done by other files and so aren't loaded here, but the next one to +arel+ will require the file provided by the Arel gem, which defines the +Arel+ module. <ruby> require 'active_support' -require 'active_support/i18n' require 'active_model' require 'arel' </ruby> -The 5th require in this file is one to +active_record/version+ which defines the +ActiveRecord::VERSION+ constant: +The file required next is +active_record/version+ which defines the +ActiveRecord::VERSION+ constant: <ruby> module ActiveRecord module VERSION #:nodoc: - MAJOR = 3 - MINOR = 1 + MAJOR = 4 + MINOR = 0 TINY = 0 PRE = "beta" @@ -1180,7 +1146,9 @@ This will set the engine for +Arel::Table+ to be +ActiveRecord::Base+. The file then finishes with this line: <ruby> -I18n.load_path << File.dirname(__FILE__) + '/active_record/locale/en.yml' +ActiveSupport.on_load(:i18n) do + I18n.load_path << File.dirname(__FILE__) + '/active_record/locale/en.yml' +end </ruby> This will add the translations from +activerecord/lib/active_record/locale/en.yml+ to the load path for +I18n+, with this file being parsed when all the translations are loaded. diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index d44465e547..660e864d2a 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -185,7 +185,7 @@ module Rails end all = (railties.all - order) - all.push(self) unless all.include?(self) + all.push(self) unless (all + order).include?(self) order.push(:all) unless order.include?(:all) index = order.index(:all) diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb index 7a047ef93a..4437e2c8af 100644 --- a/railties/test/railties/engine_test.rb +++ b/railties/test/railties/engine_test.rb @@ -1098,6 +1098,10 @@ YAML get("/assets/bar.js") assert_equal "// App's bar js\n;", last_response.body.strip + + # ensure that railties are not added twice + railties = Rails.application.ordered_railties.map(&:class) + assert_equal railties, railties.uniq end test "railties_order adds :all with lowest priority if not given" do |