diff options
Diffstat (limited to 'actionview/lib/action_view')
43 files changed, 436 insertions, 428 deletions
diff --git a/actionview/lib/action_view/base.rb b/actionview/lib/action_view/base.rb index 3071a13655..43124bb904 100644 --- a/actionview/lib/action_view/base.rb +++ b/actionview/lib/action_view/base.rb @@ -70,6 +70,14 @@ module ActionView #:nodoc: # Headline: <%= headline %> # First name: <%= person.first_name %> # + # The local variables passed to sub templates can be accessed as a hash using the <tt>local_assigns</tt> hash. This lets you access the + # variables as: + # + # Headline: <%= local_assigns[:headline] %> + # + # This is useful in cases where you aren't sure if the local variable has been assigned. Alternately, you could also use + # <tt>defined? headline</tt> to first check if the variable has been assigned before using it. + # # === Template caching # # By default, Rails will compile each template to a method in order to render it. When you alter a template, @@ -126,8 +134,8 @@ module ActionView #:nodoc: # end # end # - # For more information on Builder please consult the [source - # code](https://github.com/jimweirich/builder). + # For more information on Builder please consult the {source + # code}[https://github.com/jimweirich/builder]. class Base include Helpers, ::ERB::Util, Context diff --git a/actionview/lib/action_view/gem_version.rb b/actionview/lib/action_view/gem_version.rb index 076b8ec30b..4f45f5b8c8 100644 --- a/actionview/lib/action_view/gem_version.rb +++ b/actionview/lib/action_view/gem_version.rb @@ -5,10 +5,10 @@ module ActionView end module VERSION - MAJOR = 4 - MINOR = 2 + MAJOR = 5 + MINOR = 0 TINY = 0 - PRE = "beta2" + PRE = "alpha" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end diff --git a/actionview/lib/action_view/helpers/asset_tag_helper.rb b/actionview/lib/action_view/helpers/asset_tag_helper.rb index b7fdc16a9d..5c28043f8a 100644 --- a/actionview/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionview/lib/action_view/helpers/asset_tag_helper.rb @@ -318,6 +318,7 @@ module ActionView end def extract_dimensions(size) + size = size.to_s if size =~ %r{\A\d+x\d+\z} size.split('x') elsif size =~ %r{\A\d+\z} diff --git a/actionview/lib/action_view/helpers/atom_feed_helper.rb b/actionview/lib/action_view/helpers/atom_feed_helper.rb index 227ad4cdfa..d8be4e5678 100644 --- a/actionview/lib/action_view/helpers/atom_feed_helper.rb +++ b/actionview/lib/action_view/helpers/atom_feed_helper.rb @@ -174,7 +174,7 @@ module ActionView # # * <tt>:published</tt>: Time first published. Defaults to the created_at attribute on the record if one such exists. # * <tt>:updated</tt>: Time of update. Defaults to the updated_at attribute on the record if one such exists. - # * <tt>:url</tt>: The URL for this entry. Defaults to the polymorphic_url for the record. + # * <tt>:url</tt>: The URL for this entry or false or nil for not having a link tag. Defaults to the polymorphic_url for the record. # * <tt>:id</tt>: The ID for this entry. Defaults to "tag:#{@view.request.host},#{@feed_options[:schema_date]}:#{record.class}/#{record.id}" # * <tt>:type</tt>: The TYPE for this entry. Defaults to "text/html". def entry(record, options = {}) @@ -191,7 +191,8 @@ module ActionView type = options.fetch(:type, 'text/html') - @xml.link(:rel => 'alternate', :type => type, :href => options[:url] || @view.polymorphic_url(record)) + url = options.fetch(:url) { @view.polymorphic_url(record) } + @xml.link(:rel => 'alternate', :type => type, :href => url) if url yield AtomBuilder.new(@xml) end diff --git a/actionview/lib/action_view/helpers/capture_helper.rb b/actionview/lib/action_view/helpers/capture_helper.rb index 75d1634b2e..5a3223968f 100644 --- a/actionview/lib/action_view/helpers/capture_helper.rb +++ b/actionview/lib/action_view/helpers/capture_helper.rb @@ -31,7 +31,8 @@ module ActionView # <head><title><%= @greeting %></title></head> # <body> # <b><%= @greeting %></b> - # </body></html> + # </body> + # </html> # def capture(*args) value = nil diff --git a/actionview/lib/action_view/helpers/date_helper.rb b/actionview/lib/action_view/helpers/date_helper.rb index 9272bb5c10..46ce7cf0be 100644 --- a/actionview/lib/action_view/helpers/date_helper.rb +++ b/actionview/lib/action_view/helpers/date_helper.rb @@ -177,7 +177,9 @@ module ActionView # and +:name+ (string). A format string would be something like "%{name} (%<number>02d)" for example. # See <tt>Kernel.sprintf</tt> for documentation on format sequences. # * <tt>:date_separator</tt> - Specifies a string to separate the date fields. Default is "" (i.e. nothing). - # * <tt>:start_year</tt> - Set the start year for the year select. Default is <tt>Date.today.year - 5</tt>if + # * <tt>:time_separator</tt> - Specifies a string to separate the time fields. Default is "" (i.e. nothing). + # * <tt>:datetime_separator</tt>- Specifies a string to separate the date and time fields. Default is "" (i.e. nothing). + # * <tt>:start_year</tt> - Set the start year for the year select. Default is <tt>Date.today.year - 5</tt> if # you are creating new record. While editing existing record, <tt>:start_year</tt> defaults to # the current selected year minus 5. # * <tt>:end_year</tt> - Set the end year for the year select. Default is <tt>Date.today.year + 5</tt> if @@ -898,7 +900,7 @@ module ActionView def translated_date_order date_order = I18n.translate(:'date.order', :locale => @options[:locale], :default => []) - date_order = date_order.map { |element| element.to_sym } + date_order = date_order.map(&:to_sym) forbidden_elements = date_order - [:year, :month, :day] if forbidden_elements.any? @@ -1035,7 +1037,7 @@ module ActionView def build_selects_from_types(order) select = '' first_visible = order.find { |type| !@options[:"discard_#{type}"] } - order.reverse.each do |type| + order.reverse_each do |type| separator = separator(type) unless type == first_visible # don't add before first visible field select.insert(0, separator.to_s + send("select_#{type}").to_s) end diff --git a/actionview/lib/action_view/helpers/form_helper.rb b/actionview/lib/action_view/helpers/form_helper.rb index 038f9e384b..b76b35bf3c 100644 --- a/actionview/lib/action_view/helpers/form_helper.rb +++ b/actionview/lib/action_view/helpers/form_helper.rb @@ -4,6 +4,7 @@ require 'action_view/helpers/tag_helper' require 'action_view/helpers/form_tag_helper' require 'action_view/helpers/active_model_helper' require 'action_view/model_naming' +require 'action_view/record_identifier' require 'active_support/core_ext/module/attribute_accessors' require 'active_support/core_ext/hash/slice' require 'active_support/core_ext/string/output_safety' @@ -110,6 +111,7 @@ module ActionView include FormTagHelper include UrlHelper include ModelNaming + include RecordIdentifier # Creates a form that allows the user to create or update the attributes # of a specific model object. @@ -164,6 +166,23 @@ module ActionView # * <tt>:namespace</tt> - A namespace for your form to ensure uniqueness of # id attributes on form elements. The namespace attribute will be prefixed # with underscore on the generated HTML id. + # * <tt>:method</tt> - The method to use when submitting the form, usually + # either "get" or "post". If "patch", "put", "delete", or another verb + # is used, a hidden input with name <tt>_method</tt> is added to + # simulate the verb over post. + # * <tt>:authenticity_token</tt> - Authenticity token to use in the form. + # Use only if you need to pass custom authenticity token string, or to + # not add authenticity_token field at all (by passing <tt>false</tt>). + # Remote forms may omit the embedded authenticity token by setting + # <tt>config.action_view.embed_authenticity_token_in_remote_forms = false</tt>. + # This is helpful when you're fragment-caching the form. Remote forms + # get the authenticity token from the <tt>meta</tt> tag, so embedding is + # unnecessary unless you support browsers without JavaScript. + # * <tt>:remote</tt> - If set to true, will allow the Unobtrusive + # JavaScript drivers to control the submit behavior. By default this + # behavior is an ajax submit. + # * <tt>:enforce_utf8</tt> - If set to false, a hidden input with name + # utf8 is not output. # * <tt>:html</tt> - Optional HTML attributes for the form tag. # # Also note that +form_for+ doesn't create an exclusive scope. It's still @@ -420,6 +439,7 @@ module ActionView html_options[:data] = options.delete(:data) if options.has_key?(:data) html_options[:remote] = options.delete(:remote) if options.has_key?(:remote) html_options[:method] = options.delete(:method) if options.has_key?(:method) + html_options[:enforce_utf8] = options.delete(:enforce_utf8) if options.has_key?(:enforce_utf8) html_options[:authenticity_token] = options.delete(:authenticity_token) builder = instantiate_builder(object_name, object, options) @@ -836,6 +856,24 @@ module ActionView # # file_field(:attachment, :file, class: 'file_input') # # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" /> + # + # ==== Gotcha + # + # The HTML specification says that when a file field is empty, web browsers + # do not send any value to the server. Unfortunately this introduces a + # gotcha: if a +User+ model has an +avatar+ field, and no file is selected, + # then the +avatar+ parameter is empty. Thus, any mass-assignment idiom like + # + # @user.update(params[:user]) + # + # wouldn't update the +avatar+ field. + # + # To prevent this, the helper generates an auxiliary hidden field before + # every file field. The hidden field has the same name as the file one and + # a blank value. + # + # In case you don't want the helper to generate this hidden field you can + # specify the <tt>include_hidden: false</tt> option. def file_field(object_name, method, options = {}) Tags::FileField.new(object_name, method, self, options).render end @@ -1188,11 +1226,11 @@ module ActionView object_name = model_name_from_record_or_class(object).param_key end - builder = options[:builder] || default_form_builder + builder = options[:builder] || default_form_builder_class builder.new(object_name, object, self, options) end - def default_form_builder + def default_form_builder_class builder = ActionView::Base.default_form_builder builder.respond_to?(:constantize) ? builder.constantize : builder end @@ -1231,8 +1269,8 @@ module ActionView # end # # The above code creates a new method +div_radio_button+ which wraps a div - # around the a new radio button. Note that when options are passed in, you - # must called +objectify_options+ in order for the model object to get + # around the new radio button. Note that when options are passed in, you + # must call +objectify_options+ in order for the model object to get # correctly passed to the method. If +objectify_options+ is not called, # then the newly created helper will not be linked back to the model. # @@ -1917,6 +1955,8 @@ module ActionView end ActiveSupport.on_load(:action_view) do - cattr_accessor(:default_form_builder) { ::ActionView::Helpers::FormBuilder } + cattr_accessor(:default_form_builder, instance_writer: false, instance_reader: false) do + ::ActionView::Helpers::FormBuilder + end end end diff --git a/actionview/lib/action_view/helpers/form_options_helper.rb b/actionview/lib/action_view/helpers/form_options_helper.rb index 83b07a00d4..bbfbf482a4 100644 --- a/actionview/lib/action_view/helpers/form_options_helper.rb +++ b/actionview/lib/action_view/helpers/form_options_helper.rb @@ -351,12 +351,12 @@ module ActionView return container if String === container selected, disabled = extract_selected_and_disabled(selected).map do |r| - Array(r).map { |item| item.to_s } + Array(r).map(&:to_s) end container.map do |element| html_attributes = option_html_attributes(element) - text, value = option_text_and_value(element).map { |item| item.to_s } + text, value = option_text_and_value(element).map(&:to_s) html_attributes[:selected] ||= option_value_selected?(value, selected) html_attributes[:disabled] ||= disabled && option_value_selected?(value, disabled) diff --git a/actionview/lib/action_view/helpers/form_tag_helper.rb b/actionview/lib/action_view/helpers/form_tag_helper.rb index 7d1cdc5a68..65a0548ffb 100644 --- a/actionview/lib/action_view/helpers/form_tag_helper.rb +++ b/actionview/lib/action_view/helpers/form_tag_helper.rb @@ -84,14 +84,13 @@ module ActionView # * <tt>:disabled</tt> - If set to true, the user will not be able to use this input. # * <tt>:include_blank</tt> - If set to true, an empty option will be created. If set to a string, the string will be used as the option's content and the value will be empty. # * <tt>:prompt</tt> - Create a prompt option with blank value and the text asking user to select something. - # * <tt>:selected</tt> - Provide a default selected value. It should be of the exact type as the provided options. # * Any other key creates standard HTML attributes for the tag. # # ==== Examples # select_tag "people", options_from_collection_for_select(@people, "id", "name") # # <select id="people" name="people"><option value="1">David</option></select> # - # select_tag "people", options_from_collection_for_select(@people, "id", "name"), selected: ["1", "David"] + # select_tag "people", options_from_collection_for_select(@people, "id", "name", "1") # # <select id="people" name="people"><option value="1" selected="selected">David</option></select> # # select_tag "people", "<option>David</option>".html_safe @@ -133,12 +132,20 @@ module ActionView option_tags ||= "" html_name = (options[:multiple] == true && !name.to_s.ends_with?("[]")) ? "#{name}[]" : name - if options.delete(:include_blank) - option_tags = content_tag(:option, '', :value => '').safe_concat(option_tags) + if options.include?(:include_blank) + include_blank = options.delete(:include_blank) + + if include_blank == true + include_blank = '' + end + + if include_blank + option_tags = content_tag(:option, include_blank, value: '').safe_concat(option_tags) + end end if prompt = options.delete(:prompt) - option_tags = content_tag(:option, prompt, :value => '').safe_concat(option_tags) + option_tags = content_tag(:option, prompt, value: '').safe_concat(option_tags) end content_tag :select, option_tags, { "name" => html_name, "id" => sanitize_to_id(name) }.update(options.stringify_keys) @@ -224,7 +231,7 @@ module ActionView # # => <input id="collected_input" name="collected_input" onchange="alert('Input collected!')" # # type="hidden" value="" /> def hidden_field_tag(name, value = nil, options = {}) - text_field_tag(name, value, options.stringify_keys.update("type" => "hidden")) + text_field_tag(name, value, options.merge(type: :hidden)) end # Creates a file upload field. If you are using file uploads then you will also need @@ -263,7 +270,7 @@ module ActionView # file_field_tag 'file', accept: 'text/html', class: 'upload', value: 'index.html' # # => <input accept="text/html" class="upload" id="file" name="file" type="file" value="index.html" /> def file_field_tag(name, options = {}) - text_field_tag(name, nil, options.update("type" => "file")) + text_field_tag(name, nil, options.merge(type: :file)) end # Creates a password field, a masked text field that will hide the users input behind a mask character. @@ -296,7 +303,7 @@ module ActionView # password_field_tag 'pin', '1234', maxlength: 4, size: 6, class: "pin_input" # # => <input class="pin_input" id="pin" maxlength="4" name="pin" size="6" type="password" value="1234" /> def password_field_tag(name = "password", value = nil, options = {}) - text_field_tag(name, value, options.update("type" => "password")) + text_field_tag(name, value, options.merge(type: :password)) end # Creates a text input area; use a textarea for longer text inputs such as blog posts or descriptions. @@ -571,7 +578,7 @@ module ActionView # color_field_tag 'color', '#DEF726', class: 'special_input', disabled: true # # => <input disabled="disabled" class="special_input" id="color" name="color" type="color" value="#DEF726" /> def color_field_tag(name, value = nil, options = {}) - text_field_tag(name, value, options.stringify_keys.update("type" => "color")) + text_field_tag(name, value, options.merge(type: :color)) end # Creates a text field of type "search". @@ -592,7 +599,7 @@ module ActionView # search_field_tag 'search', 'Enter your search query here', class: 'special_input', disabled: true # # => <input disabled="disabled" class="special_input" id="search" name="search" type="search" value="Enter your search query here" /> def search_field_tag(name, value = nil, options = {}) - text_field_tag(name, value, options.stringify_keys.update("type" => "search")) + text_field_tag(name, value, options.merge(type: :search)) end # Creates a text field of type "tel". @@ -613,7 +620,7 @@ module ActionView # telephone_field_tag 'tel', '0123456789', class: 'special_input', disabled: true # # => <input disabled="disabled" class="special_input" id="tel" name="tel" type="tel" value="0123456789" /> def telephone_field_tag(name, value = nil, options = {}) - text_field_tag(name, value, options.stringify_keys.update("type" => "tel")) + text_field_tag(name, value, options.merge(type: :tel)) end alias phone_field_tag telephone_field_tag @@ -635,7 +642,7 @@ module ActionView # date_field_tag 'date', '01/01/2014', class: 'special_input', disabled: true # # => <input disabled="disabled" class="special_input" id="date" name="date" type="date" value="01/01/2014" /> def date_field_tag(name, value = nil, options = {}) - text_field_tag(name, value, options.stringify_keys.update("type" => "date")) + text_field_tag(name, value, options.merge(type: :date)) end # Creates a text field of type "time". @@ -646,7 +653,7 @@ module ActionView # * <tt>:step</tt> - The acceptable value granularity. # * Otherwise accepts the same options as text_field_tag. def time_field_tag(name, value = nil, options = {}) - text_field_tag(name, value, options.stringify_keys.update("type" => "time")) + text_field_tag(name, value, options.merge(type: :time)) end # Creates a text field of type "datetime". @@ -657,7 +664,7 @@ module ActionView # * <tt>:step</tt> - The acceptable value granularity. # * Otherwise accepts the same options as text_field_tag. def datetime_field_tag(name, value = nil, options = {}) - text_field_tag(name, value, options.stringify_keys.update("type" => "datetime")) + text_field_tag(name, value, options.merge(type: :datetime)) end # Creates a text field of type "datetime-local". @@ -668,7 +675,7 @@ module ActionView # * <tt>:step</tt> - The acceptable value granularity. # * Otherwise accepts the same options as text_field_tag. def datetime_local_field_tag(name, value = nil, options = {}) - text_field_tag(name, value, options.stringify_keys.update("type" => "datetime-local")) + text_field_tag(name, value, options.merge(type: 'datetime-local')) end # Creates a text field of type "month". @@ -679,7 +686,7 @@ module ActionView # * <tt>:step</tt> - The acceptable value granularity. # * Otherwise accepts the same options as text_field_tag. def month_field_tag(name, value = nil, options = {}) - text_field_tag(name, value, options.stringify_keys.update("type" => "month")) + text_field_tag(name, value, options.merge(type: :month)) end # Creates a text field of type "week". @@ -690,7 +697,7 @@ module ActionView # * <tt>:step</tt> - The acceptable value granularity. # * Otherwise accepts the same options as text_field_tag. def week_field_tag(name, value = nil, options = {}) - text_field_tag(name, value, options.stringify_keys.update("type" => "week")) + text_field_tag(name, value, options.merge(type: :week)) end # Creates a text field of type "url". @@ -711,7 +718,7 @@ module ActionView # url_field_tag 'url', 'http://rubyonrails.org', class: 'special_input', disabled: true # # => <input disabled="disabled" class="special_input" id="url" name="url" type="url" value="http://rubyonrails.org" /> def url_field_tag(name, value = nil, options = {}) - text_field_tag(name, value, options.stringify_keys.update("type" => "url")) + text_field_tag(name, value, options.merge(type: :url)) end # Creates a text field of type "email". @@ -732,7 +739,7 @@ module ActionView # email_field_tag 'email', 'email@example.com', class: 'special_input', disabled: true # # => <input disabled="disabled" class="special_input" id="email" name="email" type="email" value="email@example.com" /> def email_field_tag(name, value = nil, options = {}) - text_field_tag(name, value, options.stringify_keys.update("type" => "email")) + text_field_tag(name, value, options.merge(type: :email)) end # Creates a number field. @@ -769,10 +776,10 @@ module ActionView # # => <input id="quantity" name="quantity" min="1" max="9" type="number" /> # # number_field_tag 'quantity', nil, min: 1, max: 10 - # # => <input id="quantity" name="quantity" min="1" max="9" type="number" /> + # # => <input id="quantity" name="quantity" min="1" max="10" type="number" /> # # number_field_tag 'quantity', nil, min: 1, max: 10, step: 2 - # # => <input id="quantity" name="quantity" min="1" max="9" step="2" type="number" /> + # # => <input id="quantity" name="quantity" min="1" max="10" step="2" type="number" /> # # number_field_tag 'quantity', '1', class: 'special_input', disabled: true # # => <input disabled="disabled" class="special_input" id="quantity" name="quantity" type="number" value="1" /> @@ -790,7 +797,7 @@ module ActionView # ==== Options # * Accepts the same options as number_field_tag. def range_field_tag(name, value = nil, options = {}) - number_field_tag(name, value, options.stringify_keys.update("type" => "range")) + number_field_tag(name, value, options.merge(type: :range)) end # Creates the hidden UTF8 enforcer tag. Override this method in a helper @@ -862,7 +869,7 @@ module ActionView # see http://www.w3.org/TR/html4/types.html#type-name def sanitize_to_id(name) - name.to_s.delete(']').gsub(/[^-a-zA-Z0-9:.]/, "_") + name.to_s.delete(']').tr('^-a-zA-Z0-9:.', "_") end end end diff --git a/actionview/lib/action_view/helpers/number_helper.rb b/actionview/lib/action_view/helpers/number_helper.rb index 7220bded3c..cfd617cedc 100644 --- a/actionview/lib/action_view/helpers/number_helper.rb +++ b/actionview/lib/action_view/helpers/number_helper.rb @@ -1,4 +1,3 @@ -# encoding: utf-8 require 'active_support/core_ext/hash/keys' require 'active_support/core_ext/string/output_safety' @@ -306,12 +305,12 @@ module ActionView # string containing an i18n scope where to find this hash. It # might have the following keys: # * *integers*: <tt>:unit</tt>, <tt>:ten</tt>, - # *<tt>:hundred</tt>, <tt>:thousand</tt>, <tt>:million</tt>, - # *<tt>:billion</tt>, <tt>:trillion</tt>, - # *<tt>:quadrillion</tt> + # <tt>:hundred</tt>, <tt>:thousand</tt>, <tt>:million</tt>, + # <tt>:billion</tt>, <tt>:trillion</tt>, + # <tt>:quadrillion</tt> # * *fractionals*: <tt>:deci</tt>, <tt>:centi</tt>, - # *<tt>:mili</tt>, <tt>:micro</tt>, <tt>:nano</tt>, - # *<tt>:pico</tt>, <tt>:femto</tt> + # <tt>:mili</tt>, <tt>:micro</tt>, <tt>:nano</tt>, + # <tt>:pico</tt>, <tt>:femto</tt> # * <tt>:format</tt> - Sets the format of the output string # (defaults to "%n %u"). The field types are: # * %u - The quantifier (ex.: 'thousand') diff --git a/actionview/lib/action_view/helpers/record_tag_helper.rb b/actionview/lib/action_view/helpers/record_tag_helper.rb index 77c3e6d394..f7ee573035 100644 --- a/actionview/lib/action_view/helpers/record_tag_helper.rb +++ b/actionview/lib/action_view/helpers/record_tag_helper.rb @@ -1,108 +1,21 @@ -require 'action_view/record_identifier' - module ActionView - # = Action View Record Tag Helpers module Helpers module RecordTagHelper - include ActionView::RecordIdentifier - - # Produces a wrapper DIV element with id and class parameters that - # relate to the specified Active Record object. Usage example: - # - # <%= div_for(@person, class: "foo") do %> - # <%= @person.name %> - # <% end %> - # - # produces: - # - # <div id="person_123" class="person foo"> Joe Bloggs </div> - # - # You can also pass an array of Active Record objects, which will then - # get iterated over and yield each record as an argument for the block. - # For example: - # - # <%= div_for(@people, class: "foo") do |person| %> - # <%= person.name %> - # <% end %> - # - # produces: - # - # <div id="person_123" class="person foo"> Joe Bloggs </div> - # <div id="person_124" class="person foo"> Jane Bloggs </div> - # - def div_for(record, *args, &block) - content_tag_for(:div, record, *args, &block) + def div_for(*) + raise NoMethodError, "The `div_for` method has been removed from " \ + "Rails. To continue using it, add the `record_tag_helper` gem to " \ + "your Gemfile:\n" \ + " gem 'record_tag_helper', '~> 1.0'\n" \ + "Consult the Rails upgrade guide for details." end - # content_tag_for creates an HTML element with id and class parameters - # that relate to the specified Active Record object. For example: - # - # <%= content_tag_for(:tr, @person) do %> - # <td><%= @person.first_name %></td> - # <td><%= @person.last_name %></td> - # <% end %> - # - # would produce the following HTML (assuming @person is an instance of - # a Person object, with an id value of 123): - # - # <tr id="person_123" class="person">....</tr> - # - # If you require the HTML id attribute to have a prefix, you can specify it: - # - # <%= content_tag_for(:tr, @person, :foo) do %> ... - # - # produces: - # - # <tr id="foo_person_123" class="person">... - # - # You can also pass an array of objects which this method will loop through - # and yield the current object to the supplied block, reducing the need for - # having to iterate through the object (using <tt>each</tt>) beforehand. - # For example (assuming @people is an array of Person objects): - # - # <%= content_tag_for(:tr, @people) do |person| %> - # <td><%= person.first_name %></td> - # <td><%= person.last_name %></td> - # <% end %> - # - # produces: - # - # <tr id="person_123" class="person">...</tr> - # <tr id="person_124" class="person">...</tr> - # - # content_tag_for also accepts a hash of options, which will be converted to - # additional HTML attributes. If you specify a <tt>:class</tt> value, it will be combined - # with the default class name for your object. For example: - # - # <%= content_tag_for(:li, @person, class: "bar") %>... - # - # produces: - # - # <li id="person_123" class="person bar">... - # - def content_tag_for(tag_name, single_or_multiple_records, prefix = nil, options = nil, &block) - options, prefix = prefix, nil if prefix.is_a?(Hash) - - Array(single_or_multiple_records).map do |single_record| - content_tag_for_single_record(tag_name, single_record, prefix, options, &block) - end.join("\n").html_safe + def content_tag_for(*) + raise NoMethodError, "The `content_tag_for` method has been removed from " \ + "Rails. To continue using it, add the `record_tag_helper` gem to " \ + "your Gemfile:\n" \ + " gem 'record_tag_helper', '~> 1.0'\n" \ + "Consult the Rails upgrade guide for details." end - - private - - # Called by <tt>content_tag_for</tt> internally to render a content tag - # for each record. - def content_tag_for_single_record(tag_name, record, prefix, options, &block) - options = options ? options.dup : {} - options[:class] = [ dom_class(record, prefix), options[:class] ].compact - options[:id] = dom_id(record, prefix) - - if block_given? - content_tag(tag_name, capture(record, &block), options) - else - content_tag(tag_name, "", options) - end - end end end end diff --git a/actionview/lib/action_view/helpers/rendering_helper.rb b/actionview/lib/action_view/helpers/rendering_helper.rb index e11670e00d..827932d8e2 100644 --- a/actionview/lib/action_view/helpers/rendering_helper.rb +++ b/actionview/lib/action_view/helpers/rendering_helper.rb @@ -32,7 +32,7 @@ module ActionView view_renderer.render(self, options) end else - view_renderer.render_partial(self, :partial => options, :locals => locals) + view_renderer.render_partial(self, :partial => options, :locals => locals, &block) end end diff --git a/actionview/lib/action_view/helpers/sanitize_helper.rb b/actionview/lib/action_view/helpers/sanitize_helper.rb index 4f2db0a0c4..463a4e9f60 100644 --- a/actionview/lib/action_view/helpers/sanitize_helper.rb +++ b/actionview/lib/action_view/helpers/sanitize_helper.rb @@ -1,5 +1,4 @@ require 'active_support/core_ext/object/try' -require 'active_support/deprecation' require 'rails-html-sanitizer' module ActionView @@ -9,76 +8,77 @@ module ActionView # These helper methods extend Action View making them callable within your template files. module SanitizeHelper extend ActiveSupport::Concern - # This +sanitize+ helper will HTML encode all tags and strip all attributes that - # aren't specifically allowed. + # Sanitizes HTML input, stripping all tags and attributes that aren't whitelisted. # - # It also strips href/src tags with invalid protocols, like javascript: especially. - # It does its best to counter any tricks that hackers may use, like throwing in - # unicode/ascii/hex values to get past the javascript: filters. Check out - # the extensive test suite. + # It also strips href/src attributes with unsafe protocols like + # <tt>javascript:</tt>, while also protecting against attempts to use Unicode, + # ASCII, and hex character references to work around these protocol filters. # - # <%= sanitize @article.body %> + # The default sanitizer is Rails::Html::WhiteListSanitizer. See {Rails HTML + # Sanitizers}[https://github.com/rails/rails-html-sanitizer] for more information. # - # You can add or remove tags/attributes if you want to customize it a bit. - # See ActionView::Base for full docs on the available options. You can add - # tags/attributes for single uses of +sanitize+ by passing either the - # <tt>:attributes</tt> or <tt>:tags</tt> options: + # Custom sanitization rules can also be provided. # - # Normal Use - # - # <%= sanitize @article.body %> + # Please note that sanitizing user-provided text does not guarantee that the + # resulting markup is valid or even well-formed. For example, the output may still + # contain unescaped characters like <tt><</tt>, <tt>></tt>, or <tt>&</tt>. # - # Custom Use - Custom Scrubber - # (supply a Loofah::Scrubber that does the sanitization) + # ==== Options # - # scrubber can either wrap a block: - # scrubber = Loofah::Scrubber.new do |node| - # node.text = "dawn of cats" - # end + # * <tt>:tags</tt> - An array of allowed tags. + # * <tt>:attributes</tt> - An array of allowed attributes. + # * <tt>:scrubber</tt> - A {Rails::Html scrubber}[https://github.com/rails/rails-html-sanitizer] + # or {Loofah::Scrubber}[https://github.com/flavorjones/loofah] object that + # defines custom sanitization rules. A custom scrubber takes precedence over + # custom tags and attributes. # - # or be a subclass of Loofah::Scrubber which responds to scrub: - # class KittyApocalypse < Loofah::Scrubber - # def scrub(node) - # node.text = "dawn of cats" - # end - # end - # scrubber = KittyApocalypse.new + # ==== Examples # - # <%= sanitize @article.body, scrubber: scrubber %> + # Normal use: # - # A custom scrubber takes precedence over custom tags and attributes - # Learn more about scrubbers here: https://github.com/flavorjones/loofah + # <%= sanitize @comment.body %> # - # Custom Use - tags and attributes - # (only the mentioned tags and attributes are allowed, nothing else) + # Providing custom whitelisted tags and attributes: # - # <%= sanitize @article.body, tags: %w(table tr td), attributes: %w(id class style) %> + # <%= sanitize @comment.body, tags: %w(strong em a), attributes: %w(href) %> # - # Add table tags to the default allowed tags + # Providing a custom Rails::Html scrubber: # - # class Application < Rails::Application - # config.action_view.sanitized_allowed_tags = 'table', 'tr', 'td' - # end + # class CommentScrubber < Rails::Html::PermitScrubber + # def allowed_node?(node) + # !%w(form script comment blockquote).include?(node.name) + # end # - # Remove tags to the default allowed tags + # def skip_node?(node) + # node.text? + # end # - # class Application < Rails::Application - # config.after_initialize do - # ActionView::Base.sanitized_allowed_tags.delete 'div' + # def scrub_attribute?(name) + # name == 'style' # end # end # - # Change allowed default attributes + # <%= sanitize @comment.body, scrubber: CommentScrubber.new %> + # + # See {Rails HTML Sanitizer}[https://github.com/rails/rails-html-sanitizer] for + # documentation about Rails::Html scrubbers. # - # class Application < Rails::Application - # config.action_view.sanitized_allowed_attributes = ['id', 'class', 'style'] + # Providing a custom Loofah::Scrubber: + # + # scrubber = Loofah::Scrubber.new do |node| + # node.remove if node.name == 'script' # end # - # Please note that sanitizing user-provided text does not guarantee that the - # resulting markup is valid (conforming to a document type) or even well-formed. - # The output may still contain e.g. unescaped '<', '>', '&' characters and - # confuse browsers. + # <%= sanitize @comment.body, scrubber: scrubber %> + # + # See {Loofah's documentation}[https://github.com/flavorjones/loofah] for more + # information about defining custom Loofah::Scrubber objects. # + # To set the default allowed tags or attributes across your application: + # + # # In config/application.rb + # config.action_view.sanitized_allowed_tags = ['strong', 'em', 'a'] + # config.action_view.sanitized_allowed_attributes = ['href', 'title'] def sanitize(html, options = {}) self.class.white_list_sanitizer.sanitize(html, options).try(:html_safe) end @@ -88,9 +88,7 @@ module ActionView self.class.white_list_sanitizer.sanitize_css(style) end - # Strips all HTML tags from the +html+, including comments. This uses - # Nokogiri for tokenization (via Loofah) and so its HTML parsing ability - # is limited by that of Nokogiri. + # Strips all HTML tags from +html+, including comments. # # strip_tags("Strip <i>these</i> tags!") # # => Strip these tags! @@ -104,7 +102,7 @@ module ActionView self.class.full_sanitizer.sanitize(html) end - # Strips all link tags from +text+ leaving just the link text. + # Strips all link tags from +html+ leaving just the link text. # # strip_links('<a href="http://www.rubyonrails.org">Ruby on Rails</a>') # # => Ruby on Rails @@ -167,30 +165,6 @@ module ActionView def white_list_sanitizer @white_list_sanitizer ||= sanitizer_vendor.white_list_sanitizer.new end - - ## - # :method: sanitized_allowed_tags= - # - # :call-seq: sanitized_allowed_tags=(tags) - # - # Replaces the allowed tags for the +sanitize+ helper. - # - # class Application < Rails::Application - # config.action_view.sanitized_allowed_tags = 'table', 'tr', 'td' - # end - # - - ## - # :method: sanitized_allowed_attributes= - # - # :call-seq: sanitized_allowed_attributes=(attributes) - # - # Replaces the allowed HTML attributes for the +sanitize+ helper. - # - # class Application < Rails::Application - # config.action_view.sanitized_allowed_attributes = ['onclick', 'longdesc'] - # end - # end end end diff --git a/actionview/lib/action_view/helpers/tag_helper.rb b/actionview/lib/action_view/helpers/tag_helper.rb index c20800598f..a87c223a71 100644 --- a/actionview/lib/action_view/helpers/tag_helper.rb +++ b/actionview/lib/action_view/helpers/tag_helper.rb @@ -18,7 +18,7 @@ module ActionView itemscope allowfullscreen default inert sortable truespeed typemustmatch).to_set - BOOLEAN_ATTRIBUTES.merge(BOOLEAN_ATTRIBUTES.map {|attribute| attribute.to_sym }) + BOOLEAN_ATTRIBUTES.merge(BOOLEAN_ATTRIBUTES.map(&:to_sym)) TAG_PREFIXES = ['aria', 'data', :aria, :data].to_set @@ -123,7 +123,7 @@ module ActionView # cdata_section("hello]]>world") # # => <![CDATA[hello]]]]><![CDATA[>world]]> def cdata_section(content) - splitted = content.to_s.gsub(']]>', ']]]]><![CDATA[>') + splitted = content.to_s.gsub(/\]\]\>/, ']]]]><![CDATA[>') "<![CDATA[#{splitted}]]>".html_safe end @@ -160,7 +160,7 @@ module ActionView attrs << tag_option(key, value, escape) end end - " #{attrs.sort! * ' '}" unless attrs.empty? + " #{attrs * ' '}" unless attrs.empty? end def prefix_tag_option(prefix, key, value, escape) diff --git a/actionview/lib/action_view/helpers/tags.rb b/actionview/lib/action_view/helpers/tags.rb index 45c75d10c0..a4f6eb0150 100644 --- a/actionview/lib/action_view/helpers/tags.rb +++ b/actionview/lib/action_view/helpers/tags.rb @@ -5,6 +5,7 @@ module ActionView eager_autoload do autoload :Base + autoload :Translator autoload :CheckBox autoload :CollectionCheckBoxes autoload :CollectionRadioButtons diff --git a/actionview/lib/action_view/helpers/tags/base.rb b/actionview/lib/action_view/helpers/tags/base.rb index 8607da301c..acc6443a96 100644 --- a/actionview/lib/action_view/helpers/tags/base.rb +++ b/actionview/lib/action_view/helpers/tags/base.rb @@ -14,7 +14,7 @@ module ActionView @object_name.sub!(/\[\]$/,"") || @object_name.sub!(/\[\]\]$/,"]") @object = retrieve_object(options.delete(:object)) @options = options - @auto_index = retrieve_autoindex(Regexp.last_match.pre_match) if Regexp.last_match + @auto_index = Regexp.last_match ? retrieve_autoindex(Regexp.last_match.pre_match) : nil end # This is what child classes implement. @@ -25,19 +25,26 @@ module ActionView private def value(object) - object.send @method_name if object + object.public_send @method_name if object end def value_before_type_cast(object) unless object.nil? method_before_type_cast = @method_name + "_before_type_cast" - object.respond_to?(method_before_type_cast) ? - object.send(method_before_type_cast) : + if value_came_from_user?(object) && object.respond_to?(method_before_type_cast) + object.public_send(method_before_type_cast) + else value(object) + end end end + def value_came_from_user?(object) + method_name = "#{@method_name}_came_from_user?" + !object.respond_to?(method_name) || object.public_send(method_name) + end + def retrieve_object(object) if object object @@ -72,35 +79,30 @@ module ActionView end def add_default_name_and_id(options) - if options.has_key?("index") - options["name"] ||= options.fetch("name"){ tag_name_with_index(options["index"], options["multiple"]) } - options["id"] = options.fetch("id"){ tag_id_with_index(options["index"]) } - options.delete("index") - elsif defined?(@auto_index) - options["name"] ||= options.fetch("name"){ tag_name_with_index(@auto_index, options["multiple"]) } - options["id"] = options.fetch("id"){ tag_id_with_index(@auto_index) } - else - options["name"] ||= options.fetch("name"){ tag_name(options["multiple"]) } - options["id"] = options.fetch("id"){ tag_id } + index = name_and_id_index(options) + options["name"] = options.fetch("name"){ tag_name(options["multiple"], index) } + options["id"] = options.fetch("id"){ tag_id(index) } + if namespace = options.delete("namespace") + options['id'] = options['id'] ? "#{namespace}_#{options['id']}" : namespace end - - options["id"] = [options.delete('namespace'), options["id"]].compact.join("_").presence - end - - def tag_name(multiple = false) - "#{@object_name}[#{sanitized_method_name}]#{"[]" if multiple}" end - def tag_name_with_index(index, multiple = false) - "#{@object_name}[#{index}][#{sanitized_method_name}]#{"[]" if multiple}" - end - - def tag_id - "#{sanitized_object_name}_#{sanitized_method_name}" + def tag_name(multiple = false, index = nil) + # a little duplication to construct less strings + if index + "#{@object_name}[#{index}][#{sanitized_method_name}]#{"[]" if multiple}" + else + "#{@object_name}[#{sanitized_method_name}]#{"[]" if multiple}" + end end - def tag_id_with_index(index) - "#{sanitized_object_name}_#{index}_#{sanitized_method_name}" + def tag_id(index = nil) + # a little duplication to construct less strings + if index + "#{sanitized_object_name}_#{index}_#{sanitized_method_name}" + else + "#{sanitized_object_name}_#{sanitized_method_name}" + end end def sanitized_object_name @@ -142,6 +144,10 @@ module ActionView end option_tags end + + def name_and_id_index(options) + options.key?("index") ? options.delete("index") || "" : @auto_index + end end end end diff --git a/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb b/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb index 6242a2a085..1765fa6558 100644 --- a/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb +++ b/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb @@ -41,14 +41,7 @@ module ActionView end def hidden_field - hidden_name = @html_options[:name] - - hidden_name ||= if @options.has_key?(:index) - "#{tag_name_with_index(@options[:index])}[]" - else - "#{tag_name}[]" - end - + hidden_name = @html_options[:name] || "#{tag_name(false, @options[:index])}[]" @template_object.hidden_field_tag(hidden_name, "", id: nil) end end diff --git a/actionview/lib/action_view/helpers/tags/file_field.rb b/actionview/lib/action_view/helpers/tags/file_field.rb index 476b820d84..e6a1d9c62d 100644 --- a/actionview/lib/action_view/helpers/tags/file_field.rb +++ b/actionview/lib/action_view/helpers/tags/file_field.rb @@ -2,6 +2,21 @@ module ActionView module Helpers module Tags # :nodoc: class FileField < TextField # :nodoc: + + def render + options = @options.stringify_keys + + if options.fetch("include_hidden", true) + add_default_name_and_id(options) + options[:type] = "file" + tag("input", name: options["name"], type: "hidden", value: "") + tag("input", options) + else + options.delete("include_hidden") + @options = options + + super + end + end end end end diff --git a/actionview/lib/action_view/helpers/tags/label.rb b/actionview/lib/action_view/helpers/tags/label.rb index 08a23e497e..b31d5fda66 100644 --- a/actionview/lib/action_view/helpers/tags/label.rb +++ b/actionview/lib/action_view/helpers/tags/label.rb @@ -15,20 +15,10 @@ module ActionView def translation method_and_value = @tag_value.present? ? "#{@method_name}.#{@tag_value}" : @method_name - @object_name.gsub!(/\[(.*)_attributes\]\[\d+\]/, '.\1') - - if object.respond_to?(:to_model) - key = object.model_name.i18n_key - i18n_default = ["#{key}.#{method_and_value}".to_sym, ""] - end - - i18n_default ||= "" - content = I18n.t("#{@object_name}.#{method_and_value}", :default => i18n_default, :scope => "helpers.label").presence - - content ||= if object && object.class.respond_to?(:human_attribute_name) - object.class.human_attribute_name(method_and_value) - end + content ||= Translator + .new(object, @object_name, method_and_value, scope: "helpers.label") + .translate content ||= @method_name.humanize content diff --git a/actionview/lib/action_view/helpers/tags/placeholderable.rb b/actionview/lib/action_view/helpers/tags/placeholderable.rb index ae67bc13af..cf7b117614 100644 --- a/actionview/lib/action_view/helpers/tags/placeholderable.rb +++ b/actionview/lib/action_view/helpers/tags/placeholderable.rb @@ -7,24 +7,12 @@ module ActionView if tag_value = @options[:placeholder] placeholder = tag_value if tag_value.is_a?(String) - - object_name = @object_name.gsub(/\[(.*)_attributes\]\[\d+\]/, '.\1') method_and_value = tag_value.is_a?(TrueClass) ? @method_name : "#{@method_name}.#{tag_value}" - if object.respond_to?(:to_model) - key = object.class.model_name.i18n_key - i18n_default = ["#{key}.#{method_and_value}".to_sym, ""] - end - - i18n_default ||= "" - placeholder ||= I18n.t("#{object_name}.#{method_and_value}", :default => i18n_default, :scope => "helpers.placeholder").presence - - placeholder ||= if object && object.class.respond_to?(:human_attribute_name) - object.class.human_attribute_name(method_and_value) - end - + placeholder ||= Tags::Translator + .new(object, @object_name, method_and_value, scope: "helpers.placeholder") + .translate placeholder ||= @method_name.humanize - @options[:placeholder] = placeholder end end diff --git a/actionview/lib/action_view/helpers/tags/search_field.rb b/actionview/lib/action_view/helpers/tags/search_field.rb index c09e2f1be7..a848aeabfa 100644 --- a/actionview/lib/action_view/helpers/tags/search_field.rb +++ b/actionview/lib/action_view/helpers/tags/search_field.rb @@ -16,6 +16,7 @@ module ActionView options["incremental"] = true unless options.has_key?("incremental") end + @options = options super end end diff --git a/actionview/lib/action_view/helpers/tags/translator.rb b/actionview/lib/action_view/helpers/tags/translator.rb new file mode 100644 index 0000000000..8b6655481d --- /dev/null +++ b/actionview/lib/action_view/helpers/tags/translator.rb @@ -0,0 +1,40 @@ +module ActionView + module Helpers + module Tags # :nodoc: + class Translator # :nodoc: + def initialize(object, object_name, method_and_value, scope:) + @object_name = object_name.gsub(/\[(.*)_attributes\]\[\d+\]/, '.\1') + @method_and_value = method_and_value + @scope = scope + @model = object.respond_to?(:to_model) ? object.to_model : nil + end + + def translate + translated_attribute = I18n.t("#{object_name}.#{method_and_value}", default: i18n_default, scope: scope).presence + translated_attribute || human_attribute_name + end + + protected + + attr_reader :object_name, :method_and_value, :scope, :model + + private + + def i18n_default + if model + key = model.model_name.i18n_key + ["#{key}.#{method_and_value}".to_sym, ""] + else + "" + end + end + + def human_attribute_name + if model && model.class.respond_to?(:human_attribute_name) + model.class.human_attribute_name(method_and_value) + end + end + end + end + end +end diff --git a/actionview/lib/action_view/helpers/text_helper.rb b/actionview/lib/action_view/helpers/text_helper.rb index a9f1631586..2c40ed1832 100644 --- a/actionview/lib/action_view/helpers/text_helper.rb +++ b/actionview/lib/action_view/helpers/text_helper.rb @@ -309,7 +309,7 @@ module ActionView # <table> # <% @items.each do |item| %> # <tr class="<%= cycle("odd", "even") -%>"> - # <td>item</td> + # <td><%= item %></td> # </tr> # <% end %> # </table> diff --git a/actionview/lib/action_view/helpers/translation_helper.rb b/actionview/lib/action_view/helpers/translation_helper.rb index c2fda42396..342361217c 100644 --- a/actionview/lib/action_view/helpers/translation_helper.rb +++ b/actionview/lib/action_view/helpers/translation_helper.rb @@ -37,14 +37,17 @@ module ActionView # you know what kind of output to expect when you call translate in a template. def translate(key, options = {}) options = options.dup - options[:default] = wrap_translate_defaults(options[:default]) if options[:default] + remaining_defaults = Array(options.delete(:default)) + options[:default] = remaining_defaults.shift if remaining_defaults.first.kind_of? String - # If the user has specified rescue_format then pass it all through, otherwise use - # raise and do the work ourselves - options[:raise] ||= ActionView::Base.raise_on_missing_translations - - raise_error = options[:raise] || options.key?(:rescue_format) - unless raise_error + # If the user has explicitly decided to NOT raise errors, pass that option to I18n. + # Otherwise, tell I18n to raise an exception, which we rescue further in this method. + # Note: `raise_error` refers to us re-raising the error in this method. I18n is forced to raise by default. + if options[:raise] == false || (options.key?(:rescue_format) && options[:rescue_format].nil?) + raise_error = false + options[:raise] = false + else + raise_error = options[:raise] || options[:rescue_format] || ActionView::Base.raise_on_missing_translations options[:raise] = true end @@ -62,10 +65,14 @@ module ActionView I18n.translate(scope_key_by_partial(key), options) end rescue I18n::MissingTranslationData => e - raise e if raise_error + if remaining_defaults.present? + translate remaining_defaults.shift, options.merge(default: remaining_defaults) + else + raise e if raise_error - keys = I18n.normalize_keys(e.locale, e.key, e.options[:scope]) - content_tag('span', keys.last.to_s.titleize, :class => 'translation_missing', :title => "translation missing: #{keys.join('.')}") + keys = I18n.normalize_keys(e.locale, e.key, e.options[:scope]) + content_tag('span', keys.last.to_s.titleize, :class => 'translation_missing', :title => "translation missing: #{keys.join('.')}") + end end alias :t :translate @@ -94,21 +101,6 @@ module ActionView def html_safe_translation_key?(key) key.to_s =~ /(\b|_|\.)html$/ end - - def wrap_translate_defaults(defaults) - new_defaults = [] - defaults = Array(defaults) - while key = defaults.shift - if key.is_a?(Symbol) - new_defaults << lambda { |_, options| translate key, options.merge(:default => defaults) } - break - else - new_defaults << key - end - end - - new_defaults - end end end end diff --git a/actionview/lib/action_view/helpers/url_helper.rb b/actionview/lib/action_view/helpers/url_helper.rb index 4c53b5df9e..3dbce0738e 100644 --- a/actionview/lib/action_view/helpers/url_helper.rb +++ b/actionview/lib/action_view/helpers/url_helper.rb @@ -46,9 +46,9 @@ module ActionView end protected :_back_url - # Creates a link tag of the given +name+ using a URL created by the set of +options+. + # Creates an anchor element of the given +name+ using a URL created by the set of +options+. # See the valid options in the documentation for +url_for+. It's also possible to - # pass a String instead of an options hash, which generates a link tag that uses the + # pass a String instead of an options hash, which generates an anchor element that uses the # value of the String as the href for the link. Using a <tt>:back</tt> Symbol instead # of an options hash will generate a link to the referrer (a JavaScript back link # will be used in place of a referrer if none exists). If +nil+ is passed as the name @@ -428,6 +428,7 @@ module ActionView # * <tt>:body</tt> - Preset the body of the email. # * <tt>:cc</tt> - Carbon Copy additional recipients on the email. # * <tt>:bcc</tt> - Blind Carbon Copy additional recipients on the email. + # * <tt>:reply_to</tt> - Preset the Reply-To field of the email. # # ==== Obfuscation # Prior to Rails 4.0, +mail_to+ provided options for encoding the address @@ -457,10 +458,9 @@ module ActionView html_options, name = name, nil if block_given? html_options = (html_options || {}).stringify_keys - extras = %w{ cc bcc body subject }.map! { |item| - if option = html_options.delete(item).presence - "#{item}=#{Rack::Utils.escape_path(option)}" - end + extras = %w{ cc bcc body subject reply_to }.map! { |item| + option = html_options.delete(item).presence || next + "#{item.dasherize}=#{Rack::Utils.escape_path(option)}" }.compact extras = extras.empty? ? '' : '?' + extras.join('&') diff --git a/actionview/lib/action_view/layouts.rb b/actionview/lib/action_view/layouts.rb index 9ee05bd816..0b5c0b9991 100644 --- a/actionview/lib/action_view/layouts.rb +++ b/actionview/lib/action_view/layouts.rb @@ -262,7 +262,7 @@ module ActionView def layout(layout, conditions = {}) include LayoutConditions unless conditions.empty? - conditions.each {|k, v| conditions[k] = Array(v).map {|a| a.to_s} } + conditions.each {|k, v| conditions[k] = Array(v).map(&:to_s) } self._layout_conditions = conditions self._layout = layout diff --git a/actionview/lib/action_view/lookup_context.rb b/actionview/lib/action_view/lookup_context.rb index ea687d9cca..36855ec3d0 100644 --- a/actionview/lib/action_view/lookup_context.rb +++ b/actionview/lib/action_view/lookup_context.rb @@ -191,7 +191,6 @@ module ActionView def initialize(view_paths, details = {}, prefixes = []) @details, @details_key = {}, nil - @skip_default_locale = false @cache = true @prefixes = prefixes @rendered_format = nil @@ -213,12 +212,6 @@ module ActionView super(values) end - # Do not use the default locale on template lookup. - def skip_default_locale! - @skip_default_locale = true - self.locale = nil - end - # Override locale to return a symbol instead of array. def locale @details[:locale].first @@ -233,7 +226,7 @@ module ActionView config.locale = value end - super(@skip_default_locale ? I18n.locale : default_locale) + super(default_locale) end # Uses the first format in the formats array for layout lookup. diff --git a/actionview/lib/action_view/model_naming.rb b/actionview/lib/action_view/model_naming.rb index d42e436b17..b6ed13424e 100644 --- a/actionview/lib/action_view/model_naming.rb +++ b/actionview/lib/action_view/model_naming.rb @@ -1,5 +1,5 @@ module ActionView - module ModelNaming + module ModelNaming #:nodoc: # Converts the given object to an ActiveModel compliant one. def convert_to_model(object) object.respond_to?(:to_model) ? object.to_model : object diff --git a/actionview/lib/action_view/railtie.rb b/actionview/lib/action_view/railtie.rb index 81f9c40b85..ee3dce24b7 100644 --- a/actionview/lib/action_view/railtie.rb +++ b/actionview/lib/action_view/railtie.rb @@ -38,7 +38,7 @@ module ActionView initializer "action_view.setup_action_pack" do |app| ActiveSupport.on_load(:action_controller) do - ActionView::RoutingUrlFor.send(:include, ActionDispatch::Routing::UrlFor) + ActionView::RoutingUrlFor.include(ActionDispatch::Routing::UrlFor) end end diff --git a/actionview/lib/action_view/record_identifier.rb b/actionview/lib/action_view/record_identifier.rb index 63f645431a..6c6e69101b 100644 --- a/actionview/lib/action_view/record_identifier.rb +++ b/actionview/lib/action_view/record_identifier.rb @@ -2,29 +2,54 @@ require 'active_support/core_ext/module' require 'action_view/model_naming' module ActionView - # The record identifier encapsulates a number of naming conventions for dealing with records, like Active Records or - # pretty much any other model type that has an id. These patterns are then used to try elevate the view actions to - # a higher logical level. + # RecordIdentifier encapsulates methods used by various ActionView helpers + # to associate records with DOM elements. # - # # routes - # resources :posts + # Consider for example the following code that displays the body of a post: # - # # view - # <%= div_for(post) do %> <div id="post_45" class="post"> - # <%= post.body %> What a wonderful world! - # <% end %> </div> + # <%= div_for(post) do %> + # <%= post.body %> + # <% end %> # - # # controller - # def update - # post = Post.find(params[:id]) - # post.update(params[:post]) + # When +post+ is a new, unsaved ActiveRecord::Base intance, the resulting HTML + # is: # - # redirect_to(post) # Calls polymorphic_url(post) which in turn calls post_url(post) - # end + # <div id="new_post" class="post"> + # </div> + # + # When +post+ is a persisted ActiveRecord::Base instance, the resulting HTML + # is: + # + # <div id="post_42" class="post"> + # What a wonderful world! + # </div> + # + # In both cases, the +id+ and +class+ of the wrapping DOM element are + # automatically generated, following naming conventions encapsulated by the + # RecordIdentifier methods #dom_id and #dom_class: + # + # dom_id(Post.new) # => "new_post" + # dom_class(Post.new) # => "post" + # dom_id(Post.find 42) # => "post_42" + # dom_class(Post.find 42) # => "post" # - # As the example above shows, you can stop caring to a large extent what the actual id of the post is. - # You just know that one is being assigned and that the subsequent calls in redirect_to expect that - # same naming convention and allows you to write less code if you follow it. + # Note that these methods do not strictly require +Post+ to be a subclass of + # ActiveRecord::Base. + # Any +Post+ class will work as long as its instances respond to +to_key+ + # and +model_name+, given that +model_name+ responds to +param_key+. + # For instance: + # + # class Post + # attr_accessor :to_key + # + # def model_name + # OpenStruct.new param_key: 'post' + # end + # + # def self.find(id) + # new.tap { |post| post.to_key = [id] } + # end + # end module RecordIdentifier extend self extend ModelNaming @@ -78,7 +103,7 @@ module ActionView # make sure yourself that your dom ids are valid, in case you overwrite this method. def record_key_for_dom_id(record) key = convert_to_model(record).to_key - key ? key.join('_') : key + key ? key.join(JOIN) : key end end end diff --git a/actionview/lib/action_view/renderer/partial_renderer.rb b/actionview/lib/action_view/renderer/partial_renderer.rb index 0407632435..5ff15411cf 100644 --- a/actionview/lib/action_view/renderer/partial_renderer.rb +++ b/actionview/lib/action_view/renderer/partial_renderer.rb @@ -73,7 +73,7 @@ module ActionView # # <%= render partial: "account", locals: { user: @buyer } %> # - # == Rendering a collection of partials + # == \Rendering a collection of partials # # The example of partial use describes a familiar pattern where a template needs to iterate over an array and # render a sub template for each of the elements. This pattern has been implemented as a single method that @@ -105,7 +105,7 @@ module ActionView # NOTE: Due to backwards compatibility concerns, the collection can't be one of hashes. Normally you'd also # just keep domain objects, like Active Records, in there. # - # == Rendering shared partials + # == \Rendering shared partials # # Two controllers can share a set of partials and render them like this: # @@ -113,7 +113,7 @@ module ActionView # # This will render the partial "advertisement/_ad.html.erb" regardless of which controller this is being called from. # - # == Rendering objects that respond to `to_partial_path` + # == \Rendering objects that respond to `to_partial_path` # # Instead of explicitly naming the location of a partial, you can also let PartialRenderer do the work # and pick the proper path by checking `to_partial_path` method. @@ -127,7 +127,7 @@ module ActionView # # <%= render partial: "posts/post", collection: @posts %> # <%= render partial: @posts %> # - # == Rendering the default case + # == \Rendering the default case # # If you're not going to be using any of the options like collections or layouts, you can also use the short-hand # defaults of render to render partials. Examples: @@ -147,7 +147,7 @@ module ActionView # # <%= render partial: "posts/post", collection: @posts %> # <%= render @posts %> # - # == Rendering partials with layouts + # == \Rendering partials with layouts # # Partials can have their own layouts applied to them. These layouts are different than the ones that are # specified globally for the entire action, but they work in a similar fashion. Imagine a list with two types @@ -366,10 +366,12 @@ module ActionView partial = options[:partial] if String === partial + @has_object = options.key?(:object) @object = options[:object] @collection = collection_from_options @path = partial else + @has_object = true @object = partial @collection = collection_from_object || collection_from_options @@ -382,7 +384,7 @@ module ActionView end if as = options[:as] - raise_invalid_identifier(as) unless as.to_s =~ /\A[a-z_]\w*\z/ + raise_invalid_option_as(as) unless as.to_s =~ /\A[a-z_]\w*\z/ as = as.to_sym end @@ -506,7 +508,7 @@ module ActionView def retrieve_template_keys keys = @locals.keys - keys << @variable if @object || @collection + keys << @variable if @has_object || @collection if @collection keys << @variable_counter keys << @variable_iteration @@ -517,7 +519,7 @@ module ActionView def retrieve_variable(path, as) variable = as || begin base = path[-1] == "/" ? "" : File.basename(path) - raise_invalid_identifier(path) unless base =~ /\A_?([a-z]\w*)(\.\w+)*\z/ + raise_invalid_identifier(path) unless base =~ /\A_?(.*)(?:\.\w+)*\z/ $1.to_sym end if @collection @@ -528,11 +530,18 @@ module ActionView end IDENTIFIER_ERROR_MESSAGE = "The partial name (%s) is not a valid Ruby identifier; " + - "make sure your partial name starts with a lowercase letter or underscore, " + + "make sure your partial name starts with underscore." + + OPTION_AS_ERROR_MESSAGE = "The value (%s) of the option `as` is not a valid Ruby identifier; " + + "make sure it starts with lowercase letter, " + "and is followed by any combination of letters, numbers and underscores." def raise_invalid_identifier(path) raise ArgumentError.new(IDENTIFIER_ERROR_MESSAGE % (path)) end + + def raise_invalid_option_as(as) + raise ArgumentError.new(OPTION_AS_ERROR_MESSAGE % (as)) + end end end diff --git a/actionview/lib/action_view/renderer/template_renderer.rb b/actionview/lib/action_view/renderer/template_renderer.rb index f3a48ecfa0..cd21d7ab47 100644 --- a/actionview/lib/action_view/renderer/template_renderer.rb +++ b/actionview/lib/action_view/renderer/template_renderer.rb @@ -18,7 +18,7 @@ module ActionView # Determine the template to be rendered using the given options. def determine_template(options) - keys = options.fetch(:locals, {}).keys + keys = options.has_key?(:locals) ? options[:locals].keys : [] if options.key?(:body) Template::Text.new(options[:body]) diff --git a/actionview/lib/action_view/rendering.rb b/actionview/lib/action_view/rendering.rb index 5cbdfdf6c0..1e8e7415d1 100644 --- a/actionview/lib/action_view/rendering.rb +++ b/actionview/lib/action_view/rendering.rb @@ -35,13 +35,13 @@ module ActionView module ClassMethods def view_context_class @view_context_class ||= begin - include_path_helpers = supports_path? + supports_path = supports_path? routes = respond_to?(:_routes) && _routes helpers = respond_to?(:_helpers) && _helpers Class.new(ActionView::Base) do if routes - include routes.url_helpers(include_path_helpers) + include routes.url_helpers(supports_path) include routes.mounted_helpers end @@ -92,12 +92,15 @@ module ActionView # Find and render a template based on the options given. # :api: private def _render_template(options) #:nodoc: - variant = options[:variant] + variant = options.delete(:variant) + assigns = options.delete(:assigns) + context = view_context + context.assign assigns if assigns lookup_context.rendered_format = nil if options[:formats] lookup_context.variants = variant if variant - view_renderer.render(view_context, options) + view_renderer.render(context, options) end # Assign the rendered format to lookup context. diff --git a/actionview/lib/action_view/routing_url_for.rb b/actionview/lib/action_view/routing_url_for.rb index 75febb8652..f281333a41 100644 --- a/actionview/lib/action_view/routing_url_for.rb +++ b/actionview/lib/action_view/routing_url_for.rb @@ -80,21 +80,38 @@ module ActionView when String options when nil - super({:only_path => true}) + super(only_path: _generate_paths_by_default) when Hash options = options.symbolize_keys - options[:only_path] = options[:host].nil? unless options.key?(:only_path) + unless options.key?(:only_path) + if options[:host].nil? + options[:only_path] = _generate_paths_by_default + else + options[:only_path] = false + end + end + super(options) when :back _back_url - when Symbol - ActionDispatch::Routing::PolymorphicRoutes::HelperMethodBuilder.path.handle_string_call self, options when Array - polymorphic_path(options, options.extract_options!) - when Class - ActionDispatch::Routing::PolymorphicRoutes::HelperMethodBuilder.path.handle_class_call self, options + if _generate_paths_by_default + polymorphic_path(options, options.extract_options!) + else + polymorphic_url(options, options.extract_options!) + end else - ActionDispatch::Routing::PolymorphicRoutes::HelperMethodBuilder.path.handle_model_call self, options + method = _generate_paths_by_default ? :path : :url + builder = ActionDispatch::Routing::PolymorphicRoutes::HelperMethodBuilder.send(method) + + case options + when Symbol + builder.handle_string_call(self, options) + when Class + builder.handle_class_call(self, options) + else + builder.handle_model_call(self, options) + end end end diff --git a/actionview/lib/action_view/template.rb b/actionview/lib/action_view/template.rb index f398f9bfa3..305d9c6ee3 100644 --- a/actionview/lib/action_view/template.rb +++ b/actionview/lib/action_view/template.rb @@ -87,6 +87,19 @@ module ActionView # expected_encoding # ) + ## + # :method: local_assigns + # + # Returns a hash with the defined local variables. + # + # Given this sub template rendering: + # + # <%= render "shared/header", { headline: "Welcome", person: person } %> + # + # You can use +local_assigns+ in the sub templates to access the local variables: + # + # local_assigns[:headline] # => "Welcome" + eager_autoload do autoload :Error autoload :Handlers @@ -103,7 +116,7 @@ module ActionView # This finalizer is needed (and exactly with a proc inside another proc) # otherwise templates leak in development. - Finalizer = proc do |method_name, mod| + Finalizer = proc do |method_name, mod| # :nodoc: proc do mod.module_eval do remove_possible_method method_name @@ -313,15 +326,19 @@ module ActionView def locals_code #:nodoc: # Double assign to suppress the dreaded 'assigned but unused variable' warning - @locals.map { |key| "#{key} = #{key} = local_assigns[:#{key}];" }.join + @locals.each_with_object('') { |key, code| code << "#{key} = #{key} = local_assigns[:#{key}];" } end def method_name #:nodoc: - @method_name ||= "_#{identifier_method_name}__#{@identifier.hash}_#{__id__}".tr('-', "_") + @method_name ||= begin + m = "_#{identifier_method_name}__#{@identifier.hash}_#{__id__}" + m.tr!('-', '_') + m + end end def identifier_method_name #:nodoc: - inspect.gsub(/[^a-z_]/, '_') + inspect.tr('^a-z_', '_') end def instrument(action, &block) diff --git a/actionview/lib/action_view/template/error.rb b/actionview/lib/action_view/template/error.rb index 743ef6de0a..390bce98a2 100644 --- a/actionview/lib/action_view/template/error.rb +++ b/actionview/lib/action_view/template/error.rb @@ -75,7 +75,7 @@ module ActionView def sub_template_message if @sub_templates "Trace of template inclusion: " + - @sub_templates.collect { |template| template.inspect }.join(", ") + @sub_templates.collect(&:inspect).join(", ") else "" end diff --git a/actionview/lib/action_view/template/handlers.rb b/actionview/lib/action_view/template/handlers.rb index 33bfcb458c..0105e88a49 100644 --- a/actionview/lib/action_view/template/handlers.rb +++ b/actionview/lib/action_view/template/handlers.rb @@ -7,9 +7,9 @@ module ActionView #:nodoc: autoload :Raw, 'action_view/template/handlers/raw' def self.extended(base) - base.register_default_template_handler :erb, ERB.new + base.register_default_template_handler :raw, Raw.new + base.register_template_handler :erb, ERB.new base.register_template_handler :builder, Builder.new - base.register_template_handler :raw, Raw.new base.register_template_handler :ruby, :source.to_proc end @@ -22,7 +22,7 @@ module ActionView #:nodoc: # Register an object that knows how to handle template files with the given # extensions. This can be used to implement new template types. - # The handler must respond to `:call`, which will be passed the template + # The handler must respond to +:call+, which will be passed the template # and should return the rendered template as a String. def register_template_handler(*extensions, handler) raise(ArgumentError, "Extension is required") if extensions.empty? @@ -42,7 +42,7 @@ module ActionView #:nodoc: end def template_handler_extensions - @@template_handlers.keys.map {|key| key.to_s }.sort + @@template_handlers.keys.map(&:to_s).sort end def registered_template_handler(extension) diff --git a/actionview/lib/action_view/template/handlers/erb.rb b/actionview/lib/action_view/template/handlers/erb.rb index 3c2224fbf5..85a100ed4c 100644 --- a/actionview/lib/action_view/template/handlers/erb.rb +++ b/actionview/lib/action_view/template/handlers/erb.rb @@ -35,7 +35,7 @@ module ActionView end end - BLOCK_EXPR = /\s+(do|\{)(\s*\|[^|]*\|)?\s*\Z/ + BLOCK_EXPR = /\s*((\s+|\))do|\{)(\s*\|[^|]*\|)?\s*\Z/ def add_expr_literal(src, code) flush_newline_if_pending(src) diff --git a/actionview/lib/action_view/template/handlers/raw.rb b/actionview/lib/action_view/template/handlers/raw.rb index 0c0d1fffcb..397c86014a 100644 --- a/actionview/lib/action_view/template/handlers/raw.rb +++ b/actionview/lib/action_view/template/handlers/raw.rb @@ -2,7 +2,7 @@ module ActionView module Template::Handlers class Raw def call(template) - escaped = template.source.gsub(':', '\:') + escaped = template.source.gsub(/:/, '\:') '%q:' + escaped + ':;' end diff --git a/actionview/lib/action_view/template/resolver.rb b/actionview/lib/action_view/template/resolver.rb index d77421d5f5..bc0db330ea 100644 --- a/actionview/lib/action_view/template/resolver.rb +++ b/actionview/lib/action_view/template/resolver.rb @@ -138,7 +138,7 @@ module ActionView # resolver is fresher before returning it. def cached(key, path_info, details, locals) #:nodoc: name, prefix, partial = path_info - locals = locals.map { |x| x.to_s }.sort! + locals = locals.map(&:to_s).sort! if key @cache.cache(key, name, prefix, partial, locals) do @@ -196,24 +196,12 @@ module ActionView } end - if RUBY_VERSION >= '2.2.0' - def find_template_paths(query) - Dir[query].reject { |filename| - File.directory?(filename) || - # deals with case-insensitive file systems. - !File.fnmatch(query, filename, File::FNM_EXTGLOB) - } - end - else - def find_template_paths(query) - # deals with case-insensitive file systems. - sanitizer = Hash.new { |h,dir| h[dir] = Dir["#{dir}/*"] } - - Dir[query].reject { |filename| - File.directory?(filename) || - !sanitizer[File.dirname(filename)].include?(filename) - } - end + def find_template_paths(query) + Dir[query].reject { |filename| + File.directory?(filename) || + # deals with case-insensitive file systems. + !File.fnmatch(query, filename, File::FNM_EXTGLOB) + } end # Helper for building query glob string based on resolver's pattern. @@ -250,11 +238,6 @@ module ActionView pieces.shift extension = pieces.pop - unless extension - message = "The file #{path} did not specify a template handler. The default is currently ERB, " \ - "but will change to RAW in the future." - ActiveSupport::Deprecation.warn message - end handler = Template.handler_for_extension(extension) format, variant = pieces.last.split(EXTENSIONS[:variants], 2) if pieces.last diff --git a/actionview/lib/action_view/template/types.rb b/actionview/lib/action_view/template/types.rb index b84e0281ae..be45fcf742 100644 --- a/actionview/lib/action_view/template/types.rb +++ b/actionview/lib/action_view/template/types.rb @@ -9,7 +9,7 @@ module ActionView self.types = Set.new def self.register(*t) - types.merge(t.map { |type| type.to_s }) + types.merge(t.map(&:to_s)) end register :html, :text, :js, :css, :xml, :json diff --git a/actionview/lib/action_view/test_case.rb b/actionview/lib/action_view/test_case.rb index fec980462d..06810ad14d 100644 --- a/actionview/lib/action_view/test_case.rb +++ b/actionview/lib/action_view/test_case.rb @@ -125,6 +125,7 @@ module ActionView @_rendered_views ||= RenderedViewsCollection.new end + # Need to experiment if this priority is the best one: rendered => output_buffer class RenderedViewsCollection def initialize @rendered_views ||= Hash.new { |hash, key| hash[key] = [] } @@ -158,7 +159,7 @@ module ActionView # Need to experiment if this priority is the best one: rendered => output_buffer def document_root_element - Nokogiri::HTML::DocumentFragment.parse(@rendered.blank? ? @output_buffer : @rendered) + Nokogiri::HTML::Document.parse(@rendered.blank? ? @output_buffer : @rendered).root end def say_no_to_protect_against_forgery! @@ -203,7 +204,7 @@ module ActionView def view @view ||= begin view = @controller.view_context - view.singleton_class.send :include, _helpers + view.singleton_class.include(_helpers) view.extend(Locals) view.rendered_views = self.rendered_views view.output_buffer = self.output_buffer diff --git a/actionview/lib/action_view/view_paths.rb b/actionview/lib/action_view/view_paths.rb index 80a41f2418..492f67f45d 100644 --- a/actionview/lib/action_view/view_paths.rb +++ b/actionview/lib/action_view/view_paths.rb @@ -16,14 +16,9 @@ module ActionView module ClassMethods def _prefixes # :nodoc: @_prefixes ||= begin - deprecated_prefixes = handle_deprecated_parent_prefixes - if deprecated_prefixes - deprecated_prefixes - else - return local_prefixes if superclass.abstract? - - local_prefixes + superclass._prefixes - end + return local_prefixes if superclass.abstract? + + local_prefixes + superclass._prefixes end end @@ -34,13 +29,6 @@ module ActionView def local_prefixes [controller_path] end - - def handle_deprecated_parent_prefixes # TODO: remove in 4.3/5.0. - return unless respond_to?(:parent_prefixes) - - ActiveSupport::Deprecation.warn "Overriding ActionController::Base::parent_prefixes is deprecated, override .local_prefixes instead." - local_prefixes + parent_prefixes - end end # The prefixes used in render "foo" shortcuts. |