actionpack 3.0.0.beta3 → 3.0.0.beta4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of actionpack might be problematic. Click here for more details.
- data/CHANGELOG +19 -0
- data/lib/abstract_controller.rb +1 -1
- data/lib/abstract_controller/asset_paths.rb +9 -0
- data/lib/abstract_controller/base.rb +5 -13
- data/lib/abstract_controller/callbacks.rb +1 -1
- data/lib/abstract_controller/helpers.rb +0 -1
- data/lib/abstract_controller/layouts.rb +3 -3
- data/lib/abstract_controller/logger.rb +1 -1
- data/lib/abstract_controller/rendering.rb +1 -0
- data/lib/action_controller/base.rb +5 -1
- data/lib/action_controller/caching.rb +2 -3
- data/lib/action_controller/caching/actions.rb +1 -1
- data/lib/action_controller/caching/fragments.rb +1 -1
- data/lib/action_controller/caching/pages.rb +8 -8
- data/lib/action_controller/caching/sweeping.rb +1 -0
- data/lib/action_controller/deprecated/base.rb +10 -36
- data/lib/action_controller/metal.rb +45 -3
- data/lib/action_controller/metal/compatibility.rb +2 -2
- data/lib/action_controller/metal/helpers.rb +3 -3
- data/lib/action_controller/metal/http_authentication.rb +158 -0
- data/lib/action_controller/metal/instrumentation.rb +5 -5
- data/lib/action_controller/metal/rack_delegation.rb +4 -4
- data/lib/action_controller/metal/renderers.rb +3 -3
- data/lib/action_controller/metal/request_forgery_protection.rb +45 -74
- data/lib/action_controller/metal/responder.rb +1 -1
- data/lib/action_controller/metal/url_for.rb +8 -0
- data/lib/action_controller/railtie.rb +26 -39
- data/lib/action_controller/test_case.rb +147 -135
- data/lib/action_controller/vendor/html-scanner/html/tokenizer.rb +1 -0
- data/lib/action_dispatch.rb +0 -1
- data/lib/action_dispatch/http/parameters.rb +2 -1
- data/lib/action_dispatch/http/request.rb +19 -7
- data/lib/action_dispatch/http/response.rb +3 -33
- data/lib/action_dispatch/middleware/cookies.rb +44 -10
- data/lib/action_dispatch/middleware/flash.rb +11 -1
- data/lib/action_dispatch/middleware/params_parser.rb +3 -1
- data/lib/action_dispatch/middleware/session/abstract_store.rb +47 -83
- data/lib/action_dispatch/middleware/session/cookie_store.rb +19 -165
- data/lib/action_dispatch/middleware/session/mem_cache_store.rb +2 -2
- data/lib/action_dispatch/middleware/show_exceptions.rb +18 -12
- data/lib/action_dispatch/middleware/stack.rb +17 -67
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb +1 -1
- data/lib/action_dispatch/railtie.rb +0 -2
- data/lib/action_dispatch/routing/deprecated_mapper.rb +1 -0
- data/lib/action_dispatch/routing/mapper.rb +89 -23
- data/lib/action_dispatch/routing/route_set.rb +22 -16
- data/lib/action_dispatch/routing/url_for.rb +1 -1
- data/lib/action_dispatch/testing/assertions/routing.rb +1 -0
- data/lib/action_dispatch/testing/assertions/selector.rb +11 -7
- data/lib/action_dispatch/testing/test_process.rb +3 -2
- data/lib/action_pack/version.rb +1 -1
- data/lib/action_view.rb +5 -1
- data/lib/action_view/base.rb +10 -4
- data/lib/action_view/helpers/active_model_helper.rb +1 -8
- data/lib/action_view/helpers/asset_tag_helper.rb +7 -4
- data/lib/action_view/helpers/cache_helper.rb +14 -14
- data/lib/action_view/helpers/capture_helper.rb +25 -6
- data/lib/action_view/helpers/date_helper.rb +33 -44
- data/lib/action_view/helpers/form_helper.rb +47 -27
- data/lib/action_view/helpers/form_options_helper.rb +26 -3
- data/lib/action_view/helpers/form_tag_helper.rb +8 -4
- data/lib/action_view/helpers/number_helper.rb +5 -2
- data/lib/action_view/helpers/prototype_helper.rb +1 -1
- data/lib/action_view/helpers/tag_helper.rb +1 -1
- data/lib/action_view/helpers/text_helper.rb +55 -46
- data/lib/action_view/helpers/translation_helper.rb +19 -8
- data/lib/action_view/helpers/url_helper.rb +2 -4
- data/lib/action_view/locale/en.yml +14 -14
- data/lib/action_view/lookup_context.rb +52 -22
- data/lib/action_view/paths.rb +1 -0
- data/lib/action_view/render/layouts.rb +3 -12
- data/lib/action_view/render/partials.rb +21 -10
- data/lib/action_view/render/rendering.rb +1 -1
- data/lib/action_view/template.rb +172 -26
- data/lib/action_view/template/error.rb +25 -27
- data/lib/action_view/template/handlers.rb +1 -1
- data/lib/action_view/template/handlers/erb.rb +92 -45
- data/lib/action_view/template/resolver.rb +4 -1
- data/lib/action_view/test_case.rb +105 -72
- data/lib/action_view/testing/resolvers.rb +43 -0
- metadata +62 -20
- data/lib/abstract_controller/assigns.rb +0 -21
- data/lib/action_dispatch/middleware/cascade.rb +0 -29
| @@ -3,17 +3,26 @@ require 'action_view/helpers/tag_helper' | |
| 3 3 | 
             
            module ActionView
         | 
| 4 4 | 
             
              module Helpers
         | 
| 5 5 | 
             
                module TranslationHelper
         | 
| 6 | 
            -
                  # Delegates to I18n#translate but also performs  | 
| 6 | 
            +
                  # Delegates to I18n#translate but also performs three additional functions. First, it'll catch MissingTranslationData exceptions
         | 
| 7 7 | 
             
                  # and turn them into inline spans that contains the missing key, such that you can see in a view what is missing where.
         | 
| 8 8 | 
             
                  #
         | 
| 9 9 | 
             
                  # Second, it'll scope the key by the current partial if the key starts with a period. So if you call translate(".foo") from the
         | 
| 10 10 | 
             
                  # people/index.html.erb template, you'll actually be calling I18n.translate("people.index.foo"). This makes it less repetitive
         | 
| 11 11 | 
             
                  # to translate many keys within the same partials and gives you a simple framework for scoping them consistently. If you don't
         | 
| 12 12 | 
             
                  # prepend the key with a period, nothing is converted.
         | 
| 13 | 
            +
                  #
         | 
| 14 | 
            +
                  # Third, it’ll mark the translation as safe HTML if the key has the suffix "_html" or the last element of the key is the word
         | 
| 15 | 
            +
                  # "html". For example, calling translate("footer_html") or translate("footer.html") will return a safe HTML string that won’t
         | 
| 16 | 
            +
                  # be escaped by other HTML helper methods. This naming convention helps to identify translations that include HTML tags so that
         | 
| 17 | 
            +
                  # you know what kind of output to expect when you call translate in a template.
         | 
| 18 | 
            +
             | 
| 13 19 | 
             
                  def translate(key, options = {})
         | 
| 14 | 
            -
                    options | 
| 15 | 
            -
                     | 
| 16 | 
            -
             | 
| 20 | 
            +
                    translation = I18n.translate(scope_key_by_partial(key), options.merge!(:raise => true))
         | 
| 21 | 
            +
                    if html_safe_translation_key?(key) && translation.respond_to?(:html_safe)
         | 
| 22 | 
            +
                      translation.html_safe
         | 
| 23 | 
            +
                    else
         | 
| 24 | 
            +
                      translation
         | 
| 25 | 
            +
                    end
         | 
| 17 26 | 
             
                  rescue I18n::MissingTranslationData => e
         | 
| 18 27 | 
             
                    keys = I18n.normalize_keys(e.locale, e.key, e.options[:scope])
         | 
| 19 28 | 
             
                    content_tag('span', keys.join(', '), :class => 'translation_missing')
         | 
| @@ -27,12 +36,10 @@ module ActionView | |
| 27 36 | 
             
                  alias :l :localize
         | 
| 28 37 |  | 
| 29 38 | 
             
                  private
         | 
| 30 | 
            -
             | 
| 31 39 | 
             
                    def scope_key_by_partial(key)
         | 
| 32 | 
            -
                       | 
| 33 | 
            -
                      if strkey.first == "."
         | 
| 40 | 
            +
                      if key.to_s.first == "."
         | 
| 34 41 | 
             
                        if @_virtual_path
         | 
| 35 | 
            -
                          @_virtual_path.gsub(%r{/_?}, ".") +  | 
| 42 | 
            +
                          @_virtual_path.gsub(%r{/_?}, ".") + key.to_s
         | 
| 36 43 | 
             
                        else
         | 
| 37 44 | 
             
                          raise "Cannot use t(#{key.inspect}) shortcut because path is not available"
         | 
| 38 45 | 
             
                        end
         | 
| @@ -40,6 +47,10 @@ module ActionView | |
| 40 47 | 
             
                        key
         | 
| 41 48 | 
             
                      end
         | 
| 42 49 | 
             
                    end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                    def html_safe_translation_key?(key)
         | 
| 52 | 
            +
                      key.to_s =~ /(\b|_|\.)html$/
         | 
| 53 | 
            +
                    end
         | 
| 43 54 | 
             
                end
         | 
| 44 55 | 
             
              end
         | 
| 45 56 | 
             
            end
         | 
| @@ -504,7 +504,7 @@ module ActionView | |
| 504 504 | 
             
                      "document.write('#{content_tag("a", name || email_address_obfuscated.html_safe, html_options.merge({ "href" => "mailto:"+email_address+extras }))}');".each_byte do |c|
         | 
| 505 505 | 
             
                        string << sprintf("%%%x", c)
         | 
| 506 506 | 
             
                      end
         | 
| 507 | 
            -
                      "<script type=\"#{Mime::JS}\">eval(decodeURIComponent('#{string}'))</script>"
         | 
| 507 | 
            +
                      "<script type=\"#{Mime::JS}\">eval(decodeURIComponent('#{string}'))</script>".html_safe
         | 
| 508 508 | 
             
                    elsif encode == "hex"
         | 
| 509 509 | 
             
                      email_address_encoded = ''
         | 
| 510 510 | 
             
                      email_address_obfuscated.each_byte do |c|
         | 
| @@ -596,10 +596,8 @@ module ActionView | |
| 596 596 | 
             
                      html_options = {} if html_options.nil?
         | 
| 597 597 | 
             
                      html_options = html_options.stringify_keys
         | 
| 598 598 |  | 
| 599 | 
            -
                      if (options.is_a?(Hash) && options.key?('remote')) || (html_options.is_a?(Hash) && html_options.key?('remote'))
         | 
| 599 | 
            +
                      if (options.is_a?(Hash) && options.key?('remote') && options.delete('remote')) || (html_options.is_a?(Hash) && html_options.key?('remote') && html_options.delete('remote'))
         | 
| 600 600 | 
             
                        html_options['data-remote'] = 'true'
         | 
| 601 | 
            -
                        options.delete('remote') if options.is_a?(Hash)
         | 
| 602 | 
            -
                        html_options.delete('remote') if html_options.is_a?(Hash)
         | 
| 603 601 | 
             
                      end
         | 
| 604 602 |  | 
| 605 603 | 
             
                      confirm = html_options.delete("confirm")
         | 
| @@ -102,37 +102,37 @@ | |
| 102 102 | 
             
                  half_a_minute: "half a minute"
         | 
| 103 103 | 
             
                  less_than_x_seconds:
         | 
| 104 104 | 
             
                    one:   "less than 1 second"
         | 
| 105 | 
            -
                    other: "less than { | 
| 105 | 
            +
                    other: "less than %{count} seconds"
         | 
| 106 106 | 
             
                  x_seconds:
         | 
| 107 107 | 
             
                    one:   "1 second"
         | 
| 108 | 
            -
                    other: "{ | 
| 108 | 
            +
                    other: "%{count} seconds"
         | 
| 109 109 | 
             
                  less_than_x_minutes:
         | 
| 110 110 | 
             
                    one:   "less than a minute"
         | 
| 111 | 
            -
                    other: "less than { | 
| 111 | 
            +
                    other: "less than %{count} minutes"
         | 
| 112 112 | 
             
                  x_minutes:
         | 
| 113 113 | 
             
                    one:   "1 minute"
         | 
| 114 | 
            -
                    other: "{ | 
| 114 | 
            +
                    other: "%{count} minutes"
         | 
| 115 115 | 
             
                  about_x_hours:
         | 
| 116 116 | 
             
                    one:   "about 1 hour"
         | 
| 117 | 
            -
                    other: "about { | 
| 117 | 
            +
                    other: "about %{count} hours"
         | 
| 118 118 | 
             
                  x_days:
         | 
| 119 119 | 
             
                    one:   "1 day"
         | 
| 120 | 
            -
                    other: "{ | 
| 120 | 
            +
                    other: "%{count} days"
         | 
| 121 121 | 
             
                  about_x_months:
         | 
| 122 122 | 
             
                    one:   "about 1 month"
         | 
| 123 | 
            -
                    other: "about { | 
| 123 | 
            +
                    other: "about %{count} months"
         | 
| 124 124 | 
             
                  x_months:
         | 
| 125 125 | 
             
                    one:   "1 month"
         | 
| 126 | 
            -
                    other: "{ | 
| 126 | 
            +
                    other: "%{count} months"
         | 
| 127 127 | 
             
                  about_x_years:
         | 
| 128 128 | 
             
                    one:   "about 1 year"
         | 
| 129 | 
            -
                    other: "about { | 
| 129 | 
            +
                    other: "about %{count} years"
         | 
| 130 130 | 
             
                  over_x_years:
         | 
| 131 131 | 
             
                    one:   "over 1 year"
         | 
| 132 | 
            -
                    other: "over { | 
| 132 | 
            +
                    other: "over %{count} years"
         | 
| 133 133 | 
             
                  almost_x_years:
         | 
| 134 134 | 
             
                    one:   "almost 1 year"
         | 
| 135 | 
            -
                    other: "almost { | 
| 135 | 
            +
                    other: "almost %{count} years"
         | 
| 136 136 | 
             
                prompts:
         | 
| 137 137 | 
             
                  year:   "Year"
         | 
| 138 138 | 
             
                  month:  "Month"
         | 
| @@ -148,7 +148,7 @@ | |
| 148 148 |  | 
| 149 149 | 
             
                # Default translation keys for submit FormHelper
         | 
| 150 150 | 
             
                submit:
         | 
| 151 | 
            -
                  create: 'Create { | 
| 152 | 
            -
                  update: 'Update { | 
| 153 | 
            -
                  submit: 'Save { | 
| 151 | 
            +
                  create: 'Create %{model}'
         | 
| 152 | 
            +
                  update: 'Update %{model}'
         | 
| 153 | 
            +
                  submit: 'Save %{model}'
         | 
| 154 154 |  | 
| @@ -13,8 +13,13 @@ module ActionView | |
| 13 13 | 
             
                mattr_accessor :registered_details
         | 
| 14 14 | 
             
                self.registered_details = []
         | 
| 15 15 |  | 
| 16 | 
            +
                mattr_accessor :registered_detail_setters
         | 
| 17 | 
            +
                self.registered_detail_setters = []
         | 
| 18 | 
            +
             | 
| 16 19 | 
             
                def self.register_detail(name, options = {}, &block)
         | 
| 17 20 | 
             
                  self.registered_details << name
         | 
| 21 | 
            +
                  self.registered_detail_setters << [name, "#{name}="]
         | 
| 22 | 
            +
             | 
| 18 23 | 
             
                  Accessors.send :define_method, :"_#{name}_defaults", &block
         | 
| 19 24 | 
             
                  Accessors.module_eval <<-METHOD, __FILE__, __LINE__ + 1
         | 
| 20 25 | 
             
                    def #{name}
         | 
| @@ -23,12 +28,7 @@ module ActionView | |
| 23 28 |  | 
| 24 29 | 
             
                    def #{name}=(value)
         | 
| 25 30 | 
             
                      value = Array.wrap(value.presence || _#{name}_defaults)
         | 
| 26 | 
            -
             | 
| 27 | 
            -
                      if value != @details[:#{name}]
         | 
| 28 | 
            -
                        @details_key = nil
         | 
| 29 | 
            -
                        @details = @details.dup if @details.frozen?
         | 
| 30 | 
            -
                        @details[:#{name}] = value.freeze
         | 
| 31 | 
            -
                      end
         | 
| 31 | 
            +
                      _set_detail(:#{name}, value) if value != @details[:#{name}]
         | 
| 32 32 | 
             
                    end
         | 
| 33 33 | 
             
                  METHOD
         | 
| 34 34 | 
             
                end
         | 
| @@ -59,8 +59,11 @@ module ActionView | |
| 59 59 | 
             
                def initialize(view_paths, details = {})
         | 
| 60 60 | 
             
                  @details, @details_key = { :handlers => default_handlers }, nil
         | 
| 61 61 | 
             
                  @frozen_formats, @skip_default_locale = false, false
         | 
| 62 | 
            +
             | 
| 62 63 | 
             
                  self.view_paths = view_paths
         | 
| 63 | 
            -
                  self. | 
| 64 | 
            +
                  self.registered_detail_setters.each do |key, setter|
         | 
| 65 | 
            +
                    send(setter, details[key])
         | 
| 66 | 
            +
                  end
         | 
| 64 67 | 
             
                end
         | 
| 65 68 |  | 
| 66 69 | 
             
                module ViewPaths
         | 
| @@ -116,11 +119,11 @@ module ActionView | |
| 116 119 | 
             
                  end
         | 
| 117 120 |  | 
| 118 121 | 
             
                  def default_handlers #:nodoc:
         | 
| 119 | 
            -
                     | 
| 122 | 
            +
                    @@default_handlers ||= Template::Handlers.extensions
         | 
| 120 123 | 
             
                  end
         | 
| 121 124 |  | 
| 122 125 | 
             
                  def handlers_regexp #:nodoc:
         | 
| 123 | 
            -
                     | 
| 126 | 
            +
                    @@handlers_regexp ||= /\.(?:#{default_handlers.join('|')})$/
         | 
| 124 127 | 
             
                  end
         | 
| 125 128 | 
             
                end
         | 
| 126 129 |  | 
| @@ -141,10 +144,13 @@ module ActionView | |
| 141 144 | 
             
                  end
         | 
| 142 145 |  | 
| 143 146 | 
             
                  # Overload formats= to reject [:"*/*"] values.
         | 
| 144 | 
            -
                  def formats=( | 
| 145 | 
            -
                     | 
| 146 | 
            -
             | 
| 147 | 
            -
             | 
| 147 | 
            +
                  def formats=(values)
         | 
| 148 | 
            +
                    if values && values.size == 1
         | 
| 149 | 
            +
                      value = values.first
         | 
| 150 | 
            +
                      values = nil    if value == :"*/*"
         | 
| 151 | 
            +
                      values << :html if value == :js
         | 
| 152 | 
            +
                    end
         | 
| 153 | 
            +
                    super(values)
         | 
| 148 154 | 
             
                  end
         | 
| 149 155 |  | 
| 150 156 | 
             
                  # Do not use the default locale on template lookup.
         | 
| @@ -170,24 +176,48 @@ module ActionView | |
| 170 176 | 
             
                    super(@skip_default_locale ? I18n.locale : _locale_defaults)
         | 
| 171 177 | 
             
                  end
         | 
| 172 178 |  | 
| 179 | 
            +
                  # A method which only uses the first format in the formats array for layout lookup.
         | 
| 180 | 
            +
                  # This method plays straight with instance variables for performance reasons.
         | 
| 181 | 
            +
                  def with_layout_format
         | 
| 182 | 
            +
                    if formats.size == 1
         | 
| 183 | 
            +
                      yield
         | 
| 184 | 
            +
                    else
         | 
| 185 | 
            +
                      old_formats = formats
         | 
| 186 | 
            +
                      _set_detail(:formats, formats[0,1])
         | 
| 187 | 
            +
             | 
| 188 | 
            +
                      begin
         | 
| 189 | 
            +
                        yield
         | 
| 190 | 
            +
                      ensure
         | 
| 191 | 
            +
                        _set_detail(:formats, old_formats)
         | 
| 192 | 
            +
                      end
         | 
| 193 | 
            +
                    end
         | 
| 194 | 
            +
                  end
         | 
| 195 | 
            +
             | 
| 173 196 | 
             
                  # Update the details keys by merging the given hash into the current
         | 
| 174 197 | 
             
                  # details hash. If a block is given, the details are modified just during
         | 
| 175 198 | 
             
                  # the execution of the block and reverted to the previous value after.
         | 
| 176 | 
            -
                  def update_details(new_details | 
| 199 | 
            +
                  def update_details(new_details)
         | 
| 177 200 | 
             
                    old_details = @details.dup
         | 
| 178 201 |  | 
| 179 | 
            -
                     | 
| 180 | 
            -
                      send( | 
| 202 | 
            +
                    registered_detail_setters.each do |key, setter|
         | 
| 203 | 
            +
                      send(setter, new_details[key]) if new_details.key?(key)
         | 
| 181 204 | 
             
                    end
         | 
| 182 205 |  | 
| 183 | 
            -
                     | 
| 184 | 
            -
                       | 
| 185 | 
            -
             | 
| 186 | 
            -
                       | 
| 187 | 
            -
             | 
| 188 | 
            -
                      end
         | 
| 206 | 
            +
                    begin
         | 
| 207 | 
            +
                      yield
         | 
| 208 | 
            +
                    ensure
         | 
| 209 | 
            +
                      @details_key = nil
         | 
| 210 | 
            +
                      @details = old_details
         | 
| 189 211 | 
             
                    end
         | 
| 190 212 | 
             
                  end
         | 
| 213 | 
            +
             | 
| 214 | 
            +
                protected
         | 
| 215 | 
            +
             | 
| 216 | 
            +
                  def _set_detail(key, value)
         | 
| 217 | 
            +
                    @details_key = nil
         | 
| 218 | 
            +
                    @details = @details.dup if @details.frozen?
         | 
| 219 | 
            +
                    @details[key] = value.freeze
         | 
| 220 | 
            +
                  end
         | 
| 191 221 | 
             
                end
         | 
| 192 222 |  | 
| 193 223 | 
             
                include Accessors
         | 
    
        data/lib/action_view/paths.rb
    CHANGED
    
    
| @@ -57,15 +57,11 @@ module ActionView | |
| 57 57 | 
             
                # This is the method which actually finds the layout using details in the lookup
         | 
| 58 58 | 
             
                # context object. If no layout is found, it checkes if at least a layout with
         | 
| 59 59 | 
             
                # the given name exists across all details before raising the error.
         | 
| 60 | 
            -
                #
         | 
| 61 | 
            -
                # If self.formats contains several formats, just the first one is considered in
         | 
| 62 | 
            -
                # the layout lookup.
         | 
| 63 60 | 
             
                def find_layout(layout)
         | 
| 64 61 | 
             
                  begin
         | 
| 65 | 
            -
                     | 
| 66 | 
            -
                       | 
| 67 | 
            -
             | 
| 68 | 
            -
                      update_details(:formats => self.formats.first){ _find_layout(layout) }
         | 
| 62 | 
            +
                    with_layout_format do
         | 
| 63 | 
            +
                      layout =~ /^\// ?
         | 
| 64 | 
            +
                        with_fallbacks { find_template(layout) } : find_template(layout)
         | 
| 69 65 | 
             
                    end
         | 
| 70 66 | 
             
                  rescue ActionView::MissingTemplate => e
         | 
| 71 67 | 
             
                    update_details(:formats => nil) do
         | 
| @@ -74,11 +70,6 @@ module ActionView | |
| 74 70 | 
             
                  end
         | 
| 75 71 | 
             
                end
         | 
| 76 72 |  | 
| 77 | 
            -
                def _find_layout(layout) #:nodoc:
         | 
| 78 | 
            -
                  layout =~ /^\// ?
         | 
| 79 | 
            -
                    with_fallbacks { find_template(layout) } : find_template(layout)
         | 
| 80 | 
            -
                end
         | 
| 81 | 
            -
             | 
| 82 73 | 
             
                # Contains the logic that actually renders the layout.
         | 
| 83 74 | 
             
                def _render_layout(layout, locals, &block) #:nodoc:
         | 
| 84 75 | 
             
                  layout.render(self, locals){ |*name| _layout_for(*name, &block) }
         | 
| @@ -211,12 +211,12 @@ module ActionView | |
| 211 211 | 
             
                    identifier = ((@template = find_template) ? @template.identifier : @path)
         | 
| 212 212 |  | 
| 213 213 | 
             
                    if @collection
         | 
| 214 | 
            -
                      ActiveSupport::Notifications.instrument("action_view | 
| 214 | 
            +
                      ActiveSupport::Notifications.instrument("render_collection.action_view",
         | 
| 215 215 | 
             
                        :identifier => identifier || "collection", :count => @collection.size) do
         | 
| 216 216 | 
             
                        render_collection
         | 
| 217 217 | 
             
                      end
         | 
| 218 218 | 
             
                    else
         | 
| 219 | 
            -
                      content = ActiveSupport::Notifications.instrument("action_view | 
| 219 | 
            +
                      content = ActiveSupport::Notifications.instrument("render_partial.action_view",
         | 
| 220 220 | 
             
                        :identifier => identifier) do
         | 
| 221 221 | 
             
                        render_partial
         | 
| 222 222 | 
             
                      end
         | 
| @@ -241,15 +241,21 @@ module ActionView | |
| 241 241 | 
             
                  end
         | 
| 242 242 |  | 
| 243 243 | 
             
                  def collection_with_template(template = @template)
         | 
| 244 | 
            -
                    segments, locals,  | 
| 244 | 
            +
                    segments, locals, template = [], @locals, @template
         | 
| 245 245 |  | 
| 246 | 
            -
                     | 
| 247 | 
            -
             | 
| 246 | 
            +
                    if @options[:as]
         | 
| 247 | 
            +
                      as = @options[:as]
         | 
| 248 | 
            +
                      counter = "#{as}_counter".to_sym
         | 
| 249 | 
            +
                    else
         | 
| 250 | 
            +
                      as = template.variable_name
         | 
| 251 | 
            +
                      counter = template.counter_name
         | 
| 252 | 
            +
                    end
         | 
| 253 | 
            +
             | 
| 254 | 
            +
                    locals[counter] = -1
         | 
| 248 255 |  | 
| 249 256 | 
             
                    @collection.each do |object|
         | 
| 250 | 
            -
                      locals[ | 
| 257 | 
            +
                      locals[counter] += 1
         | 
| 251 258 | 
             
                      locals[as] = object
         | 
| 252 | 
            -
             | 
| 253 259 | 
             
                      segments << template.render(@view, locals)
         | 
| 254 260 | 
             
                    end
         | 
| 255 261 |  | 
| @@ -257,13 +263,18 @@ module ActionView | |
| 257 263 | 
             
                  end
         | 
| 258 264 |  | 
| 259 265 | 
             
                  def collection_without_template(collection_paths = @collection_paths)
         | 
| 260 | 
            -
                    segments, locals | 
| 261 | 
            -
                    index, template | 
| 266 | 
            +
                    segments, locals = [], @locals
         | 
| 267 | 
            +
                    index, template  = -1, nil
         | 
| 268 | 
            +
             | 
| 269 | 
            +
                    if @options[:as]
         | 
| 270 | 
            +
                      as = @options[:as]
         | 
| 271 | 
            +
                      counter = "#{as}_counter"
         | 
| 272 | 
            +
                    end
         | 
| 262 273 |  | 
| 263 274 | 
             
                    @collection.each_with_index do |object, i|
         | 
| 264 275 | 
             
                      template = find_template(collection_paths[i])
         | 
| 265 | 
            -
                      locals[template.counter_name] = (index += 1)
         | 
| 266 276 | 
             
                      locals[as || template.variable_name] = object
         | 
| 277 | 
            +
                      locals[counter || template.counter_name] = (index += 1)
         | 
| 267 278 |  | 
| 268 279 | 
             
                      segments << template.render(@view, locals)
         | 
| 269 280 | 
             
                    end
         | 
| @@ -52,7 +52,7 @@ module ActionView | |
| 52 52 | 
             
                  locals = options[:locals] || {}
         | 
| 53 53 | 
             
                  layout = find_layout(layout) if layout
         | 
| 54 54 |  | 
| 55 | 
            -
                  ActiveSupport::Notifications.instrument("action_view | 
| 55 | 
            +
                  ActiveSupport::Notifications.instrument("render_template.action_view",
         | 
| 56 56 | 
             
                    :identifier => template.identifier, :layout => layout.try(:virtual_path)) do
         | 
| 57 57 |  | 
| 58 58 | 
             
                    content = template.render(self, locals) { |*name| _layout_for(*name) }
         | 
    
        data/lib/action_view/template.rb
    CHANGED
    
    | @@ -1,12 +1,92 @@ | |
| 1 | 
            -
            # encoding: utf-8
         | 
| 2 | 
            -
            # This is so that templates compiled in this file are UTF-8
         | 
| 3 1 | 
             
            require 'active_support/core_ext/array/wrap'
         | 
| 4 2 | 
             
            require 'active_support/core_ext/object/blank'
         | 
| 3 | 
            +
            require 'active_support/core_ext/kernel/singleton_class'
         | 
| 5 4 |  | 
| 6 5 | 
             
            module ActionView
         | 
| 7 6 | 
             
              class Template
         | 
| 8 7 | 
             
                extend ActiveSupport::Autoload
         | 
| 9 8 |  | 
| 9 | 
            +
                # === Encodings in ActionView::Template
         | 
| 10 | 
            +
                #
         | 
| 11 | 
            +
                # ActionView::Template is one of a few sources of potential
         | 
| 12 | 
            +
                # encoding issues in Rails. This is because the source for
         | 
| 13 | 
            +
                # templates are usually read from disk, and Ruby (like most
         | 
| 14 | 
            +
                # encoding-aware programming languages) assumes that the
         | 
| 15 | 
            +
                # String retrieved through File IO is encoded in the
         | 
| 16 | 
            +
                # <tt>default_external</tt> encoding. In Rails, the default
         | 
| 17 | 
            +
                # <tt>default_external</tt> encoding is UTF-8.
         | 
| 18 | 
            +
                #
         | 
| 19 | 
            +
                # As a result, if a user saves their template as ISO-8859-1
         | 
| 20 | 
            +
                # (for instance, using a non-Unicode-aware text editor),
         | 
| 21 | 
            +
                # and uses characters outside of the ASCII range, their
         | 
| 22 | 
            +
                # users will see diamonds with question marks in them in
         | 
| 23 | 
            +
                # the browser.
         | 
| 24 | 
            +
                #
         | 
| 25 | 
            +
                # For the rest of this documentation, when we say "UTF-8",
         | 
| 26 | 
            +
                # we mean "UTF-8 or whatever the default_internal encoding
         | 
| 27 | 
            +
                # is set to". By default, it will be UTF-8.
         | 
| 28 | 
            +
                #
         | 
| 29 | 
            +
                # To mitigate this problem, we use a few strategies:
         | 
| 30 | 
            +
                # 1. If the source is not valid UTF-8, we raise an exception
         | 
| 31 | 
            +
                #    when the template is compiled to alert the user
         | 
| 32 | 
            +
                #    to the problem.
         | 
| 33 | 
            +
                # 2. The user can specify the encoding using Ruby-style
         | 
| 34 | 
            +
                #    encoding comments in any template engine. If such
         | 
| 35 | 
            +
                #    a comment is supplied, Rails will apply that encoding
         | 
| 36 | 
            +
                #    to the resulting compiled source returned by the
         | 
| 37 | 
            +
                #    template handler.
         | 
| 38 | 
            +
                # 3. In all cases, we transcode the resulting String to
         | 
| 39 | 
            +
                #    the UTF-8.
         | 
| 40 | 
            +
                #
         | 
| 41 | 
            +
                # This means that other parts of Rails can always assume
         | 
| 42 | 
            +
                # that templates are encoded in UTF-8, even if the original
         | 
| 43 | 
            +
                # source of the template was not UTF-8.
         | 
| 44 | 
            +
                #
         | 
| 45 | 
            +
                # From a user's perspective, the easiest thing to do is
         | 
| 46 | 
            +
                # to save your templates as UTF-8. If you do this, you
         | 
| 47 | 
            +
                # do not need to do anything else for things to "just work".
         | 
| 48 | 
            +
                #
         | 
| 49 | 
            +
                # === Instructions for template handlers
         | 
| 50 | 
            +
                #
         | 
| 51 | 
            +
                # The easiest thing for you to do is to simply ignore
         | 
| 52 | 
            +
                # encodings. Rails will hand you the template source
         | 
| 53 | 
            +
                # as the default_internal (generally UTF-8), raising
         | 
| 54 | 
            +
                # an exception for the user before sending the template
         | 
| 55 | 
            +
                # to you if it could not determine the original encoding.
         | 
| 56 | 
            +
                #
         | 
| 57 | 
            +
                # For the greatest simplicity, you can support only
         | 
| 58 | 
            +
                # UTF-8 as the <tt>default_internal</tt>. This means
         | 
| 59 | 
            +
                # that from the perspective of your handler, the
         | 
| 60 | 
            +
                # entire pipeline is just UTF-8.
         | 
| 61 | 
            +
                #
         | 
| 62 | 
            +
                # === Advanced: Handlers with alternate metadata sources
         | 
| 63 | 
            +
                #
         | 
| 64 | 
            +
                # If you want to provide an alternate mechanism for
         | 
| 65 | 
            +
                # specifying encodings (like ERB does via <%# encoding: ... %>),
         | 
| 66 | 
            +
                # you may indicate that you will handle encodings yourself
         | 
| 67 | 
            +
                # by implementing <tt>self.handles_encoding?</tt>
         | 
| 68 | 
            +
                # on your handler.
         | 
| 69 | 
            +
                #
         | 
| 70 | 
            +
                # If you do, Rails will not try to encode the String
         | 
| 71 | 
            +
                # into the default_internal, passing you the unaltered
         | 
| 72 | 
            +
                # bytes tagged with the assumed encoding (from
         | 
| 73 | 
            +
                # default_external).
         | 
| 74 | 
            +
                #
         | 
| 75 | 
            +
                # In this case, make sure you return a String from
         | 
| 76 | 
            +
                # your handler encoded in the default_internal. Since
         | 
| 77 | 
            +
                # you are handling out-of-band metadata, you are
         | 
| 78 | 
            +
                # also responsible for alerting the user to any
         | 
| 79 | 
            +
                # problems with converting the user's data to
         | 
| 80 | 
            +
                # the default_internal.
         | 
| 81 | 
            +
                #
         | 
| 82 | 
            +
                # To do so, simply raise the raise WrongEncodingError
         | 
| 83 | 
            +
                # as follows:
         | 
| 84 | 
            +
                #
         | 
| 85 | 
            +
                #     raise WrongEncodingError.new(
         | 
| 86 | 
            +
                #       problematic_string,
         | 
| 87 | 
            +
                #       expected_encoding
         | 
| 88 | 
            +
                #     )
         | 
| 89 | 
            +
             | 
| 10 90 | 
             
                eager_autoload do
         | 
| 11 91 | 
             
                  autoload :Error
         | 
| 12 92 | 
             
                  autoload :Handler
         | 
| @@ -16,20 +96,22 @@ module ActionView | |
| 16 96 |  | 
| 17 97 | 
             
                extend Template::Handlers
         | 
| 18 98 |  | 
| 19 | 
            -
                attr_reader :source, :identifier, :handler, :virtual_path, :formats
         | 
| 99 | 
            +
                attr_reader :source, :identifier, :handler, :virtual_path, :formats,
         | 
| 100 | 
            +
                            :original_encoding
         | 
| 20 101 |  | 
| 21 | 
            -
                Finalizer = proc do |method_name|
         | 
| 102 | 
            +
                Finalizer = proc do |method_name, mod|
         | 
| 22 103 | 
             
                  proc do
         | 
| 23 | 
            -
                     | 
| 104 | 
            +
                    mod.module_eval do
         | 
| 24 105 | 
             
                      remove_possible_method method_name
         | 
| 25 106 | 
             
                    end
         | 
| 26 107 | 
             
                  end
         | 
| 27 108 | 
             
                end
         | 
| 28 109 |  | 
| 29 110 | 
             
                def initialize(source, identifier, handler, details)
         | 
| 30 | 
            -
                  @source | 
| 31 | 
            -
                  @identifier | 
| 32 | 
            -
                  @handler | 
| 111 | 
            +
                  @source             = source
         | 
| 112 | 
            +
                  @identifier         = identifier
         | 
| 113 | 
            +
                  @handler            = handler
         | 
| 114 | 
            +
                  @original_encoding  = nil
         | 
| 33 115 |  | 
| 34 116 | 
             
                  @virtual_path = details[:virtual_path]
         | 
| 35 117 | 
             
                  @method_names = {}
         | 
| @@ -41,8 +123,14 @@ module ActionView | |
| 41 123 | 
             
                def render(view, locals, &block)
         | 
| 42 124 | 
             
                  # Notice that we use a bang in this instrumentation because you don't want to
         | 
| 43 125 | 
             
                  # consume this in production. This is only slow if it's being listened to.
         | 
| 44 | 
            -
                  ActiveSupport::Notifications.instrument("action_view | 
| 45 | 
            -
                     | 
| 126 | 
            +
                  ActiveSupport::Notifications.instrument("!render_template.action_view", :virtual_path => @virtual_path) do
         | 
| 127 | 
            +
                    if view.is_a?(ActionView::CompiledTemplates)
         | 
| 128 | 
            +
                      mod = ActionView::CompiledTemplates
         | 
| 129 | 
            +
                    else
         | 
| 130 | 
            +
                      mod = view.singleton_class
         | 
| 131 | 
            +
                    end
         | 
| 132 | 
            +
             | 
| 133 | 
            +
                    method_name = compile(locals, view, mod)
         | 
| 46 134 | 
             
                    view.send(method_name, locals, &block)
         | 
| 47 135 | 
             
                  end
         | 
| 48 136 | 
             
                rescue Exception => e
         | 
| @@ -50,7 +138,7 @@ module ActionView | |
| 50 138 | 
             
                    e.sub_template_of(self)
         | 
| 51 139 | 
             
                    raise e
         | 
| 52 140 | 
             
                  else
         | 
| 53 | 
            -
                    raise Template::Error.new(self, view.assigns, e)
         | 
| 141 | 
            +
                    raise Template::Error.new(self, view.respond_to?(:assigns) ? view.assigns : {}, e)
         | 
| 54 142 | 
             
                  end
         | 
| 55 143 | 
             
                end
         | 
| 56 144 |  | 
| @@ -75,37 +163,95 @@ module ActionView | |
| 75 163 | 
             
                end
         | 
| 76 164 |  | 
| 77 165 | 
             
                private
         | 
| 78 | 
            -
                   | 
| 166 | 
            +
                  # Among other things, this method is responsible for properly setting
         | 
| 167 | 
            +
                  # the encoding of the source. Until this point, we assume that the
         | 
| 168 | 
            +
                  # source is BINARY data. If no additional information is supplied,
         | 
| 169 | 
            +
                  # we assume the encoding is the same as Encoding.default_external.
         | 
| 170 | 
            +
                  #
         | 
| 171 | 
            +
                  # The user can also specify the encoding via a comment on the first
         | 
| 172 | 
            +
                  # line of the template (# encoding: NAME-OF-ENCODING). This will work
         | 
| 173 | 
            +
                  # with any template engine, as we process out the encoding comment
         | 
| 174 | 
            +
                  # before passing the source on to the template engine, leaving a
         | 
| 175 | 
            +
                  # blank line in its stead.
         | 
| 176 | 
            +
                  #
         | 
| 177 | 
            +
                  # If the template engine handles encodings, we send the encoded
         | 
| 178 | 
            +
                  # String to the engine without further processing. This allows
         | 
| 179 | 
            +
                  # the template engine to support additional mechanisms for
         | 
| 180 | 
            +
                  # specifying the encoding. For instance, ERB supports <%# encoding: %>
         | 
| 181 | 
            +
                  #
         | 
| 182 | 
            +
                  # Otherwise, after we figure out the correct encoding, we then
         | 
| 183 | 
            +
                  # encode the source into Encoding.default_internal. In general,
         | 
| 184 | 
            +
                  # this means that templates will be UTF-8 inside of Rails,
         | 
| 185 | 
            +
                  # regardless of the original source encoding.
         | 
| 186 | 
            +
                  def compile(locals, view, mod)
         | 
| 79 187 | 
             
                    method_name = build_method_name(locals)
         | 
| 80 188 | 
             
                    return method_name if view.respond_to?(method_name)
         | 
| 81 189 |  | 
| 82 190 | 
             
                    locals_code = locals.keys.map! { |key| "#{key} = local_assigns[:#{key}];" }.join
         | 
| 83 191 |  | 
| 84 | 
            -
                     | 
| 85 | 
            -
             | 
| 86 | 
            -
                       | 
| 87 | 
            -
             | 
| 88 | 
            -
                       | 
| 192 | 
            +
                    if source.encoding_aware?
         | 
| 193 | 
            +
                      # Look for # encoding: *. If we find one, we'll encode the
         | 
| 194 | 
            +
                      # String in that encoding, otherwise, we'll use the
         | 
| 195 | 
            +
                      # default external encoding.
         | 
| 196 | 
            +
                      if source.sub!(/\A#{ENCODING_FLAG}/, '')
         | 
| 197 | 
            +
                        encoding = magic_encoding = $1
         | 
| 198 | 
            +
                      else
         | 
| 199 | 
            +
                        encoding = Encoding.default_external
         | 
| 200 | 
            +
                      end
         | 
| 201 | 
            +
             | 
| 202 | 
            +
                      # Tag the source with the default external encoding
         | 
| 203 | 
            +
                      # or the encoding specified in the file
         | 
| 204 | 
            +
                      source.force_encoding(encoding)
         | 
| 205 | 
            +
             | 
| 206 | 
            +
                      # If the user didn't specify an encoding, and the handler
         | 
| 207 | 
            +
                      # handles encodings, we simply pass the String as is to
         | 
| 208 | 
            +
                      # the handler (with the default_external tag)
         | 
| 209 | 
            +
                      if !magic_encoding && @handler.respond_to?(:handles_encoding?) && @handler.handles_encoding?
         | 
| 210 | 
            +
                        source
         | 
| 211 | 
            +
                      # Otherwise, if the String is valid in the encoding,
         | 
| 212 | 
            +
                      # encode immediately to default_internal. This means
         | 
| 213 | 
            +
                      # that if a handler doesn't handle encodings, it will
         | 
| 214 | 
            +
                      # always get Strings in the default_internal
         | 
| 215 | 
            +
                      elsif source.valid_encoding?
         | 
| 216 | 
            +
                        source.encode!
         | 
| 217 | 
            +
                      # Otherwise, since the String is invalid in the encoding
         | 
| 218 | 
            +
                      # specified, raise an exception
         | 
| 219 | 
            +
                      else
         | 
| 220 | 
            +
                        raise WrongEncodingError.new(source, encoding)
         | 
| 221 | 
            +
                      end
         | 
| 89 222 | 
             
                    end
         | 
| 90 223 |  | 
| 224 | 
            +
                    code = @handler.call(self)
         | 
| 225 | 
            +
             | 
| 226 | 
            +
                    # Make sure that the resulting String to be evalled is in the
         | 
| 227 | 
            +
                    # encoding of the code
         | 
| 91 228 | 
             
                    source = <<-end_src
         | 
| 92 229 | 
             
                      def #{method_name}(local_assigns)
         | 
| 93 | 
            -
                        _old_virtual_path, @_virtual_path = @_virtual_path, #{@virtual_path.inspect};_old_output_buffer = output_buffer;#{locals_code};#{code}
         | 
| 230 | 
            +
                        _old_virtual_path, @_virtual_path = @_virtual_path, #{@virtual_path.inspect};_old_output_buffer = @output_buffer;#{locals_code};#{code}
         | 
| 94 231 | 
             
                      ensure
         | 
| 95 | 
            -
                        @_virtual_path,  | 
| 232 | 
            +
                        @_virtual_path, @output_buffer = _old_virtual_path, _old_output_buffer
         | 
| 96 233 | 
             
                      end
         | 
| 97 234 | 
             
                    end_src
         | 
| 98 235 |  | 
| 99 | 
            -
                    if  | 
| 100 | 
            -
                      source  | 
| 101 | 
            -
                       | 
| 102 | 
            -
             | 
| 103 | 
            -
                       | 
| 236 | 
            +
                    if source.encoding_aware?
         | 
| 237 | 
            +
                      # Make sure the source is in the encoding of the returned code
         | 
| 238 | 
            +
                      source.force_encoding(code.encoding)
         | 
| 239 | 
            +
             | 
| 240 | 
            +
                      # In case we get back a String from a handler that is not in
         | 
| 241 | 
            +
                      # BINARY or the default_internal, encode it to the default_internal
         | 
| 242 | 
            +
                      source.encode!
         | 
| 243 | 
            +
             | 
| 244 | 
            +
                      # Now, validate that the source we got back from the template
         | 
| 245 | 
            +
                      # handler is valid in the default_internal. This is for handlers
         | 
| 246 | 
            +
                      # that handle encoding but screw up
         | 
| 247 | 
            +
                      unless source.valid_encoding?
         | 
| 248 | 
            +
                        raise WrongEncodingError.new(@source, Encoding.default_internal)
         | 
| 249 | 
            +
                      end
         | 
| 104 250 | 
             
                    end
         | 
| 105 251 |  | 
| 106 252 | 
             
                    begin
         | 
| 107 | 
            -
                       | 
| 108 | 
            -
                      ObjectSpace.define_finalizer(self, Finalizer[method_name])
         | 
| 253 | 
            +
                      mod.module_eval(source, identifier, 0)
         | 
| 254 | 
            +
                      ObjectSpace.define_finalizer(self, Finalizer[method_name, mod])
         | 
| 109 255 |  | 
| 110 256 | 
             
                      method_name
         | 
| 111 257 | 
             
                    rescue Exception => e # errors from template code
         |