diff options
Diffstat (limited to 'actionpack/lib')
190 files changed, 1286 insertions, 16062 deletions
diff --git a/actionpack/lib/abstract_controller.rb b/actionpack/lib/abstract_controller.rb index 867a7954e0..fe9802e395 100644 --- a/actionpack/lib/abstract_controller.rb +++ b/actionpack/lib/abstract_controller.rb @@ -10,12 +10,11 @@ module AbstractController autoload :Base autoload :Callbacks autoload :Collector + autoload :DoubleRenderError, "abstract_controller/rendering" autoload :Helpers - autoload :Layouts autoload :Logger autoload :Rendering autoload :Translation autoload :AssetPaths - autoload :ViewPaths autoload :UrlFor end diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb index 56dc9ab7a1..af5de815bb 100644 --- a/actionpack/lib/abstract_controller/base.rb +++ b/actionpack/lib/abstract_controller/base.rb @@ -35,7 +35,7 @@ module AbstractController end def inherited(klass) # :nodoc: - # define the abstract ivar on subclasses so that we don't get + # Define the abstract ivar on subclasses so that we don't get # uninitialized ivar warnings unless klass.instance_variable_defined?(:@abstract) klass.instance_variable_set(:@abstract, false) diff --git a/actionpack/lib/abstract_controller/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb index 599fff81c2..d6c941832f 100644 --- a/actionpack/lib/abstract_controller/callbacks.rb +++ b/actionpack/lib/abstract_controller/callbacks.rb @@ -8,7 +8,9 @@ module AbstractController include ActiveSupport::Callbacks included do - define_callbacks :process_action, :terminator => "response_body", :skip_after_callbacks_if_terminated => true + define_callbacks :process_action, + terminator: ->(controller,_) { controller.response_body }, + skip_after_callbacks_if_terminated: true end # Override AbstractController::Base's process_action to run the @@ -36,7 +38,7 @@ module AbstractController def _normalize_callback_option(options, from, to) # :nodoc: if from = options[from] from = Array(from).map {|o| "action_name == '#{o}'"}.join(" || ") - options[to] = Array(options[to]) << from + options[to] = Array(options[to]).unshift(from) end end @@ -69,7 +71,7 @@ module AbstractController # * <tt>name</tt> - The callback to be added # * <tt>options</tt> - A hash of options to be used when adding the callback def _insert_callbacks(callbacks, block = nil) - options = callbacks.last.is_a?(Hash) ? callbacks.pop : {} + options = callbacks.extract_options! _normalize_callback_options(options) callbacks.push(block) if block callbacks.each do |callback| diff --git a/actionpack/lib/abstract_controller/helpers.rb b/actionpack/lib/abstract_controller/helpers.rb index 812a35735f..e77e4e01e9 100644 --- a/actionpack/lib/abstract_controller/helpers.rb +++ b/actionpack/lib/abstract_controller/helpers.rb @@ -12,7 +12,24 @@ module AbstractController self._helper_methods = Array.new end + class MissingHelperError < LoadError + def initialize(error, path) + @error = error + @path = "helpers/#{path}.rb" + set_backtrace error.backtrace + + if error.path =~ /^#{path}(\.rb)?$/ + super("Missing helper file helpers/%s.rb" % path) + else + raise error + end + end + end + module ClassMethods + MissingHelperError = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('AbstractController::Helpers::ClassMethods::MissingHelperError', + 'AbstractController::Helpers::MissingHelperError') + # When a class is inherited, wrap its helper module in a new module. # This ensures that the parent class's module can be changed # independently of the child class's. @@ -29,7 +46,7 @@ module AbstractController # helper_method :current_user, :logged_in? # # def current_user - # @current_user ||= User.find_by_id(session[:user]) + # @current_user ||= User.find_by(id: session[:user]) # end # # def logged_in? @@ -59,7 +76,7 @@ module AbstractController # The +helper+ class method can take a series of helper module names, a block, or both. # # ==== Options - # * <tt>*args</tt> - Module, Symbol, String, :all + # * <tt>*args</tt> - Module, Symbol, String # * <tt>block</tt> - A block defining helper methods # # When the argument is a module it will be included directly in the template class. @@ -134,7 +151,7 @@ module AbstractController begin require_dependency(file_name) rescue LoadError => e - raise MissingHelperError.new(e, file_name) + raise AbstractController::Helpers::MissingHelperError.new(e, file_name) end file_name.camelize.constantize when Module @@ -145,15 +162,6 @@ module AbstractController end end - class MissingHelperError < LoadError - def initialize(error, path) - @error = error - @path = "helpers/#{path}.rb" - set_backtrace error.backtrace - super("Missing helper file helpers/%s.rb" % path) - end - end - private # Makes all the (instance) methods in the helper module available to templates # rendered through this controller. diff --git a/actionpack/lib/abstract_controller/layouts.rb b/actionpack/lib/abstract_controller/layouts.rb deleted file mode 100644 index 91864f2a35..0000000000 --- a/actionpack/lib/abstract_controller/layouts.rb +++ /dev/null @@ -1,418 +0,0 @@ -require "active_support/core_ext/module/remove_method" - -module AbstractController - # Layouts reverse the common pattern of including shared headers and footers in many templates to isolate changes in - # repeated setups. The inclusion pattern has pages that look like this: - # - # <%= render "shared/header" %> - # Hello World - # <%= render "shared/footer" %> - # - # This approach is a decent way of keeping common structures isolated from the changing content, but it's verbose - # and if you ever want to change the structure of these two includes, you'll have to change all the templates. - # - # With layouts, you can flip it around and have the common structure know where to insert changing content. This means - # that the header and footer are only mentioned in one place, like this: - # - # // The header part of this layout - # <%= yield %> - # // The footer part of this layout - # - # And then you have content pages that look like this: - # - # hello world - # - # At rendering time, the content page is computed and then inserted in the layout, like this: - # - # // The header part of this layout - # hello world - # // The footer part of this layout - # - # == Accessing shared variables - # - # Layouts have access to variables specified in the content pages and vice versa. This allows you to have layouts with - # references that won't materialize before rendering time: - # - # <h1><%= @page_title %></h1> - # <%= yield %> - # - # ...and content pages that fulfill these references _at_ rendering time: - # - # <% @page_title = "Welcome" %> - # Off-world colonies offers you a chance to start a new life - # - # The result after rendering is: - # - # <h1>Welcome</h1> - # Off-world colonies offers you a chance to start a new life - # - # == Layout assignment - # - # You can either specify a layout declaratively (using the #layout class method) or give - # it the same name as your controller, and place it in <tt>app/views/layouts</tt>. - # If a subclass does not have a layout specified, it inherits its layout using normal Ruby inheritance. - # - # For instance, if you have PostsController and a template named <tt>app/views/layouts/posts.html.erb</tt>, - # that template will be used for all actions in PostsController and controllers inheriting - # from PostsController. - # - # If you use a module, for instance Weblog::PostsController, you will need a template named - # <tt>app/views/layouts/weblog/posts.html.erb</tt>. - # - # Since all your controllers inherit from ApplicationController, they will use - # <tt>app/views/layouts/application.html.erb</tt> if no other layout is specified - # or provided. - # - # == Inheritance Examples - # - # class BankController < ActionController::Base - # # bank.html.erb exists - # - # class ExchangeController < BankController - # # exchange.html.erb exists - # - # class CurrencyController < BankController - # - # class InformationController < BankController - # layout "information" - # - # class TellerController < InformationController - # # teller.html.erb exists - # - # class EmployeeController < InformationController - # # employee.html.erb exists - # layout nil - # - # class VaultController < BankController - # layout :access_level_layout - # - # class TillController < BankController - # layout false - # - # In these examples, we have three implicit lookup scenarios: - # * The BankController uses the "bank" layout. - # * The ExchangeController uses the "exchange" layout. - # * The CurrencyController inherits the layout from BankController. - # - # However, when a layout is explicitly set, the explicitly set layout wins: - # * The InformationController uses the "information" layout, explicitly set. - # * The TellerController also uses the "information" layout, because the parent explicitly set it. - # * The EmployeeController uses the "employee" layout, because it set the layout to nil, resetting the parent configuration. - # * The VaultController chooses a layout dynamically by calling the <tt>access_level_layout</tt> method. - # * The TillController does not use a layout at all. - # - # == Types of layouts - # - # Layouts are basically just regular templates, but the name of this template needs not be specified statically. Sometimes - # you want to alternate layouts depending on runtime information, such as whether someone is logged in or not. This can - # be done either by specifying a method reference as a symbol or using an inline method (as a proc). - # - # The method reference is the preferred approach to variable layouts and is used like this: - # - # class WeblogController < ActionController::Base - # layout :writers_and_readers - # - # def index - # # fetching posts - # end - # - # private - # def writers_and_readers - # logged_in? ? "writer_layout" : "reader_layout" - # end - # end - # - # Now when a new request for the index action is processed, the layout will vary depending on whether the person accessing - # is logged in or not. - # - # If you want to use an inline method, such as a proc, do something like this: - # - # class WeblogController < ActionController::Base - # layout proc { |controller| controller.logged_in? ? "writer_layout" : "reader_layout" } - # end - # - # If an argument isn't given to the proc, it's evaluated in the context of - # the current controller anyway. - # - # class WeblogController < ActionController::Base - # layout proc { logged_in? ? "writer_layout" : "reader_layout" } - # end - # - # Of course, the most common way of specifying a layout is still just as a plain template name: - # - # class WeblogController < ActionController::Base - # layout "weblog_standard" - # end - # - # The template will be looked always in <tt>app/views/layouts/</tt> folder. But you can point - # <tt>layouts</tt> folder direct also. <tt>layout "layouts/demo"</tt> is the same as <tt>layout "demo"</tt>. - # - # Setting the layout to nil forces it to be looked up in the filesystem and fallbacks to the parent behavior if none exists. - # Setting it to nil is useful to re-enable template lookup overriding a previous configuration set in the parent: - # - # class ApplicationController < ActionController::Base - # layout "application" - # end - # - # class PostsController < ApplicationController - # # Will use "application" layout - # end - # - # class CommentsController < ApplicationController - # # Will search for "comments" layout and fallback "application" layout - # layout nil - # end - # - # == Conditional layouts - # - # If you have a layout that by default is applied to all the actions of a controller, you still have the option of rendering - # a given action or set of actions without a layout, or restricting a layout to only a single action or a set of actions. The - # <tt>:only</tt> and <tt>:except</tt> options can be passed to the layout call. For example: - # - # class WeblogController < ActionController::Base - # layout "weblog_standard", except: :rss - # - # # ... - # - # end - # - # This will assign "weblog_standard" as the WeblogController's layout for all actions except for the +rss+ action, which will - # be rendered directly, without wrapping a layout around the rendered view. - # - # Both the <tt>:only</tt> and <tt>:except</tt> condition can accept an arbitrary number of method references, so - # #<tt>except: [ :rss, :text_only ]</tt> is valid, as is <tt>except: :rss</tt>. - # - # == Using a different layout in the action render call - # - # If most of your actions use the same layout, it makes perfect sense to define a controller-wide layout as described above. - # Sometimes you'll have exceptions where one action wants to use a different layout than the rest of the controller. - # You can do this by passing a <tt>:layout</tt> option to the <tt>render</tt> call. For example: - # - # class WeblogController < ActionController::Base - # layout "weblog_standard" - # - # def help - # render action: "help", layout: "help" - # end - # end - # - # This will override the controller-wide "weblog_standard" layout, and will render the help action with the "help" layout instead. - module Layouts - extend ActiveSupport::Concern - - include Rendering - - included do - class_attribute :_layout, :_layout_conditions, :instance_accessor => false - self._layout = nil - self._layout_conditions = {} - _write_layout_method - end - - delegate :_layout_conditions, to: :class - - module ClassMethods - def inherited(klass) # :nodoc: - super - klass._write_layout_method - end - - # This module is mixed in if layout conditions are provided. This means - # that if no layout conditions are used, this method is not used - module LayoutConditions # :nodoc: - private - - # Determines whether the current action has a layout definition by - # checking the action name against the :only and :except conditions - # set by the <tt>layout</tt> method. - # - # ==== Returns - # * <tt> Boolean</tt> - True if the action has a layout definition, false otherwise. - def _conditional_layout? - return unless super - - conditions = _layout_conditions - - if only = conditions[:only] - only.include?(action_name) - elsif except = conditions[:except] - !except.include?(action_name) - else - true - end - end - end - - # Specify the layout to use for this class. - # - # If the specified layout is a: - # String:: the String is the template name - # Symbol:: call the method specified by the symbol, which will return the template name - # false:: There is no layout - # true:: raise an ArgumentError - # nil:: Force default layout behavior with inheritance - # - # ==== Parameters - # * <tt>layout</tt> - The layout to use. - # - # ==== Options (conditions) - # * :only - A list of actions to apply this layout to. - # * :except - Apply this layout to all actions but this one. - def layout(layout, conditions = {}) - include LayoutConditions unless conditions.empty? - - conditions.each {|k, v| conditions[k] = Array(v).map {|a| a.to_s} } - self._layout_conditions = conditions - - self._layout = layout - _write_layout_method - end - - # If no layout is supplied, look for a template named the return - # value of this method. - # - # ==== Returns - # * <tt>String</tt> - A template name - def _implied_layout_name # :nodoc: - controller_path - end - - # Creates a _layout method to be called by _default_layout . - # - # If a layout is not explicitly mentioned then look for a layout with the controller's name. - # if nothing is found then try same procedure to find super class's layout. - def _write_layout_method # :nodoc: - remove_possible_method(:_layout) - - prefixes = _implied_layout_name =~ /\blayouts/ ? [] : ["layouts"] - name_clause = if name - <<-RUBY - lookup_context.find_all("#{_implied_layout_name}", #{prefixes.inspect}).first || super - RUBY - else - <<-RUBY - super - RUBY - end - - layout_definition = case _layout - when String - _layout.inspect - when Symbol - <<-RUBY - #{_layout}.tap do |layout| - unless layout.is_a?(String) || !layout - raise ArgumentError, "Your layout method :#{_layout} returned \#{layout}. It " \ - "should have returned a String, false, or nil" - end - end - RUBY - when Proc - define_method :_layout_from_proc, &_layout - _layout.arity == 0 ? "_layout_from_proc" : "_layout_from_proc(self)" - when false - nil - when true - raise ArgumentError, "Layouts must be specified as a String, Symbol, Proc, false, or nil" - when nil - name_clause - end - - self.class_eval <<-RUBY, __FILE__, __LINE__ + 1 - def _layout - if _conditional_layout? - #{layout_definition} - else - #{name_clause} - end - end - private :_layout - RUBY - end - end - - def _normalize_options(options) # :nodoc: - super - - if _include_layout?(options) - layout = options.delete(:layout) { :default } - options[:layout] = _layout_for_option(layout) - end - end - - attr_internal_writer :action_has_layout - - def initialize(*) # :nodoc: - @_action_has_layout = true - super - end - - # Controls whether an action should be rendered using a layout. - # If you want to disable any <tt>layout</tt> settings for the - # current action so that it is rendered without a layout then - # either override this method in your controller to return false - # for that action or set the <tt>action_has_layout</tt> attribute - # to false before rendering. - def action_has_layout? - @_action_has_layout - end - - private - - def _conditional_layout? - true - end - - # This will be overwritten by _write_layout_method - def _layout; end - - # Determine the layout for a given name, taking into account the name type. - # - # ==== Parameters - # * <tt>name</tt> - The name of the template - def _layout_for_option(name) - case name - when String then _normalize_layout(name) - when Proc then name - when true then Proc.new { _default_layout(true) } - when :default then Proc.new { _default_layout(false) } - when false, nil then nil - else - raise ArgumentError, - "String, Proc, :default, true, or false, expected for `layout'; you passed #{name.inspect}" - end - end - - def _normalize_layout(value) - value.is_a?(String) && value !~ /\blayouts/ ? "layouts/#{value}" : value - end - - # Returns the default layout for this controller. - # Optionally raises an exception if the layout could not be found. - # - # ==== Parameters - # * <tt>require_layout</tt> - If set to true and layout is not found, - # an ArgumentError exception is raised (defaults to false) - # - # ==== Returns - # * <tt>template</tt> - The template object for the default layout (or nil) - def _default_layout(require_layout = false) - begin - value = _layout if action_has_layout? - rescue NameError => e - raise e, "Could not render layout: #{e.message}" - end - - if require_layout && action_has_layout? && !value - raise ArgumentError, - "There was no default layout for #{self.class} in #{view_paths.inspect}" - end - - _normalize_layout(value) - end - - def _include_layout?(options) - (options.keys & [:text, :inline, :partial]).empty? || options.key?(:layout) - end - end -end diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb index f8e4cb4384..6f6079d3c5 100644 --- a/actionpack/lib/abstract_controller/rendering.rb +++ b/actionpack/lib/abstract_controller/rendering.rb @@ -1,5 +1,5 @@ -require "abstract_controller/base" -require "action_view" +require 'active_support/concern' +require 'active_support/core_ext/class/attribute' module AbstractController class DoubleRenderError < Error @@ -10,91 +10,22 @@ module AbstractController end end - # This is a class to fix I18n global state. Whenever you provide I18n.locale during a request, - # it will trigger the lookup_context and consequently expire the cache. - class I18nProxy < ::I18n::Config #:nodoc: - attr_reader :original_config, :lookup_context - - def initialize(original_config, lookup_context) - original_config = original_config.original_config if original_config.respond_to?(:original_config) - @original_config, @lookup_context = original_config, lookup_context - end - - def locale - @original_config.locale - end - - def locale=(value) - @lookup_context.locale = value - end - end - module Rendering extend ActiveSupport::Concern - include AbstractController::ViewPaths included do class_attribute :protected_instance_variables self.protected_instance_variables = [] end - # Overwrite process to setup I18n proxy. - def process(*) #:nodoc: - old_config, I18n.config = I18n.config, I18nProxy.new(I18n.config, lookup_context) - super - ensure - I18n.config = old_config - end - - module ClassMethods - def view_context_class - @view_context_class ||= begin - routes = respond_to?(:_routes) && _routes - helpers = respond_to?(:_helpers) && _helpers - - Class.new(ActionView::Base) do - if routes - include routes.url_helpers - include routes.mounted_helpers - end - - if helpers - include helpers - end - end - end - end - end - - attr_internal_writer :view_context_class - - def view_context_class - @_view_context_class ||= self.class.view_context_class - end - - # An instance of a view class. The default view class is ActionView::Base - # - # The view class must have the following methods: - # View.new[lookup_context, assigns, controller] - # Create a new ActionView instance for a controller - # View#render[options] - # Returns String with the rendered template - # - # Override this method in a module to change the default behavior. - def view_context - view_context_class.new(view_renderer, view_assigns, self) - end - - # Returns an object that is able to render templates. - def view_renderer - @_view_renderer ||= ActionView::Renderer.new(lookup_context) - end - # Normalize arguments, options and then delegates render_to_body and # sticks the result in self.response_body. + # :api: public def render(*args, &block) options = _normalize_render(*args, &block) self.response_body = render_to_body(options) + _process_format(rendered_format) + self.response_body end # Raw rendering of a template to a string. @@ -105,32 +36,29 @@ module AbstractController # # If a component extends the semantics of response_body # (as Action Controller extends it to be anything that - # responds to the method each), this method needs to - # overriden in order to still return a string. + # responds to the method each), this method needs to be + # overridden in order to still return a string. # :api: plugin def render_to_string(*args, &block) options = _normalize_render(*args, &block) render_to_body(options) end - # Raw rendering of a template. - # :api: plugin + # Performs the actual template rendering. + # :api: public def render_to_body(options = {}) - _process_options(options) - _render_template(options) end - # Find and renders a template based on the options given. - # :api: private - def _render_template(options) #:nodoc: - lookup_context.rendered_format = nil if options[:formats] - view_renderer.render(view_context, options) + # Return Content-Type of rendered content + # :api: public + def rendered_format + Mime::TEXT end - DEFAULT_PROTECTED_INSTANCE_VARIABLES = [ - :@_action_name, :@_response_body, :@_formats, :@_prefixes, :@_config, - :@_view_context_class, :@_view_renderer, :@_lookup_context - ] + DEFAULT_PROTECTED_INSTANCE_VARIABLES = %w( + @_action_name @_response_body @_formats @_prefixes @_config + @_view_context_class @_view_renderer @_lookup_context + ) # This method should return a hash with assigns. # You can overwrite this configuration per controller. @@ -144,53 +72,40 @@ module AbstractController hash end - private - - # Normalize args and options. - # :api: private - def _normalize_render(*args, &block) - options = _normalize_args(*args, &block) - _normalize_options(options) - options - end - # Normalize args by converting render "foo" to render :action => "foo" and # render "foo/bar" to render :file => "foo/bar". # :api: plugin def _normalize_args(action=nil, options={}) - case action - when NilClass - when Hash - options = action - when String, Symbol - action = action.to_s - key = action.include?(?/) ? :file : :action - options[key] = action + if action.is_a? Hash + action else - options[:partial] = action + options end - - options end # Normalize options. # :api: plugin def _normalize_options(options) - if options[:partial] == true - options[:partial] = action_name - end - - if (options.keys & [:partial, :file, :template]).empty? - options[:prefixes] ||= _prefixes - end - - options[:template] ||= (options[:action] || action_name).to_s options end # Process extra options. # :api: plugin def _process_options(options) + options + end + + # Process the rendered format. + # :api: private + def _process_format(format) + end + + # Normalize args and options. + # :api: private + def _normalize_render(*args, &block) + options = _normalize_args(*args, &block) + _normalize_options(options) + options end end end diff --git a/actionpack/lib/abstract_controller/view_paths.rb b/actionpack/lib/abstract_controller/view_paths.rb deleted file mode 100644 index c08b3a0e2a..0000000000 --- a/actionpack/lib/abstract_controller/view_paths.rb +++ /dev/null @@ -1,96 +0,0 @@ -require 'action_view/base' - -module AbstractController - module ViewPaths - extend ActiveSupport::Concern - - included do - class_attribute :_view_paths - self._view_paths = ActionView::PathSet.new - self._view_paths.freeze - end - - delegate :template_exists?, :view_paths, :formats, :formats=, - :locale, :locale=, :to => :lookup_context - - module ClassMethods - def parent_prefixes - @parent_prefixes ||= begin - parent_controller = superclass - prefixes = [] - - until parent_controller.abstract? - prefixes << parent_controller.controller_path - parent_controller = parent_controller.superclass - end - - prefixes - end - end - end - - # The prefixes used in render "foo" shortcuts. - def _prefixes - @_prefixes ||= begin - parent_prefixes = self.class.parent_prefixes - parent_prefixes.dup.unshift(controller_path) - end - end - - # LookupContext is the object responsible to hold all information required to lookup - # templates, i.e. view paths and details. Check ActionView::LookupContext for more - # information. - def lookup_context - @_lookup_context ||= - ActionView::LookupContext.new(self.class._view_paths, details_for_lookup, _prefixes) - end - - def details_for_lookup - { } - end - - def append_view_path(path) - lookup_context.view_paths.push(*path) - end - - def prepend_view_path(path) - lookup_context.view_paths.unshift(*path) - end - - module ClassMethods - # Append a path to the list of view paths for this controller. - # - # ==== Parameters - # * <tt>path</tt> - If a String is provided, it gets converted into - # the default view path. You may also provide a custom view path - # (see ActionView::PathSet for more information) - def append_view_path(path) - self._view_paths = view_paths + Array(path) - end - - # Prepend a path to the list of view paths for this controller. - # - # ==== Parameters - # * <tt>path</tt> - If a String is provided, it gets converted into - # the default view path. You may also provide a custom view path - # (see ActionView::PathSet for more information) - def prepend_view_path(path) - self._view_paths = ActionView::PathSet.new(Array(path) + view_paths) - end - - # A list of all of the default view paths for this controller. - def view_paths - _view_paths - end - - # Set the view paths. - # - # ==== Parameters - # * <tt>paths</tt> - If a PathSet is provided, use that; - # otherwise, process the parameter into a PathSet. - def view_paths=(paths) - self._view_paths = ActionView::PathSet.new(Array(paths)) - end - end - end -end diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index 9cacb3862b..417d2efec2 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -40,31 +40,15 @@ module ActionController autoload :UrlFor end - autoload :Integration, 'action_controller/deprecated/integration_test' - autoload :IntegrationTest, 'action_controller/deprecated/integration_test' - autoload :Routing, 'action_controller/deprecated' autoload :TestCase, 'action_controller/test_case' autoload :TemplateAssertions, 'action_controller/test_case' - eager_autoload do - autoload :RecordIdentifier - end - def self.eager_load! super ActionController::Caching.eager_load! - HTML.eager_load! end end -# All of these simply register additional autoloads -require 'action_view' -require 'action_view/vendor/html-scanner' - -ActiveSupport.on_load(:action_view) do - ActionView::RoutingUrlFor.send(:include, ActionDispatch::Routing::UrlFor) -end - # Common Active Support usage in Action Controller require 'active_support/core_ext/class/attribute_accessors' require 'active_support/core_ext/load_error' diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 971c4189c8..3b0d094f4f 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -2,8 +2,22 @@ require "action_controller/log_subscriber" require "action_controller/metal/params_wrapper" module ActionController + # The <tt>metal</tt> anonymous class was introduced to solve issue with including modules in <tt>ActionController::Base</tt>. + # Modules needs to be included in particluar order. First we need to have <tt>AbstractController::Rendering</tt> included, + # next we should include actuall implementation which would be for example <tt>ActionView::Rendering</tt> and after that + # <tt>ActionController::Rendering</tt>. This order must be preserved and as we want to have middle module included dynamicaly + # <tt>metal</tt> class was introduced. It has <tt>AbstractController::Rendering</tt> included and is parent class of + # <tt>ActionController::Base</tt> which includes <tt>ActionController::Rendering</tt>. If we include <tt>ActionView::Rendering</tt> + # beetween them to perserve the required order, we can simply do this by: + # + # ActionController::Base.superclass.send(:include, ActionView::Rendering) + # + metal = Class.new(Metal) do + include AbstractController::Rendering + end + # Action Controllers are the core of a web request in \Rails. They are made up of one or more actions that are executed - # on request and then either render a template or redirect to another action. An action is defined as a public method + # on request and then either it renders a template or redirects to another action. An action is defined as a public method # on the controller, which will automatically be made accessible to the web-server through \Rails Routes. # # By default, only the ApplicationController in a \Rails application inherits from <tt>ActionController::Base</tt>. All other @@ -59,7 +73,7 @@ module ActionController # <input type="text" name="post[address]" value="hyacintvej"> # # A request stemming from a form holding these inputs will include <tt>{ "post" => { "name" => "david", "address" => "hyacintvej" } }</tt>. - # If the address input had been named "post[address][street]", the params would have included + # If the address input had been named <tt>post[address][street]</tt>, the params would have included # <tt>{ "post" => { "address" => { "street" => "hyacintvej" } } }</tt>. There's no limit to the depth of the nesting. # # == Sessions @@ -160,7 +174,7 @@ module ActionController # render action: "overthere" # won't be called if monkeys is nil # end # - class Base < Metal + class Base < metal abstract! # We document the request and response methods here because albeit they are @@ -200,7 +214,6 @@ module ActionController end MODULES = [ - AbstractController::Layouts, AbstractController::Translation, AbstractController::AssetPaths, @@ -223,7 +236,6 @@ module ActionController ForceSSL, Streaming, DataStreaming, - RecordIdentifier, HttpAuthentication::Basic::ControllerMethods, HttpAuthentication::Digest::ControllerMethods, HttpAuthentication::Token::ControllerMethods, diff --git a/actionpack/lib/action_controller/caching.rb b/actionpack/lib/action_controller/caching.rb index ea33d975ef..12d798d0c1 100644 --- a/actionpack/lib/action_controller/caching.rb +++ b/actionpack/lib/action_controller/caching.rb @@ -9,7 +9,7 @@ module ActionController # You can read more about each approach by clicking the modules below. # # Note: To turn off all caching, set - # config.action_controller.perform_caching = false. + # config.action_controller.perform_caching = false # # == \Caching stores # @@ -58,16 +58,6 @@ module ActionController config_accessor :default_static_extension self.default_static_extension ||= '.html' - def self.page_cache_extension=(extension) - ActiveSupport::Deprecation.deprecation_warning(:page_cache_extension, :default_static_extension) - self.default_static_extension = extension - end - - def self.page_cache_extension - ActiveSupport::Deprecation.deprecation_warning(:page_cache_extension, :default_static_extension) - default_static_extension - end - config_accessor :perform_caching self.perform_caching = true if perform_caching.nil? diff --git a/actionpack/lib/action_controller/deprecated.rb b/actionpack/lib/action_controller/deprecated.rb deleted file mode 100644 index 2405bebb97..0000000000 --- a/actionpack/lib/action_controller/deprecated.rb +++ /dev/null @@ -1,7 +0,0 @@ -ActionController::AbstractRequest = ActionController::Request = ActionDispatch::Request -ActionController::AbstractResponse = ActionController::Response = ActionDispatch::Response -ActionController::Routing = ActionDispatch::Routing - -ActiveSupport::Deprecation.warn 'ActionController::AbstractRequest and ActionController::Request are deprecated and will be removed, use ActionDispatch::Request instead.' -ActiveSupport::Deprecation.warn 'ActionController::AbstractResponse and ActionController::Response are deprecated and will be removed, use ActionDispatch::Response instead.' -ActiveSupport::Deprecation.warn 'ActionController::Routing is deprecated and will be removed, use ActionDispatch::Routing instead.'
\ No newline at end of file diff --git a/actionpack/lib/action_controller/deprecated/integration_test.rb b/actionpack/lib/action_controller/deprecated/integration_test.rb deleted file mode 100644 index 54eae48f47..0000000000 --- a/actionpack/lib/action_controller/deprecated/integration_test.rb +++ /dev/null @@ -1,5 +0,0 @@ -ActionController::Integration = ActionDispatch::Integration -ActionController::IntegrationTest = ActionDispatch::IntegrationTest - -ActiveSupport::Deprecation.warn 'ActionController::Integration is deprecated and will be removed, use ActionDispatch::Integration instead.' -ActiveSupport::Deprecation.warn 'ActionController::IntegrationTest is deprecated and will be removed, use ActionDispatch::IntegrationTest instead.' diff --git a/actionpack/lib/action_controller/log_subscriber.rb b/actionpack/lib/action_controller/log_subscriber.rb index 7318c8b7ec..9279d8bcea 100644 --- a/actionpack/lib/action_controller/log_subscriber.rb +++ b/actionpack/lib/action_controller/log_subscriber.rb @@ -33,7 +33,7 @@ module ActionController end def halted_callback(event) - info("Filter chain halted as #{event.payload[:filter]} rendered or redirected") + info("Filter chain halted as #{event.payload[:filter].inspect} rendered or redirected") end def send_file(event) diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb index 143b3e0cbd..b84c9e78c3 100644 --- a/actionpack/lib/action_controller/metal.rb +++ b/actionpack/lib/action_controller/metal.rb @@ -36,8 +36,7 @@ module ActionController raise "MiddlewareStack#build requires an app" unless app middlewares.reverse.inject(app) do |a, middleware| - middleware.valid?(action) ? - middleware.build(a) : a + middleware.valid?(action) ? middleware.build(a) : a end end end @@ -57,7 +56,7 @@ module ActionController # And then to route requests to your metal controller, you would add # something like this to <tt>config/routes.rb</tt>: # - # match 'hello', to: HelloController.action(:index) + # get 'hello', to: HelloController.action(:index) # # The +action+ method returns a valid Rack application for the \Rails # router to dispatch to. diff --git a/actionpack/lib/action_controller/metal/flash.rb b/actionpack/lib/action_controller/metal/flash.rb index b078beb675..65351284b9 100644 --- a/actionpack/lib/action_controller/metal/flash.rb +++ b/actionpack/lib/action_controller/metal/flash.rb @@ -11,6 +11,23 @@ module ActionController #:nodoc: end module ClassMethods + # Creates new flash types. You can pass as many types as you want to create + # flash types other than the default <tt>alert</tt> and <tt>notice</tt> in + # your controllers and views. For instance: + # + # # in application_controller.rb + # class ApplicationController < ActionController::Base + # add_flash_types :warning + # end + # + # # in your controller + # redirect_to user_path(@user), warning: "Incomplete profile" + # + # # in your view + # <%= warning %> + # + # This method will automatically define a new method for each of the given + # names, and it will be available in your views. def add_flash_types(*types) types.each do |type| next if _flash_types.include?(type) @@ -20,7 +37,7 @@ module ActionController #:nodoc: end helper_method type - _flash_types << type + self._flash_types += [type] end end end diff --git a/actionpack/lib/action_controller/metal/force_ssl.rb b/actionpack/lib/action_controller/metal/force_ssl.rb index f1e8714a86..a2cb6d1e66 100644 --- a/actionpack/lib/action_controller/metal/force_ssl.rb +++ b/actionpack/lib/action_controller/metal/force_ssl.rb @@ -1,3 +1,6 @@ +require 'active_support/core_ext/hash/except' +require 'active_support/core_ext/hash/slice' + module ActionController # This module provides a method which will redirect browser to use HTTPS # protocol. This will ensure that user's sensitive information will be @@ -14,6 +17,10 @@ module ActionController extend ActiveSupport::Concern include AbstractController::Callbacks + ACTION_OPTIONS = [:only, :except, :if, :unless] + URL_OPTIONS = [:protocol, :host, :domain, :subdomain, :port, :path] + REDIRECT_OPTIONS = [:status, :flash, :alert, :notice] + module ClassMethods # Force the request to this particular controller or specified actions to be # under HTTPS protocol. @@ -29,18 +36,34 @@ module ActionController # end # end # - # ==== Options - # * <tt>host</tt> - Redirect to a different host name - # * <tt>only</tt> - The callback should be run only for this action - # * <tt>except</tt> - The callback should be run for all actions except this action - # * <tt>if</tt> - A symbol naming an instance method or a proc; the callback - # will be called only when it returns a true value. - # * <tt>unless</tt> - A symbol naming an instance method or a proc; the callback - # will be called only when it returns a false value. + # ==== URL Options + # You can pass any of the following options to affect the redirect url + # * <tt>host</tt> - Redirect to a different host name + # * <tt>subdomain</tt> - Redirect to a different subdomain + # * <tt>domain</tt> - Redirect to a different domain + # * <tt>port</tt> - Redirect to a non-standard port + # * <tt>path</tt> - Redirect to a different path + # + # ==== Redirect Options + # You can pass any of the following options to affect the redirect status and response + # * <tt>status</tt> - Redirect with a custom status (default is 301 Moved Permanently) + # * <tt>flash</tt> - Set a flash message when redirecting + # * <tt>alert</tt> - Set an alert message when redirecting + # * <tt>notice</tt> - Set a notice message when redirecting + # + # ==== Action Options + # You can pass any of the following options to affect the before_action callback + # * <tt>only</tt> - The callback should be run only for this action + # * <tt>except</tt> - The callback should be run for all actions except this action + # * <tt>if</tt> - A symbol naming an instance method or a proc; the callback + # will be called only when it returns a true value. + # * <tt>unless</tt> - A symbol naming an instance method or a proc; the callback + # will be called only when it returns a false value. def force_ssl(options = {}) - host = options.delete(:host) - before_action(options) do - force_ssl_redirect(host) + action_options = options.slice(*ACTION_OPTIONS) + redirect_options = options.except(*ACTION_OPTIONS) + before_action(action_options) do + force_ssl_redirect(redirect_options) end end end @@ -48,14 +71,26 @@ module ActionController # Redirect the existing request to use the HTTPS protocol. # # ==== Parameters - # * <tt>host</tt> - Redirect to a different host name - def force_ssl_redirect(host = nil) + # * <tt>host_or_options</tt> - Either a host name or any of the url & redirect options + # available to the <tt>force_ssl</tt> method. + def force_ssl_redirect(host_or_options = nil) unless request.ssl? - redirect_options = {:protocol => 'https://', :status => :moved_permanently} - redirect_options.merge!(:host => host) if host - redirect_options.merge!(:params => request.query_parameters) + options = { + :protocol => 'https://', + :host => request.host, + :path => request.fullpath, + :status => :moved_permanently + } + + if host_or_options.is_a?(Hash) + options.merge!(host_or_options) + elsif host_or_options + options.merge!(:host => host_or_options) + end + + secure_url = ActionDispatch::Http::URL.url_for(options.slice(*URL_OPTIONS)) flash.keep if respond_to?(:flash) - redirect_to redirect_options + redirect_to secure_url, options.slice(*REDIRECT_OPTIONS) end end end diff --git a/actionpack/lib/action_controller/metal/head.rb b/actionpack/lib/action_controller/metal/head.rb index 8237db15ca..424473801d 100644 --- a/actionpack/lib/action_controller/metal/head.rb +++ b/actionpack/lib/action_controller/metal/head.rb @@ -1,7 +1,5 @@ module ActionController module Head - extend ActiveSupport::Concern - # Return a response that has no content (merely headers). The options # argument is interpreted to be a hash of header names and values. # This allows you to easily return a response that consists only of diff --git a/actionpack/lib/action_controller/metal/helpers.rb b/actionpack/lib/action_controller/metal/helpers.rb index 35facd13c8..a9c3e438fb 100644 --- a/actionpack/lib/action_controller/metal/helpers.rb +++ b/actionpack/lib/action_controller/metal/helpers.rb @@ -5,7 +5,7 @@ module ActionController # # In addition to using the standard template helpers provided, creating custom helpers to # extract complicated logic or reusable functionality is strongly encouraged. By default, each controller - # will include all helpers. + # will include all helpers. These helpers are only accessible on the controller through <tt>.helpers</tt> # # In previous versions of \Rails the controller will include a helper whose # name matches that of the controller, e.g., <tt>MyController</tt> will automatically @@ -73,7 +73,11 @@ module ActionController # Provides a proxy to access helpers methods from outside the view. def helpers - @helper_proxy ||= ActionView::Base.new.extend(_helpers) + @helper_proxy ||= begin + proxy = ActionView::Base.new + proxy.config = config.inheritable_copy + proxy.extend(_helpers) + end end # Overwrite modules_for_helpers to accept :all as argument, which loads @@ -94,7 +98,6 @@ module ActionController extract = /^#{Regexp.quote(_path.to_s)}\/?(.*)_helper.rb$/ names = Dir["#{_path}/**/*_helper.rb"].map { |file| file.sub(extract, '\1') } names.sort! - names end helpers.uniq! helpers diff --git a/actionpack/lib/action_controller/metal/hide_actions.rb b/actionpack/lib/action_controller/metal/hide_actions.rb index 2aa6b7adaf..af36ffa240 100644 --- a/actionpack/lib/action_controller/metal/hide_actions.rb +++ b/actionpack/lib/action_controller/metal/hide_actions.rb @@ -27,7 +27,7 @@ module ActionController end def visible_action?(action_name) - action_methods.include?(action_name) + not hidden_actions.include?(action_name) end # Overrides AbstractController::Base#action_methods to remove any methods diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb index e295002b16..158d552ec7 100644 --- a/actionpack/lib/action_controller/metal/http_authentication.rb +++ b/actionpack/lib/action_controller/metal/http_authentication.rb @@ -29,7 +29,7 @@ module ActionController # # protected # def set_account - # @account = Account.find_by_url_name(request.subdomains.first) + # @account = Account.find_by(url_name: request.subdomains.first) # end # # def authenticate @@ -299,6 +299,7 @@ module ActionController # allow a user to use new nonce without prompting user again for their # username and password. def validate_nonce(secret_key, request, value, seconds_to_timeout=5*60) + return false if value.nil? t = ::Base64.decode64(value).split(":").first.to_i nonce(secret_key, t) == value && (t - Time.now.to_i).abs <= seconds_to_timeout end @@ -344,7 +345,7 @@ module ActionController # # protected # def set_account - # @account = Account.find_by_url_name(request.subdomains.first) + # @account = Account.find_by(url_name: request.subdomains.first) # end # # def authenticate diff --git a/actionpack/lib/action_controller/metal/live.rb b/actionpack/lib/action_controller/metal/live.rb index 9d628c916f..0dd788645b 100644 --- a/actionpack/lib/action_controller/metal/live.rb +++ b/actionpack/lib/action_controller/metal/live.rb @@ -1,5 +1,6 @@ require 'action_dispatch/http/response' require 'delegate' +require 'active_support/json' module ActionController # Mix this module in to your controller, and all actions in that controller @@ -32,8 +33,82 @@ module ActionController # the main thread. Make sure your actions are thread safe, and this shouldn't # be a problem (don't share state across threads, etc). module Live + # This class provides the ability to write an SSE (Server Sent Event) + # to an IO stream. The class is initialized with a stream and can be used + # to either write a JSON string or an object which can be converted to JSON. + # + # Writing an object will convert it into standard SSE format with whatever + # options you have configured. You may choose to set the following options: + # + # 1) Event. If specified, an event with this name will be dispatched on + # the browser. + # 2) Retry. The reconnection time in milliseconds used when attempting + # to send the event. + # 3) Id. If the connection dies while sending an SSE to the browser, then + # the server will receive a +Last-Event-ID+ header with value equal to +id+. + # + # After setting an option in the constructor of the SSE object, all future + # SSEs sent accross the stream will use those options unless overridden. + # + # Example Usage: + # + # class MyController < ActionController::Base + # include ActionController::Live + # + # def index + # response.headers['Content-Type'] = 'text/event-stream' + # sse = SSE.new(response.stream, retry: 300, event: "event-name") + # sse.write({ name: 'John'}) + # sse.write({ name: 'John'}, id: 10) + # sse.write({ name: 'John'}, id: 10, event: "other-event") + # sse.write({ name: 'John'}, id: 10, event: "other-event", retry: 500) + # ensure + # sse.close + # end + # end + # + # Note: SSEs are not currently supported by IE. However, they are supported + # by Chrome, Firefox, Opera, and Safari. + class SSE + + WHITELISTED_OPTIONS = %w( retry event id ) + + def initialize(stream, options = {}) + @stream = stream + @options = options + end + + def close + @stream.close + end + + def write(object, options = {}) + case object + when String + perform_write(object, options) + else + perform_write(ActiveSupport::JSON.encode(object), options) + end + end + + private + + def perform_write(json, options) + current_options = @options.merge(options).stringify_keys + + WHITELISTED_OPTIONS.each do |option_name| + if (option_value = current_options[option_name]) + @stream.write "#{option_name}: #{option_value}\n" + end + end + + @stream.write "data: #{json}\n\n" + end + end + class Buffer < ActionDispatch::Response::Buffer #:nodoc: def initialize(response) + @error_callback = nil super(response, SizedQueue.new(10)) end @@ -56,6 +131,14 @@ module ActionController super @buf.push nil end + + def on_error(&block) + @error_callback = block + end + + def call_on_error + @error_callback.call + end end class Response < ActionDispatch::Response #:nodoc: all @@ -98,6 +181,10 @@ module ActionController def merge_default_headers(original, default) Header.new self, super end + + def handle_conditional_get! + super unless committed? + end end def process(name) @@ -117,6 +204,16 @@ module ActionController begin super(name) + rescue => e + begin + @_response.stream.write(ActionView::Base.streaming_completion_on_exception) if request.format == :html + @_response.stream.call_on_error + rescue => exception + log_error(exception) + ensure + log_error(e) + @_response.stream.close + end ensure @_response.commit! end @@ -125,6 +222,16 @@ module ActionController @_response.await_commit end + def log_error(exception) + logger = ActionController::Base.logger + return unless logger + + message = "\n#{exception.class} (#{exception.message}):\n" + message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code) + message << " " << exception.backtrace.join("\n ") + logger.fatal("#{message}\n\n") + end + def response_body=(body) super response.stream.close if response diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb index 834d44f045..a072fce1a1 100644 --- a/actionpack/lib/action_controller/metal/mime_responds.rb +++ b/actionpack/lib/action_controller/metal/mime_responds.rb @@ -326,6 +326,7 @@ module ActionController #:nodoc: if collector = retrieve_collector_from_mimes(&block) options = resources.size == 1 ? {} : resources.extract_options! + options = options.clone options[:default_response] = collector.response (options.delete(:responder) || self.class.responder).call(self, resources, options) end @@ -364,9 +365,7 @@ module ActionController #:nodoc: format = collector.negotiate_format(request) if format - self.content_type ||= format.to_s - lookup_context.formats = [format.to_sym] - lookup_context.rendered_format = lookup_context.formats.first + _process_format(format) collector else raise ActionController::UnknownFormat diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb index e9031f3fac..ab14a61b97 100644 --- a/actionpack/lib/action_controller/metal/redirecting.rb +++ b/actionpack/lib/action_controller/metal/redirecting.rb @@ -71,6 +71,26 @@ module ActionController self.response_body = "<html><body>You are being <a href=\"#{ERB::Util.h(location)}\">redirected</a>.</body></html>" end + def _compute_redirect_to_location(options) #:nodoc: + case options + # The scheme name consist of a letter followed by any combination of + # letters, digits, and the plus ("+"), period ("."), or hyphen ("-") + # characters; and is terminated by a colon (":"). + # See http://tools.ietf.org/html/rfc3986#section-3.1 + # The protocol relative scheme starts with a double slash "//". + when /\A([a-z][a-z\d\-+\.]*:|\/\/).*/i + options + when String + request.protocol + request.host_with_port + options + when :back + request.headers["Referer"] or raise RedirectBackError + when Proc + _compute_redirect_to_location options.call + else + url_for(options) + end.delete("\0\r\n") + end + private def _extract_redirect_to_status(options, response_status) if options.is_a?(Hash) && options.key?(:status) @@ -81,24 +101,5 @@ module ActionController 302 end end - - def _compute_redirect_to_location(options) - case options - # The scheme name consist of a letter followed by any combination of - # letters, digits, and the plus ("+"), period ("."), or hyphen ("-") - # characters; and is terminated by a colon (":"). - # The protocol relative scheme starts with a double slash "//" - when %r{\A(\w[\w+.-]*:|//).*} - options - when String - request.protocol + request.host_with_port + options - when :back - request.headers["Referer"] or raise RedirectBackError - when Proc - _compute_redirect_to_location options.call - else - url_for(options) - end.delete("\0\r\n") - end end end diff --git a/actionpack/lib/action_controller/metal/renderers.rb b/actionpack/lib/action_controller/metal/renderers.rb index 5272dc6cdb..62a3844b04 100644 --- a/actionpack/lib/action_controller/metal/renderers.rb +++ b/actionpack/lib/action_controller/metal/renderers.rb @@ -6,6 +6,12 @@ module ActionController Renderers.add(key, &block) end + class MissingRenderer < LoadError + def initialize(format) + super "No renderer defined for format: #{format}" + end + end + module Renderers extend ActiveSupport::Concern diff --git a/actionpack/lib/action_controller/metal/rendering.rb b/actionpack/lib/action_controller/metal/rendering.rb index c5e7d4e357..90f0ef0b1c 100644 --- a/actionpack/lib/action_controller/metal/rendering.rb +++ b/actionpack/lib/action_controller/metal/rendering.rb @@ -2,39 +2,45 @@ module ActionController module Rendering extend ActiveSupport::Concern - include AbstractController::Rendering - # Before processing, set the request formats in current controller formats. def process_action(*) #:nodoc: - self.formats = request.formats.map { |x| x.ref } + self.formats = request.formats.map(&:ref).compact super end # Check for double render errors and set the content_type after rendering. def render(*args) #:nodoc: - raise ::AbstractController::DoubleRenderError if response_body + raise ::AbstractController::DoubleRenderError if self.response_body super - self.content_type ||= Mime[lookup_context.rendered_format].to_s - response_body end # Overwrite render_to_string because body can now be set to a rack body. def render_to_string(*) - if self.response_body = super + result = super + if result.respond_to?(:each) string = "" - response_body.each { |r| string << r } + result.each { |r| string << r } string + else + result end - ensure - self.response_body = nil end - def render_to_body(*) - super || " " + def render_to_body(options = {}) + super || if options[:text].present? + options[:text] + else + " " + end end private + def _process_format(format) + super + self.content_type ||= format.to_s + end + # Normalize arguments by catching blocks and setting them on :update. def _normalize_args(action=nil, options={}, &blk) #:nodoc: options = super diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb index d275a854fd..bd64b1f812 100644 --- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb +++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb @@ -124,6 +124,9 @@ module ActionController #:nodoc: @loaded = true end + # no-op + def destroy; end + def exists? true end @@ -199,6 +202,7 @@ module ActionController #:nodoc: params[request_forgery_protection_token] end + # Checks if the controller allows forgery protection. def protect_against_forgery? allow_forgery_protection end diff --git a/actionpack/lib/action_controller/metal/responder.rb b/actionpack/lib/action_controller/metal/responder.rb index 891819968b..b4ba169e8f 100644 --- a/actionpack/lib/action_controller/metal/responder.rb +++ b/actionpack/lib/action_controller/metal/responder.rb @@ -97,8 +97,12 @@ module ActionController #:nodoc: # # This will return status 201 if the task was saved successfully. If not, # it will simply ignore the given options and return status 422 and the - # resource errors. To customize the failure scenario, you can pass a - # a block to <code>respond_with</code>: + # resource errors. You can also override the location to redirect to: + # + # respond_with(@project, location: root_path) + # + # To customize the failure scenario, you can pass a block to + # <code>respond_with</code>: # # def create # @project = Project.find(params[:project_id]) @@ -140,7 +144,7 @@ module ActionController #:nodoc: undef_method(:to_json) if method_defined?(:to_json) undef_method(:to_yaml) if method_defined?(:to_yaml) - # Initializes a new responder an invoke the proper format. If the format is + # Initializes a new responder and invokes the proper format. If the format is # not defined, call to_format. # def self.call(*args) @@ -198,6 +202,7 @@ module ActionController #:nodoc: # This is the common behavior for formats associated with APIs, such as :xml and :json. def api_behavior(error) raise error unless resourceful? + raise MissingRenderer.new(format) unless has_renderer? if get? display resource @@ -265,6 +270,11 @@ module ActionController #:nodoc: resource.respond_to?(:errors) && !resource.errors.empty? end + # Check whether the neceessary Renderer is available + def has_renderer? + Renderers::RENDERERS.include?(format) + end + # By default, render the <code>:edit</code> action for HTML requests with errors, unless # the verb was POST. # diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb index 73e9b5660d..62d5931b45 100644 --- a/actionpack/lib/action_controller/metal/streaming.rb +++ b/actionpack/lib/action_controller/metal/streaming.rb @@ -193,31 +193,29 @@ module ActionController #:nodoc: module Streaming extend ActiveSupport::Concern - include AbstractController::Rendering - protected - # Set proper cache control and transfer encoding when streaming - def _process_options(options) #:nodoc: - super - if options[:stream] - if env["HTTP_VERSION"] == "HTTP/1.0" - options.delete(:stream) - else - headers["Cache-Control"] ||= "no-cache" - headers["Transfer-Encoding"] = "chunked" - headers.delete("Content-Length") + # Set proper cache control and transfer encoding when streaming + def _process_options(options) #:nodoc: + super + if options[:stream] + if env["HTTP_VERSION"] == "HTTP/1.0" + options.delete(:stream) + else + headers["Cache-Control"] ||= "no-cache" + headers["Transfer-Encoding"] = "chunked" + headers.delete("Content-Length") + end end end - end - # Call render_body if we are streaming instead of usual +render+. - def _render_template(options) #:nodoc: - if options.delete(:stream) - Rack::Chunked::Body.new view_renderer.render_body(view_context, options) - else - super + # Call render_body if we are streaming instead of usual +render+. + def _render_template(options) #:nodoc: + if options.delete(:stream) + Rack::Chunked::Body.new view_renderer.render_body(view_context, options) + else + super + end end - end end end diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb index 0748d75a69..8ae7e474a3 100644 --- a/actionpack/lib/action_controller/metal/strong_parameters.rb +++ b/actionpack/lib/action_controller/metal/strong_parameters.rb @@ -2,6 +2,7 @@ require 'active_support/core_ext/hash/indifferent_access' require 'active_support/core_ext/array/wrap' require 'active_support/rescuable' require 'action_dispatch/http/upload' +require 'stringio' module ActionController # Raised when a required parameter is missing. @@ -80,6 +81,8 @@ module ActionController # ActionController::UnpermittedParameters exception. The default value is <tt>:log</tt> # in test and development environments, +false+ otherwise. # + # Examples: + # # params = ActionController::Parameters.new # params.permitted? # => false # @@ -212,6 +215,7 @@ module ActionController # You may declare that the parameter should be an array of permitted scalars # by mapping it to an empty array: # + # params = ActionController::Parameters.new(tags: ['rails', 'parameters']) # params.permit(tags: []) # # You can also use +permit+ on nested parameters, like: @@ -241,7 +245,7 @@ module ActionController # params = ActionController::Parameters.new({ # person: { # contact: { - # email: 'none@test.com' + # email: 'none@test.com', # phone: '555-1234' # } # } @@ -294,7 +298,14 @@ module ActionController # params.fetch(:none, 'Francesco') # => "Francesco" # params.fetch(:none) { 'Francesco' } # => "Francesco" def fetch(key, *args) - convert_hashes_to_parameters(key, super) + value = super + # Don't rely on +convert_hashes_to_parameters+ + # so as to not mutate via a +fetch+ + if value.is_a?(Hash) + value = self.class.new(value) + value.permit! if permitted? + end + value rescue KeyError raise ActionController::ParameterMissing.new(key) end @@ -308,7 +319,7 @@ module ActionController # params.slice(:d) # => {} def slice(*keys) self.class.new(super).tap do |new_instance| - new_instance.instance_variable_set :@permitted, @permitted + new_instance.permitted = @permitted end end @@ -322,10 +333,15 @@ module ActionController # copy_params.permitted? # => true def dup super.tap do |duplicate| - duplicate.instance_variable_set :@permitted, @permitted + duplicate.permitted = @permitted end end + protected + def permitted=(new_permitted) + @permitted = new_permitted + end + private def convert_hashes_to_parameters(key, value) if value.is_a?(Parameters) || !value.is_a?(Hash) @@ -339,7 +355,7 @@ module ActionController def each_element(object) if object.is_a?(Array) object.map { |el| yield el }.compact - elsif object.is_a?(Hash) && object.keys.all? { |k| k =~ /\A-?\d+\z/ } + elsif fields_for_style?(object) hash = object.class.new object.each { |k,v| hash[k] = yield v } hash @@ -348,6 +364,10 @@ module ActionController end end + def fields_for_style?(object) + object.is_a?(Hash) && object.all? { |k, v| k =~ /\A-?\d+\z/ && v.is_a?(Hash) } + end + def unpermitted_parameters!(params) unpermitted_keys = unpermitted_keys(params) if unpermitted_keys.any? @@ -426,13 +446,13 @@ module ActionController # Slicing filters out non-declared keys. slice(*filter.keys).each do |key, value| - return unless value + next unless value if filter[key] == EMPTY_ARRAY # Declaration { comment_ids: [] }. array_of_permitted_scalars_filter(params, key) else - # Declaration { user: :name } or { user: [:name, :age, { adress: ... }] }. + # Declaration { user: :name } or { user: [:name, :age, { address: ... }] }. params[key] = each_element(value) do |element| if element.is_a?(Hash) element = self.class.new(element) unless element.respond_to?(:permit) diff --git a/actionpack/lib/action_controller/metal/url_for.rb b/actionpack/lib/action_controller/metal/url_for.rb index 505f3b4e61..754249cbc8 100644 --- a/actionpack/lib/action_controller/metal/url_for.rb +++ b/actionpack/lib/action_controller/metal/url_for.rb @@ -32,7 +32,8 @@ module ActionController if (same_origin = _routes.equal?(env["action_dispatch.routes"])) || (script_name = env["ROUTES_#{_routes.object_id}_SCRIPT_NAME"]) || - (original_script_name = env['SCRIPT_NAME']) + (original_script_name = env['ORIGINAL_SCRIPT_NAME']) + @_url_options.dup.tap do |options| if original_script_name options[:original_script_name] = original_script_name diff --git a/actionpack/lib/action_controller/railtie.rb b/actionpack/lib/action_controller/railtie.rb index 5379547c57..0833e65d23 100644 --- a/actionpack/lib/action_controller/railtie.rb +++ b/actionpack/lib/action_controller/railtie.rb @@ -1,7 +1,6 @@ require "rails" require "action_controller" require "action_dispatch/railtie" -require "action_view/railtie" require "abstract_controller/railties/routes_helpers" require "action_controller/railties/helpers" diff --git a/actionpack/lib/action_controller/record_identifier.rb b/actionpack/lib/action_controller/record_identifier.rb deleted file mode 100644 index d598bac467..0000000000 --- a/actionpack/lib/action_controller/record_identifier.rb +++ /dev/null @@ -1,31 +0,0 @@ -require 'action_view/record_identifier' - -module ActionController - module RecordIdentifier - MODULE_MESSAGE = 'Calling ActionController::RecordIdentifier.%s is deprecated and ' \ - 'will be removed in Rails 4.1, please call using ActionView::RecordIdentifier instead.' - INSTANCE_MESSAGE = '%s method will no longer be included by default in controllers ' \ - 'since Rails 4.1. If you would like to use it in controllers, please include ' \ - 'ActionView::RecordIdentifier module.' - - def dom_id(record, prefix = nil) - ActiveSupport::Deprecation.warn(INSTANCE_MESSAGE % 'dom_id') - ActionView::RecordIdentifier.dom_id(record, prefix) - end - - def dom_class(record, prefix = nil) - ActiveSupport::Deprecation.warn(INSTANCE_MESSAGE % 'dom_class') - ActionView::RecordIdentifier.dom_class(record, prefix) - end - - def self.dom_id(record, prefix = nil) - ActiveSupport::Deprecation.warn(MODULE_MESSAGE % 'dom_id') - ActionView::RecordIdentifier.dom_id(record, prefix) - end - - def self.dom_class(record, prefix = nil) - ActiveSupport::Deprecation.warn(MODULE_MESSAGE % 'dom_class') - ActionView::RecordIdentifier.dom_class(record, prefix) - end - end -end diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index e12bf0a1c6..5ed3d2ebc1 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -18,7 +18,7 @@ module ActionController @_layouts = Hash.new(0) @_files = Hash.new(0) - ActiveSupport::Notifications.subscribe("render_template.action_view") do |name, start, finish, id, payload| + ActiveSupport::Notifications.subscribe("render_template.action_view") do |_name, _start, _finish, _id, payload| path = payload[:layout] if path @_layouts[path] += 1 @@ -28,7 +28,7 @@ module ActionController end end - ActiveSupport::Notifications.subscribe("!render_template.action_view") do |name, start, finish, id, payload| + ActiveSupport::Notifications.subscribe("!render_template.action_view") do |_name, _start, _finish, _id, payload| path = payload[:virtual_path] next unless path partial = path =~ /^.*\/_[^\/]*$/ @@ -41,14 +41,14 @@ module ActionController @_templates[path] += 1 end - ActiveSupport::Notifications.subscribe("!render_template.action_view") do |name, start, finish, id, payload| - path = payload[:identifier] + ActiveSupport::Notifications.subscribe("!render_template.action_view") do |_name, _start, _finish, _id, payload| next if payload[:virtual_path] # files don't have virtual path + + path = payload[:identifier] if path @_files[path] += 1 @_files[path.split("/").last] += 1 end - end end @@ -316,7 +316,7 @@ module ActionController # assert_response :found # # # Assert that the controller really put the book in the database. - # assert_not_nil Book.find_by_title("Love Hina") + # assert_not_nil Book.find_by(title: "Love Hina") # end # end # @@ -451,41 +451,54 @@ module ActionController end - # Executes a request simulating GET HTTP method and set/volley the response + # Simulate a GET request with the given parameters. + # + # - +action+: The controller action to call. + # - +parameters+: The HTTP parameters that you want to pass. This may + # be +nil+, a hash, or a string that is appropriately encoded + # (<tt>application/x-www-form-urlencoded</tt> or <tt>multipart/form-data</tt>). + # - +session+: A hash of parameters to store in the session. This may be +nil+. + # - +flash+: A hash of parameters to store in the flash. This may be +nil+. + # + # You can also simulate POST, PATCH, PUT, DELETE, HEAD, and OPTIONS requests with + # +post+, +patch+, +put+, +delete+, +head+, and +options+. + # + # Note that the request method is not verified. The different methods are + # available to make the tests more expressive. def get(action, *args) process(action, "GET", *args) end - # Executes a request simulating POST HTTP method and set/volley the response + # Simulate a POST request with the given parameters and set/volley the response. + # See +get+ for more details. def post(action, *args) process(action, "POST", *args) end - # Executes a request simulating PATCH HTTP method and set/volley the response + # Simulate a PATCH request with the given parameters and set/volley the response. + # See +get+ for more details. def patch(action, *args) process(action, "PATCH", *args) end - # Executes a request simulating PUT HTTP method and set/volley the response + # Simulate a PUT request with the given parameters and set/volley the response. + # See +get+ for more details. def put(action, *args) process(action, "PUT", *args) end - # Executes a request simulating DELETE HTTP method and set/volley the response + # Simulate a DELETE request with the given parameters and set/volley the response. + # See +get+ for more details. def delete(action, *args) process(action, "DELETE", *args) end - # Executes a request simulating HEAD HTTP method and set/volley the response + # Simulate a HEAD request with the given parameters and set/volley the response. + # See +get+ for more details. def head(action, *args) process(action, "HEAD", *args) end - # Executes a request simulating OPTIONS HTTP method and set/volley the response - def options(action, *args) - process(action, "OPTIONS", *args) - end - def xml_http_request(request_method, action, parameters = nil, session = nil, flash = nil) @request.env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest' @request.env['HTTP_ACCEPT'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ') @@ -511,7 +524,6 @@ module ActionController def process(action, http_method = 'GET', *args) check_required_ivars - http_method, args = handle_old_process_api(http_method, args, caller) if args.first.is_a?(String) && http_method != 'HEAD' @request.env['RAW_POST_DATA'] = args.shift @@ -539,7 +551,7 @@ module ActionController parameters ||= {} controller_class_name = @controller.class.anonymous? ? "anonymous" : - @controller.class.name.underscore.sub(/_controller$/, '') + @controller.class.controller_path @request.assign_parameters(@routes, controller_class_name, action.to_s, parameters) @@ -615,17 +627,6 @@ module ActionController end end - def handle_old_process_api(http_method, args, callstack) - # 4.0: Remove this method. - if http_method.is_a?(Hash) - ActiveSupport::Deprecation.warn("TestCase#process now expects the HTTP method as second argument: process(action, http_method, params, session, flash)", callstack) - args.unshift(http_method) - http_method = args.last.is_a?(String) ? args.last : "GET" - end - - [http_method, args] - end - def build_request_uri(action, parameters) unless @request.env["PATH_INFO"] options = @controller.respond_to?(:url_options) ? @controller.__send__(:url_options).merge(parameters) : parameters diff --git a/actionpack/lib/action_controller/vendor/html-scanner.rb b/actionpack/lib/action_controller/vendor/html-scanner.rb deleted file mode 100644 index 896208bc05..0000000000 --- a/actionpack/lib/action_controller/vendor/html-scanner.rb +++ /dev/null @@ -1,5 +0,0 @@ -require 'action_view/vendor/html-scanner' -require 'active_support/deprecation' - -ActiveSupport::Deprecation.warn 'Vendored html-scanner was moved to action_view, please require "action_view/vendor/html-scanner" instead. ' + - 'This file will be removed in Rails 4.1' diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb index 618e2f3033..24a3d4741e 100644 --- a/actionpack/lib/action_dispatch.rb +++ b/actionpack/lib/action_dispatch.rb @@ -82,12 +82,10 @@ module ActionDispatch end module Session - autoload :AbstractStore, 'action_dispatch/middleware/session/abstract_store' - autoload :CookieStore, 'action_dispatch/middleware/session/cookie_store' - autoload :EncryptedCookieStore, 'action_dispatch/middleware/session/cookie_store' - autoload :UpgradeSignatureToEncryptionCookieStore, 'action_dispatch/middleware/session/cookie_store' - autoload :MemCacheStore, 'action_dispatch/middleware/session/mem_cache_store' - autoload :CacheStore, 'action_dispatch/middleware/session/cache_store' + autoload :AbstractStore, 'action_dispatch/middleware/session/abstract_store' + autoload :CookieStore, 'action_dispatch/middleware/session/cookie_store' + autoload :MemCacheStore, 'action_dispatch/middleware/session/mem_cache_store' + autoload :CacheStore, 'action_dispatch/middleware/session/cache_store' end mattr_accessor :test_app diff --git a/actionpack/lib/action_dispatch/http/cache.rb b/actionpack/lib/action_dispatch/http/cache.rb index 0d6015d993..f9b278349e 100644 --- a/actionpack/lib/action_dispatch/http/cache.rb +++ b/actionpack/lib/action_dispatch/http/cache.rb @@ -92,7 +92,7 @@ module ActionDispatch LAST_MODIFIED = "Last-Modified".freeze ETAG = "ETag".freeze CACHE_CONTROL = "Cache-Control".freeze - SPESHUL_KEYS = %w[extras no-cache max-age public must-revalidate] + SPECIAL_KEYS = %w[extras no-cache max-age public must-revalidate] def cache_control_segments if cache_control = self[CACHE_CONTROL] @@ -108,7 +108,7 @@ module ActionDispatch cache_control_segments.each do |segment| directive, argument = segment.split('=', 2) - if SPESHUL_KEYS.include? directive + if SPECIAL_KEYS.include? directive key = directive.tr('-', '_') cache_control[key.to_sym] = argument || true else diff --git a/actionpack/lib/action_dispatch/http/headers.rb b/actionpack/lib/action_dispatch/http/headers.rb index dc04d4577b..2666cd4b0a 100644 --- a/actionpack/lib/action_dispatch/http/headers.rb +++ b/actionpack/lib/action_dispatch/http/headers.rb @@ -1,38 +1,62 @@ module ActionDispatch module Http class Headers + CGI_VARIABLES = %w( + CONTENT_TYPE CONTENT_LENGTH + HTTPS AUTH_TYPE GATEWAY_INTERFACE + PATH_INFO PATH_TRANSLATED QUERY_STRING + REMOTE_ADDR REMOTE_HOST REMOTE_IDENT REMOTE_USER + REQUEST_METHOD SCRIPT_NAME + SERVER_NAME SERVER_PORT SERVER_PROTOCOL SERVER_SOFTWARE + ) + HTTP_HEADER = /\A[A-Za-z0-9-]+\z/ + include Enumerable + attr_reader :env def initialize(env = {}) - @headers = env + @env = env + end + + def [](key) + @env[env_name(key)] end - def [](header_name) - @headers[env_name(header_name)] + def []=(key, value) + @env[env_name(key)] = value end - def []=(k,v); @headers[k] = v; end - def key?(k); @headers.key? k; end + def key?(key); @env.key? key; end alias :include? :key? - def fetch(header_name, *args, &block) - @headers.fetch env_name(header_name), *args, &block + def fetch(key, *args, &block) + @env.fetch env_name(key), *args, &block end def each(&block) - @headers.each(&block) + @env.each(&block) end - private + def merge(headers_or_env) + headers = Http::Headers.new(env.dup) + headers.merge!(headers_or_env) + headers + end - # Converts a HTTP header name to an environment variable name if it is - # not contained within the headers hash. - def env_name(header_name) - @headers.include?(header_name) ? header_name : cgi_name(header_name) + def merge!(headers_or_env) + headers_or_env.each do |key, value| + self[env_name(key)] = value + end end - def cgi_name(k) - "HTTP_#{k.upcase.gsub(/-/, '_')}" + private + def env_name(key) + key = key.to_s + if key =~ HTTP_HEADER + key = key.upcase.tr('-', '_') + key = "HTTP_" + key unless CGI_VARIABLES.include?(key) + end + key end end end diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb index 912da741b7..ef144c3c76 100644 --- a/actionpack/lib/action_dispatch/http/mime_type.rb +++ b/actionpack/lib/action_dispatch/http/mime_type.rb @@ -53,10 +53,6 @@ module Mime @@html_types = Set.new [:html, :all] cattr_reader :html_types - # These are the content types which browsers can generate without using ajax, flash, etc - # i.e. following a link, getting an image or posting a form. CSRF protection - # only needs to protect against these types. - @@browser_generated_types = Set.new [:html, :url_encoded_form, :multipart_form, :text] attr_reader :symbol @register_callbacks = [] @@ -179,7 +175,7 @@ module Mime def parse(accept_header) if accept_header !~ /,/ accept_header = accept_header.split(PARAMETER_SEPARATOR_REGEXP).first - parse_trailing_star(accept_header) || [Mime::Type.lookup(accept_header)] + parse_trailing_star(accept_header) || [Mime::Type.lookup(accept_header)].compact else list, index = AcceptList.new, 0 accept_header.split(',').each do |header| @@ -223,8 +219,8 @@ module Mime Mime.instance_eval { remove_const(symbol) } SET.delete_if { |v| v.eql?(mime) } - LOOKUP.delete_if { |k,v| v.eql?(mime) } - EXTENSION_LOOKUP.delete_if { |k,v| v.eql?(mime) } + LOOKUP.delete_if { |_,v| v.eql?(mime) } + EXTENSION_LOOKUP.delete_if { |_,v| v.eql?(mime) } end end @@ -272,18 +268,6 @@ module Mime end end - # Returns true if Action Pack should check requests using this Mime Type for possible request forgery. See - # ActionController::RequestForgeryProtection. - def verify_request? - ActiveSupport::Deprecation.warn "Mime::Type#verify_request? is deprecated and will be removed in Rails 4.1" - @@browser_generated_types.include?(to_sym) - end - - def self.browser_generated_types - ActiveSupport::Deprecation.warn "Mime::Type.browser_generated_types is deprecated and will be removed in Rails 4.1" - @@browser_generated_types - end - def html? @@html_types.include?(to_sym) || @string =~ /html/ end @@ -306,12 +290,20 @@ module Mime method.to_s.ends_with? '?' end end - + class NullType def nil? true end + def ref + nil + end + + def respond_to_missing?(method, include_private = false) + method.to_s.ends_with? '?' + end + private def method_missing(method, *args) false if method.to_s.ends_with? '?' diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb index 446862aad0..dcb299ed03 100644 --- a/actionpack/lib/action_dispatch/http/parameters.rb +++ b/actionpack/lib/action_dispatch/http/parameters.rb @@ -18,7 +18,7 @@ module ActionDispatch query_parameters.dup end params.merge!(path_parameters) - encode_params(params).with_indifferent_access + params.with_indifferent_access end end alias :params :parameters @@ -50,39 +50,31 @@ module ActionDispatch private + # Convert nested Hash to HashWithIndifferentAccess + # and UTF-8 encode both keys and values in nested Hash. + # # TODO: Validate that the characters are UTF-8. If they aren't, # you'll get a weird error down the road, but our form handling # should really prevent that from happening - def encode_params(params) - if params.is_a?(String) - return params.force_encoding(Encoding::UTF_8).encode! - elsif !params.is_a?(Hash) - return params - end - - params.each do |k, v| - case v - when Hash - encode_params(v) - when Array - v.map! {|el| encode_params(el) } + def normalize_encode_params(params) + case params + when String + params.force_encoding(Encoding::UTF_8).encode! + when Hash + if params.has_key?(:tempfile) + UploadedFile.new(params) else - encode_params(v) + params.each_with_object({}) do |(key, val), new_hash| + new_key = key.is_a?(String) ? key.dup.force_encoding(Encoding::UTF_8).encode! : key + new_hash[new_key] = if val.is_a?(Array) + val.map! { |el| normalize_encode_params(el) } + else + normalize_encode_params(val) + end + end.with_indifferent_access end - end - end - - # Convert nested Hash to ActiveSupport::HashWithIndifferentAccess - def normalize_parameters(value) - case value - when Hash - h = {} - value.each { |k, v| h[k] = normalize_parameters(v) } - h.with_indifferent_access - when Array - value.map { |e| normalize_parameters(e) } else - value + params end end end diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index 7b04d6e851..aba8f66118 100644 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -18,10 +18,10 @@ module ActionDispatch include ActionDispatch::Http::MimeNegotiation include ActionDispatch::Http::Parameters include ActionDispatch::Http::FilterParameters - include ActionDispatch::Http::Upload include ActionDispatch::Http::URL autoload :Session, 'action_dispatch/request/session' + autoload :Utils, 'action_dispatch/request/utils' LOCALHOST = Regexp.union [/^127\.0\.0\.\d{1,3}$/, /^::1$/, /^0:0:0:0:0:0:0:1(%.*)?$/] @@ -156,14 +156,29 @@ module ActionDispatch @original_fullpath ||= (env["ORIGINAL_FULLPATH"] || fullpath) end + # Returns the +String+ full path including params of the last URL requested. + # + # # get "/articles" + # request.fullpath # => "/articles" + # + # # get "/articles?page=2" + # request.fullpath # => "/articles?page=2" def fullpath @fullpath ||= super end + # Returns the original request URL as a +String+. + # + # # get "/articles?page=2" + # request.original_url # => "http://www.example.com/articles?page=2" def original_url base_url + original_fullpath end + # The +String+ MIME type of the request. + # + # # get "/articles" + # request.media_type # => "application/x-www-form-urlencoded" def media_type content_mime_type.to_s end @@ -210,7 +225,7 @@ module ActionDispatch def raw_post unless @env.include? 'RAW_POST_DATA' raw_post_body = body - @env['RAW_POST_DATA'] = raw_post_body.read(@env['CONTENT_LENGTH'].to_i) + @env['RAW_POST_DATA'] = raw_post_body.read(content_length) raw_post_body.rewind if raw_post_body.respond_to?(:rewind) end @env['RAW_POST_DATA'] @@ -256,7 +271,7 @@ module ActionDispatch # Override Rack's GET method to support indifferent access def GET - @env["action_dispatch.request.query_parameters"] ||= (normalize_parameters(super) || {}) + @env["action_dispatch.request.query_parameters"] ||= (normalize_encode_params(super) || {}) rescue TypeError => e raise ActionController::BadRequest.new(:query, e) end @@ -264,7 +279,7 @@ module ActionDispatch # Override Rack's POST method to support indifferent access def POST - @env["action_dispatch.request.request_parameters"] ||= (normalize_parameters(super) || {}) + @env["action_dispatch.request.request_parameters"] ||= (normalize_encode_params(super) || {}) rescue TypeError => e raise ActionController::BadRequest.new(:request, e) end @@ -284,26 +299,10 @@ module ActionDispatch LOCALHOST =~ remote_addr && LOCALHOST =~ remote_ip end - # Remove nils from the params hash - def deep_munge(hash) - hash.each do |k, v| - case v - when Array - v.grep(Hash) { |x| deep_munge(x) } - v.compact! - hash[k] = nil if v.empty? - when Hash - deep_munge(v) - end - end - - hash - end - protected def parse_query(qs) - deep_munge(super) + Utils.deep_munge(super) end private diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb index 06e936cdb0..5247e61a23 100644 --- a/actionpack/lib/action_dispatch/http/response.rb +++ b/actionpack/lib/action_dispatch/http/response.rb @@ -31,10 +31,17 @@ module ActionDispatch # :nodoc: # end # end class Response - attr_accessor :request, :header + # The request that the response is responding to. + attr_accessor :request + + # The HTTP status code. attr_reader :status + attr_writer :sending_file + # Get and set headers for this response. + attr_accessor :header + alias_method :headers=, :header= alias_method :headers, :header @@ -49,12 +56,16 @@ module ActionDispatch # :nodoc: # If a character set has been defined for this response (see charset=) then # the character set information will also be included in the content type # information. - attr_accessor :charset attr_reader :content_type + # The charset of the response. HTML wants to know the encoding of the + # content you're giving them, so we need to send that along. + attr_accessor :charset + CONTENT_TYPE = "Content-Type".freeze SET_COOKIE = "Set-Cookie".freeze LOCATION = "Location".freeze + NO_CONTENT_CODES = [204, 304] cattr_accessor(:default_charset) { "utf-8" } cattr_accessor(:default_headers) @@ -92,6 +103,7 @@ module ActionDispatch # :nodoc: end end + # The underlying body, as a streamable object. attr_reader :stream def initialize(status = 200, header = {}, body = []) @@ -141,6 +153,7 @@ module ActionDispatch # :nodoc: @status = Rack::Utils.status_code(status) end + # Sets the HTTP content type. def content_type=(content_type) @content_type = content_type.to_s end @@ -168,9 +181,9 @@ module ActionDispatch # :nodoc: end alias_method :status_message, :message - def respond_to?(method) + def respond_to?(method, include_private = false) if method.to_s == 'to_path' - stream.respond_to?(:to_path) + stream.respond_to?(method) else super end @@ -197,7 +210,9 @@ module ActionDispatch # :nodoc: if body.respond_to?(:to_path) @stream = body else - @stream = build_buffer self, munge_body_object(body) + synchronize do + @stream = build_buffer self, munge_body_object(body) + end end end @@ -215,11 +230,13 @@ module ActionDispatch # :nodoc: ::Rack::Utils.delete_cookie_header!(header, key, value) end + # The location header we'll be responding with. def location headers[LOCATION] end alias_method :redirect_url, :location + # Sets the location header we'll be responding with. def location=(url) headers[LOCATION] = url end @@ -228,11 +245,13 @@ module ActionDispatch # :nodoc: stream.close if stream.respond_to?(:close) end + # Turns the Response into a Rack-compatible array of the status, headers, + # and body. def to_a rack_response @status, @header.to_hash end alias prepare! to_a - alias to_ary to_a # For implicit splat on 1.9.2 + alias to_ary to_a # Returns the response cookies, converted to a Hash of (name => value) pairs # @@ -289,7 +308,7 @@ module ActionDispatch # :nodoc: header[SET_COOKIE] = header[SET_COOKIE].join("\n") if header[SET_COOKIE].respond_to?(:join) - if [204, 304].include?(@status) + if NO_CONTENT_CODES.include?(@status) header.delete CONTENT_TYPE [status, header, []] else diff --git a/actionpack/lib/action_dispatch/http/upload.rb b/actionpack/lib/action_dispatch/http/upload.rb index 67cb7fbcb5..a8d2dc3950 100644 --- a/actionpack/lib/action_dispatch/http/upload.rb +++ b/actionpack/lib/action_dispatch/http/upload.rb @@ -6,7 +6,7 @@ module ActionDispatch # of its interface is available directly for convenience. # # Uploaded files are temporary files whose lifespan is one request. When - # the object is finalized Ruby unlinks the file, so there is not need to + # the object is finalized Ruby unlinks the file, so there is no need to # clean them with a separate maintenance task. class UploadedFile # The basename of the file in the client. @@ -73,18 +73,5 @@ module ActionDispatch filename.force_encoding(Encoding::UTF_8).encode! if filename end end - - module Upload # :nodoc: - # Convert nested Hash to ActiveSupport::HashWithIndifferentAccess and replace - # file upload hash with UploadedFile objects - def normalize_parameters(value) - if Hash === value && value.has_key?(:tempfile) - UploadedFile.new(value) - else - super - end - end - private :normalize_parameters - end end end diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb index 97ac462411..6f5a52c568 100644 --- a/actionpack/lib/action_dispatch/http/url.rb +++ b/actionpack/lib/action_dispatch/http/url.rb @@ -4,7 +4,9 @@ require 'active_support/core_ext/hash/slice' module ActionDispatch module Http module URL - IP_HOST_REGEXP = /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/ + IP_HOST_REGEXP = /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/ + HOST_REGEXP = /(^.*:\/\/)?([^:]+)(?::(\d+$))?/ + PROTOCOL_REGEXP = /^([^:]+)(:)?(\/\/)?$/ mattr_accessor :tld_length self.tld_length = 1 @@ -28,6 +30,7 @@ module ActionDispatch end def url_for(options = {}) + options = options.dup path = options.delete(:script_name).to_s.chomp("/") path << options.delete(:path).to_s @@ -59,14 +62,20 @@ module ActionDispatch result = "" unless options[:only_path] - unless options[:protocol] == false - result << (options[:protocol] || "http") - result << ":" unless result.match(%r{:|//}) + if match = options[:host].match(HOST_REGEXP) + options[:protocol] ||= match[1] unless options[:protocol] == false + options[:host] = match[2] + options[:port] = match[3] unless options.key?(:port) end - result << "//" unless result.match("//") + + options[:protocol] = normalize_protocol(options) + options[:host] = normalize_host(options) + options[:port] = normalize_port(options) + + result << options[:protocol] result << rewrite_authentication(options) - result << host_or_subdomain_and_domain(options) - result << ":#{options.delete(:port)}" if options[:port] + result << options[:host] + result << ":#{options[:port]}" if options[:port] end result end @@ -75,6 +84,10 @@ module ActionDispatch host && IP_HOST_REGEXP !~ host end + def same_host?(options) + (options[:subdomain] == true || !options.key?(:subdomain)) && options[:domain].nil? + end + def rewrite_authentication(options) if options[:user] && options[:password] "#{Rack::Utils.escape(options[:user])}:#{Rack::Utils.escape(options[:password])}@" @@ -83,19 +96,47 @@ module ActionDispatch end end - def host_or_subdomain_and_domain(options) - return options[:host] if !named_host?(options[:host]) || (options[:subdomain].nil? && options[:domain].nil?) + def normalize_protocol(options) + case options[:protocol] + when nil + "http://" + when false, "//" + "//" + when PROTOCOL_REGEXP + "#{$1}://" + else + raise ArgumentError, "Invalid :protocol option: #{options[:protocol].inspect}" + end + end + + def normalize_host(options) + return options[:host] if !named_host?(options[:host]) || same_host?(options) tld_length = options[:tld_length] || @@tld_length host = "" - unless options[:subdomain] == false - host << (options[:subdomain] || extract_subdomain(options[:host], tld_length)).to_param - host << "." + if options[:subdomain] == true || !options.key?(:subdomain) + host << extract_subdomain(options[:host], tld_length).to_param + elsif options[:subdomain].present? + host << options[:subdomain].to_param end + host << "." unless host.empty? host << (options[:domain] || extract_domain(options[:host], tld_length)) host end + + def normalize_port(options) + return nil if options[:port].nil? || options[:port] == false + + case options[:protocol] + when "//" + nil + when "https://" + options[:port].to_i == 443 ? nil : options[:port] + else + options[:port].to_i == 80 ? nil : options[:port] + end + end end def initialize(env) diff --git a/actionpack/lib/action_dispatch/journey/formatter.rb b/actionpack/lib/action_dispatch/journey/formatter.rb index 82c55660ea..7764763791 100644 --- a/actionpack/lib/action_dispatch/journey/formatter.rb +++ b/actionpack/lib/action_dispatch/journey/formatter.rb @@ -3,7 +3,7 @@ require 'action_controller/metal/exceptions' module ActionDispatch module Journey # The Formatter class is used for formatting URLs. For example, parameters - # passed to +url_for+ in rails will eventually call Formatter#generate. + # passed to +url_for+ in Rails will eventually call Formatter#generate. class Formatter # :nodoc: attr_reader :routes @@ -18,7 +18,11 @@ module ActionDispatch match_route(name, constraints) do |route| parameterized_parts = extract_parameterized_parts(route, options, recall, parameterize) - next if !name && route.requirements.empty? && route.parts.empty? + + # Skip this route unless a name has been provided or it is a + # standard Rails route since we can't determine whether an options + # hash passed to url_for matches a Rack application or a redirect. + next unless name || route.dispatcher? missing_keys = missing_keys(route, parameterized_parts) next unless missing_keys.empty? @@ -58,7 +62,7 @@ module ActionDispatch end end - parameterized_parts.keep_if { |_, v| v } + parameterized_parts.keep_if { |_, v| v } parameterized_parts end diff --git a/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb b/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb index da0cddd93c..971cb3447f 100644 --- a/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb +++ b/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb @@ -9,8 +9,8 @@ module ActionDispatch attr_reader :memos def initialize - @regexp_states = Hash.new { |h,k| h[k] = {} } - @string_states = Hash.new { |h,k| h[k] = {} } + @regexp_states = {} + @string_states = {} @accepting = {} @memos = Hash.new { |h,k| h[k] = [] } end @@ -111,14 +111,8 @@ module ActionDispatch end def []=(from, to, sym) - case sym - when String - @string_states[from][sym] = to - when Regexp - @regexp_states[from][sym] = to - else - raise ArgumentError, 'unknown symbol: %s' % sym.class - end + to_mappings = states_hash_for(sym)[from] ||= {} + to_mappings[sym] = to end def states @@ -137,18 +131,35 @@ module ActionDispatch private + def states_hash_for(sym) + case sym + when String + @string_states + when Regexp + @regexp_states + else + raise ArgumentError, 'unknown symbol: %s' % sym.class + end + end + def move_regexp(t, a) return [] if t.empty? t.map { |s| - @regexp_states[s].map { |re, v| re === a ? v : nil } + if states = @regexp_states[s] + states.map { |re, v| re === a ? v : nil } + end }.flatten.compact.uniq end def move_string(t, a) return [] if t.empty? - t.map { |s| @string_states[s][a] }.compact + t.map do |s| + if states = @string_states[s] + states[a] + end + end.compact end end end diff --git a/actionpack/lib/action_dispatch/journey/parser.y b/actionpack/lib/action_dispatch/journey/parser.y index a2e1afed32..040f8d5922 100644 --- a/actionpack/lib/action_dispatch/journey/parser.y +++ b/actionpack/lib/action_dispatch/journey/parser.y @@ -36,6 +36,7 @@ rule ; literal : LITERAL { result = Literal.new(val.first) } + ; dot : DOT { result = Dot.new(val.first) } ; diff --git a/actionpack/lib/action_dispatch/journey/route.rb b/actionpack/lib/action_dispatch/journey/route.rb index 6fda085681..c8eb0f6f2d 100644 --- a/actionpack/lib/action_dispatch/journey/route.rb +++ b/actionpack/lib/action_dispatch/journey/route.rb @@ -16,6 +16,14 @@ module ActionDispatch @app = app @path = path + # Unwrap any constraints so we can see what's inside for route generation. + # This allows the formatter to skip over any mounted applications or redirects + # that shouldn't be matched when using a url_for without a route name. + while app.is_a?(Routing::Mapper::Constraints) do + app = app.app + end + @dispatcher = app.is_a?(Routing::RouteSet::Dispatcher) + @constraints = constraints @defaults = defaults @required_defaults = nil @@ -93,6 +101,10 @@ module ActionDispatch end end + def dispatcher? + @dispatcher + end + def matches?(request) constraints.all? do |method, value| next true unless request.respond_to?(method) @@ -102,6 +114,10 @@ module ActionDispatch value === request.send(method).to_s when Array value.include?(request.send(method)) + when TrueClass + request.send(method).present? + when FalseClass + request.send(method).blank? else value === request.send(method) end diff --git a/actionpack/lib/action_dispatch/journey/router.rb b/actionpack/lib/action_dispatch/journey/router.rb index 31868b1814..da32f1bfe7 100644 --- a/actionpack/lib/action_dispatch/journey/router.rb +++ b/actionpack/lib/action_dispatch/journey/router.rb @@ -38,7 +38,9 @@ module ActionDispatch env['REMOTE_ADDR'] end - def [](k); env[k]; end + def [](k) + env[k] + end end attr_reader :request_class, :formatter @@ -52,7 +54,7 @@ module ActionDispatch end def call(env) - env['PATH_INFO'] = Utils.normalize_path(env['PATH_INFO']) + env['PATH_INFO'] = normalize_path(env['PATH_INFO']) find_routes(env).each do |match, parameters, route| script_name, path_info, set_params = env.values_at('SCRIPT_NAME', @@ -101,6 +103,12 @@ module ActionDispatch private + def normalize_path(path) + path = "/#{path}" + path.squeeze!('/') + path + end + def partitioned_routes routes.partitioned_routes end diff --git a/actionpack/lib/action_dispatch/journey/router/utils.rb b/actionpack/lib/action_dispatch/journey/router/utils.rb index 462f1a122d..d1a004af50 100644 --- a/actionpack/lib/action_dispatch/journey/router/utils.rb +++ b/actionpack/lib/action_dispatch/journey/router/utils.rb @@ -7,15 +7,18 @@ module ActionDispatch # Normalizes URI path. # # Strips off trailing slash and ensures there is a leading slash. + # Also converts downcase url encoded string to uppercase. # # normalize_path("/foo") # => "/foo" # normalize_path("/foo/") # => "/foo" # normalize_path("foo") # => "/foo" # normalize_path("") # => "/" + # normalize_path("/%ab") # => "/%AB" def self.normalize_path(path) path = "/#{path}" path.squeeze!('/') path.sub!(%r{/+\Z}, '') + path.gsub!(/(%[a-f0-9]{2})/) { $1.upcase } path = '/' if path == '' path end @@ -35,7 +38,7 @@ module ActionDispatch UNSAFE_FRAGMENT = Regexp.new("[^#{safe_fragment}]", false).freeze end - Parser = URI.const_defined?(:Parser) ? URI::Parser.new : URI + Parser = URI::Parser.new def self.escape_path(path) Parser.escape(path.to_s, UriEscape::UNSAFE_SEGMENT) diff --git a/actionpack/lib/action_dispatch/journey/routes.rb b/actionpack/lib/action_dispatch/journey/routes.rb index a99d6d0d6a..80e3818ccd 100644 --- a/actionpack/lib/action_dispatch/journey/routes.rb +++ b/actionpack/lib/action_dispatch/journey/routes.rb @@ -30,6 +30,7 @@ module ActionDispatch def clear routes.clear + named_routes.clear end def partitioned_routes diff --git a/actionpack/lib/action_dispatch/journey/visitors.rb b/actionpack/lib/action_dispatch/journey/visitors.rb index 2964d80d9f..9e66cab052 100644 --- a/actionpack/lib/action_dispatch/journey/visitors.rb +++ b/actionpack/lib/action_dispatch/journey/visitors.rb @@ -1,10 +1,13 @@ # encoding: utf-8 + +require 'thread_safe' + module ActionDispatch module Journey # :nodoc: module Visitors # :nodoc: class Visitor # :nodoc: - DISPATCH_CACHE = Hash.new { |h,k| - h[k] = "visit_#{k}" + DISPATCH_CACHE = ThreadSafe::Cache.new { |h,k| + h[k] = :"visit_#{k}" } def accept(node) @@ -84,44 +87,43 @@ module ActionDispatch # Used for formatting urls (url_for) class Formatter < Visitor # :nodoc: - attr_reader :options, :consumed + attr_reader :options def initialize(options) @options = options - @consumed = {} end private - def visit_GROUP(node) - if consumed == options - nil - else - route = visit(node.left) - route.include?("\0") ? nil : route + def visit(node, optional = false) + case node.type + when :LITERAL, :SLASH, :DOT + node.left + when :STAR + visit(node.left) + when :GROUP + visit(node.left, true) + when :CAT + visit_CAT(node, optional) + when :SYMBOL + visit_SYMBOL(node) end end - def terminal(node) - node.left - end - - def binary(node) - [visit(node.left), visit(node.right)].join - end + def visit_CAT(node, optional) + left = visit(node.left, optional) + right = visit(node.right, optional) - def nary(node) - node.children.map { |c| visit(c) }.join + if optional && !(right && left) + "" + else + [left, right].join + end end def visit_SYMBOL(node) - key = node.to_sym - - if value = options[key] - consumed[key] = value + if value = options[node.to_sym] Router::Utils.escape_path(value) - else - "\0" end end end diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb index 36a0db6e61..3ccd0c9ee8 100644 --- a/actionpack/lib/action_dispatch/middleware/cookies.rb +++ b/actionpack/lib/action_dispatch/middleware/cookies.rb @@ -1,5 +1,6 @@ require 'active_support/core_ext/hash/keys' require 'active_support/core_ext/module/attribute_accessors' +require 'active_support/core_ext/object/blank' require 'active_support/key_generator' require 'active_support/message_verifier' @@ -30,7 +31,7 @@ module ActionDispatch # # # Sets a signed cookie, which prevents users from tampering with its value. # # The cookie is signed by your app's <tt>config.secret_key_base</tt> value. - # # It can be read using the signed method <tt>cookies.signed[:key]</tt> + # # It can be read using the signed method <tt>cookies.signed[:name]</tt> # cookies.signed[:user_id] = current_user.id # # # Sets a "permanent" cookie (which expires in 20 years from now). @@ -52,13 +53,13 @@ module ActionDispatch # # Please note that if you specify a :domain when setting a cookie, you must also specify the domain when deleting the cookie: # - # cookies[:key] = { + # cookies[:name] = { # value: 'a yummy cookie', # expires: 1.year.from_now, # domain: 'domain.com' # } # - # cookies.delete(:key, domain: 'domain.com') + # cookies.delete(:name, domain: 'domain.com') # # The option symbols for setting cookies are: # @@ -69,14 +70,14 @@ module ActionDispatch # restrict to the domain level. If you use a schema like www.example.com # and want to share session with user.example.com set <tt>:domain</tt> # to <tt>:all</tt>. Make sure to specify the <tt>:domain</tt> option with - # <tt>:all</tt> again when deleting keys. + # <tt>:all</tt> again when deleting cookies. # # domain: nil # Does not sets cookie domain. (default) # domain: :all # Allow the cookie for the top most level # domain and subdomains. # # * <tt>:expires</tt> - The time at which this cookie expires, as a \Time object. - # * <tt>:secure</tt> - Whether this cookie is a only transmitted to HTTPS servers. + # * <tt>:secure</tt> - Whether this cookie is only transmitted to HTTPS servers. # Default is +false+. # * <tt>:httponly</tt> - Whether this cookie is accessible via scripting or # only HTTP. Defaults to +false+. @@ -86,7 +87,8 @@ module ActionDispatch SIGNED_COOKIE_SALT = "action_dispatch.signed_cookie_salt".freeze ENCRYPTED_COOKIE_SALT = "action_dispatch.encrypted_cookie_salt".freeze ENCRYPTED_SIGNED_COOKIE_SALT = "action_dispatch.encrypted_signed_cookie_salt".freeze - TOKEN_KEY = "action_dispatch.secret_token".freeze + SECRET_TOKEN = "action_dispatch.secret_token".freeze + SECRET_KEY_BASE = "action_dispatch.secret_key_base".freeze # Cookies can typically store 4096 bytes. MAX_COOKIE_SIZE = 4096 @@ -94,8 +96,99 @@ module ActionDispatch # Raised when storing more than 4K of session data. CookieOverflow = Class.new StandardError + # Include in a cookie jar to allow chaining, e.g. cookies.permanent.signed + module ChainedCookieJars + # Returns a jar that'll automatically set the assigned cookies to have an expiration date 20 years from now. Example: + # + # cookies.permanent[:prefers_open_id] = true + # # => Set-Cookie: prefers_open_id=true; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT + # + # This jar is only meant for writing. You'll read permanent cookies through the regular accessor. + # + # This jar allows chaining with the signed jar as well, so you can set permanent, signed cookies. Examples: + # + # cookies.permanent.signed[:remember_me] = current_user.id + # # => Set-Cookie: remember_me=BAhU--848956038e692d7046deab32b7131856ab20e14e; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT + def permanent + @permanent ||= PermanentCookieJar.new(self, @key_generator, @options) + end + + # Returns a jar that'll automatically generate a signed representation of cookie value and verify it when reading from + # the cookie again. This is useful for creating cookies with values that the user is not supposed to change. If a signed + # cookie was tampered with by the user (or a 3rd party), nil will be returned. + # + # If +config.secret_key_base+ and +config.secret_token+ (deprecated) are both set, + # legacy cookies signed with the old key generator will be transparently upgraded. + # + # This jar requires that you set a suitable secret for the verification on your app's +config.secret_key_base+. + # + # Example: + # + # cookies.signed[:discount] = 45 + # # => Set-Cookie: discount=BAhpMg==--2c1c6906c90a3bc4fd54a51ffb41dffa4bf6b5f7; path=/ + # + # cookies.signed[:discount] # => 45 + def signed + @signed ||= + if @options[:upgrade_legacy_signed_cookies] + UpgradeLegacySignedCookieJar.new(self, @key_generator, @options) + else + SignedCookieJar.new(self, @key_generator, @options) + end + end + + # Returns a jar that'll automatically encrypt cookie values before sending them to the client and will decrypt them for read. + # If the cookie was tampered with by the user (or a 3rd party), nil will be returned. + # + # If +config.secret_key_base+ and +config.secret_token+ (deprecated) are both set, + # legacy cookies signed with the old key generator will be transparently upgraded. + # + # This jar requires that you set a suitable secret for the verification on your app's +config.secret_key_base+. + # + # Example: + # + # cookies.encrypted[:discount] = 45 + # # => Set-Cookie: discount=ZS9ZZ1R4cG1pcUJ1bm80anhQang3dz09LS1mbDZDSU5scGdOT3ltQ2dTdlhSdWpRPT0%3D--ab54663c9f4e3bc340c790d6d2b71e92f5b60315; path=/ + # + # cookies.encrypted[:discount] # => 45 + def encrypted + @encrypted ||= + if @options[:upgrade_legacy_signed_cookies] + UpgradeLegacyEncryptedCookieJar.new(self, @key_generator, @options) + else + EncryptedCookieJar.new(self, @key_generator, @options) + end + end + + # Returns the +signed+ or +encrypted+ jar, preferring +encrypted+ if +secret_key_base+ is set. + # Used by ActionDispatch::Session::CookieStore to avoid the need to introduce new cookie stores. + def signed_or_encrypted + @signed_or_encrypted ||= + if @options[:secret_key_base].present? + encrypted + else + signed + end + end + end + + module VerifyAndUpgradeLegacySignedMessage + def initialize(*args) + super + @legacy_verifier = ActiveSupport::MessageVerifier.new(@options[:secret_token]) + end + + def verify_and_upgrade_legacy_signed_message(name, signed_message) + @legacy_verifier.verify(signed_message).tap do |value| + self[name] = value + end + rescue ActiveSupport::MessageVerifier::InvalidSignature + nil + end + end + class CookieJar #:nodoc: - include Enumerable + include Enumerable, ChainedCookieJars # This regular expression is used to split the levels of a domain. # The top level domain can be any string without a period or @@ -115,7 +208,10 @@ module ActionDispatch { signed_cookie_salt: env[SIGNED_COOKIE_SALT] || '', encrypted_cookie_salt: env[ENCRYPTED_COOKIE_SALT] || '', encrypted_signed_cookie_salt: env[ENCRYPTED_SIGNED_COOKIE_SALT] || '', - token_key: env[TOKEN_KEY] } + secret_token: env[SECRET_TOKEN], + secret_key_base: env[SECRET_KEY_BASE], + upgrade_legacy_signed_cookies: env[SECRET_TOKEN].present? && env[SECRET_KEY_BASE].present? + } end def self.build(request) @@ -184,7 +280,7 @@ module ActionDispatch # Sets the cookie named +name+. The second argument may be the very cookie # value, or a hash of options as documented above. - def []=(key, options) + def []=(name, options) if options.is_a?(Hash) options.symbolize_keys! value = options[:value] @@ -195,10 +291,10 @@ module ActionDispatch handle_options(options) - if @cookies[key.to_s] != value or options[:expires] - @cookies[key.to_s] = value - @set_cookies[key.to_s] = options - @delete_cookies.delete(key.to_s) + if @cookies[name.to_s] != value or options[:expires] + @cookies[name.to_s] = value + @set_cookies[name.to_s] = options + @delete_cookies.delete(name.to_s) end value @@ -207,24 +303,24 @@ module ActionDispatch # Removes the cookie on the client machine by setting the value to an empty string # and the expiration date in the past. Like <tt>[]=</tt>, you can pass in # an options hash to delete cookies with extra data such as a <tt>:path</tt>. - def delete(key, options = {}) - return unless @cookies.has_key? key.to_s + def delete(name, options = {}) + return unless @cookies.has_key? name.to_s options.symbolize_keys! handle_options(options) - value = @cookies.delete(key.to_s) - @delete_cookies[key.to_s] = options + value = @cookies.delete(name.to_s) + @delete_cookies[name.to_s] = options value end # Whether the given cookie is to be deleted by this CookieJar. # Like <tt>[]=</tt>, you can pass in an options hash to test if a # deletion applies to a specific <tt>:path</tt>, <tt>:domain</tt> etc. - def deleted?(key, options = {}) + def deleted?(name, options = {}) options.symbolize_keys! handle_options(options) - @delete_cookies[key.to_s] == options + @delete_cookies[name.to_s] == options end # Removes all cookies on the client machine by calling <tt>delete</tt> for each cookie @@ -232,59 +328,6 @@ module ActionDispatch @cookies.each_key{ |k| delete(k, options) } end - # Returns a jar that'll automatically set the assigned cookies to have an expiration date 20 years from now. Example: - # - # cookies.permanent[:prefers_open_id] = true - # # => Set-Cookie: prefers_open_id=true; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT - # - # This jar is only meant for writing. You'll read permanent cookies through the regular accessor. - # - # This jar allows chaining with the signed jar as well, so you can set permanent, signed cookies. Examples: - # - # cookies.permanent.signed[:remember_me] = current_user.id - # # => Set-Cookie: remember_me=BAhU--848956038e692d7046deab32b7131856ab20e14e; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT - def permanent - @permanent ||= PermanentCookieJar.new(self, @key_generator, @options) - end - - # Returns a jar that'll automatically generate a signed representation of cookie value and verify it when reading from - # the cookie again. This is useful for creating cookies with values that the user is not supposed to change. If a signed - # cookie was tampered with by the user (or a 3rd party), an ActiveSupport::MessageVerifier::InvalidSignature exception will - # be raised. - # - # This jar requires that you set a suitable secret for the verification on your app's +config.secret_key_base+. - # - # Example: - # - # cookies.signed[:discount] = 45 - # # => Set-Cookie: discount=BAhpMg==--2c1c6906c90a3bc4fd54a51ffb41dffa4bf6b5f7; path=/ - # - # cookies.signed[:discount] # => 45 - def signed - @signed ||= SignedCookieJar.new(self, @key_generator, @options) - end - - # Only needed for supporting the +UpgradeSignatureToEncryptionCookieStore+, users and plugin authors should not use this - def signed_using_old_secret #:nodoc: - @signed_using_old_secret ||= SignedCookieJar.new(self, ActiveSupport::DummyKeyGenerator.new(@options[:token_key]), @options) - end - - # Returns a jar that'll automatically encrypt cookie values before sending them to the client and will decrypt them for read. - # If the cookie was tampered with by the user (or a 3rd party), an ActiveSupport::MessageVerifier::InvalidSignature exception - # will be raised. - # - # This jar requires that you set a suitable secret for the verification on your app's +config.secret_key_base+. - # - # Example: - # - # cookies.encrypted[:discount] = 45 - # # => Set-Cookie: discount=ZS9ZZ1R4cG1pcUJ1bm80anhQang3dz09LS1mbDZDSU5scGdOT3ltQ2dTdlhSdWpRPT0%3D--ab54663c9f4e3bc340c790d6d2b71e92f5b60315; path=/ - # - # cookies.encrypted[:discount] # => 45 - def encrypted - @encrypted ||= EncryptedCookieJar.new(self, @key_generator, @options) - end - def write(headers) @set_cookies.each { |k, v| ::Rack::Utils.set_cookie_header!(headers, k, v) if write_cookie?(v) } @delete_cookies.each { |k, v| ::Rack::Utils.delete_cookie_header!(headers, k, v) } @@ -299,24 +342,25 @@ module ActionDispatch self.always_write_cookie = false private - def write_cookie?(cookie) @secure || !cookie[:secure] || always_write_cookie end end class PermanentCookieJar #:nodoc: + include ChainedCookieJars + def initialize(parent_jar, key_generator, options = {}) @parent_jar = parent_jar @key_generator = key_generator @options = options end - def [](key) + def [](name) @parent_jar[name.to_s] end - def []=(key, options) + def []=(name, options) if options.is_a?(Hash) options.symbolize_keys! else @@ -324,28 +368,13 @@ module ActionDispatch end options[:expires] = 20.years.from_now - @parent_jar[key] = options - end - - def permanent - @permanent ||= PermanentCookieJar.new(self, @key_generator, @options) - end - - def signed - @signed ||= SignedCookieJar.new(self, @key_generator, @options) - end - - def encrypted - @encrypted ||= EncryptedCookieJar.new(self, @key_generator, @options) - end - - def method_missing(method, *arguments, &block) - ActiveSupport::Deprecation.warn "#{method} is deprecated with no replacement. " + - "You probably want to try this method over the parent CookieJar." + @parent_jar[name] = options end end class SignedCookieJar #:nodoc: + include ChainedCookieJars + def initialize(parent_jar, key_generator, options = {}) @parent_jar = parent_jar @options = options @@ -355,13 +384,11 @@ module ActionDispatch def [](name) if signed_message = @parent_jar[name] - @verifier.verify(signed_message) + verify(signed_message) end - rescue ActiveSupport::MessageVerifier::InvalidSignature - nil end - def []=(key, options) + def []=(name, options) if options.is_a?(Hash) options.symbolize_keys! options[:value] = @verifier.generate(options[:value]) @@ -370,32 +397,38 @@ module ActionDispatch end raise CookieOverflow if options[:value].size > MAX_COOKIE_SIZE - @parent_jar[key] = options + @parent_jar[name] = options end - def permanent - @permanent ||= PermanentCookieJar.new(self, @key_generator, @options) - end - - def signed - @signed ||= SignedCookieJar.new(self, @key_generator, @options) - end + private + def verify(signed_message) + @verifier.verify(signed_message) + rescue ActiveSupport::MessageVerifier::InvalidSignature + nil + end + end - def encrypted - @encrypted ||= EncryptedCookieJar.new(self, @key_generator, @options) - end + # UpgradeLegacySignedCookieJar is used instead of SignedCookieJar if + # config.secret_token and config.secret_key_base are both set. It reads + # legacy cookies signed with the old dummy key generator and re-saves + # them using the new key generator to provide a smooth upgrade path. + class UpgradeLegacySignedCookieJar < SignedCookieJar #:nodoc: + include VerifyAndUpgradeLegacySignedMessage - def method_missing(method, *arguments, &block) - ActiveSupport::Deprecation.warn "#{method} is deprecated with no replacement. " + - "You probably want to try this method over the parent CookieJar." + def [](name) + if signed_message = @parent_jar[name] + verify(signed_message) || verify_and_upgrade_legacy_signed_message(name, signed_message) + end end end class EncryptedCookieJar #:nodoc: + include ChainedCookieJars + def initialize(parent_jar, key_generator, options = {}) - if ActiveSupport::DummyKeyGenerator === key_generator - raise "Encrypted Cookies must be used in conjunction with config.secret_key_base." + - "Set config.secret_key_base in config/initializers/secret_token.rb" + if ActiveSupport::LegacyKeyGenerator === key_generator + raise "You didn't set config.secret_key_base, which is required for this cookie jar. " + + "Read the upgrade documentation to learn more about this new config option." end @parent_jar = parent_jar @@ -405,16 +438,13 @@ module ActionDispatch @encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret) end - def [](key) - if encrypted_message = @parent_jar[key] - @encryptor.decrypt_and_verify(encrypted_message) + def [](name) + if encrypted_message = @parent_jar[name] + decrypt_and_verify(encrypted_message) end - rescue ActiveSupport::MessageVerifier::InvalidSignature, - ActiveSupport::MessageEncryptor::InvalidMessage - nil end - def []=(key, options) + def []=(name, options) if options.is_a?(Hash) options.symbolize_keys! else @@ -423,24 +453,28 @@ module ActionDispatch options[:value] = @encryptor.encrypt_and_sign(options[:value]) raise CookieOverflow if options[:value].size > MAX_COOKIE_SIZE - @parent_jar[key] = options + @parent_jar[name] = options end - def permanent - @permanent ||= PermanentCookieJar.new(self, @key_generator, @options) - end - - def signed - @signed ||= SignedCookieJar.new(self, @key_generator, @options) - end + private + def decrypt_and_verify(encrypted_message) + @encryptor.decrypt_and_verify(encrypted_message) + rescue ActiveSupport::MessageVerifier::InvalidSignature, ActiveSupport::MessageEncryptor::InvalidMessage + nil + end + end - def encrypted - @encrypted ||= EncryptedCookieJar.new(self, @key_generator, @options) - end + # UpgradeLegacyEncryptedCookieJar is used by ActionDispatch::Session::CookieStore + # instead of EncryptedCookieJar if config.secret_token and config.secret_key_base + # are both set. It reads legacy cookies signed with the old dummy key generator and + # encrypts and re-saves them using the new key generator to provide a smooth upgrade path. + class UpgradeLegacyEncryptedCookieJar < EncryptedCookieJar #:nodoc: + include VerifyAndUpgradeLegacySignedMessage - def method_missing(method, *arguments, &block) - ActiveSupport::Deprecation.warn "#{method} is deprecated with no replacement. " + - "You probably want to try this method over the parent CookieJar." + def [](name) + if encrypted_or_signed_message = @parent_jar[name] + decrypt_and_verify(encrypted_or_signed_message) || verify_and_upgrade_legacy_signed_message(name, encrypted_or_signed_message) + end end end diff --git a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb index 64230ff1ae..0ca1a87645 100644 --- a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb @@ -34,27 +34,35 @@ module ActionDispatch log_error(env, wrapper) if env['action_dispatch.show_detailed_exceptions'] + request = Request.new(env) template = ActionView::Base.new([RESCUES_TEMPLATE_PATH], - :request => Request.new(env), - :exception => wrapper.exception, - :application_trace => wrapper.application_trace, - :framework_trace => wrapper.framework_trace, - :full_trace => wrapper.full_trace, - :routes_inspector => routes_inspector(exception), - :source_extract => wrapper.source_extract, - :line_number => wrapper.line_number, - :file => wrapper.file + request: request, + exception: wrapper.exception, + application_trace: wrapper.application_trace, + framework_trace: wrapper.framework_trace, + full_trace: wrapper.full_trace, + routes_inspector: routes_inspector(exception), + source_extract: wrapper.source_extract, + line_number: wrapper.line_number, + file: wrapper.file ) file = "rescues/#{wrapper.rescue_template}" - body = template.render(:template => file, :layout => 'rescues/layout') - render(wrapper.status_code, body) + + if request.xhr? + body = template.render(template: file, layout: false, formats: [:text]) + format = "text/plain" + else + body = template.render(template: file, layout: 'rescues/layout') + format = "text/html" + end + render(wrapper.status_code, body, format) else raise exception end end - def render(status, body) - [status, {'Content-Type' => "text/html; charset=#{Response.default_charset}", 'Content-Length' => body.bytesize.to_s}, [body]] + def render(status, body, format) + [status, {'Content-Type' => "#{format}; charset=#{Response.default_charset}", 'Content-Length' => body.bytesize.to_s}, [body]] end def log_error(env, wrapper) diff --git a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb index af32a1f9d7..daddaccadd 100644 --- a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb +++ b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb @@ -9,9 +9,11 @@ module ActionDispatch 'ActionController::RoutingError' => :not_found, 'AbstractController::ActionNotFound' => :not_found, 'ActionController::MethodNotAllowed' => :method_not_allowed, + 'ActionController::UnknownHttpMethod' => :method_not_allowed, 'ActionController::NotImplemented' => :not_implemented, 'ActionController::UnknownFormat' => :not_acceptable, 'ActionController::InvalidAuthenticityToken' => :unprocessable_entity, + 'ActionDispatch::ParamsParser::ParseError' => :bad_request, 'ActionController::BadRequest' => :bad_request, 'ActionController::ParameterMissing' => :bad_request, 'ActionController::EmptyParameter' => :bad_request @@ -95,7 +97,7 @@ module ActionDispatch def source_fragment(path, line) return unless Rails.respond_to?(:root) && Rails.root full_path = Rails.root.join(path) - if File.exists?(full_path) + if File.exist?(full_path) File.open(full_path, "r") do |file| start = [line - 3, 0].max lines = file.each_line.drop(start).take(6) diff --git a/actionpack/lib/action_dispatch/middleware/flash.rb b/actionpack/lib/action_dispatch/middleware/flash.rb index 7e56feb90a..89003e7a5e 100644 --- a/actionpack/lib/action_dispatch/middleware/flash.rb +++ b/actionpack/lib/action_dispatch/middleware/flash.rb @@ -243,19 +243,13 @@ module ActionDispatch session = Request::Session.find(env) || {} flash_hash = env[KEY] - if flash_hash - if !flash_hash.empty? || session.key?('flash') - session["flash"] = flash_hash.to_session_value - new_hash = flash_hash.dup - else - new_hash = flash_hash - end - - env[KEY] = new_hash + if flash_hash && (flash_hash.present? || session.key?('flash')) + session["flash"] = flash_hash.to_session_value + env[KEY] = flash_hash.dup end if (!session.respond_to?(:loaded?) || session.loaded?) && # (reset_session uses {}, which doesn't implement #loaded?) - session.key?('flash') && session['flash'].nil? + session.key?('flash') && session['flash'].nil? session.delete('flash') end end diff --git a/actionpack/lib/action_dispatch/middleware/params_parser.rb b/actionpack/lib/action_dispatch/middleware/params_parser.rb index 0fa1e9b859..b426183488 100644 --- a/actionpack/lib/action_dispatch/middleware/params_parser.rb +++ b/actionpack/lib/action_dispatch/middleware/params_parser.rb @@ -41,9 +41,9 @@ module ActionDispatch when Proc strategy.call(request.raw_post) when :json - data = ActiveSupport::JSON.decode(request.body) + data = ActiveSupport::JSON.decode(request.raw_post) data = {:_json => data} unless data.is_a?(Hash) - request.deep_munge(data).with_indifferent_access + Request::Utils.deep_munge(data).with_indifferent_access else false end diff --git a/actionpack/lib/action_dispatch/middleware/public_exceptions.rb b/actionpack/lib/action_dispatch/middleware/public_exceptions.rb index 53bedaa40a..cbb2d475b1 100644 --- a/actionpack/lib/action_dispatch/middleware/public_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/public_exceptions.rb @@ -7,11 +7,10 @@ module ActionDispatch end def call(env) - exception = env["action_dispatch.exception"] status = env["PATH_INFO"][1..-1] request = ActionDispatch::Request.new(env) content_type = request.formats.first - body = { :status => status, :error => exception.message } + body = { :status => status, :error => Rack::Utils::HTTP_STATUS_CODES.fetch(status.to_i, Rack::Utils::HTTP_STATUS_CODES[500]) } render(status, content_type, body) end @@ -19,7 +18,7 @@ module ActionDispatch private def render(status, content_type, body) - format = content_type && "to_#{content_type.to_sym}" + format = "to_#{content_type.to_sym}" if content_type if format && body.respond_to?(format) render_format(status, content_type, body.public_send(format)) else diff --git a/actionpack/lib/action_dispatch/middleware/remote_ip.rb b/actionpack/lib/action_dispatch/middleware/remote_ip.rb index 93a2b52996..57bc6d5cd0 100644 --- a/actionpack/lib/action_dispatch/middleware/remote_ip.rb +++ b/actionpack/lib/action_dispatch/middleware/remote_ip.rb @@ -101,7 +101,7 @@ module ActionDispatch (([0-9A-Fa-f]{1,4}:){0,4}:([0-9A-Fa-f]{1,4}:){1}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4 (::([0-9A-Fa-f]{1,4}:){0,5}((\b((25[0-5])|(1\d{2})|(2[0-4]\d) |(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4 ([0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4}) | # ip v6 with compatible to v4 - (::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4}) | # ip v6 with double colon at the begining + (::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4}) | # ip v6 with double colon at the beginning (([0-9A-Fa-f]{1,4}:){1,7}:) # ip v6 without ending )$) }x @@ -143,7 +143,7 @@ module ActionDispatch # proxies with incompatible IP header conventions, and there is no way # for us to determine which header is the right one after the fact. # Since we have no idea, we give up and explode. - should_check_ip = @check_ip && client_ips.last + should_check_ip = @check_ip && client_ips.last && forwarded_ips.last if should_check_ip && !forwarded_ips.include?(client_ips.last) # We don't know which came from the proxy, and which from the user raise IpSpoofAttackError, "IP spoofing attack?! " + diff --git a/actionpack/lib/action_dispatch/middleware/request_id.rb b/actionpack/lib/action_dispatch/middleware/request_id.rb index 44290445d4..5d1740d0d4 100644 --- a/actionpack/lib/action_dispatch/middleware/request_id.rb +++ b/actionpack/lib/action_dispatch/middleware/request_id.rb @@ -18,7 +18,7 @@ module ActionDispatch def call(env) env["action_dispatch.request_id"] = external_request_id(env) || internal_request_id - @app.call(env).tap { |status, headers, body| headers["X-Request-Id"] = env["action_dispatch.request_id"] } + @app.call(env).tap { |_status, headers, _body| headers["X-Request-Id"] = env["action_dispatch.request_id"] } end private diff --git a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb index 1e6ed624b0..b9eb8036e9 100644 --- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb @@ -4,36 +4,51 @@ require 'rack/session/cookie' module ActionDispatch module Session - # This cookie-based session store is the Rails default. Sessions typically - # contain at most a user_id and flash message; both fit within the 4K cookie - # size limit. Cookie-based sessions are dramatically faster than the - # alternatives. + # This cookie-based session store is the Rails default. It is + # dramatically faster than the alternatives. # - # If you have more than 4K of session data or don't want your data to be - # visible to the user, pick another session store. + # Sessions typically contain at most a user_id and flash message; both fit + # within the 4K cookie size limit. A CookieOverflow exception is raised if + # you attempt to store more than 4K of data. # - # CookieOverflow is raised if you attempt to store more than 4K of data. + # The cookie jar used for storage is automatically configured to be the + # best possible option given your application's configuration. # - # A message digest is included with the cookie to ensure data integrity: - # a user cannot alter his +user_id+ without knowing the secret key - # included in the hash. New apps are generated with a pregenerated secret - # in config/environment.rb. Set your own for old apps you're upgrading. + # If you only have secret_token set, your cookies will be signed, but + # not encrypted. This means a user cannot alter his +user_id+ without + # knowing your app's secret key, but can easily read his +user_id+. This + # was the default for Rails 3 apps. # - # Session options: + # If you have secret_key_base set, your cookies will be encrypted. This + # goes a step further than signed cookies in that encrypted cookies cannot + # be altered or read by users. This is the default starting in Rails 4. # - # * <tt>:secret</tt>: An application-wide key string. It's important that - # the secret is not vulnerable to a dictionary attack. Therefore, you - # should choose a secret consisting of random numbers and letters and - # more than 30 characters. + # If you have both secret_token and secret_key base set, your cookies will + # be encrypted, and signed cookies generated by Rails 3 will be + # transparently read and encrypted to provide a smooth upgrade path. # - # secret: '449fe2e7daee471bffae2fd8dc02313d' + # Configure your session store in config/initializers/session_store.rb: # - # * <tt>:digest</tt>: The message digest algorithm used to verify session - # integrity defaults to 'SHA1' but may be any digest provided by OpenSSL, - # such as 'MD5', 'RIPEMD160', 'SHA256', etc. + # Myapp::Application.config.session_store :cookie_store, key: '_your_app_session' # - # To generate a secret key for an existing application, run - # "rake secret" and set the key in config/initializers/secret_token.rb. + # Configure your secret key in config/initializers/secret_token.rb: + # + # Myapp::Application.config.secret_key_base 'secret key' + # + # To generate a secret key for an existing application, run `rake secret`. + # + # If you are upgrading an existing Rails 3 app, you should leave your + # existing secret_token in place and simply add the new secret_key_base. + # Note that you should wait to set secret_key_base until you have 100% of + # your userbase on Rails 4 and are reasonably sure you will not need to + # rollback to Rails 3. This is because cookies signed based on the new + # secret_key_base in Rails 4 are not backwards compatible with Rails 3. + # You are free to leave your existing secret_token in place, not set the + # new secret_key_base, and ignore the deprecation warnings until you are + # reasonably sure that your upgrade is otherwise complete. Additionally, + # you should take care to make sure you are not relying on the ability to + # decode signed cookies generated by your app in external applications or + # Javascript before upgrading. # # Note that changing digest or secret invalidates all existing sessions! class CookieStore < Rack::Session::Abstract::ID @@ -100,42 +115,7 @@ module ActionDispatch def cookie_jar(env) request = ActionDispatch::Request.new(env) - request.cookie_jar.signed - end - end - - class EncryptedCookieStore < CookieStore - - private - - def cookie_jar(env) - request = ActionDispatch::Request.new(env) - request.cookie_jar.encrypted - end - end - - # This cookie store helps you upgrading apps that use +CookieStore+ to the new default +EncryptedCookieStore+ - # To use this CookieStore set - # - # Myapp::Application.config.session_store :upgrade_signature_to_encryption_cookie_store, key: '_myapp_session' - # - # in your config/initializers/session_store.rb - # - # You will also need to add - # - # Myapp::Application.config.secret_key_base = 'some secret' - # - # in your config/initializers/secret_token.rb, but do not remove +Myapp::Application.config.secret_token = 'some secret'+ - class UpgradeSignatureToEncryptionCookieStore < EncryptedCookieStore - private - - def get_cookie(env) - signed_using_old_secret_cookie_jar(env)[@key] || cookie_jar(env)[@key] - end - - def signed_using_old_secret_cookie_jar(env) - request = ActionDispatch::Request.new(env) - request.cookie_jar.signed_using_old_secret + request.cookie_jar.signed_or_encrypted end end end diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb index fcc5bc12c4..1d4f0f89a6 100644 --- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb @@ -29,8 +29,11 @@ module ActionDispatch def call(env) @app.call(env) rescue Exception => exception - raise exception if env['action_dispatch.show_exceptions'] == false - render_exception(env, exception) + if env['action_dispatch.show_exceptions'] == false + raise exception + else + render_exception(env, exception) + end end private diff --git a/actionpack/lib/action_dispatch/middleware/ssl.rb b/actionpack/lib/action_dispatch/middleware/ssl.rb index 9e03cbf2b7..999c022535 100644 --- a/actionpack/lib/action_dispatch/middleware/ssl.rb +++ b/actionpack/lib/action_dispatch/middleware/ssl.rb @@ -36,8 +36,7 @@ module ActionDispatch url.scheme = "https" url.host = @host if @host url.port = @port if @port - headers = hsts_headers.merge('Content-Type' => 'text/html', - 'Location' => url.to_s) + headers = { 'Content-Type' => 'text/html', 'Location' => url.to_s } [301, headers, []] end @@ -58,7 +57,7 @@ module ActionDispatch cookies = cookies.split("\n") headers['Set-Cookie'] = cookies.map { |cookie| - if cookie !~ /;\s+secure(;|$)/ + if cookie !~ /;\s*secure\s*(;|$)/i "#{cookie}; secure" else cookie diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb index 550f4dbd0d..db219c8fa9 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb @@ -13,7 +13,7 @@ request_dump = clean_params.empty? ? 'None' : clean_params.inspect.gsub(',', ",\n") def debug_hash(object) - object.to_hash.sort_by { |k, v| k.to_s }.map { |k, v| "#{k}: #{v.inspect rescue $!.message}" }.join("\n") + object.to_hash.sort_by { |k, _| k.to_s }.map { |k, v| "#{k}: #{v.inspect rescue $!.message}" }.join("\n") end unless self.class.method_defined?(:debug_hash) %> diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb new file mode 100644 index 0000000000..396768ecee --- /dev/null +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb @@ -0,0 +1,23 @@ +<% + clean_params = @request.filtered_parameters.clone + clean_params.delete("action") + clean_params.delete("controller") + + request_dump = clean_params.empty? ? 'None' : clean_params.inspect.gsub(',', ",\n") + + def debug_hash(object) + object.to_hash.sort_by { |k, _| k.to_s }.map { |k, v| "#{k}: #{v.inspect rescue $!.message}" }.join("\n") + end unless self.class.method_defined?(:debug_hash) +%> + +Request parameters +<%= request_dump %> + +Session dump +<%= debug_hash @request.session %> + +Env dump +<%= debug_hash @request.env.slice(*@request.class::ENV_METHODS) %> + +Response headers +<%= defined?(@response) ? @response.headers.inspect.gsub(',', ",\n") : 'None' %> diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb index 9d947aea40..b181909bff 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb @@ -1,10 +1,8 @@ <% - traces = [ - ["Application Trace", @application_trace], - ["Framework Trace", @framework_trace], - ["Full Trace", @full_trace] - ] - names = traces.collect {|name, trace| name} + traces = { "Application Trace" => @application_trace, + "Framework Trace" => @framework_trace, + "Full Trace" => @full_trace } + names = traces.keys %> <p><code>Rails.root: <%= defined?(Rails) && Rails.respond_to?(:root) ? Rails.root : "unset" %></code></p> diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb new file mode 100644 index 0000000000..d4af5c9b06 --- /dev/null +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb @@ -0,0 +1,15 @@ +<% + traces = { "Application Trace" => @application_trace, + "Framework Trace" => @framework_trace, + "Full Trace" => @full_trace } +%> + +Rails.root: <%= defined?(Rails) && Rails.respond_to?(:root) ? Rails.root : "unset" %> + +<% traces.each do |name, trace| %> +<% if trace.any? %> +<%= name %> +<%= trace.join("\n") %> + +<% end %> +<% end %> diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb index 57a2940802..f154021ae6 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb @@ -8,7 +8,7 @@ </header> <div id="container"> - <h2><%= @exception.message %></h2> + <h2><%= h @exception.message %></h2> <%= render template: "rescues/_source" %> <%= render template: "rescues/_trace" %> diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb index 891c87ac27..bc5d03dc10 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb @@ -34,6 +34,12 @@ padding: 0.5em 1.5em; } + h1 { + margin: 0.2em 0; + line-height: 1.1em; + font-size: 2em; + } + h2 { color: #C52F24; line-height: 25px; diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb index ca14215946..5c016e544e 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb @@ -3,5 +3,5 @@ </header> <div id="container"> - <h2><%= @exception.message %></h2> + <h2><%= h @exception.message %></h2> </div> diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.text.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.text.erb new file mode 100644 index 0000000000..ae62d9eb02 --- /dev/null +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.text.erb @@ -0,0 +1,3 @@ +Template is missing + +<%= @exception.message %> diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb index cd3daff065..7e9cedb95e 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb @@ -2,7 +2,7 @@ <h1>Routing Error</h1> </header> <div id="container"> - <h2><%= @exception.message %></h2> + <h2><%= h @exception.message %></h2> <% unless @exception.failures.empty? %> <p> <h2>Failure reasons:</h2> diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.text.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.text.erb new file mode 100644 index 0000000000..f6e4dac1f3 --- /dev/null +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.text.erb @@ -0,0 +1,11 @@ +Routing Error + +<%= @exception.message %> +<% unless @exception.failures.empty? %> +Failure reasons: +<% @exception.failures.each do |route, reason| %> + - <%= route.inspect.delete('\\') %></code> failed because <%= reason.downcase %> +<% end %> +<% end %> + +<%= render template: "rescues/_trace", format: :text %> diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb index 63216ef7c5..027a0f5b3e 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb @@ -2,7 +2,7 @@ <header> <h1> <%= @exception.original_exception.class.to_s %> in - <%= @request.parameters["controller"].capitalize if @request.parameters["controller"]%>#<%= @request.parameters["action"] %> + <%= @request.parameters["controller"].camelize if @request.parameters["controller"] %>#<%= @request.parameters["action"] %> </h1> </header> @@ -10,7 +10,7 @@ <p> Showing <i><%= @exception.file_name %></i> where line <b>#<%= @exception.line_number %></b> raised: </p> - <pre><code><%= @exception.message %></code></pre> + <pre><code><%= h @exception.message %></code></pre> <div class="source"> <div class="info"> diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb new file mode 100644 index 0000000000..5da21d9784 --- /dev/null +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb @@ -0,0 +1,8 @@ +<% @source_extract = @exception.source_extract(0, :html) %> +<%= @exception.original_exception.class.to_s %> in <%= @request.parameters["controller"].camelize if @request.parameters["controller"] %>#<%= @request.parameters["action"] %> + +Showing <%= @exception.file_name %> where line #<%= @exception.line_number %> raised: +<%= @exception.message %> +<%= @exception.sub_template_message %> +<%= render template: "rescues/_trace", format: :text %> +<%= render template: "rescues/_request_and_response", format: :text %> diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/unknown_action.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb index c1fbf67eed..259fb2bb3b 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/unknown_action.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb @@ -2,5 +2,5 @@ <h1>Unknown action</h1> </header> <div id="container"> - <h2><%= @exception.message %></h2> + <h2><%= h @exception.message %></h2> </div> diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb new file mode 100644 index 0000000000..83973addcb --- /dev/null +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb @@ -0,0 +1,3 @@ +Unknown action + +<%= @exception.message %> diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb index edf37bb9a5..2dfaab3587 100644 --- a/actionpack/lib/action_dispatch/railtie.rb +++ b/actionpack/lib/action_dispatch/railtie.rb @@ -20,8 +20,7 @@ module ActionDispatch config.action_dispatch.default_headers = { 'X-Frame-Options' => 'SAMEORIGIN', 'X-XSS-Protection' => '1; mode=block', - 'X-Content-Type-Options' => 'nosniff', - 'X-UA-Compatible' => 'chrome=1' + 'X-Content-Type-Options' => 'nosniff' } config.eager_load_namespaces << ActionDispatch diff --git a/actionpack/lib/action_dispatch/request/session.rb b/actionpack/lib/action_dispatch/request/session.rb index 7bc812fd22..6d911a75f1 100644 --- a/actionpack/lib/action_dispatch/request/session.rb +++ b/actionpack/lib/action_dispatch/request/session.rb @@ -127,6 +127,18 @@ module ActionDispatch @delegate.delete key.to_s end + def fetch(key, default=nil) + if self.key?(key) + self[key] + elsif default + self[key] = default + elsif block_given? + self[key] = yield(key) + else + raise KeyError + end + end + def inspect if loaded? super diff --git a/actionpack/lib/action_dispatch/request/utils.rb b/actionpack/lib/action_dispatch/request/utils.rb new file mode 100644 index 0000000000..8b43cdada8 --- /dev/null +++ b/actionpack/lib/action_dispatch/request/utils.rb @@ -0,0 +1,24 @@ +module ActionDispatch + class Request < Rack::Request + class Utils # :nodoc: + class << self + # Remove nils from the params hash + def deep_munge(hash) + hash.each do |k, v| + case v + when Array + v.grep(Hash) { |x| deep_munge(x) } + v.compact! + hash[k] = nil if v.empty? + when Hash + deep_munge(v) + end + end + + hash + end + end + end + end +end + diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb index d55eb8109a..a9ac2bce1d 100644 --- a/actionpack/lib/action_dispatch/routing.rb +++ b/actionpack/lib/action_dispatch/routing.rb @@ -69,6 +69,22 @@ module ActionDispatch # <tt>Routing::Mapper::Scoping#namespace</tt>, and # <tt>Routing::Mapper::Scoping#scope</tt>. # + # == Non-resourceful routes + # + # For routes that don't fit the <tt>resources</tt> mold, you can use the HTTP helper + # methods <tt>get</tt>, <tt>post</tt>, <tt>patch</tt>, <tt>put</tt> and <tt>delete</tt>. + # + # get 'post/:id' => 'posts#show' + # post 'post/:id' => 'posts#create_comment' + # + # If your route needs to respond to more than one HTTP method (or all methods) then using the + # <tt>:via</tt> option on <tt>match</tt> is preferable. + # + # match 'post/:id' => 'posts#show', via: [:get, :post] + # + # Now, if you POST to <tt>/posts/:id</tt>, it will route to the <tt>create_comment</tt> action. A GET on the same + # URL will route to the <tt>show</tt> action. + # # == Named routes # # Routes can be named by passing an <tt>:as</tt> option, @@ -78,7 +94,7 @@ module ActionDispatch # Example: # # # In routes.rb - # match '/login' => 'accounts#login', as: 'login' + # get '/login' => 'accounts#login', as: 'login' # # # With render, redirect_to, tests, etc. # redirect_to login_url @@ -104,9 +120,9 @@ module ActionDispatch # # # In routes.rb # controller :blog do - # match 'blog/show' => :list - # match 'blog/delete' => :delete - # match 'blog/edit/:id' => :edit + # get 'blog/show' => :list + # get 'blog/delete' => :delete + # get 'blog/edit/:id' => :edit # end # # # provides named routes for show, delete, and edit @@ -116,7 +132,7 @@ module ActionDispatch # # Routes can generate pretty URLs. For example: # - # match '/articles/:year/:month/:day' => 'articles#find_by_id', constraints: { + # get '/articles/:year/:month/:day' => 'articles#find_by_id', constraints: { # year: /\d{4}/, # month: /\d{1,2}/, # day: /\d{1,2}/ @@ -131,7 +147,7 @@ module ActionDispatch # You can specify a regular expression to define a format for a parameter. # # controller 'geocode' do - # match 'geocode/:postalcode' => :show, constraints: { + # get 'geocode/:postalcode' => :show, constraints: { # postalcode: /\d{5}(-\d{4})?/ # } # @@ -139,13 +155,13 @@ module ActionDispatch # expression modifiers: # # controller 'geocode' do - # match 'geocode/:postalcode' => :show, constraints: { + # get 'geocode/:postalcode' => :show, constraints: { # postalcode: /hx\d\d\s\d[a-z]{2}/i # } # end # # controller 'geocode' do - # match 'geocode/:postalcode' => :show, constraints: { + # get 'geocode/:postalcode' => :show, constraints: { # postalcode: /# Postcode format # \d{5} #Prefix # (-\d{4})? #Suffix @@ -153,73 +169,21 @@ module ActionDispatch # } # end # - # Using the multiline match modifier will raise an +ArgumentError+. + # Using the multiline modifier will raise an +ArgumentError+. # Encoding regular expression modifiers are silently ignored. The # match will always use the default encoding or ASCII. # - # == Default route - # - # Consider the following route, which you will find commented out at the - # bottom of your generated <tt>config/routes.rb</tt>: - # - # match ':controller(/:action(/:id))(.:format)' - # - # This route states that it expects requests to consist of a - # <tt>:controller</tt> followed optionally by an <tt>:action</tt> that in - # turn is followed optionally by an <tt>:id</tt>, which in turn is followed - # optionally by a <tt>:format</tt>. - # - # Suppose you get an incoming request for <tt>/blog/edit/22</tt>, you'll end - # up with: - # - # params = { controller: 'blog', - # action: 'edit', - # id: '22' - # } - # - # By not relying on default routes, you improve the security of your - # application since not all controller actions, which includes actions you - # might add at a later time, are exposed by default. - # - # == HTTP Methods - # - # Using the <tt>:via</tt> option when specifying a route allows you to - # restrict it to a specific HTTP method. Possible values are <tt>:post</tt>, - # <tt>:get</tt>, <tt>:patch</tt>, <tt>:put</tt>, <tt>:delete</tt> and - # <tt>:any</tt>. If your route needs to respond to more than one method you - # can use an array, e.g. <tt>[ :get, :post ]</tt>. The default value is - # <tt>:any</tt> which means that the route will respond to any of the HTTP - # methods. - # - # match 'post/:id' => 'posts#show', via: :get - # match 'post/:id' => 'posts#create_comment', via: :post - # - # Now, if you POST to <tt>/posts/:id</tt>, it will route to the <tt>create_comment</tt> action. A GET on the same - # URL will route to the <tt>show</tt> action. - # - # === HTTP helper methods - # - # An alternative method of specifying which HTTP method a route should respond to is to use the helper - # methods <tt>get</tt>, <tt>post</tt>, <tt>patch</tt>, <tt>put</tt> and <tt>delete</tt>. - # - # get 'post/:id' => 'posts#show' - # post 'post/:id' => 'posts#create_comment' - # - # This syntax is less verbose and the intention is more apparent to someone else reading your code, - # however if your route needs to respond to more than one HTTP method (or all methods) then using the - # <tt>:via</tt> option on <tt>match</tt> is preferable. - # # == External redirects # # You can redirect any path to another path using the redirect helper in your router: # - # match "/stories" => redirect("/posts") + # get "/stories" => redirect("/posts") # # == Unicode character routes # # You can specify unicode character routes in your router: # - # match "こんにちは" => "welcome#index" + # get "こんにちは" => "welcome#index" # # == Routing to Rack Applications # @@ -227,7 +191,7 @@ module ActionDispatch # index action in the PostsController, you can specify any Rack application # as the endpoint for a matcher: # - # match "/application.js" => Sprockets + # get "/application.js" => Sprockets # # == Reloading routes # @@ -282,11 +246,13 @@ module ActionDispatch # Target specific controllers by prefixing the command with <tt>CONTROLLER=x</tt>. # module Routing - autoload :Mapper, 'action_dispatch/routing/mapper' - autoload :RouteSet, 'action_dispatch/routing/route_set' - autoload :RoutesProxy, 'action_dispatch/routing/routes_proxy' - autoload :UrlFor, 'action_dispatch/routing/url_for' - autoload :PolymorphicRoutes, 'action_dispatch/routing/polymorphic_routes' + extend ActiveSupport::Autoload + + autoload :Mapper + autoload :RouteSet + autoload :RoutesProxy + autoload :UrlFor + autoload :PolymorphicRoutes SEPARATORS = %w( / . ? ) #:nodoc: HTTP_METHODS = [:get, :head, :post, :patch, :put, :delete, :options] #:nodoc: diff --git a/actionpack/lib/action_dispatch/routing/inspector.rb b/actionpack/lib/action_dispatch/routing/inspector.rb index d251de33df..cffb814e1e 100644 --- a/actionpack/lib/action_dispatch/routing/inspector.rb +++ b/actionpack/lib/action_dispatch/routing/inspector.rb @@ -69,7 +69,7 @@ module ActionDispatch end def internal? - controller =~ %r{\Arails/(info|welcome)} || path =~ %r{\A#{Rails.application.config.assets.prefix}} + controller.to_s =~ %r{\Arails/(info|welcome)} || path =~ %r{\A#{Rails.application.config.assets.prefix}} end def engine? diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index c5f2b33602..db9c993590 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -10,6 +10,9 @@ module ActionDispatch module Routing class Mapper URL_OPTIONS = [:protocol, :subdomain, :domain, :host, :port] + SCOPE_OPTIONS = [:path, :shallow_path, :as, :shallow_prefix, :module, + :controller, :action, :path_names, :constraints, + :shallow, :blocks, :defaults, :options] class Constraints #:nodoc: def self.new(app, constraints, request = Rack::Request) @@ -58,8 +61,8 @@ module ActionDispatch @set, @scope, @path, @options = set, scope, path, options @requirements, @conditions, @defaults = {}, {}, {} - normalize_path! normalize_options! + normalize_path! normalize_requirements! normalize_conditions! normalize_defaults! @@ -296,7 +299,7 @@ module ActionDispatch end end - # Invokes Rack::Mount::Utils.normalize path and ensure that + # Invokes Journey::Router::Utils.normalize_path and ensure that # (:locale) becomes (/:locale) instead of /(:locale). Except # for root cases, where the latter is the correct one. def self.normalize_path(path) @@ -359,8 +362,9 @@ module ActionDispatch # # Yes, controller actions are just rack endpoints # match 'photos/:id', to: PhotosController.action(:show) # - # Because request various HTTP verbs with a single action has security - # implications, is recommendable use HttpHelpers[rdoc-ref:HttpHelpers] + # Because requesting various HTTP verbs with a single action has security + # implications, you must either specify the actions in + # the via options or use one of the HtttpHelpers[rdoc-ref:HttpHelpers] # instead +match+ # # === Options @@ -429,10 +433,10 @@ module ActionDispatch # # match 'json_only', constraints: { format: 'json' } # - # class Blacklist + # class Whitelist # def matches?(request) request.remote_ip == '1.2.3.4' end # end - # match 'path', to: 'c#a', constraints: Blacklist.new + # match 'path', to: 'c#a', constraints: Whitelist.new # # See <tt>Scoping#constraints</tt> for more examples with its scope # equivalent. @@ -486,7 +490,7 @@ module ActionDispatch end options = app - app, path = options.find { |k, v| k.respond_to?(:call) } + app, path = options.find { |k, _| k.respond_to?(:call) } options.delete(app) if app end @@ -512,6 +516,11 @@ module ActionDispatch end end + # Query if the following named route was already defined. + def has_named_route?(name) + @set.named_routes.routes[name.to_sym] + end + private def app_name(app) return unless app.respond_to?(:routes) @@ -589,8 +598,7 @@ module ActionDispatch private def map_method(method, args, &block) options = args.extract_options! - options[:via] = method - options[:path] ||= args.first if args.first.is_a?(String) + options[:via] = method match(*args, options, &block) self end @@ -698,19 +706,21 @@ module ActionDispatch block, options[:constraints] = options[:constraints], {} end - scope_options.each do |option| - if value = options.delete(option) + SCOPE_OPTIONS.each do |option| + if option == :blocks + value = block + elsif option == :options + value = options + else + value = options.delete(option) + end + + if value recover[option] = @scope[option] @scope[option] = send("merge_#{option}_scope", @scope[option], value) end end - recover[:blocks] = @scope[:blocks] - @scope[:blocks] = merge_blocks_scope(@scope[:blocks], block) - - recover[:options] = @scope[:options] - @scope[:options] = merge_options_scope(@scope[:options], options) - yield self ensure @@ -841,10 +851,6 @@ module ActionDispatch end private - def scope_options #:nodoc: - @scope_options ||= private_methods.grep(/^merge_(.+)_scope$/) { $1.to_sym } - end - def merge_path_scope(parent, child) #:nodoc: Mapper.normalize_path("#{parent}/#{child}") end @@ -869,6 +875,10 @@ module ActionDispatch child end + def merge_action_scope(parent, child) #:nodoc: + child + end + def merge_path_names_scope(parent, child) #:nodoc: merge_options_scope(parent, child) end @@ -945,6 +955,8 @@ module ActionDispatch VALID_ON_OPTIONS = [:new, :collection, :member] RESOURCE_OPTIONS = [:as, :controller, :path, :only, :except, :param, :concerns] CANONICAL_ACTIONS = %w(index create new show update destroy) + RESOURCE_METHOD_SCOPES = [:collection, :member, :new] + RESOURCE_SCOPES = [:resource, :resources] class Resource #:nodoc: attr_reader :controller, :path, :options, :param @@ -1055,18 +1067,18 @@ module ActionDispatch # a singular resource to map /profile (rather than /profile/:id) to # the show action: # - # resource :geocoder + # resource :profile # # creates six different routes in your application, all mapping to - # the +GeoCoders+ controller (note that the controller is named after + # the +Profiles+ controller (note that the controller is named after # the plural): # - # GET /geocoder/new - # POST /geocoder - # GET /geocoder - # GET /geocoder/edit - # PATCH/PUT /geocoder - # DELETE /geocoder + # GET /profile/new + # POST /profile + # GET /profile + # GET /profile/edit + # PATCH/PUT /profile + # DELETE /profile # # === Options # Takes same options as +resources+. @@ -1361,7 +1373,7 @@ module ActionDispatch def match(path, *rest) if rest.empty? && Hash === path options = path - path, to = options.find { |name, value| name.is_a?(String) } + path, to = options.find { |name, _value| name.is_a?(String) } options[:to] = to options.delete(path) paths = [path] @@ -1370,18 +1382,27 @@ module ActionDispatch paths = [path] + rest end - path_without_format = path.to_s.sub(/\(\.:format\)$/, '') - if using_match_shorthand?(path_without_format, options) - options[:to] ||= path_without_format.gsub(%r{^/}, "").sub(%r{/([^/]*)$}, '#\1') - end - options[:anchor] = true unless options.key?(:anchor) if options[:on] && !VALID_ON_OPTIONS.include?(options[:on]) raise ArgumentError, "Unknown scope #{on.inspect} given to :on" end - paths.each { |_path| decomposed_match(_path, options.dup) } + if @scope[:controller] && @scope[:action] + options[:to] ||= "#{@scope[:controller]}##{@scope[:action]}" + end + + paths.each do |_path| + route_options = options.dup + route_options[:path] ||= _path if _path.is_a?(String) + + path_without_format = _path.to_s.sub(/\(\.:format\)$/, '') + if using_match_shorthand?(path_without_format, route_options) + route_options[:to] ||= path_without_format.gsub(%r{^/}, "").sub(%r{/([^/]*)$}, '#\1') + end + + decomposed_match(_path, route_options) + end self end @@ -1494,11 +1515,11 @@ module ActionDispatch end def resource_scope? #:nodoc: - [:resource, :resources].include? @scope[:scope_level] + RESOURCE_SCOPES.include? @scope[:scope_level] end def resource_method_scope? #:nodoc: - [:collection, :member, :new].include? @scope[:scope_level] + RESOURCE_METHOD_SCOPES.include? @scope[:scope_level] end def with_exclusive_scope diff --git a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb index 6d3f8da932..2fb03f2712 100644 --- a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb +++ b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb @@ -74,6 +74,19 @@ module ActionDispatch # * <tt>:routing_type</tt> - Allowed values are <tt>:path</tt> or <tt>:url</tt>. # Default is <tt>:url</tt>. # + # Also includes all the options from <tt>url_for</tt>. These include such + # things as <tt>:anchor</tt> or <tt>:trailing_slash</tt>. Example usage + # is given below: + # + # polymorphic_url([blog, post], anchor: 'my_anchor') + # # => "http://example.com/blogs/1/posts/1#my_anchor" + # polymorphic_url([blog, post], anchor: 'my_anchor', script_name: "/my_app") + # # => "http://example.com/my_app/blogs/1/posts/1#my_anchor" + # + # For all of these options, see the documentation for <tt>url_for</tt>. + # + # ==== Functionality + # # # an Article record # polymorphic_url(record) # same as article_url(record) # diff --git a/actionpack/lib/action_dispatch/routing/redirection.rb b/actionpack/lib/action_dispatch/routing/redirection.rb index d751e04e6a..3e54c7e71c 100644 --- a/actionpack/lib/action_dispatch/routing/redirection.rb +++ b/actionpack/lib/action_dispatch/routing/redirection.rb @@ -17,7 +17,7 @@ module ActionDispatch def call(env) req = Request.new(env) - # If any of the path parameters has a invalid encoding then + # If any of the path parameters has an invalid encoding then # raise since it's likely to trigger errors further on. req.symbolized_path_parameters.each do |key, value| unless value.valid_encoding? @@ -30,6 +30,10 @@ module ActionDispatch uri.host ||= req.host uri.port ||= req.port unless req.standard_port? + if relative_path?(uri.path) + uri.path = "#{req.script_name}/#{uri.path}" + end + body = %(<html><body>You are being <a href="#{ERB::Util.h(uri.to_s)}">redirected</a>.</body></html>) headers = { @@ -48,6 +52,11 @@ module ActionDispatch def inspect "redirect(#{status})" end + + private + def relative_path?(path) + path && !path.empty? && path[0] != '/' + end end class PathRedirect < Redirect @@ -81,6 +90,11 @@ module ActionDispatch url_options[:path] = (url_options[:path] % escape_path(params)) end + if relative_path?(url_options[:path]) + url_options[:path] = "/#{url_options[:path]}" + url_options[:script_name] = request.script_name + end + ActionDispatch::Http::URL.url_for url_options end @@ -104,6 +118,10 @@ module ActionDispatch # # get 'docs/:article', to: redirect('/wiki/%{article}') # + # Note that if you return a path without a leading slash then the url is prefixed with the + # current SCRIPT_NAME environment variable. This is typically '/' but may be different in + # a mounted engine or where the application is deployed to a subdirectory of a website. + # # Alternatively you can use one of the other syntaxes: # # The block version of redirect allows for the easy encapsulation of any logic associated with diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 619dd22ec1..b8abdabca5 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -28,7 +28,7 @@ module ActionDispatch def call(env) params = env[PARAMETERS_KEY] - # If any of the path parameters has a invalid encoding then + # If any of the path parameters has an invalid encoding then # raise since it's likely to trigger errors further on. params.each do |key, value| next unless value.respond_to?(:valid_encoding?) @@ -170,9 +170,10 @@ module ActionDispatch def call(t, args) if args.size == arg_size && !args.last.is_a?(Hash) && optimize_routes_generation?(t) - @options.merge!(t.url_options) if t.respond_to?(:url_options) - @options[:path] = optimized_helper(args) - ActionDispatch::Http::URL.url_for(@options) + options = @options.dup + options.merge!(t.url_options) if t.respond_to?(:url_options) + options[:path] = optimized_helper(args) + ActionDispatch::Http::URL.url_for(options) else super end @@ -185,10 +186,16 @@ module ActionDispatch klass = Journey::Router::Utils @path_parts.zip(args) do |part, arg| + parameterized_arg = arg.to_param + + if parameterized_arg.nil? || parameterized_arg.empty? + raise_generation_error(args) + end + # Replace each route parameter # e.g. :id for regular parameter or *path for globbing # with ruby string interpolation code - path.gsub!(/(\*|:)#{part}/, klass.escape_fragment(arg.to_param)) + path.gsub!(/(\*|:)#{part}/, klass.escape_fragment(parameterized_arg)) end path end @@ -196,6 +203,25 @@ module ActionDispatch def optimize_routes_generation?(t) t.send(:optimize_routes_generation?) end + + def raise_generation_error(args) + parts, missing_keys = [], [] + + @path_parts.zip(args) do |part, arg| + parameterized_arg = arg.to_param + + if parameterized_arg.nil? || parameterized_arg.empty? + missing_keys << part + end + + parts << [part, arg] + end + + message = "No route matches #{Hash[parts].inspect}" + message << " missing required keys: #{missing_keys.inspect}" + + raise ActionController::UrlGenerationError, message + end end def initialize(route, options) @@ -217,6 +243,7 @@ module ActionDispatch keys -= t.url_options.keys if t.respond_to?(:url_options) keys -= options.keys end + keys -= inner_options.keys result.merge!(Hash[keys.zip(args)]) end @@ -403,11 +430,19 @@ module ActionDispatch def add_route(app, conditions = {}, requirements = {}, defaults = {}, name = nil, anchor = true) raise ArgumentError, "Invalid route name: '#{name}'" unless name.blank? || name.to_s.match(/^[_a-z]\w*$/i) + if name && named_routes[name] + raise ArgumentError, "Invalid route name, already in use: '#{name}' \n" \ + "You may have defined two routes with the same name using the `:as` option, or " \ + "you may be overriding a route already defined by a resource with the same naming. " \ + "For the latter, you can restrict the routes created with `resources` as explained here: \n" \ + "http://guides.rubyonrails.org/routing.html#restricting-the-routes-created" + end + path = build_path(conditions.delete(:path_info), requirements, SEPARATORS, anchor) conditions = build_conditions(conditions, path.names.map { |x| x.to_sym }) route = @set.add_route(app, path, conditions, defaults, name) - named_routes[name] = route if name && !named_routes[name] + named_routes[name] = route if name route end @@ -479,11 +514,12 @@ module ActionDispatch @recall = recall.dup @set = set + normalize_recall! normalize_options! normalize_controller_action_id! use_relative_controller! normalize_controller! - handle_nil_action! + normalize_action! end def controller @@ -502,6 +538,11 @@ module ActionDispatch end end + # Set 'index' as default action for recall + def normalize_recall! + @recall[:action] ||= 'index' + end + def normalize_options! # If an explicit :controller was given, always make :action explicit # too, so that action expiry works as expected for things like @@ -517,8 +558,8 @@ module ActionDispatch options[:controller] = options[:controller].to_s end - if options[:action] - options[:action] = options[:action].to_s + if options.key?(:action) + options[:action] = (options[:action] || 'index').to_s end end @@ -528,8 +569,6 @@ module ActionDispatch # :controller, :action or :id is not found, don't pull any # more keys from the recall. def normalize_controller_action_id! - @recall[:action] ||= 'index' if current_controller - use_recall_for(:controller) or return use_recall_for(:action) or return use_recall_for(:id) @@ -551,13 +590,11 @@ module ActionDispatch @options[:controller] = controller.sub(%r{^/}, '') if controller end - # This handles the case of action: nil being explicitly passed. - # It is identical to action: "index" - def handle_nil_action! - if options.has_key?(:action) && options[:action].nil? - options[:action] = 'index' + # Move 'index' action from options to recall + def normalize_action! + if @options[:action] == 'index' + @recall[:action] = @options.delete(:action) end - recall[:action] = options.delete(:action) if options[:action] == 'index' end # Generates a path from routes, returns [path, params]. @@ -657,7 +694,7 @@ module ActionDispatch end req = @request_class.new(env) - @router.recognize(req) do |route, matches, params| + @router.recognize(req) do |route, _matches, params| params.merge!(extras) params.each do |key, value| if value.is_a?(String) diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb index 8e19025722..bcebe532bf 100644 --- a/actionpack/lib/action_dispatch/routing/url_for.rb +++ b/actionpack/lib/action_dispatch/routing/url_for.rb @@ -20,7 +20,7 @@ module ActionDispatch # # <%= link_to('Click here', controller: 'users', # action: 'new', message: 'Welcome!') %> - # # => "/users/new?message=Welcome%21" + # # => <a href="/users/new?message=Welcome%21">Click here</a> # # link_to, and all other functions that require URL generation functionality, # actually use ActionController::UrlFor under the hood. And in particular, diff --git a/actionpack/lib/action_dispatch/testing/assertions/dom.rb b/actionpack/lib/action_dispatch/testing/assertions/dom.rb index 8f90a1223e..241a39393a 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/dom.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/dom.rb @@ -7,20 +7,20 @@ module ActionDispatch # # # assert that the referenced method generates the appropriate HTML string # assert_dom_equal '<a href="http://www.example.com">Apples</a>', link_to("Apples", "http://www.example.com") - def assert_dom_equal(expected, actual, message = "") + def assert_dom_equal(expected, actual, message = nil) expected_dom = HTML::Document.new(expected).root actual_dom = HTML::Document.new(actual).root - assert_equal expected_dom, actual_dom + assert_equal expected_dom, actual_dom, message end # The negated form of +assert_dom_equivalent+. # # # assert that the referenced method does not generate the specified HTML string # assert_dom_not_equal '<a href="http://www.example.com">Apples</a>', link_to("Oranges", "http://www.example.com") - def assert_dom_not_equal(expected, actual, message = "") + def assert_dom_not_equal(expected, actual, message = nil) expected_dom = HTML::Document.new(expected).root actual_dom = HTML::Document.new(actual).root - assert_not_equal expected_dom, actual_dom + assert_not_equal expected_dom, actual_dom, message end end end diff --git a/actionpack/lib/action_dispatch/testing/assertions/response.rb b/actionpack/lib/action_dispatch/testing/assertions/response.rb index 44ed0ac1f3..93f9fab9c2 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/response.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/response.rb @@ -67,21 +67,11 @@ module ActionDispatch end def normalize_argument_to_redirection(fragment) - normalized = case fragment - when Regexp - fragment - when %r{^\w[A-Za-z\d+.-]*:.*} - fragment - when String - @request.protocol + @request.host_with_port + fragment - when :back - raise RedirectBackError unless refer = @request.headers["Referer"] - refer - else - @controller.url_for(fragment) - end - - normalized.respond_to?(:delete) ? normalized.delete("\0\r\n") : normalized + if Regexp === fragment + fragment + else + @controller._compute_redirect_to_location(fragment) + end end end end diff --git a/actionpack/lib/action_dispatch/testing/assertions/routing.rb b/actionpack/lib/action_dispatch/testing/assertions/routing.rb index 9210bffd1d..496682e8bd 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb @@ -81,7 +81,7 @@ module ActionDispatch # Load routes.rb if it hasn't been loaded. generated_path, extra_keys = @routes.generate_extras(options, defaults) - found_extras = options.reject {|k, v| ! extra_keys.include? k} + found_extras = options.reject { |k, _| ! extra_keys.include? k } msg = message || sprintf("found extras <%s>, not <%s>", found_extras, extras) assert_equal(extras, found_extras, msg) @@ -120,7 +120,7 @@ module ActionDispatch options[:controller] = "/#{controller}" end - generate_options = options.dup.delete_if{ |k,v| defaults.key?(k) } + generate_options = options.dup.delete_if{ |k, _| defaults.key?(k) } assert_generates(path.is_a?(Hash) ? path[:path] : path, generate_options, defaults, extras, message) end diff --git a/actionpack/lib/action_dispatch/testing/assertions/selector.rb b/actionpack/lib/action_dispatch/testing/assertions/selector.rb index e481f3b245..3253a3d424 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/selector.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/selector.rb @@ -377,8 +377,8 @@ module ActionDispatch node.content.gsub(/<!\[CDATA\[(.*)(\]\]>)?/m) { Rack::Utils.escapeHTML($1) } end - selected = elements.map do |_element| - text = _element.children.select{ |c| not c.tag? }.map{ |c| fix_content[c] }.join + selected = elements.map do |elem| + text = elem.children.select{ |c| not c.tag? }.map{ |c| fix_content[c] }.join root = HTML::Document.new(CGI.unescapeHTML("<encoded>#{text}</encoded>")).root css_select(root, "encoded:root", &block)[0] end diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb index ed4e88aab6..9beb30307b 100644 --- a/actionpack/lib/action_dispatch/testing/integration.rb +++ b/actionpack/lib/action_dispatch/testing/integration.rb @@ -3,7 +3,7 @@ require 'uri' require 'active_support/core_ext/kernel/singleton_class' require 'active_support/core_ext/object/try' require 'rack/test' -require 'minitest/unit' +require 'minitest' module ActionDispatch module Integration #:nodoc: @@ -17,7 +17,7 @@ module ActionDispatch # a Hash, or a String that is appropriately encoded # (<tt>application/x-www-form-urlencoded</tt> or # <tt>multipart/form-data</tt>). - # - +headers+: Additional headers to pass, as a Hash. The headers will be + # - +headers_or_env+: Additional headers to pass, as a Hash. The headers will be # merged into the Rack env hash. # # This method returns a Response object, which one can use to @@ -28,44 +28,38 @@ module ActionDispatch # # You can also perform POST, PATCH, PUT, DELETE, and HEAD requests with # +#post+, +#patch+, +#put+, +#delete+, and +#head+. - def get(path, parameters = nil, headers = nil) - process :get, path, parameters, headers + def get(path, parameters = nil, headers_or_env = nil) + process :get, path, parameters, headers_or_env end # Performs a POST request with the given parameters. See +#get+ for more # details. - def post(path, parameters = nil, headers = nil) - process :post, path, parameters, headers + def post(path, parameters = nil, headers_or_env = nil) + process :post, path, parameters, headers_or_env end # Performs a PATCH request with the given parameters. See +#get+ for more # details. - def patch(path, parameters = nil, headers = nil) - process :patch, path, parameters, headers + def patch(path, parameters = nil, headers_or_env = nil) + process :patch, path, parameters, headers_or_env end # Performs a PUT request with the given parameters. See +#get+ for more # details. - def put(path, parameters = nil, headers = nil) - process :put, path, parameters, headers + def put(path, parameters = nil, headers_or_env = nil) + process :put, path, parameters, headers_or_env end # Performs a DELETE request with the given parameters. See +#get+ for # more details. - def delete(path, parameters = nil, headers = nil) - process :delete, path, parameters, headers + def delete(path, parameters = nil, headers_or_env = nil) + process :delete, path, parameters, headers_or_env end # Performs a HEAD request with the given parameters. See +#get+ for more # details. - def head(path, parameters = nil, headers = nil) - process :head, path, parameters, headers - end - - # Performs a OPTIONS request with the given parameters. See +#get+ for - # more details. - def options(path, parameters = nil, headers = nil) - process :options, path, parameters, headers + def head(path, parameters = nil, headers_or_env = nil) + process :head, path, parameters, headers_or_env end # Performs an XMLHttpRequest request with the given parameters, mirroring @@ -74,11 +68,11 @@ module ActionDispatch # The request_method is +:get+, +:post+, +:patch+, +:put+, +:delete+ or # +:head+; the parameters are +nil+, a hash, or a url-encoded or multipart # string; the headers are a hash. - def xml_http_request(request_method, path, parameters = nil, headers = nil) - headers ||= {} - headers['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest' - headers['HTTP_ACCEPT'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ') - process(request_method, path, parameters, headers) + def xml_http_request(request_method, path, parameters = nil, headers_or_env = nil) + headers_or_env ||= {} + headers_or_env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest' + headers_or_env['HTTP_ACCEPT'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ') + process(request_method, path, parameters, headers_or_env) end alias xhr :xml_http_request @@ -95,40 +89,40 @@ module ActionDispatch # redirect. Note that the redirects are followed until the response is # not a redirect--this means you may run into an infinite loop if your # redirect loops back to itself. - def request_via_redirect(http_method, path, parameters = nil, headers = nil) - process(http_method, path, parameters, headers) + def request_via_redirect(http_method, path, parameters = nil, headers_or_env = nil) + process(http_method, path, parameters, headers_or_env) follow_redirect! while redirect? status end # Performs a GET request, following any subsequent redirect. # See +request_via_redirect+ for more information. - def get_via_redirect(path, parameters = nil, headers = nil) - request_via_redirect(:get, path, parameters, headers) + def get_via_redirect(path, parameters = nil, headers_or_env = nil) + request_via_redirect(:get, path, parameters, headers_or_env) end # Performs a POST request, following any subsequent redirect. # See +request_via_redirect+ for more information. - def post_via_redirect(path, parameters = nil, headers = nil) - request_via_redirect(:post, path, parameters, headers) + def post_via_redirect(path, parameters = nil, headers_or_env = nil) + request_via_redirect(:post, path, parameters, headers_or_env) end # Performs a PATCH request, following any subsequent redirect. # See +request_via_redirect+ for more information. - def patch_via_redirect(path, parameters = nil, headers = nil) - request_via_redirect(:patch, path, parameters, headers) + def patch_via_redirect(path, parameters = nil, headers_or_env = nil) + request_via_redirect(:patch, path, parameters, headers_or_env) end # Performs a PUT request, following any subsequent redirect. # See +request_via_redirect+ for more information. - def put_via_redirect(path, parameters = nil, headers = nil) - request_via_redirect(:put, path, parameters, headers) + def put_via_redirect(path, parameters = nil, headers_or_env = nil) + request_via_redirect(:put, path, parameters, headers_or_env) end # Performs a DELETE request, following any subsequent redirect. # See +request_via_redirect+ for more information. - def delete_via_redirect(path, parameters = nil, headers = nil) - request_via_redirect(:delete, path, parameters, headers) + def delete_via_redirect(path, parameters = nil, headers_or_env = nil) + request_via_redirect(:delete, path, parameters, headers_or_env) end end @@ -268,8 +262,7 @@ module ActionDispatch end # Performs the actual request. - def process(method, path, parameters = nil, rack_env = nil) - rack_env ||= {} + def process(method, path, parameters = nil, headers_or_env = nil) if path =~ %r{://} location = URI.parse(path) https! URI::HTTPS === location if location.scheme @@ -300,11 +293,11 @@ module ActionDispatch "CONTENT_TYPE" => "application/x-www-form-urlencoded", "HTTP_ACCEPT" => accept } + # this modifies the passed env directly + Http::Headers.new(env).merge!(headers_or_env || {}) session = Rack::Test::Session.new(_mock_session) - env.merge!(rack_env) - # NOTE: rack-test v0.5 doesn't build a default uri correctly # Make sure requested path is always a full uri uri = URI.parse('/') @@ -341,7 +334,7 @@ module ActionDispatch @integration_session = Integration::Session.new(app) end - %w(get post patch put head delete options cookies assigns + %w(get post patch put head delete cookies assigns xml_http_request xhr get_via_redirect post_via_redirect).each do |method| define_method(method) do |*args| reset! unless integration_session @@ -494,10 +487,6 @@ module ActionDispatch @@app = nil def self.app - if !@@app && !ActionDispatch.test_app - ActiveSupport::Deprecation.warn "Rails application fallback is deprecated and no longer works, please set ActionDispatch.test_app" - end - @@app || ActionDispatch.test_app end diff --git a/actionpack/lib/action_dispatch/testing/test_process.rb b/actionpack/lib/action_dispatch/testing/test_process.rb index e657283cec..630e6a9b78 100644 --- a/actionpack/lib/action_dispatch/testing/test_process.rb +++ b/actionpack/lib/action_dispatch/testing/test_process.rb @@ -6,7 +6,7 @@ module ActionDispatch module TestProcess def assigns(key = nil) assigns = {}.with_indifferent_access - @controller.view_assigns.each {|k, v| assigns.regular_writer(k, v)} + @controller.view_assigns.each { |k, v| assigns.regular_writer(k, v) } key.nil? ? assigns : assigns[key] end diff --git a/actionpack/lib/action_dispatch/testing/test_request.rb b/actionpack/lib/action_dispatch/testing/test_request.rb index c63778f870..57c678843b 100644 --- a/actionpack/lib/action_dispatch/testing/test_request.rb +++ b/actionpack/lib/action_dispatch/testing/test_request.rb @@ -3,7 +3,11 @@ require 'rack/utils' module ActionDispatch class TestRequest < Request - DEFAULT_ENV = Rack::MockRequest.env_for('/') + DEFAULT_ENV = Rack::MockRequest.env_for('/', + 'HTTP_HOST' => 'test.host', + 'REMOTE_ADDR' => '0.0.0.0', + 'HTTP_USER_AGENT' => 'Rails Testing' + ) def self.new(env = {}) super @@ -12,10 +16,6 @@ module ActionDispatch def initialize(env = {}) env = Rails.application.env_config.merge(env) if defined?(Rails.application) && Rails.application super(default_env.merge(env)) - - self.host = 'test.host' - self.remote_addr = '0.0.0.0' - self.user_agent = 'Rails Testing' end def request_method=(method) diff --git a/actionpack/lib/action_pack/version.rb b/actionpack/lib/action_pack/version.rb index 5c87a9cd7c..fd08f392aa 100644 --- a/actionpack/lib/action_pack/version.rb +++ b/actionpack/lib/action_pack/version.rb @@ -1,10 +1,11 @@ module ActionPack - module VERSION #:nodoc: - MAJOR = 4 - MINOR = 0 - TINY = 0 - PRE = "beta1" + # Returns the version of the currently loaded ActionPack as a Gem::Version + def self.version + Gem::Version.new "4.1.0.beta" + end - STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.') + module VERSION #:nodoc: + MAJOR, MINOR, TINY, PRE = ActionPack.version.segments + STRING = ActionPack.version.to_s end end diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb deleted file mode 100644 index 4aafbcb655..0000000000 --- a/actionpack/lib/action_view.rb +++ /dev/null @@ -1,93 +0,0 @@ -#-- -# Copyright (c) 2004-2013 David Heinemeier Hansson -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -#++ - -require 'active_support' -require 'active_support/rails' -require 'action_pack' - -module ActionView - extend ActiveSupport::Autoload - - eager_autoload do - autoload :Base - autoload :Context - autoload :CompiledTemplates, "action_view/context" - autoload :Digestor - autoload :Helpers - autoload :LookupContext - autoload :PathSet - autoload :RecordIdentifier - autoload :RoutingUrlFor - autoload :Template - - autoload_under "renderer" do - autoload :Renderer - autoload :AbstractRenderer - autoload :PartialRenderer - autoload :TemplateRenderer - autoload :StreamingTemplateRenderer - end - - autoload_at "action_view/template/resolver" do - autoload :Resolver - autoload :PathResolver - autoload :FileSystemResolver - autoload :OptimizedFileSystemResolver - autoload :FallbackFileSystemResolver - end - - autoload_at "action_view/buffers" do - autoload :OutputBuffer - autoload :StreamingBuffer - end - - autoload_at "action_view/flows" do - autoload :OutputFlow - autoload :StreamingFlow - end - - autoload_at "action_view/template/error" do - autoload :MissingTemplate - autoload :ActionViewError - autoload :EncodingError - autoload :MissingRequestError - autoload :TemplateError - autoload :WrongEncodingError - end - end - - autoload :TestCase - - ENCODING_FLAG = '#.*coding[:=]\s*(\S+)[ \t]*' - - def self.eager_load! - super - ActionView::Template.eager_load! - end -end - -require 'active_support/core_ext/string/output_safety' - -ActiveSupport.on_load(:i18n) do - I18n.load_path << "#{File.dirname(__FILE__)}/action_view/locale/en.yml" -end diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb deleted file mode 100644 index 08253de3f4..0000000000 --- a/actionpack/lib/action_view/base.rb +++ /dev/null @@ -1,201 +0,0 @@ -require 'active_support/core_ext/module/attr_internal' -require 'active_support/core_ext/class/attribute_accessors' -require 'active_support/ordered_options' -require 'action_view/log_subscriber' - -module ActionView #:nodoc: - # = Action View Base - # - # Action View templates can be written in several ways. If the template file has a <tt>.erb</tt> extension then it uses a mixture of ERB - # (included in Ruby) and HTML. If the template file has a <tt>.builder</tt> extension then Jim Weirich's Builder::XmlMarkup library is used. - # - # == ERB - # - # You trigger ERB by using embeddings such as <% %>, <% -%>, and <%= %>. The <%= %> tag set is used when you want output. Consider the - # following loop for names: - # - # <b>Names of all the people</b> - # <% @people.each do |person| %> - # Name: <%= person.name %><br/> - # <% end %> - # - # The loop is setup in regular embedding tags <% %> and the name is written using the output embedding tag <%= %>. Note that this - # is not just a usage suggestion. Regular output functions like print or puts won't work with ERB templates. So this would be wrong: - # - # <%# WRONG %> - # Hi, Mr. <% puts "Frodo" %> - # - # If you absolutely must write from within a function use +concat+. - # - # <%- and -%> suppress leading and trailing whitespace, including the trailing newline, and can be used interchangeably with <% and %>. - # - # === Using sub templates - # - # Using sub templates allows you to sidestep tedious replication and extract common display structures in shared templates. The - # classic example is the use of a header and footer (even though the Action Pack-way would be to use Layouts): - # - # <%= render "shared/header" %> - # Something really specific and terrific - # <%= render "shared/footer" %> - # - # As you see, we use the output embeddings for the render methods. The render call itself will just return a string holding the - # result of the rendering. The output embedding writes it to the current template. - # - # But you don't have to restrict yourself to static includes. Templates can share variables amongst themselves by using instance - # variables defined using the regular embedding tags. Like this: - # - # <% @page_title = "A Wonderful Hello" %> - # <%= render "shared/header" %> - # - # Now the header can pick up on the <tt>@page_title</tt> variable and use it for outputting a title tag: - # - # <title><%= @page_title %></title> - # - # === Passing local variables to sub templates - # - # You can pass local variables to sub templates by using a hash with the variable names as keys and the objects as values: - # - # <%= render "shared/header", { headline: "Welcome", person: person } %> - # - # These can now be accessed in <tt>shared/header</tt> with: - # - # Headline: <%= headline %> - # First name: <%= person.first_name %> - # - # If you need to find out whether a certain local variable has been assigned a value in a particular render call, - # you need to use the following pattern: - # - # <% if local_assigns.has_key? :headline %> - # Headline: <%= headline %> - # <% end %> - # - # Testing using <tt>defined? headline</tt> will not work. This is an implementation restriction. - # - # === Template caching - # - # By default, Rails will compile each template to a method in order to render it. When you alter a template, - # Rails will check the file's modification time and recompile it in development mode. - # - # == Builder - # - # Builder templates are a more programmatic alternative to ERB. They are especially useful for generating XML content. An XmlMarkup object - # named +xml+ is automatically made available to templates with a <tt>.builder</tt> extension. - # - # Here are some basic examples: - # - # xml.em("emphasized") # => <em>emphasized</em> - # xml.em { xml.b("emph & bold") } # => <em><b>emph & bold</b></em> - # xml.a("A Link", "href" => "http://onestepback.org") # => <a href="http://onestepback.org">A Link</a> - # xml.target("name" => "compile", "option" => "fast") # => <target option="fast" name="compile"\> - # # NOTE: order of attributes is not specified. - # - # Any method with a block will be treated as an XML markup tag with nested markup in the block. For example, the following: - # - # xml.div do - # xml.h1(@person.name) - # xml.p(@person.bio) - # end - # - # would produce something like: - # - # <div> - # <h1>David Heinemeier Hansson</h1> - # <p>A product of Danish Design during the Winter of '79...</p> - # </div> - # - # A full-length RSS example actually used on Basecamp: - # - # xml.rss("version" => "2.0", "xmlns:dc" => "http://purl.org/dc/elements/1.1/") do - # xml.channel do - # xml.title(@feed_title) - # xml.link(@url) - # xml.description "Basecamp: Recent items" - # xml.language "en-us" - # xml.ttl "40" - # - # @recent_items.each do |item| - # xml.item do - # xml.title(item_title(item)) - # xml.description(item_description(item)) if item_description(item) - # xml.pubDate(item_pubDate(item)) - # xml.guid(@person.firm.account.url + @recent_items.url(item)) - # xml.link(@person.firm.account.url + @recent_items.url(item)) - # - # xml.tag!("dc:creator", item.author_name) if item_has_creator?(item) - # end - # end - # end - # end - # - # More builder documentation can be found at http://builder.rubyforge.org. - class Base - include Helpers, ::ERB::Util, Context - - # Specify the proc used to decorate input tags that refer to attributes with errors. - cattr_accessor :field_error_proc - @@field_error_proc = Proc.new{ |html_tag, instance| "<div class=\"field_with_errors\">#{html_tag}</div>".html_safe } - - # How to complete the streaming when an exception occurs. - # This is our best guess: first try to close the attribute, then the tag. - cattr_accessor :streaming_completion_on_exception - @@streaming_completion_on_exception = %("><script>window.location = "/500.html"</script></html>) - - # Specify whether rendering within namespaced controllers should prefix - # the partial paths for ActiveModel objects with the namespace. - # (e.g., an Admin::PostsController would render @post using /admin/posts/_post.erb) - cattr_accessor :prefix_partial_path_with_controller_namespace - @@prefix_partial_path_with_controller_namespace = true - - # Specify default_formats that can be rendered. - cattr_accessor :default_formats - - class_attribute :_routes - class_attribute :logger - - class << self - delegate :erb_trim_mode=, :to => 'ActionView::Template::Handlers::ERB' - - def cache_template_loading - ActionView::Resolver.caching? - end - - def cache_template_loading=(value) - ActionView::Resolver.caching = value - end - - def xss_safe? #:nodoc: - true - end - end - - attr_accessor :view_renderer - attr_internal :config, :assigns - - delegate :lookup_context, :to => :view_renderer - delegate :formats, :formats=, :locale, :locale=, :view_paths, :view_paths=, :to => :lookup_context - - def assign(new_assigns) # :nodoc: - @_assigns = new_assigns.each { |key, value| instance_variable_set("@#{key}", value) } - end - - def initialize(context = nil, assigns = {}, controller = nil, formats = nil) #:nodoc: - @_config = ActiveSupport::InheritableOptions.new - - if context.is_a?(ActionView::Renderer) - @view_renderer = context - else - lookup_context = context.is_a?(ActionView::LookupContext) ? - context : ActionView::LookupContext.new(context) - lookup_context.formats = formats if formats - lookup_context.prefixes = controller._prefixes if controller - @view_renderer = ActionView::Renderer.new(lookup_context) - end - - assign(assigns) - assign_controller(controller) - _prepare_context - end - - ActiveSupport.run_load_hooks(:action_view, self) - end -end diff --git a/actionpack/lib/action_view/buffers.rb b/actionpack/lib/action_view/buffers.rb deleted file mode 100644 index 2372d3c433..0000000000 --- a/actionpack/lib/action_view/buffers.rb +++ /dev/null @@ -1,43 +0,0 @@ -require 'active_support/core_ext/string/output_safety' - -module ActionView - class OutputBuffer < ActiveSupport::SafeBuffer #:nodoc: - def initialize(*) - super - encode! - end - - def <<(value) - super(value.to_s) - end - alias :append= :<< - alias :safe_append= :safe_concat - end - - class StreamingBuffer #:nodoc: - def initialize(block) - @block = block - end - - def <<(value) - value = value.to_s - value = ERB::Util.h(value) unless value.html_safe? - @block.call(value) - end - alias :concat :<< - alias :append= :<< - - def safe_concat(value) - @block.call(value.to_s) - end - alias :safe_append= :safe_concat - - def html_safe? - true - end - - def html_safe - self - end - end -end diff --git a/actionpack/lib/action_view/context.rb b/actionpack/lib/action_view/context.rb deleted file mode 100644 index ee263df484..0000000000 --- a/actionpack/lib/action_view/context.rb +++ /dev/null @@ -1,36 +0,0 @@ -module ActionView - module CompiledTemplates #:nodoc: - # holds compiled template code - end - - # = Action View Context - # - # Action View contexts are supplied to Action Controller to render a template. - # The default Action View context is ActionView::Base. - # - # In order to work with ActionController, a Context must just include this module. - # The initialization of the variables used by the context (@output_buffer, @view_flow, - # and @virtual_path) is responsibility of the object that includes this module - # (although you can call _prepare_context defined below). - module Context - include CompiledTemplates - attr_accessor :output_buffer, :view_flow - - # Prepares the context by setting the appropriate instance variables. - # :api: plugin - def _prepare_context - @view_flow = OutputFlow.new - @output_buffer = nil - @virtual_path = nil - end - - # Encapsulates the interaction with the view flow so it - # returns the correct buffer on +yield+. This is usually - # overwritten by helpers to add more behavior. - # :api: plugin - def _layout_for(name=nil) - name ||= :layout - view_flow.get(name).html_safe - end - end -end diff --git a/actionpack/lib/action_view/dependency_tracker.rb b/actionpack/lib/action_view/dependency_tracker.rb deleted file mode 100644 index a2a555dfcb..0000000000 --- a/actionpack/lib/action_view/dependency_tracker.rb +++ /dev/null @@ -1,93 +0,0 @@ -require 'thread_safe' - -module ActionView - class DependencyTracker - @trackers = ThreadSafe::Cache.new - - def self.find_dependencies(name, template) - tracker = @trackers[template.handler] - - if tracker.present? - tracker.call(name, template) - else - [] - end - end - - def self.register_tracker(extension, tracker) - handler = Template.handler_for_extension(extension) - @trackers[handler] = tracker - end - - def self.remove_tracker(handler) - @trackers.delete(handler) - end - - class ERBTracker - EXPLICIT_DEPENDENCY = /# Template Dependency: (\S+)/ - - # Matches: - # render partial: "comments/comment", collection: commentable.comments - # render "comments/comments" - # render 'comments/comments' - # render('comments/comments') - # - # render(@topic) => render("topics/topic") - # render(topics) => render("topics/topic") - # render(message.topics) => render("topics/topic") - RENDER_DEPENDENCY = / - render\s* # render, followed by optional whitespace - \(? # start an optional parenthesis for the render call - (partial:|:partial\s+=>)?\s* # naming the partial, used with collection -- 1st capture - ([@a-z"'][@a-z_\/\."']+) # the template name itself -- 2nd capture - /x - - def self.call(name, template) - new(name, template).dependencies - end - - def initialize(name, template) - @name, @template = name, template - end - - def dependencies - render_dependencies + explicit_dependencies - end - - attr_reader :name, :template - private :name, :template - - private - - def source - template.source - end - - def directory - name.split("/")[0..-2].join("/") - end - - def render_dependencies - source.scan(RENDER_DEPENDENCY). - collect(&:second).uniq. - - # render(@topic) => render("topics/topic") - # render(topics) => render("topics/topic") - # render(message.topics) => render("topics/topic") - collect { |name| name.sub(/\A@?([a-z]+\.)*([a-z_]+)\z/) { "#{$2.pluralize}/#{$2.singularize}" } }. - - # render("headline") => render("message/headline") - collect { |name| name.include?("/") ? name : "#{directory}/#{name}" }. - - # replace quotes from string renders - collect { |name| name.gsub(/["']/, "") } - end - - def explicit_dependencies - source.scan(EXPLICIT_DEPENDENCY).flatten.uniq - end - end - - register_tracker :erb, ERBTracker - end -end diff --git a/actionpack/lib/action_view/digestor.rb b/actionpack/lib/action_view/digestor.rb deleted file mode 100644 index 9324a1ac50..0000000000 --- a/actionpack/lib/action_view/digestor.rb +++ /dev/null @@ -1,85 +0,0 @@ -require 'thread_safe' -require 'action_view/dependency_tracker' - -module ActionView - class Digestor - cattr_reader(:cache) - @@cache = ThreadSafe::Cache.new - - def self.digest(name, format, finder, options = {}) - cache_key = [name, format] + Array.wrap(options[:dependencies]) - @@cache[cache_key.join('.')] ||= begin - klass = options[:partial] || name.include?("/_") ? PartialDigestor : Digestor - klass.new(name, format, finder, options).digest - end - end - - attr_reader :name, :format, :finder, :options - - def initialize(name, format, finder, options={}) - @name, @format, @finder, @options = name, format, finder, options - end - - def digest - Digest::MD5.hexdigest("#{source}-#{dependency_digest}").tap do |digest| - logger.try :info, "Cache digest for #{name}.#{format}: #{digest}" - end - rescue ActionView::MissingTemplate - logger.try :error, "Couldn't find template for digesting: #{name}.#{format}" - '' - end - - def dependencies - DependencyTracker.find_dependencies(name, template) - rescue ActionView::MissingTemplate - [] # File doesn't exist, so no dependencies - end - - def nested_dependencies - dependencies.collect do |dependency| - dependencies = PartialDigestor.new(dependency, format, finder).nested_dependencies - dependencies.any? ? { dependency => dependencies } : dependency - end - end - - private - - def logger - ActionView::Base.logger - end - - def logical_name - name.gsub(%r|/_|, "/") - end - - def partial? - false - end - - def template - @template ||= finder.find(logical_name, [], partial?, formats: [ format ]) - end - - def source - template.source - end - - def dependency_digest - template_digests = dependencies.collect do |template_name| - Digestor.digest(template_name, format, finder, partial: true) - end - - (template_digests + injected_dependencies).join("-") - end - - def injected_dependencies - Array.wrap(options[:dependencies]) - end - end - - class PartialDigestor < Digestor # :nodoc: - def partial? - true - end - end -end diff --git a/actionpack/lib/action_view/flows.rb b/actionpack/lib/action_view/flows.rb deleted file mode 100644 index c0e458cd41..0000000000 --- a/actionpack/lib/action_view/flows.rb +++ /dev/null @@ -1,76 +0,0 @@ -require 'active_support/core_ext/string/output_safety' - -module ActionView - class OutputFlow #:nodoc: - attr_reader :content - - def initialize - @content = Hash.new { |h,k| h[k] = ActiveSupport::SafeBuffer.new } - end - - # Called by _layout_for to read stored values. - def get(key) - @content[key] - end - - # Called by each renderer object to set the layout contents. - def set(key, value) - @content[key] = value - end - - # Called by content_for - def append(key, value) - @content[key] << value - end - alias_method :append!, :append - - end - - class StreamingFlow < OutputFlow #:nodoc: - def initialize(view, fiber) - @view = view - @parent = nil - @child = view.output_buffer - @content = view.view_flow.content - @fiber = fiber - @root = Fiber.current.object_id - end - - # Try to get an stored content. If the content - # is not available and we are inside the layout - # fiber, we set that we are waiting for the given - # key and yield. - def get(key) - return super if @content.key?(key) - - if inside_fiber? - view = @view - - begin - @waiting_for = key - view.output_buffer, @parent = @child, view.output_buffer - Fiber.yield - ensure - @waiting_for = nil - view.output_buffer, @child = @parent, view.output_buffer - end - end - - super - end - - # Appends the contents for the given key. This is called - # by provides and resumes back to the fiber if it is - # the key it is waiting for. - def append!(key, value) - super - @fiber.resume if @waiting_for == key - end - - private - - def inside_fiber? - Fiber.current.object_id != @root - end - end -end
\ No newline at end of file diff --git a/actionpack/lib/action_view/helpers.rb b/actionpack/lib/action_view/helpers.rb deleted file mode 100644 index 8a78685ae1..0000000000 --- a/actionpack/lib/action_view/helpers.rb +++ /dev/null @@ -1,58 +0,0 @@ -require 'active_support/benchmarkable' - -module ActionView #:nodoc: - module Helpers #:nodoc: - extend ActiveSupport::Autoload - - autoload :ActiveModelHelper - autoload :AssetTagHelper - autoload :AssetUrlHelper - autoload :AtomFeedHelper - autoload :CacheHelper - autoload :CaptureHelper - autoload :ControllerHelper - autoload :CsrfHelper - autoload :DateHelper - autoload :DebugHelper - autoload :FormHelper - autoload :FormOptionsHelper - autoload :FormTagHelper - autoload :JavaScriptHelper, "action_view/helpers/javascript_helper" - autoload :NumberHelper - autoload :OutputSafetyHelper - autoload :RecordTagHelper - autoload :RenderingHelper - autoload :SanitizeHelper - autoload :TagHelper - autoload :TextHelper - autoload :TranslationHelper - autoload :UrlHelper - - extend ActiveSupport::Concern - - include ActiveSupport::Benchmarkable - include ActiveModelHelper - include AssetTagHelper - include AssetUrlHelper - include AtomFeedHelper - include CacheHelper - include CaptureHelper - include ControllerHelper - include CsrfHelper - include DateHelper - include DebugHelper - include FormHelper - include FormOptionsHelper - include FormTagHelper - include JavaScriptHelper - include NumberHelper - include OutputSafetyHelper - include RecordTagHelper - include RenderingHelper - include SanitizeHelper - include TagHelper - include TextHelper - include TranslationHelper - include UrlHelper - end -end diff --git a/actionpack/lib/action_view/helpers/active_model_helper.rb b/actionpack/lib/action_view/helpers/active_model_helper.rb deleted file mode 100644 index 901f433c70..0000000000 --- a/actionpack/lib/action_view/helpers/active_model_helper.rb +++ /dev/null @@ -1,49 +0,0 @@ -require 'active_support/core_ext/class/attribute_accessors' -require 'active_support/core_ext/enumerable' - -module ActionView - # = Active Model Helpers - module Helpers - module ActiveModelHelper - end - - module ActiveModelInstanceTag - def object - @active_model_object ||= begin - object = super - object.respond_to?(:to_model) ? object.to_model : object - end - end - - def content_tag(*) - error_wrapping(super) - end - - def tag(type, options, *) - tag_generate_errors?(options) ? error_wrapping(super) : super - end - - def error_wrapping(html_tag) - if object_has_errors? - Base.field_error_proc.call(html_tag, self) - else - html_tag - end - end - - def error_message - object.errors[@method_name] - end - - private - - def object_has_errors? - object.respond_to?(:errors) && object.errors.respond_to?(:[]) && error_message.present? - end - - def tag_generate_errors?(options) - options['type'] != 'hidden' - end - end - end -end diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb deleted file mode 100644 index 31e37893c6..0000000000 --- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb +++ /dev/null @@ -1,315 +0,0 @@ -require 'active_support/core_ext/array/extract_options' -require 'active_support/core_ext/hash/keys' -require 'action_view/helpers/asset_url_helper' -require 'action_view/helpers/tag_helper' - -module ActionView - # = Action View Asset Tag Helpers - module Helpers #:nodoc: - # This module provides methods for generating HTML that links views to assets such - # as images, javascripts, stylesheets, and feeds. These methods do not verify - # the assets exist before linking to them: - # - # image_tag("rails.png") - # # => <img alt="Rails" src="/assets/rails.png" /> - # stylesheet_link_tag("application") - # # => <link href="/assets/application.css?body=1" media="screen" rel="stylesheet" /> - # - module AssetTagHelper - extend ActiveSupport::Concern - - include AssetUrlHelper - include TagHelper - - # Returns an HTML script tag for each of the +sources+ provided. - # - # Sources may be paths to JavaScript files. Relative paths are assumed to be relative - # to <tt>assets/javascripts</tt>, full paths are assumed to be relative to the document - # root. Relative paths are idiomatic, use absolute paths only when needed. - # - # When passing paths, the ".js" extension is optional. - # - # You can modify the HTML attributes of the script tag by passing a hash as the - # last argument. - # - # When the Asset Pipeline is enabled, you can pass the name of your manifest as - # source, and include other JavaScript or CoffeeScript files inside the manifest. - # - # javascript_include_tag "xmlhr" - # # => <script src="/assets/xmlhr.js?1284139606"></script> - # - # javascript_include_tag "xmlhr.js" - # # => <script src="/assets/xmlhr.js?1284139606"></script> - # - # javascript_include_tag "common.javascript", "/elsewhere/cools" - # # => <script src="/assets/common.javascript?1284139606"></script> - # # <script src="/elsewhere/cools.js?1423139606"></script> - # - # javascript_include_tag "http://www.example.com/xmlhr" - # # => <script src="http://www.example.com/xmlhr"></script> - # - # javascript_include_tag "http://www.example.com/xmlhr.js" - # # => <script src="http://www.example.com/xmlhr.js"></script> - # - def javascript_include_tag(*sources) - options = sources.extract_options!.stringify_keys - path_options = options.extract!('protocol').symbolize_keys - - sources.uniq.map { |source| - tag_options = { - "src" => path_to_javascript(source, path_options) - }.merge(options) - content_tag(:script, "", tag_options) - }.join("\n").html_safe - end - - # Returns a stylesheet link tag for the sources specified as arguments. If - # you don't specify an extension, <tt>.css</tt> will be appended automatically. - # You can modify the link attributes by passing a hash as the last argument. - # For historical reasons, the 'media' attribute will always be present and defaults - # to "screen", so you must explicitely set it to "all" for the stylesheet(s) to - # apply to all media types. - # - # stylesheet_link_tag "style" - # # => <link href="/assets/style.css" media="screen" rel="stylesheet" /> - # - # stylesheet_link_tag "style.css" - # # => <link href="/assets/style.css" media="screen" rel="stylesheet" /> - # - # stylesheet_link_tag "http://www.example.com/style.css" - # # => <link href="http://www.example.com/style.css" media="screen" rel="stylesheet" /> - # - # stylesheet_link_tag "style", media: "all" - # # => <link href="/assets/style.css" media="all" rel="stylesheet" /> - # - # stylesheet_link_tag "style", media: "print" - # # => <link href="/assets/style.css" media="print" rel="stylesheet" /> - # - # stylesheet_link_tag "random.styles", "/css/stylish" - # # => <link href="/assets/random.styles" media="screen" rel="stylesheet" /> - # # <link href="/css/stylish.css" media="screen" rel="stylesheet" /> - # - def stylesheet_link_tag(*sources) - options = sources.extract_options!.stringify_keys - path_options = options.extract!('protocol').symbolize_keys - - sources.uniq.map { |source| - tag_options = { - "rel" => "stylesheet", - "media" => "screen", - "href" => path_to_stylesheet(source, path_options) - }.merge(options) - tag(:link, tag_options) - }.join("\n").html_safe - end - - # Returns a link tag that browsers and news readers can use to auto-detect - # an RSS or Atom feed. The +type+ can either be <tt>:rss</tt> (default) or - # <tt>:atom</tt>. Control the link options in url_for format using the - # +url_options+. You can modify the LINK tag itself in +tag_options+. - # - # ==== Options - # * <tt>:rel</tt> - Specify the relation of this link, defaults to "alternate" - # * <tt>:type</tt> - Override the auto-generated mime type - # * <tt>:title</tt> - Specify the title of the link, defaults to the +type+ - # - # auto_discovery_link_tag - # # => <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.currenthost.com/controller/action" /> - # auto_discovery_link_tag(:atom) - # # => <link rel="alternate" type="application/atom+xml" title="ATOM" href="http://www.currenthost.com/controller/action" /> - # auto_discovery_link_tag(:rss, {action: "feed"}) - # # => <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.currenthost.com/controller/feed" /> - # auto_discovery_link_tag(:rss, {action: "feed"}, {title: "My RSS"}) - # # => <link rel="alternate" type="application/rss+xml" title="My RSS" href="http://www.currenthost.com/controller/feed" /> - # auto_discovery_link_tag(:rss, {controller: "news", action: "feed"}) - # # => <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.currenthost.com/news/feed" /> - # auto_discovery_link_tag(:rss, "http://www.example.com/feed.rss", {title: "Example RSS"}) - # # => <link rel="alternate" type="application/rss+xml" title="Example RSS" href="http://www.example.com/feed" /> - def auto_discovery_link_tag(type = :rss, url_options = {}, tag_options = {}) - if !(type == :rss || type == :atom) && tag_options[:type].blank? - message = "You have passed type other than :rss or :atom to auto_discovery_link_tag and haven't supplied " + - "the :type option key. This behavior is deprecated and will be remove in Rails 4.1. You should pass " + - ":type option explicitly if you want to use other types, for example: " + - "auto_discovery_link_tag(:xml, '/feed.xml', :type => 'application/xml')" - ActiveSupport::Deprecation.warn message - end - - tag( - "link", - "rel" => tag_options[:rel] || "alternate", - "type" => tag_options[:type] || Mime::Type.lookup_by_extension(type.to_s).to_s, - "title" => tag_options[:title] || type.to_s.upcase, - "href" => url_options.is_a?(Hash) ? url_for(url_options.merge(:only_path => false)) : url_options - ) - end - - # Returns a link loading a favicon file. You may specify a different file - # in the first argument. The helper accepts an additional options hash where - # you can override "rel" and "type". - # - # ==== Options - # * <tt>:rel</tt> - Specify the relation of this link, defaults to 'shortcut icon' - # * <tt>:type</tt> - Override the auto-generated mime type, defaults to 'image/vnd.microsoft.icon' - # - # favicon_link_tag '/myicon.ico' - # # => <link href="/assets/favicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon" /> - # - # Mobile Safari looks for a different <link> tag, pointing to an image that - # will be used if you add the page to the home screen of an iPod Touch, iPhone, or iPad. - # The following call would generate such a tag: - # - # favicon_link_tag '/mb-icon.png', rel: 'apple-touch-icon', type: 'image/png' - # # => <link href="/assets/mb-icon.png" rel="apple-touch-icon" type="image/png" /> - # - def favicon_link_tag(source='favicon.ico', options={}) - tag('link', { - :rel => 'shortcut icon', - :type => 'image/vnd.microsoft.icon', - :href => path_to_image(source) - }.merge(options.symbolize_keys)) - end - - # Returns an HTML image tag for the +source+. The +source+ can be a full - # path or a file. - # - # ==== Options - # You can add HTML attributes using the +options+. The +options+ supports - # three additional keys for convenience and conformance: - # - # * <tt>:alt</tt> - If no alt text is given, the file name part of the - # +source+ is used (capitalized and without the extension) - # * <tt>:size</tt> - Supplied as "{Width}x{Height}" or "{Number}", so "30x45" becomes - # width="30" and height="45", and "50" becomes width="50" and height="50". - # <tt>:size</tt> will be ignored if the value is not in the correct format. - # - # ==== Examples - # - # image_tag("icon") - # # => <img alt="Icon" src="/assets/icon" /> - # image_tag("icon.png") - # # => <img alt="Icon" src="/assets/icon.png" /> - # image_tag("icon.png", size: "16x10", alt: "Edit Entry") - # # => <img src="/assets/icon.png" width="16" height="10" alt="Edit Entry" /> - # image_tag("/icons/icon.gif", size: "16") - # # => <img src="/icons/icon.gif" width="16" height="16" alt="Icon" /> - # image_tag("/icons/icon.gif", height: '32', width: '32') - # # => <img alt="Icon" height="32" src="/icons/icon.gif" width="32" /> - # image_tag("/icons/icon.gif", class: "menu_icon") - # # => <img alt="Icon" class="menu_icon" src="/icons/icon.gif" /> - def image_tag(source, options={}) - options = options.symbolize_keys - - src = options[:src] = path_to_image(source) - - unless src =~ /^(?:cid|data):/ || src.blank? - options[:alt] = options.fetch(:alt){ image_alt(src) } - end - - if size = options.delete(:size) - options[:width], options[:height] = size.split("x") if size =~ %r{\A\d+x\d+\z} - options[:width] = options[:height] = size if size =~ %r{\A\d+\z} - end - - tag("img", options) - end - - # Returns a string suitable for an html image tag alt attribute. - # The +src+ argument is meant to be an image file path. - # The method removes the basename of the file path and the digest, - # if any. It also removes hyphens and underscores from file names and - # replaces them with spaces, returning a space-separated, titleized - # string. - # - # ==== Examples - # - # image_tag('rails.png') - # # => <img alt="Rails" src="/assets/rails.png" /> - # - # image_tag('hyphenated-file-name.png') - # # => <img alt="Hyphenated file name" src="/assets/hyphenated-file-name.png" /> - # - # image_tag('underscored_file_name.png') - # # => <img alt="Underscored file name" src="/assets/underscored_file_name.png" /> - def image_alt(src) - File.basename(src, '.*').sub(/-[[:xdigit:]]{32}\z/, '').tr('-_', ' ').capitalize - end - - # Returns an html video tag for the +sources+. If +sources+ is a string, - # a single video tag will be returned. If +sources+ is an array, a video - # tag with nested source tags for each source will be returned. The - # +sources+ can be full paths or files that exists in your public videos - # directory. - # - # ==== Options - # You can add HTML attributes using the +options+. The +options+ supports - # two additional keys for convenience and conformance: - # - # * <tt>:poster</tt> - Set an image (like a screenshot) to be shown - # before the video loads. The path is calculated like the +src+ of +image_tag+. - # * <tt>:size</tt> - Supplied as "{Width}x{Height}", so "30x45" becomes - # width="30" and height="45". <tt>:size</tt> will be ignored if the - # value is not in the correct format. - # - # video_tag("trailer") - # # => <video src="/videos/trailer" /> - # video_tag("trailer.ogg") - # # => <video src="/videos/trailer.ogg" /> - # video_tag("trailer.ogg", controls: true, autobuffer: true) - # # => <video autobuffer="autobuffer" controls="controls" src="/videos/trailer.ogg" /> - # video_tag("trailer.m4v", size: "16x10", poster: "screenshot.png") - # # => <video src="/videos/trailer.m4v" width="16" height="10" poster="/assets/screenshot.png" /> - # video_tag("/trailers/hd.avi", size: "16x16") - # # => <video src="/trailers/hd.avi" width="16" height="16" /> - # video_tag("/trailers/hd.avi", height: '32', width: '32') - # # => <video height="32" src="/trailers/hd.avi" width="32" /> - # video_tag("trailer.ogg", "trailer.flv") - # # => <video><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video> - # video_tag(["trailer.ogg", "trailer.flv"]) - # # => <video><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video> - # video_tag(["trailer.ogg", "trailer.flv"], size: "160x120") - # # => <video height="120" width="160"><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video> - def video_tag(*sources) - multiple_sources_tag('video', sources) do |options| - options[:poster] = path_to_image(options[:poster]) if options[:poster] - - if size = options.delete(:size) - options[:width], options[:height] = size.split("x") if size =~ %r{^\d+x\d+$} - end - end - end - - # Returns an HTML audio tag for the +source+. - # The +source+ can be full path or file that exists in - # your public audios directory. - # - # audio_tag("sound") - # # => <audio src="/audios/sound" /> - # audio_tag("sound.wav") - # # => <audio src="/audios/sound.wav" /> - # audio_tag("sound.wav", autoplay: true, controls: true) - # # => <audio autoplay="autoplay" controls="controls" src="/audios/sound.wav" /> - # audio_tag("sound.wav", "sound.mid") - # # => <audio><source src="/audios/sound.wav" /><source src="/audios/sound.mid" /></audio> - def audio_tag(*sources) - multiple_sources_tag('audio', sources) - end - - private - def multiple_sources_tag(type, sources) - options = sources.extract_options!.symbolize_keys - sources.flatten! - - yield options if block_given? - - if sources.size > 1 - content_tag(type, options) do - safe_join sources.map { |source| tag("source", :src => send("path_to_#{type}", source)) } - end - else - options[:src] = send("path_to_#{type}", sources.first) - content_tag(type, nil, options) - end - end - end - end -end diff --git a/actionpack/lib/action_view/helpers/asset_url_helper.rb b/actionpack/lib/action_view/helpers/asset_url_helper.rb deleted file mode 100644 index 71b78cf0b5..0000000000 --- a/actionpack/lib/action_view/helpers/asset_url_helper.rb +++ /dev/null @@ -1,354 +0,0 @@ -require 'zlib' - -module ActionView - # = Action View Asset URL Helpers - module Helpers - # This module provides methods for generating asset paths and - # urls. - # - # image_path("rails.png") - # # => "/assets/rails.png" - # - # image_url("rails.png") - # # => "http://www.example.com/assets/rails.png" - # - # === Using asset hosts - # - # By default, Rails links to these assets on the current host in the public - # folder, but you can direct Rails to link to assets from a dedicated asset - # server by setting <tt>ActionController::Base.asset_host</tt> in the application - # configuration, typically in <tt>config/environments/production.rb</tt>. - # For example, you'd define <tt>assets.example.com</tt> to be your asset - # host this way, inside the <tt>configure</tt> block of your environment-specific - # configuration files or <tt>config/application.rb</tt>: - # - # config.action_controller.asset_host = "assets.example.com" - # - # Helpers take that into account: - # - # image_tag("rails.png") - # # => <img alt="Rails" src="http://assets.example.com/assets/rails.png" /> - # stylesheet_link_tag("application") - # # => <link href="http://assets.example.com/assets/application.css" media="screen" rel="stylesheet" /> - # - # Browsers typically open at most two simultaneous connections to a single - # host, which means your assets often have to wait for other assets to finish - # downloading. You can alleviate this by using a <tt>%d</tt> wildcard in the - # +asset_host+. For example, "assets%d.example.com". If that wildcard is - # present Rails distributes asset requests among the corresponding four hosts - # "assets0.example.com", ..., "assets3.example.com". With this trick browsers - # will open eight simultaneous connections rather than two. - # - # image_tag("rails.png") - # # => <img alt="Rails" src="http://assets0.example.com/assets/rails.png" /> - # stylesheet_link_tag("application") - # # => <link href="http://assets2.example.com/assets/application.css" media="screen" rel="stylesheet" /> - # - # To do this, you can either setup four actual hosts, or you can use wildcard - # DNS to CNAME the wildcard to a single asset host. You can read more about - # setting up your DNS CNAME records from your ISP. - # - # Note: This is purely a browser performance optimization and is not meant - # for server load balancing. See http://www.die.net/musings/page_load_time/ - # for background. - # - # Alternatively, you can exert more control over the asset host by setting - # +asset_host+ to a proc like this: - # - # ActionController::Base.asset_host = Proc.new { |source| - # "http://assets#{Digest::MD5.hexdigest(source).to_i(16) % 2 + 1}.example.com" - # } - # image_tag("rails.png") - # # => <img alt="Rails" src="http://assets1.example.com/assets/rails.png" /> - # stylesheet_link_tag("application") - # # => <link href="http://assets2.example.com/assets/application.css" media="screen" rel="stylesheet" /> - # - # The example above generates "http://assets1.example.com" and - # "http://assets2.example.com". This option is useful for example if - # you need fewer/more than four hosts, custom host names, etc. - # - # As you see the proc takes a +source+ parameter. That's a string with the - # absolute path of the asset, for example "/assets/rails.png". - # - # ActionController::Base.asset_host = Proc.new { |source| - # if source.ends_with?('.css') - # "http://stylesheets.example.com" - # else - # "http://assets.example.com" - # end - # } - # image_tag("rails.png") - # # => <img alt="Rails" src="http://assets.example.com/assets/rails.png" /> - # stylesheet_link_tag("application") - # # => <link href="http://stylesheets.example.com/assets/application.css" media="screen" rel="stylesheet" /> - # - # Alternatively you may ask for a second parameter +request+. That one is - # particularly useful for serving assets from an SSL-protected page. The - # example proc below disables asset hosting for HTTPS connections, while - # still sending assets for plain HTTP requests from asset hosts. If you don't - # have SSL certificates for each of the asset hosts this technique allows you - # to avoid warnings in the client about mixed media. - # - # config.action_controller.asset_host = Proc.new { |source, request| - # if request.ssl? - # "#{request.protocol}#{request.host_with_port}" - # else - # "#{request.protocol}assets.example.com" - # end - # } - # - # You can also implement a custom asset host object that responds to +call+ - # and takes either one or two parameters just like the proc. - # - # config.action_controller.asset_host = AssetHostingWithMinimumSsl.new( - # "http://asset%d.example.com", "https://asset1.example.com" - # ) - # - module AssetUrlHelper - URI_REGEXP = %r{^[-a-z]+://|^(?:cid|data):|^//} - - # Computes the path to asset in public directory. If :type - # options is set, a file extension will be appended and scoped - # to the corresponding public directory. - # - # All other asset *_path helpers delegate through this method. - # - # asset_path "application.js" # => /application.js - # asset_path "application", type: :javascript # => /javascripts/application.js - # asset_path "application", type: :stylesheet # => /stylesheets/application.css - # asset_path "http://www.example.com/js/xmlhr.js" # => http://www.example.com/js/xmlhr.js - def asset_path(source, options = {}) - source = source.to_s - return "" unless source.present? - return source if source =~ URI_REGEXP - - tail, source = source[/([\?#].+)$/], source.sub(/([\?#].+)$/, '') - - if extname = compute_asset_extname(source, options) - source = "#{source}#{extname}" - end - - if source[0] != ?/ - source = compute_asset_path(source, options) - end - - relative_url_root = defined?(config.relative_url_root) && config.relative_url_root - if relative_url_root - source = "#{relative_url_root}#{source}" unless source.starts_with?("#{relative_url_root}/") - end - - if host = compute_asset_host(source, options) - source = "#{host}#{source}" - end - - "#{source}#{tail}" - end - alias_method :path_to_asset, :asset_path # aliased to avoid conflicts with a asset_path named route - - # Computes the full URL to a asset in the public directory. This - # will use +asset_path+ internally, so most of their behaviors - # will be the same. - def asset_url(source, options = {}) - path_to_asset(source, options.merge(:protocol => :request)) - end - alias_method :url_to_asset, :asset_url # aliased to avoid conflicts with an asset_url named route - - ASSET_EXTENSIONS = { - javascript: '.js', - stylesheet: '.css' - } - - # Compute extname to append to asset path. Returns nil if - # nothing should be added. - def compute_asset_extname(source, options = {}) - return if options[:extname] == false - extname = options[:extname] || ASSET_EXTENSIONS[options[:type]] - extname if extname && File.extname(source) != extname - end - - # Maps asset types to public directory. - ASSET_PUBLIC_DIRECTORIES = { - audio: '/audios', - font: '/fonts', - image: '/images', - javascript: '/javascripts', - stylesheet: '/stylesheets', - video: '/videos' - } - - # Computes asset path to public directory. Plugins and - # extensions can override this method to point to custom assets - # or generate digested paths or query strings. - def compute_asset_path(source, options = {}) - dir = ASSET_PUBLIC_DIRECTORIES[options[:type]] || "" - File.join(dir, source) - end - - # Pick an asset host for this source. Returns +nil+ if no host is set, - # the host if no wildcard is set, the host interpolated with the - # numbers 0-3 if it contains <tt>%d</tt> (the number is the source hash mod 4), - # or the value returned from invoking call on an object responding to call - # (proc or otherwise). - def compute_asset_host(source = "", options = {}) - request = self.request if respond_to?(:request) - host = config.asset_host if defined? config.asset_host - host ||= request.base_url if request && options[:protocol] == :request - return unless host - - if host.respond_to?(:call) - arity = host.respond_to?(:arity) ? host.arity : host.method(:call).arity - args = [source] - args << request if request && (arity > 1 || arity < 0) - host = host.call(*args) - elsif host =~ /%d/ - host = host % (Zlib.crc32(source) % 4) - end - - if host =~ URI_REGEXP - host - else - protocol = options[:protocol] || config.default_asset_host_protocol || (request ? :request : :relative) - case protocol - when :relative - "//#{host}" - when :request - "#{request.protocol}#{host}" - else - "#{protocol}://#{host}" - end - end - end - - # Computes the path to a javascript asset in the public javascripts directory. - # If the +source+ filename has no extension, .js will be appended (except for explicit URIs) - # Full paths from the document root will be passed through. - # Used internally by javascript_include_tag to build the script path. - # - # javascript_path "xmlhr" # => /javascripts/xmlhr.js - # javascript_path "dir/xmlhr.js" # => /javascripts/dir/xmlhr.js - # javascript_path "/dir/xmlhr" # => /dir/xmlhr.js - # javascript_path "http://www.example.com/js/xmlhr" # => http://www.example.com/js/xmlhr - # javascript_path "http://www.example.com/js/xmlhr.js" # => http://www.example.com/js/xmlhr.js - def javascript_path(source, options = {}) - path_to_asset(source, {type: :javascript}.merge!(options)) - end - alias_method :path_to_javascript, :javascript_path # aliased to avoid conflicts with a javascript_path named route - - # Computes the full URL to a javascript asset in the public javascripts directory. - # This will use +javascript_path+ internally, so most of their behaviors will be the same. - def javascript_url(source, options = {}) - url_to_asset(source, {type: :javascript}.merge!(options)) - end - alias_method :url_to_javascript, :javascript_url # aliased to avoid conflicts with a javascript_url named route - - # Computes the path to a stylesheet asset in the public stylesheets directory. - # If the +source+ filename has no extension, <tt>.css</tt> will be appended (except for explicit URIs). - # Full paths from the document root will be passed through. - # Used internally by +stylesheet_link_tag+ to build the stylesheet path. - # - # stylesheet_path "style" # => /stylesheets/style.css - # stylesheet_path "dir/style.css" # => /stylesheets/dir/style.css - # stylesheet_path "/dir/style.css" # => /dir/style.css - # stylesheet_path "http://www.example.com/css/style" # => http://www.example.com/css/style - # stylesheet_path "http://www.example.com/css/style.css" # => http://www.example.com/css/style.css - def stylesheet_path(source, options = {}) - path_to_asset(source, {type: :stylesheet}.merge!(options)) - end - alias_method :path_to_stylesheet, :stylesheet_path # aliased to avoid conflicts with a stylesheet_path named route - - # Computes the full URL to a stylesheet asset in the public stylesheets directory. - # This will use +stylesheet_path+ internally, so most of their behaviors will be the same. - def stylesheet_url(source, options = {}) - url_to_asset(source, {type: :stylesheet}.merge!(options)) - end - alias_method :url_to_stylesheet, :stylesheet_url # aliased to avoid conflicts with a stylesheet_url named route - - # Computes the path to an image asset. - # Full paths from the document root will be passed through. - # Used internally by +image_tag+ to build the image path: - # - # image_path("edit") # => "/assets/edit" - # image_path("edit.png") # => "/assets/edit.png" - # image_path("icons/edit.png") # => "/assets/icons/edit.png" - # image_path("/icons/edit.png") # => "/icons/edit.png" - # image_path("http://www.example.com/img/edit.png") # => "http://www.example.com/img/edit.png" - # - # If you have images as application resources this method may conflict with their named routes. - # The alias +path_to_image+ is provided to avoid that. Rails uses the alias internally, and - # plugin authors are encouraged to do so. - def image_path(source, options = {}) - path_to_asset(source, {type: :image}.merge!(options)) - end - alias_method :path_to_image, :image_path # aliased to avoid conflicts with an image_path named route - - # Computes the full URL to an image asset. - # This will use +image_path+ internally, so most of their behaviors will be the same. - def image_url(source, options = {}) - url_to_asset(source, {type: :image}.merge!(options)) - end - alias_method :url_to_image, :image_url # aliased to avoid conflicts with an image_url named route - - # Computes the path to a video asset in the public videos directory. - # Full paths from the document root will be passed through. - # Used internally by +video_tag+ to build the video path. - # - # video_path("hd") # => /videos/hd - # video_path("hd.avi") # => /videos/hd.avi - # video_path("trailers/hd.avi") # => /videos/trailers/hd.avi - # video_path("/trailers/hd.avi") # => /trailers/hd.avi - # video_path("http://www.example.com/vid/hd.avi") # => http://www.example.com/vid/hd.avi - def video_path(source, options = {}) - path_to_asset(source, {type: :video}.merge!(options)) - end - alias_method :path_to_video, :video_path # aliased to avoid conflicts with a video_path named route - - # Computes the full URL to a video asset in the public videos directory. - # This will use +video_path+ internally, so most of their behaviors will be the same. - def video_url(source, options = {}) - url_to_asset(source, {type: :video}.merge!(options)) - end - alias_method :url_to_video, :video_url # aliased to avoid conflicts with an video_url named route - - # Computes the path to an audio asset in the public audios directory. - # Full paths from the document root will be passed through. - # Used internally by +audio_tag+ to build the audio path. - # - # audio_path("horse") # => /audios/horse - # audio_path("horse.wav") # => /audios/horse.wav - # audio_path("sounds/horse.wav") # => /audios/sounds/horse.wav - # audio_path("/sounds/horse.wav") # => /sounds/horse.wav - # audio_path("http://www.example.com/sounds/horse.wav") # => http://www.example.com/sounds/horse.wav - def audio_path(source, options = {}) - path_to_asset(source, {type: :audio}.merge!(options)) - end - alias_method :path_to_audio, :audio_path # aliased to avoid conflicts with an audio_path named route - - # Computes the full URL to an audio asset in the public audios directory. - # This will use +audio_path+ internally, so most of their behaviors will be the same. - def audio_url(source, options = {}) - url_to_asset(source, {type: :audio}.merge!(options)) - end - alias_method :url_to_audio, :audio_url # aliased to avoid conflicts with an audio_url named route - - # Computes the path to a font asset. - # Full paths from the document root will be passed through. - # - # font_path("font") # => /assets/font - # font_path("font.ttf") # => /assets/font.ttf - # font_path("dir/font.ttf") # => /assets/dir/font.ttf - # font_path("/dir/font.ttf") # => /dir/font.ttf - # font_path("http://www.example.com/dir/font.ttf") # => http://www.example.com/dir/font.ttf - def font_path(source, options = {}) - path_to_asset(source, {type: :font}.merge!(options)) - end - alias_method :path_to_font, :font_path # aliased to avoid conflicts with an font_path named route - - # Computes the full URL to a font asset. - # This will use +font_path+ internally, so most of their behaviors will be the same. - def font_url(source, options = {}) - url_to_asset(source, {type: :font}.merge!(options)) - end - alias_method :url_to_font, :font_url # aliased to avoid conflicts with an font_url named route - end - end -end diff --git a/actionpack/lib/action_view/helpers/atom_feed_helper.rb b/actionpack/lib/action_view/helpers/atom_feed_helper.rb deleted file mode 100644 index 42b1dd8933..0000000000 --- a/actionpack/lib/action_view/helpers/atom_feed_helper.rb +++ /dev/null @@ -1,203 +0,0 @@ -require 'set' - -module ActionView - # = Action View Atom Feed Helpers - module Helpers - module AtomFeedHelper - # Adds easy defaults to writing Atom feeds with the Builder template engine (this does not work on ERB or any other - # template languages). - # - # Full usage example: - # - # config/routes.rb: - # Basecamp::Application.routes.draw do - # resources :posts - # root to: "posts#index" - # end - # - # app/controllers/posts_controller.rb: - # class PostsController < ApplicationController::Base - # # GET /posts.html - # # GET /posts.atom - # def index - # @posts = Post.all - # - # respond_to do |format| - # format.html - # format.atom - # end - # end - # end - # - # app/views/posts/index.atom.builder: - # atom_feed do |feed| - # feed.title("My great blog!") - # feed.updated(@posts[0].created_at) if @posts.length > 0 - # - # @posts.each do |post| - # feed.entry(post) do |entry| - # entry.title(post.title) - # entry.content(post.body, type: 'html') - # - # entry.author do |author| - # author.name("DHH") - # end - # end - # end - # end - # - # The options for atom_feed are: - # - # * <tt>:language</tt>: Defaults to "en-US". - # * <tt>:root_url</tt>: The HTML alternative that this feed is doubling for. Defaults to / on the current host. - # * <tt>:url</tt>: The URL for this feed. Defaults to the current URL. - # * <tt>:id</tt>: The id for this feed. Defaults to "tag:#{request.host},#{options[:schema_date]}:#{request.fullpath.split(".")[0]}" - # * <tt>:schema_date</tt>: The date at which the tag scheme for the feed was first used. A good default is the year you - # created the feed. See http://feedvalidator.org/docs/error/InvalidTAG.html for more information. If not specified, - # 2005 is used (as an "I don't care" value). - # * <tt>:instruct</tt>: Hash of XML processing instructions in the form {target => {attribute => value, }} or {target => [{attribute => value, }, ]} - # - # Other namespaces can be added to the root element: - # - # app/views/posts/index.atom.builder: - # atom_feed({'xmlns:app' => 'http://www.w3.org/2007/app', - # 'xmlns:openSearch' => 'http://a9.com/-/spec/opensearch/1.1/'}) do |feed| - # feed.title("My great blog!") - # feed.updated((@posts.first.created_at)) - # feed.tag!(openSearch:totalResults, 10) - # - # @posts.each do |post| - # feed.entry(post) do |entry| - # entry.title(post.title) - # entry.content(post.body, type: 'html') - # entry.tag!('app:edited', Time.now) - # - # entry.author do |author| - # author.name("DHH") - # end - # end - # end - # end - # - # The Atom spec defines five elements (content rights title subtitle - # summary) which may directly contain xhtml content if type: 'xhtml' - # is specified as an attribute. If so, this helper will take care of - # the enclosing div and xhtml namespace declaration. Example usage: - # - # entry.summary type: 'xhtml' do |xhtml| - # xhtml.p pluralize(order.line_items.count, "line item") - # xhtml.p "Shipped to #{order.address}" - # xhtml.p "Paid by #{order.pay_type}" - # end - # - # - # <tt>atom_feed</tt> yields an +AtomFeedBuilder+ instance. Nested elements yield - # an +AtomBuilder+ instance. - def atom_feed(options = {}, &block) - if options[:schema_date] - options[:schema_date] = options[:schema_date].strftime("%Y-%m-%d") if options[:schema_date].respond_to?(:strftime) - else - options[:schema_date] = "2005" # The Atom spec copyright date - end - - xml = options.delete(:xml) || eval("xml", block.binding) - xml.instruct! - if options[:instruct] - options[:instruct].each do |target,attrs| - if attrs.respond_to?(:keys) - xml.instruct!(target, attrs) - elsif attrs.respond_to?(:each) - attrs.each { |attr_group| xml.instruct!(target, attr_group) } - end - end - end - - feed_opts = {"xml:lang" => options[:language] || "en-US", "xmlns" => 'http://www.w3.org/2005/Atom'} - feed_opts.merge!(options).reject!{|k,v| !k.to_s.match(/^xml/)} - - xml.feed(feed_opts) do - xml.id(options[:id] || "tag:#{request.host},#{options[:schema_date]}:#{request.fullpath.split(".")[0]}") - xml.link(:rel => 'alternate', :type => 'text/html', :href => options[:root_url] || (request.protocol + request.host_with_port)) - xml.link(:rel => 'self', :type => 'application/atom+xml', :href => options[:url] || request.url) - - yield AtomFeedBuilder.new(xml, self, options) - end - end - - class AtomBuilder #:nodoc: - XHTML_TAG_NAMES = %w(content rights title subtitle summary).to_set - - def initialize(xml) - @xml = xml - end - - private - # Delegate to xml builder, first wrapping the element in a xhtml - # namespaced div element if the method and arguments indicate - # that an xhtml_block? is desired. - def method_missing(method, *arguments, &block) - if xhtml_block?(method, arguments) - @xml.__send__(method, *arguments) do - @xml.div(:xmlns => 'http://www.w3.org/1999/xhtml') do |xhtml| - block.call(xhtml) - end - end - else - @xml.__send__(method, *arguments, &block) - end - end - - # True if the method name matches one of the five elements defined - # in the Atom spec as potentially containing XHTML content and - # if type: 'xhtml' is, in fact, specified. - def xhtml_block?(method, arguments) - if XHTML_TAG_NAMES.include?(method.to_s) - last = arguments.last - last.is_a?(Hash) && last[:type].to_s == 'xhtml' - end - end - end - - class AtomFeedBuilder < AtomBuilder #:nodoc: - def initialize(xml, view, feed_options = {}) - @xml, @view, @feed_options = xml, view, feed_options - end - - # Accepts a Date or Time object and inserts it in the proper format. If nil is passed, current time in UTC is used. - def updated(date_or_time = nil) - @xml.updated((date_or_time || Time.now.utc).xmlschema) - end - - # Creates an entry tag for a specific record and prefills the id using class and id. - # - # Options: - # - # * <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>: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 = {}) - @xml.entry do - @xml.id(options[:id] || "tag:#{@view.request.host},#{@feed_options[:schema_date]}:#{record.class}/#{record.id}") - - if options[:published] || (record.respond_to?(:created_at) && record.created_at) - @xml.published((options[:published] || record.created_at).xmlschema) - end - - if options[:updated] || (record.respond_to?(:updated_at) && record.updated_at) - @xml.updated((options[:updated] || record.updated_at).xmlschema) - end - - type = options.fetch(:type, 'text/html') - - @xml.link(:rel => 'alternate', :type => type, :href => options[:url] || @view.polymorphic_url(record)) - - yield AtomBuilder.new(@xml) - end - end - end - - end - end -end diff --git a/actionpack/lib/action_view/helpers/cache_helper.rb b/actionpack/lib/action_view/helpers/cache_helper.rb deleted file mode 100644 index 8fc78ea7fb..0000000000 --- a/actionpack/lib/action_view/helpers/cache_helper.rb +++ /dev/null @@ -1,196 +0,0 @@ -module ActionView - # = Action View Cache Helper - module Helpers - module CacheHelper - # This helper exposes a method for caching fragments of a view - # rather than an entire action or page. This technique is useful - # caching pieces like menus, lists of newstopics, static HTML - # fragments, and so on. This method takes a block that contains - # the content you wish to cache. - # - # The best way to use this is by doing key-based cache expiration - # on top of a cache store like Memcached that'll automatically - # kick out old entries. For more on key-based expiration, see: - # http://37signals.com/svn/posts/3113-how-key-based-cache-expiration-works - # - # When using this method, you list the cache dependency as the name of the cache, like so: - # - # <% cache project do %> - # <b>All the topics on this project</b> - # <%= render project.topics %> - # <% end %> - # - # This approach will assume that when a new topic is added, you'll touch - # the project. The cache key generated from this call will be something like: - # - # views/projects/123-20120806214154/7a1156131a6928cb0026877f8b749ac9 - # ^class ^id ^updated_at ^template tree digest - # - # The cache is thus automatically bumped whenever the project updated_at is touched. - # - # If your template cache depends on multiple sources (try to avoid this to keep things simple), - # you can name all these dependencies as part of an array: - # - # <% cache [ project, current_user ] do %> - # <b>All the topics on this project</b> - # <%= render project.topics %> - # <% end %> - # - # This will include both records as part of the cache key and updating either of them will - # expire the cache. - # - # ==== Template digest - # - # The template digest that's added to the cache key is computed by taking an md5 of the - # contents of the entire template file. This ensures that your caches will automatically - # expire when you change the template file. - # - # Note that the md5 is taken of the entire template file, not just what's within the - # cache do/end call. So it's possible that changing something outside of that call will - # still expire the cache. - # - # Additionally, the digestor will automatically look through your template file for - # explicit and implicit dependencies, and include those as part of the digest. - # - # The digestor can be bypassed by passing skip_digest: true as an option to the cache call: - # - # <% cache project, skip_digest: true do %> - # <b>All the topics on this project</b> - # <%= render project.topics %> - # <% end %> - # - # ==== Implicit dependencies - # - # Most template dependencies can be derived from calls to render in the template itself. - # Here are some examples of render calls that Cache Digests knows how to decode: - # - # render partial: "comments/comment", collection: commentable.comments - # render "comments/comments" - # render 'comments/comments' - # render('comments/comments') - # - # render "header" => render("comments/header") - # - # render(@topic) => render("topics/topic") - # render(topics) => render("topics/topic") - # render(message.topics) => render("topics/topic") - # - # It's not possible to derive all render calls like that, though. Here are a few examples of things that can't be derived: - # - # render group_of_attachments - # render @project.documents.where(published: true).order('created_at') - # - # You will have to rewrite those to the explicit form: - # - # render partial: 'attachments/attachment', collection: group_of_attachments - # render partial: 'documents/document', collection: @project.documents.where(published: true).order('created_at') - # - # === Explicit dependencies - # - # Some times you'll have template dependencies that can't be derived at all. This is typically - # the case when you have template rendering that happens in helpers. Here's an example: - # - # <%= render_sortable_todolists @project.todolists %> - # - # You'll need to use a special comment format to call those out: - # - # <%# Template Dependency: todolists/todolist %> - # <%= render_sortable_todolists @project.todolists %> - # - # The pattern used to match these is /# Template Dependency: ([^ ]+)/, so it's important that you type it out just so. - # You can only declare one template dependency per line. - # - # === External dependencies - # - # If you use a helper method, for example, inside of a cached block and you then update that helper, - # you'll have to bump the cache as well. It doesn't really matter how you do it, but the md5 of the template file - # must change. One recommendation is to simply be explicit in a comment, like: - # - # <%# Helper Dependency Updated: May 6, 2012 at 6pm %> - # <%= some_helper_method(person) %> - # - # Now all you'll have to do is change that timestamp when the helper method changes. - def cache(name = {}, options = nil, &block) - if controller.perform_caching - safe_concat(fragment_for(cache_fragment_name(name, options), options, &block)) - else - yield - end - - nil - end - - # Cache fragments of a view if +condition+ is true - # - # <%= cache_if admin?, project do %> - # <b>All the topics on this project</b> - # <%= render project.topics %> - # <% end %> - def cache_if(condition, name = {}, options = nil, &block) - if condition - cache(name, options, &block) - else - yield - end - - nil - end - - # Cache fragments of a view unless +condition+ is true - # - # <%= cache_unless admin?, project do %> - # <b>All the topics on this project</b> - # <%= render project.topics %> - # <% end %> - def cache_unless(condition, name = {}, options = nil, &block) - cache_if !condition, name, options, &block - end - - # This helper returns the name of a cache key for a given fragment cache - # call. By supplying skip_digest: true to cache, the digestion of cache - # fragments can be manually bypassed. This is useful when cache fragments - # cannot be manually expired unless you know the exact key which is the - # case when using memcached. - def cache_fragment_name(name = {}, options = nil) - skip_digest = options && options[:skip_digest] - - if skip_digest - name - else - fragment_name_with_digest(name) - end - end - - private - - def fragment_name_with_digest(name) #:nodoc: - if @virtual_path - [ - *Array(name.is_a?(Hash) ? controller.url_for(name).split("://").last : name), - Digestor.digest(@virtual_path, formats.last.to_sym, lookup_context, dependencies: view_cache_dependencies) - ] - else - name - end - end - - # TODO: Create an object that has caching read/write on it - def fragment_for(name = {}, options = nil, &block) #:nodoc: - if fragment = controller.read_fragment(name, options) - fragment - else - # VIEW TODO: Make #capture usable outside of ERB - # This dance is needed because Builder can't use capture - pos = output_buffer.length - yield - output_safe = output_buffer.html_safe? - fragment = output_buffer.slice!(pos..-1) - if output_safe - self.output_buffer = output_buffer.class.new(output_buffer) - end - controller.write_fragment(name, fragment, options) - end - end - end - end -end diff --git a/actionpack/lib/action_view/helpers/capture_helper.rb b/actionpack/lib/action_view/helpers/capture_helper.rb deleted file mode 100644 index 5afe435459..0000000000 --- a/actionpack/lib/action_view/helpers/capture_helper.rb +++ /dev/null @@ -1,216 +0,0 @@ -require 'active_support/core_ext/string/output_safety' - -module ActionView - # = Action View Capture Helper - module Helpers - # CaptureHelper exposes methods to let you extract generated markup which - # can be used in other parts of a template or layout file. - # - # It provides a method to capture blocks into variables through capture and - # a way to capture a block of markup for use in a layout through content_for. - module CaptureHelper - # The capture method allows you to extract part of a template into a - # variable. You can then use this variable anywhere in your templates or layout. - # - # The capture method can be used in ERB templates... - # - # <% @greeting = capture do %> - # Welcome to my shiny new web page! The date and time is - # <%= Time.now %> - # <% end %> - # - # ...and Builder (RXML) templates. - # - # @timestamp = capture do - # "The current timestamp is #{Time.now}." - # end - # - # You can then use that variable anywhere else. For example: - # - # <html> - # <head><title><%= @greeting %></title></head> - # <body> - # <b><%= @greeting %></b> - # </body></html> - # - def capture(*args) - value = nil - buffer = with_output_buffer { value = yield(*args) } - if string = buffer.presence || value and string.is_a?(String) - ERB::Util.html_escape string - end - end - - # Calling content_for stores a block of markup in an identifier for later use. - # In order to access this stored content in other templates, helper modules - # or the layout, you would pass the identifier as an argument to <tt>content_for</tt>. - # - # Note: <tt>yield</tt> can still be used to retrieve the stored content, but calling - # <tt>yield</tt> doesn't work in helper modules, while <tt>content_for</tt> does. - # - # <% content_for :not_authorized do %> - # alert('You are not authorized to do that!') - # <% end %> - # - # You can then use <tt>content_for :not_authorized</tt> anywhere in your templates. - # - # <%= content_for :not_authorized if current_user.nil? %> - # - # This is equivalent to: - # - # <%= yield :not_authorized if current_user.nil? %> - # - # <tt>content_for</tt>, however, can also be used in helper modules. - # - # module StorageHelper - # def stored_content - # content_for(:storage) || "Your storage is empty" - # end - # end - # - # This helper works just like normal helpers. - # - # <%= stored_content %> - # - # You can also use the <tt>yield</tt> syntax alongside an existing call to - # <tt>yield</tt> in a layout. For example: - # - # <%# This is the layout %> - # <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> - # <head> - # <title>My Website</title> - # <%= yield :script %> - # </head> - # <body> - # <%= yield %> - # </body> - # </html> - # - # And now, we'll create a view that has a <tt>content_for</tt> call that - # creates the <tt>script</tt> identifier. - # - # <%# This is our view %> - # Please login! - # - # <% content_for :script do %> - # <script>alert('You are not authorized to view this page!')</script> - # <% end %> - # - # Then, in another view, you could to do something like this: - # - # <%= link_to 'Logout', action: 'logout', remote: true %> - # - # <% content_for :script do %> - # <%= javascript_include_tag :defaults %> - # <% end %> - # - # That will place +script+ tags for your default set of JavaScript files on the page; - # this technique is useful if you'll only be using these scripts in a few views. - # - # Note that content_for concatenates (default) the blocks it is given for a particular - # identifier in order. For example: - # - # <% content_for :navigation do %> - # <li><%= link_to 'Home', action: 'index' %></li> - # <% end %> - # - # And in other place: - # - # <% content_for :navigation do %> - # <li><%= link_to 'Login', action: 'login' %></li> - # <% end %> - # - # Then, in another template or layout, this code would render both links in order: - # - # <ul><%= content_for :navigation %></ul> - # - # If the flush parameter is true content_for replaces the blocks it is given. For example: - # - # <% content_for :navigation do %> - # <li><%= link_to 'Home', action: 'index' %></li> - # <% end %> - # - # <%# Add some other content, or use a different template: %> - # - # <% content_for :navigation, flush: true do %> - # <li><%= link_to 'Login', action: 'login' %></li> - # <% end %> - # - # Then, in another template or layout, this code would render only the last link: - # - # <ul><%= content_for :navigation %></ul> - # - # Lastly, simple content can be passed as a parameter: - # - # <% content_for :script, javascript_include_tag(:defaults) %> - # - # WARNING: content_for is ignored in caches. So you shouldn't use it for elements that will be fragment cached. - def content_for(name, content = nil, options = {}, &block) - if content || block_given? - if block_given? - options = content if content - content = capture(&block) - end - if content - options[:flush] ? @view_flow.set(name, content) : @view_flow.append(name, content) - end - nil - else - @view_flow.get(name).presence - end - end - - # The same as +content_for+ but when used with streaming flushes - # straight back to the layout. In other words, if you want to - # concatenate several times to the same buffer when rendering a given - # template, you should use +content_for+, if not, use +provide+ to tell - # the layout to stop looking for more contents. - def provide(name, content = nil, &block) - content = capture(&block) if block_given? - result = @view_flow.append!(name, content) if content - result unless content - end - - # content_for? checks whether any content has been captured yet using `content_for`. - # Useful to render parts of your layout differently based on what is in your views. - # - # <%# This is the layout %> - # <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> - # <head> - # <title>My Website</title> - # <%= yield :script %> - # </head> - # <body class="<%= content_for?(:right_col) ? 'two-column' : 'one-column' %>"> - # <%= yield %> - # <%= yield :right_col %> - # </body> - # </html> - def content_for?(name) - @view_flow.get(name).present? - end - - # Use an alternate output buffer for the duration of the block. - # Defaults to a new empty string. - def with_output_buffer(buf = nil) #:nodoc: - unless buf - buf = ActionView::OutputBuffer.new - buf.force_encoding(output_buffer.encoding) if output_buffer - end - self.output_buffer, old_buffer = buf, output_buffer - yield - output_buffer - ensure - self.output_buffer = old_buffer - end - - # Add the output buffer to the response body and start a new one. - def flush_output_buffer #:nodoc: - if output_buffer && !output_buffer.empty? - response.stream.write output_buffer - self.output_buffer = output_buffer.respond_to?(:clone_empty) ? output_buffer.clone_empty : output_buffer[0, 0] - nil - end - end - end - end -end diff --git a/actionpack/lib/action_view/helpers/controller_helper.rb b/actionpack/lib/action_view/helpers/controller_helper.rb deleted file mode 100644 index 74ef25f7c1..0000000000 --- a/actionpack/lib/action_view/helpers/controller_helper.rb +++ /dev/null @@ -1,25 +0,0 @@ -require 'active_support/core_ext/module/attr_internal' - -module ActionView - module Helpers - # This module keeps all methods and behavior in ActionView - # that simply delegates to the controller. - module ControllerHelper #:nodoc: - attr_internal :controller, :request - - delegate :request_forgery_protection_token, :params, :session, :cookies, :response, :headers, - :flash, :action_name, :controller_name, :controller_path, :to => :controller - - def assign_controller(controller) - if @_controller = controller - @_request = controller.request if controller.respond_to?(:request) - @_config = controller.config.inheritable_copy if controller.respond_to?(:config) - end - end - - def logger - controller.logger if controller.respond_to?(:logger) - end - end - end -end diff --git a/actionpack/lib/action_view/helpers/csrf_helper.rb b/actionpack/lib/action_view/helpers/csrf_helper.rb deleted file mode 100644 index eeb0ed94b9..0000000000 --- a/actionpack/lib/action_view/helpers/csrf_helper.rb +++ /dev/null @@ -1,30 +0,0 @@ -module ActionView - # = Action View CSRF Helper - module Helpers - module CsrfHelper - # Returns meta tags "csrf-param" and "csrf-token" with the name of the cross-site - # request forgery protection parameter and token, respectively. - # - # <head> - # <%= csrf_meta_tags %> - # </head> - # - # These are used to generate the dynamic forms that implement non-remote links with - # <tt>:method</tt>. - # - # Note that regular forms generate hidden fields, and that Ajax calls are whitelisted, - # so they do not use these tags. - def csrf_meta_tags - if protect_against_forgery? - [ - tag('meta', :name => 'csrf-param', :content => request_forgery_protection_token), - tag('meta', :name => 'csrf-token', :content => form_authenticity_token) - ].join("\n").html_safe - end - end - - # For backwards compatibility. - alias csrf_meta_tag csrf_meta_tags - end - end -end diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb deleted file mode 100644 index d3953c26b7..0000000000 --- a/actionpack/lib/action_view/helpers/date_helper.rb +++ /dev/null @@ -1,1083 +0,0 @@ -require 'date' -require 'action_view/helpers/tag_helper' -require 'active_support/core_ext/array/extract_options' -require 'active_support/core_ext/date/conversions' -require 'active_support/core_ext/hash/slice' -require 'active_support/core_ext/object/with_options' - -module ActionView - module Helpers - # = Action View Date Helpers - # - # The Date Helper primarily creates select/option tags for different kinds of dates and times or date and time - # elements. All of the select-type methods share a number of common options that are as follows: - # - # * <tt>:prefix</tt> - overwrites the default prefix of "date" used for the select names. So specifying "birthday" - # would give \birthday[month] instead of \date[month] if passed to the <tt>select_month</tt> method. - # * <tt>:include_blank</tt> - set to true if it should be possible to set an empty date. - # * <tt>:discard_type</tt> - set to true if you want to discard the type part of the select name. If set to true, - # the <tt>select_month</tt> method would use simply "date" (which can be overwritten using <tt>:prefix</tt>) instead - # of \date[month]. - module DateHelper - # Reports the approximate distance in time between two Time, Date or DateTime objects or integers as seconds. - # Pass <tt>include_seconds: true</tt> if you want more detailed approximations when distance < 1 min, 29 secs. - # Distances are reported based on the following table: - # - # 0 <-> 29 secs # => less than a minute - # 30 secs <-> 1 min, 29 secs # => 1 minute - # 1 min, 30 secs <-> 44 mins, 29 secs # => [2..44] minutes - # 44 mins, 30 secs <-> 89 mins, 29 secs # => about 1 hour - # 89 mins, 30 secs <-> 23 hrs, 59 mins, 29 secs # => about [2..24] hours - # 23 hrs, 59 mins, 30 secs <-> 41 hrs, 59 mins, 29 secs # => 1 day - # 41 hrs, 59 mins, 30 secs <-> 29 days, 23 hrs, 59 mins, 29 secs # => [2..29] days - # 29 days, 23 hrs, 59 mins, 30 secs <-> 44 days, 23 hrs, 59 mins, 29 secs # => about 1 month - # 44 days, 23 hrs, 59 mins, 30 secs <-> 59 days, 23 hrs, 59 mins, 29 secs # => about 2 months - # 59 days, 23 hrs, 59 mins, 30 secs <-> 1 yr minus 1 sec # => [2..12] months - # 1 yr <-> 1 yr, 3 months # => about 1 year - # 1 yr, 3 months <-> 1 yr, 9 months # => over 1 year - # 1 yr, 9 months <-> 2 yr minus 1 sec # => almost 2 years - # 2 yrs <-> max time or date # => (same rules as 1 yr) - # - # With <tt>include_seconds: true</tt> and the difference < 1 minute 29 seconds: - # 0-4 secs # => less than 5 seconds - # 5-9 secs # => less than 10 seconds - # 10-19 secs # => less than 20 seconds - # 20-39 secs # => half a minute - # 40-59 secs # => less than a minute - # 60-89 secs # => 1 minute - # - # from_time = Time.now - # distance_of_time_in_words(from_time, from_time + 50.minutes) # => about 1 hour - # distance_of_time_in_words(from_time, 50.minutes.from_now) # => about 1 hour - # distance_of_time_in_words(from_time, from_time + 15.seconds) # => less than a minute - # distance_of_time_in_words(from_time, from_time + 15.seconds, include_seconds: true) # => less than 20 seconds - # distance_of_time_in_words(from_time, 3.years.from_now) # => about 3 years - # distance_of_time_in_words(from_time, from_time + 60.hours) # => 3 days - # distance_of_time_in_words(from_time, from_time + 45.seconds, include_seconds: true) # => less than a minute - # distance_of_time_in_words(from_time, from_time - 45.seconds, include_seconds: true) # => less than a minute - # distance_of_time_in_words(from_time, 76.seconds.from_now) # => 1 minute - # distance_of_time_in_words(from_time, from_time + 1.year + 3.days) # => about 1 year - # distance_of_time_in_words(from_time, from_time + 3.years + 6.months) # => over 3 years - # distance_of_time_in_words(from_time, from_time + 4.years + 9.days + 30.minutes + 5.seconds) # => about 4 years - # - # to_time = Time.now + 6.years + 19.days - # distance_of_time_in_words(from_time, to_time, include_seconds: true) # => about 6 years - # distance_of_time_in_words(to_time, from_time, include_seconds: true) # => about 6 years - # distance_of_time_in_words(Time.now, Time.now) # => less than a minute - def distance_of_time_in_words(from_time, to_time = 0, include_seconds_or_options = {}, options = {}) - if include_seconds_or_options.is_a?(Hash) - options = include_seconds_or_options - else - ActiveSupport::Deprecation.warn "distance_of_time_in_words and time_ago_in_words now accept :include_seconds " + - "as a part of options hash, not a boolean argument" - options[:include_seconds] ||= !!include_seconds_or_options - end - - options = { - scope: :'datetime.distance_in_words' - }.merge!(options) - - from_time = from_time.to_time if from_time.respond_to?(:to_time) - to_time = to_time.to_time if to_time.respond_to?(:to_time) - from_time, to_time = to_time, from_time if from_time > to_time - distance_in_minutes = ((to_time - from_time)/60.0).round - distance_in_seconds = (to_time - from_time).round - - I18n.with_options :locale => options[:locale], :scope => options[:scope] do |locale| - case distance_in_minutes - when 0..1 - return distance_in_minutes == 0 ? - locale.t(:less_than_x_minutes, :count => 1) : - locale.t(:x_minutes, :count => distance_in_minutes) unless options[:include_seconds] - - case distance_in_seconds - when 0..4 then locale.t :less_than_x_seconds, :count => 5 - when 5..9 then locale.t :less_than_x_seconds, :count => 10 - when 10..19 then locale.t :less_than_x_seconds, :count => 20 - when 20..39 then locale.t :half_a_minute - when 40..59 then locale.t :less_than_x_minutes, :count => 1 - else locale.t :x_minutes, :count => 1 - end - - when 2...45 then locale.t :x_minutes, :count => distance_in_minutes - when 45...90 then locale.t :about_x_hours, :count => 1 - # 90 mins up to 24 hours - when 90...1440 then locale.t :about_x_hours, :count => (distance_in_minutes.to_f / 60.0).round - # 24 hours up to 42 hours - when 1440...2520 then locale.t :x_days, :count => 1 - # 42 hours up to 30 days - when 2520...43200 then locale.t :x_days, :count => (distance_in_minutes.to_f / 1440.0).round - # 30 days up to 60 days - when 43200...86400 then locale.t :about_x_months, :count => (distance_in_minutes.to_f / 43200.0).round - # 60 days up to 365 days - when 86400...525600 then locale.t :x_months, :count => (distance_in_minutes.to_f / 43200.0).round - else - if from_time.acts_like?(:time) && to_time.acts_like?(:time) - fyear = from_time.year - fyear += 1 if from_time.month >= 3 - tyear = to_time.year - tyear -= 1 if to_time.month < 3 - leap_years = (fyear > tyear) ? 0 : (fyear..tyear).count{|x| Date.leap?(x)} - minute_offset_for_leap_year = leap_years * 1440 - # Discount the leap year days when calculating year distance. - # e.g. if there are 20 leap year days between 2 dates having the same day - # and month then the based on 365 days calculation - # the distance in years will come out to over 80 years when in written - # english it would read better as about 80 years. - minutes_with_offset = distance_in_minutes - minute_offset_for_leap_year - else - minutes_with_offset = distance_in_minutes - end - remainder = (minutes_with_offset % 525600) - distance_in_years = (minutes_with_offset.div 525600) - if remainder < 131400 - locale.t(:about_x_years, :count => distance_in_years) - elsif remainder < 394200 - locale.t(:over_x_years, :count => distance_in_years) - else - locale.t(:almost_x_years, :count => distance_in_years + 1) - end - end - end - end - - # Like <tt>distance_of_time_in_words</tt>, but where <tt>to_time</tt> is fixed to <tt>Time.now</tt>. - # - # time_ago_in_words(3.minutes.from_now) # => 3 minutes - # time_ago_in_words(3.minutes.ago) # => 3 minutes - # time_ago_in_words(Time.now - 15.hours) # => about 15 hours - # time_ago_in_words(Time.now) # => less than a minute - # time_ago_in_words(Time.now, include_seconds: true) # => less than 5 seconds - # - # from_time = Time.now - 3.days - 14.minutes - 25.seconds - # time_ago_in_words(from_time) # => 3 days - # - # from_time = (3.days + 14.minutes + 25.seconds).ago - # time_ago_in_words(from_time) # => 3 days - # - # Note that you cannot pass a <tt>Numeric</tt> value to <tt>time_ago_in_words</tt>. - # - def time_ago_in_words(from_time, include_seconds_or_options = {}) - distance_of_time_in_words(from_time, Time.now, include_seconds_or_options) - end - - alias_method :distance_of_time_in_words_to_now, :time_ago_in_words - - # Returns a set of select tags (one for year, month, and day) pre-selected for accessing a specified date-based - # attribute (identified by +method+) on an object assigned to the template (identified by +object+). - # - # ==== Options - # * <tt>:use_month_numbers</tt> - Set to true if you want to use month numbers rather than month names (e.g. - # "2" instead of "February"). - # * <tt>:use_two_digit_numbers</tt> - Set to true if you want to display two digit month and day numbers (e.g. - # "02" instead of "February" and "08" instead of "8"). - # * <tt>:use_short_month</tt> - Set to true if you want to use abbreviated month names instead of full - # month names (e.g. "Feb" instead of "February"). - # * <tt>:add_month_numbers</tt> - Set to true if you want to use both month numbers and month names (e.g. - # "2 - February" instead of "February"). - # * <tt>:use_month_names</tt> - Set to an array with 12 month names if you want to customize month names. - # Note: You can also use Rails' i18n functionality for this. - # * <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>Time.now.year - 5</tt>. - # * <tt>:end_year</tt> - Set the end year for the year select. Default is <tt>Time.now.year + 5</tt>. - # * <tt>:discard_day</tt> - Set to true if you don't want to show a day select. This includes the day - # as a hidden field instead of showing a select field. Also note that this implicitly sets the day to be the - # first of the given month in order to not create invalid dates like 31 February. - # * <tt>:discard_month</tt> - Set to true if you don't want to show a month select. This includes the month - # as a hidden field instead of showing a select field. Also note that this implicitly sets :discard_day to true. - # * <tt>:discard_year</tt> - Set to true if you don't want to show a year select. This includes the year - # as a hidden field instead of showing a select field. - # * <tt>:order</tt> - Set to an array containing <tt>:day</tt>, <tt>:month</tt> and <tt>:year</tt> to - # customize the order in which the select fields are shown. If you leave out any of the symbols, the respective - # select will not be shown (like when you set <tt>discard_xxx: true</tt>. Defaults to the order defined in - # the respective locale (e.g. [:year, :month, :day] in the en locale that ships with Rails). - # * <tt>:include_blank</tt> - Include a blank option in every select field so it's possible to set empty - # dates. - # * <tt>:default</tt> - Set a default date if the affected date isn't set or is nil. - # * <tt>:selected</tt> - Set a date that overrides the actual value. - # * <tt>:disabled</tt> - Set to true if you want show the select fields as disabled. - # * <tt>:prompt</tt> - Set to true (for a generic prompt), a prompt string or a hash of prompt strings - # for <tt>:year</tt>, <tt>:month</tt>, <tt>:day</tt>, <tt>:hour</tt>, <tt>:minute</tt> and <tt>:second</tt>. - # Setting this option prepends a select option with a generic prompt (Day, Month, Year, Hour, Minute, Seconds) - # or the given prompt string. - # * <tt>:with_css_classes</tt> - Set to true if you want assign different styles for 'select' tags. This option - # automatically set classes 'year', 'month', 'day', 'hour', 'minute' and 'second' for your 'select' tags. - # - # If anything is passed in the +html_options+ hash it will be applied to every select tag in the set. - # - # NOTE: Discarded selects will default to 1. So if no month select is available, January will be assumed. - # - # # Generates a date select that when POSTed is stored in the article variable, in the written_on attribute. - # date_select("article", "written_on") - # - # # Generates a date select that when POSTed is stored in the article variable, in the written_on attribute, - # # with the year in the year drop down box starting at 1995. - # date_select("article", "written_on", start_year: 1995) - # - # # Generates a date select that when POSTed is stored in the article variable, in the written_on attribute, - # # with the year in the year drop down box starting at 1995, numbers used for months instead of words, - # # and without a day select box. - # date_select("article", "written_on", start_year: 1995, use_month_numbers: true, - # discard_day: true, include_blank: true) - # - # # Generates a date select that when POSTed is stored in the article variable, in the written_on attribute, - # # with two digit numbers used for months and days. - # date_select("article", "written_on", use_two_digit_numbers: true) - # - # # Generates a date select that when POSTed is stored in the article variable, in the written_on attribute - # # with the fields ordered as day, month, year rather than month, day, year. - # date_select("article", "written_on", order: [:day, :month, :year]) - # - # # Generates a date select that when POSTed is stored in the user variable, in the birthday attribute - # # lacking a year field. - # date_select("user", "birthday", order: [:month, :day]) - # - # # Generates a date select that when POSTed is stored in the article variable, in the written_on attribute - # # which is initially set to the date 3 days from the current date - # date_select("article", "written_on", default: 3.days.from_now) - # - # # Generates a date select that when POSTed is stored in the article variable, in the written_on attribute - # # which is set in the form with todays date, regardless of the value in the Active Record object. - # date_select("article", "written_on", selected: Date.today) - # - # # Generates a date select that when POSTed is stored in the credit_card variable, in the bill_due attribute - # # that will have a default day of 20. - # date_select("credit_card", "bill_due", default: { day: 20 }) - # - # # Generates a date select with custom prompts. - # date_select("article", "written_on", prompt: { day: 'Select day', month: 'Select month', year: 'Select year' }) - # - # The selects are prepared for multi-parameter assignment to an Active Record object. - # - # Note: If the day is not included as an option but the month is, the day will be set to the 1st to ensure that - # all month choices are valid. - def date_select(object_name, method, options = {}, html_options = {}) - Tags::DateSelect.new(object_name, method, self, options, html_options).render - end - - # Returns a set of select tags (one for hour, minute and optionally second) pre-selected for accessing a - # specified time-based attribute (identified by +method+) on an object assigned to the template (identified by - # +object+). You can include the seconds with <tt>:include_seconds</tt>. You can get hours in the AM/PM format - # with <tt>:ampm</tt> option. - # - # This method will also generate 3 input hidden tags, for the actual year, month and day unless the option - # <tt>:ignore_date</tt> is set to +true+. If you set the <tt>:ignore_date</tt> to +true+, you must have a - # +date_select+ on the same method within the form otherwise an exception will be raised. - # - # If anything is passed in the html_options hash it will be applied to every select tag in the set. - # - # # Creates a time select tag that, when POSTed, will be stored in the article variable in the sunrise attribute. - # time_select("article", "sunrise") - # - # # Creates a time select tag with a seconds field that, when POSTed, will be stored in the article variables in - # # the sunrise attribute. - # time_select("article", "start_time", include_seconds: true) - # - # # You can set the <tt>:minute_step</tt> to 15 which will give you: 00, 15, 30 and 45. - # time_select 'game', 'game_time', {minute_step: 15} - # - # # Creates a time select tag with a custom prompt. Use <tt>prompt: true</tt> for generic prompts. - # time_select("article", "written_on", prompt: {hour: 'Choose hour', minute: 'Choose minute', second: 'Choose seconds'}) - # time_select("article", "written_on", prompt: {hour: true}) # generic prompt for hours - # time_select("article", "written_on", prompt: true) # generic prompts for all - # - # # You can set :ampm option to true which will show the hours as: 12 PM, 01 AM .. 11 PM. - # time_select 'game', 'game_time', {ampm: true} - # - # The selects are prepared for multi-parameter assignment to an Active Record object. - # - # Note: If the day is not included as an option but the month is, the day will be set to the 1st to ensure that - # all month choices are valid. - def time_select(object_name, method, options = {}, html_options = {}) - Tags::TimeSelect.new(object_name, method, self, options, html_options).render - end - - # Returns a set of select tags (one for year, month, day, hour, and minute) pre-selected for accessing a - # specified datetime-based attribute (identified by +method+) on an object assigned to the template (identified - # by +object+). - # - # If anything is passed in the html_options hash it will be applied to every select tag in the set. - # - # # Generates a datetime select that, when POSTed, will be stored in the article variable in the written_on - # # attribute. - # datetime_select("article", "written_on") - # - # # Generates a datetime select with a year select that starts at 1995 that, when POSTed, will be stored in the - # # article variable in the written_on attribute. - # datetime_select("article", "written_on", start_year: 1995) - # - # # Generates a datetime select with a default value of 3 days from the current time that, when POSTed, will - # # be stored in the trip variable in the departing attribute. - # datetime_select("trip", "departing", default: 3.days.from_now) - # - # # Generate a datetime select with hours in the AM/PM format - # datetime_select("article", "written_on", ampm: true) - # - # # Generates a datetime select that discards the type that, when POSTed, will be stored in the article variable - # # as the written_on attribute. - # datetime_select("article", "written_on", discard_type: true) - # - # # Generates a datetime select with a custom prompt. Use <tt>prompt: true</tt> for generic prompts. - # datetime_select("article", "written_on", prompt: {day: 'Choose day', month: 'Choose month', year: 'Choose year'}) - # datetime_select("article", "written_on", prompt: {hour: true}) # generic prompt for hours - # datetime_select("article", "written_on", prompt: true) # generic prompts for all - # - # The selects are prepared for multi-parameter assignment to an Active Record object. - def datetime_select(object_name, method, options = {}, html_options = {}) - Tags::DatetimeSelect.new(object_name, method, self, options, html_options).render - end - - # Returns a set of html select-tags (one for year, month, day, hour, minute, and second) pre-selected with the - # +datetime+. It's also possible to explicitly set the order of the tags using the <tt>:order</tt> option with - # an array of symbols <tt>:year</tt>, <tt>:month</tt> and <tt>:day</tt> in the desired order. If you do not - # supply a Symbol, it will be appended onto the <tt>:order</tt> passed in. You can also add - # <tt>:date_separator</tt>, <tt>:datetime_separator</tt> and <tt>:time_separator</tt> keys to the +options+ to - # control visual display of the elements. - # - # If anything is passed in the html_options hash it will be applied to every select tag in the set. - # - # my_date_time = Time.now + 4.days - # - # # Generates a datetime select that defaults to the datetime in my_date_time (four days after today). - # select_datetime(my_date_time) - # - # # Generates a datetime select that defaults to today (no specified datetime) - # select_datetime() - # - # # Generates a datetime select that defaults to the datetime in my_date_time (four days after today) - # # with the fields ordered year, month, day rather than month, day, year. - # select_datetime(my_date_time, order: [:year, :month, :day]) - # - # # Generates a datetime select that defaults to the datetime in my_date_time (four days after today) - # # with a '/' between each date field. - # select_datetime(my_date_time, date_separator: '/') - # - # # Generates a datetime select that defaults to the datetime in my_date_time (four days after today) - # # with a date fields separated by '/', time fields separated by '' and the date and time fields - # # separated by a comma (','). - # select_datetime(my_date_time, date_separator: '/', time_separator: '', datetime_separator: ',') - # - # # Generates a datetime select that discards the type of the field and defaults to the datetime in - # # my_date_time (four days after today) - # select_datetime(my_date_time, discard_type: true) - # - # # Generate a datetime field with hours in the AM/PM format - # select_datetime(my_date_time, ampm: true) - # - # # Generates a datetime select that defaults to the datetime in my_date_time (four days after today) - # # prefixed with 'payday' rather than 'date' - # select_datetime(my_date_time, prefix: 'payday') - # - # # Generates a datetime select with a custom prompt. Use <tt>prompt: true</tt> for generic prompts. - # select_datetime(my_date_time, prompt: {day: 'Choose day', month: 'Choose month', year: 'Choose year'}) - # select_datetime(my_date_time, prompt: {hour: true}) # generic prompt for hours - # select_datetime(my_date_time, prompt: true) # generic prompts for all - def select_datetime(datetime = Time.current, options = {}, html_options = {}) - DateTimeSelector.new(datetime, options, html_options).select_datetime - end - - # Returns a set of html select-tags (one for year, month, and day) pre-selected with the +date+. - # It's possible to explicitly set the order of the tags using the <tt>:order</tt> option with an array of - # symbols <tt>:year</tt>, <tt>:month</tt> and <tt>:day</tt> in the desired order. - # If the array passed to the <tt>:order</tt> option does not contain all the three symbols, all tags will be hidden. - # - # If anything is passed in the html_options hash it will be applied to every select tag in the set. - # - # my_date = Time.now + 6.days - # - # # Generates a date select that defaults to the date in my_date (six days after today). - # select_date(my_date) - # - # # Generates a date select that defaults to today (no specified date). - # select_date() - # - # # Generates a date select that defaults to the date in my_date (six days after today) - # # with the fields ordered year, month, day rather than month, day, year. - # select_date(my_date, order: [:year, :month, :day]) - # - # # Generates a date select that discards the type of the field and defaults to the date in - # # my_date (six days after today). - # select_date(my_date, discard_type: true) - # - # # Generates a date select that defaults to the date in my_date, - # # which has fields separated by '/'. - # select_date(my_date, date_separator: '/') - # - # # Generates a date select that defaults to the datetime in my_date (six days after today) - # # prefixed with 'payday' rather than 'date'. - # select_date(my_date, prefix: 'payday') - # - # # Generates a date select with a custom prompt. Use <tt>prompt: true</tt> for generic prompts. - # select_date(my_date, prompt: {day: 'Choose day', month: 'Choose month', year: 'Choose year'}) - # select_date(my_date, prompt: {hour: true}) # generic prompt for hours - # select_date(my_date, prompt: true) # generic prompts for all - def select_date(date = Date.current, options = {}, html_options = {}) - DateTimeSelector.new(date, options, html_options).select_date - end - - # Returns a set of html select-tags (one for hour and minute). - # You can set <tt>:time_separator</tt> key to format the output, and - # the <tt>:include_seconds</tt> option to include an input for seconds. - # - # If anything is passed in the html_options hash it will be applied to every select tag in the set. - # - # my_time = Time.now + 5.days + 7.hours + 3.minutes + 14.seconds - # - # # Generates a time select that defaults to the time in my_time. - # select_time(my_time) - # - # # Generates a time select that defaults to the current time (no specified time). - # select_time() - # - # # Generates a time select that defaults to the time in my_time, - # # which has fields separated by ':'. - # select_time(my_time, time_separator: ':') - # - # # Generates a time select that defaults to the time in my_time, - # # that also includes an input for seconds. - # select_time(my_time, include_seconds: true) - # - # # Generates a time select that defaults to the time in my_time, that has fields - # # separated by ':' and includes an input for seconds. - # select_time(my_time, time_separator: ':', include_seconds: true) - # - # # Generate a time select field with hours in the AM/PM format - # select_time(my_time, ampm: true) - # - # # Generates a time select field with hours that range from 2 to 14 - # select_time(my_time, start_hour: 2, end_hour: 14) - # - # # Generates a time select with a custom prompt. Use <tt>:prompt</tt> to true for generic prompts. - # select_time(my_time, prompt: {day: 'Choose day', month: 'Choose month', year: 'Choose year'}) - # select_time(my_time, prompt: {hour: true}) # generic prompt for hours - # select_time(my_time, prompt: true) # generic prompts for all - def select_time(datetime = Time.current, options = {}, html_options = {}) - DateTimeSelector.new(datetime, options, html_options).select_time - end - - # Returns a select tag with options for each of the seconds 0 through 59 with the current second selected. - # The <tt>datetime</tt> can be either a +Time+ or +DateTime+ object or an integer. - # Override the field name using the <tt>:field_name</tt> option, 'second' by default. - # - # my_time = Time.now + 16.minutes - # - # # Generates a select field for seconds that defaults to the seconds for the time in my_time. - # select_second(my_time) - # - # # Generates a select field for seconds that defaults to the number given. - # select_second(33) - # - # # Generates a select field for seconds that defaults to the seconds for the time in my_time - # # that is named 'interval' rather than 'second'. - # select_second(my_time, field_name: 'interval') - # - # # Generates a select field for seconds with a custom prompt. Use <tt>prompt: true</tt> for a - # # generic prompt. - # select_second(14, prompt: 'Choose seconds') - def select_second(datetime, options = {}, html_options = {}) - DateTimeSelector.new(datetime, options, html_options).select_second - end - - # Returns a select tag with options for each of the minutes 0 through 59 with the current minute selected. - # Also can return a select tag with options by <tt>minute_step</tt> from 0 through 59 with the 00 minute - # selected. The <tt>datetime</tt> can be either a +Time+ or +DateTime+ object or an integer. - # Override the field name using the <tt>:field_name</tt> option, 'minute' by default. - # - # my_time = Time.now + 6.hours - # - # # Generates a select field for minutes that defaults to the minutes for the time in my_time. - # select_minute(my_time) - # - # # Generates a select field for minutes that defaults to the number given. - # select_minute(14) - # - # # Generates a select field for minutes that defaults to the minutes for the time in my_time - # # that is named 'moment' rather than 'minute'. - # select_minute(my_time, field_name: 'moment') - # - # # Generates a select field for minutes with a custom prompt. Use <tt>prompt: true</tt> for a - # # generic prompt. - # select_minute(14, prompt: 'Choose minutes') - def select_minute(datetime, options = {}, html_options = {}) - DateTimeSelector.new(datetime, options, html_options).select_minute - end - - # Returns a select tag with options for each of the hours 0 through 23 with the current hour selected. - # The <tt>datetime</tt> can be either a +Time+ or +DateTime+ object or an integer. - # Override the field name using the <tt>:field_name</tt> option, 'hour' by default. - # - # my_time = Time.now + 6.hours - # - # # Generates a select field for hours that defaults to the hour for the time in my_time. - # select_hour(my_time) - # - # # Generates a select field for hours that defaults to the number given. - # select_hour(13) - # - # # Generates a select field for hours that defaults to the hour for the time in my_time - # # that is named 'stride' rather than 'hour'. - # select_hour(my_time, field_name: 'stride') - # - # # Generates a select field for hours with a custom prompt. Use <tt>prompt: true</tt> for a - # # generic prompt. - # select_hour(13, prompt: 'Choose hour') - # - # # Generate a select field for hours in the AM/PM format - # select_hour(my_time, ampm: true) - # - # # Generates a select field that includes options for hours from 2 to 14. - # select_hour(my_time, start_hour: 2, end_hour: 14) - def select_hour(datetime, options = {}, html_options = {}) - DateTimeSelector.new(datetime, options, html_options).select_hour - end - - # Returns a select tag with options for each of the days 1 through 31 with the current day selected. - # The <tt>date</tt> can also be substituted for a day number. - # If you want to display days with a leading zero set the <tt>:use_two_digit_numbers</tt> key in +options+ to true. - # Override the field name using the <tt>:field_name</tt> option, 'day' by default. - # - # my_date = Time.now + 2.days - # - # # Generates a select field for days that defaults to the day for the date in my_date. - # select_day(my_time) - # - # # Generates a select field for days that defaults to the number given. - # select_day(5) - # - # # Generates a select field for days that defaults to the number given, but displays it with two digits. - # select_day(5, use_two_digit_numbers: true) - # - # # Generates a select field for days that defaults to the day for the date in my_date - # # that is named 'due' rather than 'day'. - # select_day(my_time, field_name: 'due') - # - # # Generates a select field for days with a custom prompt. Use <tt>prompt: true</tt> for a - # # generic prompt. - # select_day(5, prompt: 'Choose day') - def select_day(date, options = {}, html_options = {}) - DateTimeSelector.new(date, options, html_options).select_day - end - - # Returns a select tag with options for each of the months January through December with the current month - # selected. The month names are presented as keys (what's shown to the user) and the month numbers (1-12) are - # used as values (what's submitted to the server). It's also possible to use month numbers for the presentation - # instead of names -- set the <tt>:use_month_numbers</tt> key in +options+ to true for this to happen. If you - # want both numbers and names, set the <tt>:add_month_numbers</tt> key in +options+ to true. If you would prefer - # to show month names as abbreviations, set the <tt>:use_short_month</tt> key in +options+ to true. If you want - # to use your own month names, set the <tt>:use_month_names</tt> key in +options+ to an array of 12 month names. - # If you want to display months with a leading zero set the <tt>:use_two_digit_numbers</tt> key in +options+ to true. - # Override the field name using the <tt>:field_name</tt> option, 'month' by default. - # - # # Generates a select field for months that defaults to the current month that - # # will use keys like "January", "March". - # select_month(Date.today) - # - # # Generates a select field for months that defaults to the current month that - # # is named "start" rather than "month". - # select_month(Date.today, field_name: 'start') - # - # # Generates a select field for months that defaults to the current month that - # # will use keys like "1", "3". - # select_month(Date.today, use_month_numbers: true) - # - # # Generates a select field for months that defaults to the current month that - # # will use keys like "1 - January", "3 - March". - # select_month(Date.today, add_month_numbers: true) - # - # # Generates a select field for months that defaults to the current month that - # # will use keys like "Jan", "Mar". - # select_month(Date.today, use_short_month: true) - # - # # Generates a select field for months that defaults to the current month that - # # will use keys like "Januar", "Marts." - # select_month(Date.today, use_month_names: %w(Januar Februar Marts ...)) - # - # # Generates a select field for months that defaults to the current month that - # # will use keys with two digit numbers like "01", "03". - # select_month(Date.today, use_two_digit_numbers: true) - # - # # Generates a select field for months with a custom prompt. Use <tt>prompt: true</tt> for a - # # generic prompt. - # select_month(14, prompt: 'Choose month') - def select_month(date, options = {}, html_options = {}) - DateTimeSelector.new(date, options, html_options).select_month - end - - # Returns a select tag with options for each of the five years on each side of the current, which is selected. - # The five year radius can be changed using the <tt>:start_year</tt> and <tt>:end_year</tt> keys in the - # +options+. Both ascending and descending year lists are supported by making <tt>:start_year</tt> less than or - # greater than <tt>:end_year</tt>. The <tt>date</tt> can also be substituted for a year given as a number. - # Override the field name using the <tt>:field_name</tt> option, 'year' by default. - # - # # Generates a select field for years that defaults to the current year that - # # has ascending year values. - # select_year(Date.today, start_year: 1992, end_year: 2007) - # - # # Generates a select field for years that defaults to the current year that - # # is named 'birth' rather than 'year'. - # select_year(Date.today, field_name: 'birth') - # - # # Generates a select field for years that defaults to the current year that - # # has descending year values. - # select_year(Date.today, start_year: 2005, end_year: 1900) - # - # # Generates a select field for years that defaults to the year 2006 that - # # has ascending year values. - # select_year(2006, start_year: 2000, end_year: 2010) - # - # # Generates a select field for years with a custom prompt. Use <tt>prompt: true</tt> for a - # # generic prompt. - # select_year(14, prompt: 'Choose year') - def select_year(date, options = {}, html_options = {}) - DateTimeSelector.new(date, options, html_options).select_year - end - - # Returns an html time tag for the given date or time. - # - # time_tag Date.today # => - # <time datetime="2010-11-04">November 04, 2010</time> - # time_tag Time.now # => - # <time datetime="2010-11-04T17:55:45+01:00">November 04, 2010 17:55</time> - # time_tag Date.yesterday, 'Yesterday' # => - # <time datetime="2010-11-03">Yesterday</time> - # time_tag Date.today, pubdate: true # => - # <time datetime="2010-11-04" pubdate="pubdate">November 04, 2010</time> - # time_tag Date.today, datetime: Date.today.strftime('%G-W%V') # => - # <time datetime="2010-W44">November 04, 2010</time> - # - # <%= time_tag Time.now do %> - # <span>Right now</span> - # <% end %> - # # => <time datetime="2010-11-04T17:55:45+01:00"><span>Right now</span></time> - def time_tag(date_or_time, *args, &block) - options = args.extract_options! - format = options.delete(:format) || :long - content = args.first || I18n.l(date_or_time, :format => format) - datetime = date_or_time.acts_like?(:time) ? date_or_time.xmlschema : date_or_time.iso8601 - - content_tag(:time, content, options.reverse_merge(:datetime => datetime), &block) - end - end - - class DateTimeSelector #:nodoc: - include ActionView::Helpers::TagHelper - - DEFAULT_PREFIX = 'date'.freeze - POSITION = { - :year => 1, :month => 2, :day => 3, :hour => 4, :minute => 5, :second => 6 - }.freeze - - AMPM_TRANSLATION = Hash[ - [[0, "12 AM"], [1, "01 AM"], [2, "02 AM"], [3, "03 AM"], - [4, "04 AM"], [5, "05 AM"], [6, "06 AM"], [7, "07 AM"], - [8, "08 AM"], [9, "09 AM"], [10, "10 AM"], [11, "11 AM"], - [12, "12 PM"], [13, "01 PM"], [14, "02 PM"], [15, "03 PM"], - [16, "04 PM"], [17, "05 PM"], [18, "06 PM"], [19, "07 PM"], - [20, "08 PM"], [21, "09 PM"], [22, "10 PM"], [23, "11 PM"]] - ].freeze - - def initialize(datetime, options = {}, html_options = {}) - @options = options.dup - @html_options = html_options.dup - @datetime = datetime - @options[:datetime_separator] ||= ' — ' - @options[:time_separator] ||= ' : ' - end - - def select_datetime - order = date_order.dup - order -= [:hour, :minute, :second] - @options[:discard_year] ||= true unless order.include?(:year) - @options[:discard_month] ||= true unless order.include?(:month) - @options[:discard_day] ||= true if @options[:discard_month] || !order.include?(:day) - @options[:discard_minute] ||= true if @options[:discard_hour] - @options[:discard_second] ||= true unless @options[:include_seconds] && !@options[:discard_minute] - - set_day_if_discarded - - if @options[:tag] && @options[:ignore_date] - select_time - else - [:day, :month, :year].each { |o| order.unshift(o) unless order.include?(o) } - order += [:hour, :minute, :second] unless @options[:discard_hour] - - build_selects_from_types(order) - end - end - - def select_date - order = date_order.dup - - @options[:discard_hour] = true - @options[:discard_minute] = true - @options[:discard_second] = true - - @options[:discard_year] ||= true unless order.include?(:year) - @options[:discard_month] ||= true unless order.include?(:month) - @options[:discard_day] ||= true if @options[:discard_month] || !order.include?(:day) - - set_day_if_discarded - - [:day, :month, :year].each { |o| order.unshift(o) unless order.include?(o) } - - build_selects_from_types(order) - end - - def select_time - order = [] - - @options[:discard_month] = true - @options[:discard_year] = true - @options[:discard_day] = true - @options[:discard_second] ||= true unless @options[:include_seconds] - - order += [:year, :month, :day] unless @options[:ignore_date] - - order += [:hour, :minute] - order << :second if @options[:include_seconds] - - build_selects_from_types(order) - end - - def select_second - if @options[:use_hidden] || @options[:discard_second] - build_hidden(:second, sec) if @options[:include_seconds] - else - build_options_and_select(:second, sec) - end - end - - def select_minute - if @options[:use_hidden] || @options[:discard_minute] - build_hidden(:minute, min) - else - build_options_and_select(:minute, min, :step => @options[:minute_step]) - end - end - - def select_hour - if @options[:use_hidden] || @options[:discard_hour] - build_hidden(:hour, hour) - else - options = {} - options[:ampm] = @options[:ampm] || false - options[:start] = @options[:start_hour] || 0 - options[:end] = @options[:end_hour] || 23 - build_options_and_select(:hour, hour, options) - end - end - - def select_day - if @options[:use_hidden] || @options[:discard_day] - build_hidden(:day, day || 1) - else - build_options_and_select(:day, day, :start => 1, :end => 31, :leading_zeros => false, :use_two_digit_numbers => @options[:use_two_digit_numbers]) - end - end - - def select_month - if @options[:use_hidden] || @options[:discard_month] - build_hidden(:month, month || 1) - else - month_options = [] - 1.upto(12) do |month_number| - options = { :value => month_number } - options[:selected] = "selected" if month == month_number - month_options << content_tag(:option, month_name(month_number), options) + "\n" - end - build_select(:month, month_options.join) - end - end - - def select_year - if !@datetime || @datetime == 0 - val = '1' - middle_year = Date.today.year - else - val = middle_year = year - end - - if @options[:use_hidden] || @options[:discard_year] - build_hidden(:year, val) - else - options = {} - options[:start] = @options[:start_year] || middle_year - 5 - options[:end] = @options[:end_year] || middle_year + 5 - options[:step] = options[:start] < options[:end] ? 1 : -1 - options[:leading_zeros] = false - options[:max_years_allowed] = @options[:max_years_allowed] || 1000 - - if (options[:end] - options[:start]).abs > options[:max_years_allowed] - raise ArgumentError, "There're too many years options to be built. Are you sure you haven't mistyped something? You can provide the :max_years_allowed parameter" - end - - build_options_and_select(:year, val, options) - end - end - - private - %w( sec min hour day month year ).each do |method| - define_method(method) do - @datetime.kind_of?(Numeric) ? @datetime : @datetime.send(method) if @datetime - end - end - - # If the day is hidden, the day should be set to the 1st so all month and year choices are - # valid. Otherwise, February 31st or February 29th, 2011 can be selected, which are invalid. - def set_day_if_discarded - if @datetime && @options[:discard_day] - @datetime = @datetime.change(:day => 1) - end - end - - # Returns translated month names, but also ensures that a custom month - # name array has a leading nil element. - def month_names - @month_names ||= begin - month_names = @options[:use_month_names] || translated_month_names - month_names.unshift(nil) if month_names.size < 13 - month_names - end - end - - # Returns translated month names. - # => [nil, "January", "February", "March", - # "April", "May", "June", "July", - # "August", "September", "October", - # "November", "December"] - # - # If <tt>:use_short_month</tt> option is set - # => [nil, "Jan", "Feb", "Mar", "Apr", "May", "Jun", - # "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] - def translated_month_names - key = @options[:use_short_month] ? :'date.abbr_month_names' : :'date.month_names' - I18n.translate(key, :locale => @options[:locale]) - end - - # Lookup month name for number. - # month_name(1) => "January" - # - # If <tt>:use_month_numbers</tt> option is passed - # month_name(1) => 1 - # - # If <tt>:use_two_month_numbers</tt> option is passed - # month_name(1) => '01' - # - # If <tt>:add_month_numbers</tt> option is passed - # month_name(1) => "1 - January" - def month_name(number) - if @options[:use_month_numbers] - number - elsif @options[:use_two_digit_numbers] - sprintf "%02d", number - elsif @options[:add_month_numbers] - "#{number} - #{month_names[number]}" - else - month_names[number] - end - end - - def date_order - @date_order ||= @options[:order] || translated_date_order - end - - def translated_date_order - date_order = I18n.translate(:'date.order', :locale => @options[:locale], :default => []) - date_order = date_order.map { |element| element.to_sym } - - forbidden_elements = date_order - [:year, :month, :day] - if forbidden_elements.any? - raise StandardError, - "#{@options[:locale]}.date.order only accepts :year, :month and :day" - end - - date_order - end - - # Build full select tag from date type and options. - def build_options_and_select(type, selected, options = {}) - build_select(type, build_options(selected, options)) - end - - # Build select option html from date value and options. - # build_options(15, start: 1, end: 31) - # => "<option value="1">1</option> - # <option value="2">2</option> - # <option value="3">3</option>..." - # - # If <tt>use_two_digit_numbers: true</tt> option is passed - # build_options(15, start: 1, end: 31, use_two_digit_numbers: true) - # => "<option value="1">01</option> - # <option value="2">02</option> - # <option value="3">03</option>..." - # - # If <tt>:step</tt> options is passed - # build_options(15, start: 1, end: 31, step: 2) - # => "<option value="1">1</option> - # <option value="3">3</option> - # <option value="5">5</option>..." - def build_options(selected, options = {}) - options = { - leading_zeros: true, ampm: false, use_two_digit_numbers: false - }.merge!(options) - - start = options.delete(:start) || 0 - stop = options.delete(:end) || 59 - step = options.delete(:step) || 1 - leading_zeros = options.delete(:leading_zeros) - - select_options = [] - start.step(stop, step) do |i| - value = leading_zeros ? sprintf("%02d", i) : i - tag_options = { :value => value } - tag_options[:selected] = "selected" if selected == i - text = options[:use_two_digit_numbers] ? sprintf("%02d", i) : value - text = options[:ampm] ? AMPM_TRANSLATION[i] : text - select_options << content_tag(:option, text, tag_options) - end - - (select_options.join("\n") + "\n").html_safe - end - - # Builds select tag from date type and html select options. - # build_select(:month, "<option value="1">January</option>...") - # => "<select id="post_written_on_2i" name="post[written_on(2i)]"> - # <option value="1">January</option>... - # </select>" - def build_select(type, select_options_as_html) - select_options = { - :id => input_id_from_type(type), - :name => input_name_from_type(type) - }.merge!(@html_options) - select_options[:disabled] = 'disabled' if @options[:disabled] - select_options[:class] = type if @options[:with_css_classes] - - select_html = "\n" - select_html << content_tag(:option, '', :value => '') + "\n" if @options[:include_blank] - select_html << prompt_option_tag(type, @options[:prompt]) + "\n" if @options[:prompt] - select_html << select_options_as_html - - (content_tag(:select, select_html.html_safe, select_options) + "\n").html_safe - end - - # Builds a prompt option tag with supplied options or from default options. - # prompt_option_tag(:month, prompt: 'Select month') - # => "<option value="">Select month</option>" - def prompt_option_tag(type, options) - prompt = case options - when Hash - default_options = {:year => false, :month => false, :day => false, :hour => false, :minute => false, :second => false} - default_options.merge!(options)[type.to_sym] - when String - options - else - I18n.translate(:"datetime.prompts.#{type}", :locale => @options[:locale]) - end - - prompt ? content_tag(:option, prompt, :value => '') : '' - end - - # Builds hidden input tag for date part and value. - # build_hidden(:year, 2008) - # => "<input id="post_written_on_1i" name="post[written_on(1i)]" type="hidden" value="2008" />" - def build_hidden(type, value) - select_options = { - :type => "hidden", - :id => input_id_from_type(type), - :name => input_name_from_type(type), - :value => value - }.merge!(@html_options.slice(:disabled)) - select_options[:disabled] = 'disabled' if @options[:disabled] - - tag(:input, select_options) + "\n".html_safe - end - - # Returns the name attribute for the input tag. - # => post[written_on(1i)] - def input_name_from_type(type) - prefix = @options[:prefix] || ActionView::Helpers::DateTimeSelector::DEFAULT_PREFIX - prefix += "[#{@options[:index]}]" if @options.has_key?(:index) - - field_name = @options[:field_name] || type - if @options[:include_position] - field_name += "(#{ActionView::Helpers::DateTimeSelector::POSITION[type]}i)" - end - - @options[:discard_type] ? prefix : "#{prefix}[#{field_name}]" - end - - # Returns the id attribute for the input tag. - # => "post_written_on_1i" - def input_id_from_type(type) - id = input_name_from_type(type).gsub(/([\[\(])|(\]\[)/, '_').gsub(/[\]\)]/, '') - id = @options[:namespace] + '_' + id if @options[:namespace] - - id - end - - # Given an ordering of datetime components, create the selection HTML - # and join them with their appropriate separators. - def build_selects_from_types(order) - select = '' - first_visible = order.find { |type| !@options[:"discard_#{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 - select.html_safe - end - - # Returns the separator for a given datetime component. - def separator(type) - return "" if @options[:use_hidden] - - case type - when :year, :month, :day - @options[:"discard_#{type}"] ? "" : @options[:date_separator] - when :hour - (@options[:discard_year] && @options[:discard_day]) ? "" : @options[:datetime_separator] - when :minute, :second - @options[:"discard_#{type}"] ? "" : @options[:time_separator] - end - end - end - - class FormBuilder - # Wraps ActionView::Helpers::DateHelper#date_select for form builders: - # - # <%= form_for @person do |f| %> - # <%= f.date_select :birth_date %> - # <%= f.submit %> - # <% end %> - # - # Please refer to the documentation of the base helper for details. - def date_select(method, options = {}, html_options = {}) - @template.date_select(@object_name, method, objectify_options(options), html_options) - end - - # Wraps ActionView::Helpers::DateHelper#time_select for form builders: - # - # <%= form_for @race do |f| %> - # <%= f.time_select :average_lap %> - # <%= f.submit %> - # <% end %> - # - # Please refer to the documentation of the base helper for details. - def time_select(method, options = {}, html_options = {}) - @template.time_select(@object_name, method, objectify_options(options), html_options) - end - - # Wraps ActionView::Helpers::DateHelper#datetime_select for form builders: - # - # <%= form_for @person do |f| %> - # <%= f.time_select :last_request_at %> - # <%= f.submit %> - # <% end %> - # - # Please refer to the documentation of the base helper for details. - def datetime_select(method, options = {}, html_options = {}) - @template.datetime_select(@object_name, method, objectify_options(options), html_options) - end - end - end -end diff --git a/actionpack/lib/action_view/helpers/debug_helper.rb b/actionpack/lib/action_view/helpers/debug_helper.rb deleted file mode 100644 index c29c1b1eea..0000000000 --- a/actionpack/lib/action_view/helpers/debug_helper.rb +++ /dev/null @@ -1,39 +0,0 @@ -module ActionView - # = Action View Debug Helper - # - # Provides a set of methods for making it easier to debug Rails objects. - module Helpers - module DebugHelper - - include TagHelper - - # Returns a YAML representation of +object+ wrapped with <pre> and </pre>. - # If the object cannot be converted to YAML using +to_yaml+, +inspect+ will be called instead. - # Useful for inspecting an object at the time of rendering. - # - # @user = User.new({ username: 'testing', password: 'xyz', age: 42}) %> - # debug(@user) - # # => - # <pre class='debug_dump'>--- !ruby/object:User - # attributes: - # updated_at: - # username: testing - # - # age: 42 - # password: xyz - # created_at: - # attributes_cache: {} - # - # new_record: true - # </pre> - def debug(object) - Marshal::dump(object) - object = ERB::Util.html_escape(object.to_yaml).gsub(" ", " ").html_safe - content_tag(:pre, object, :class => "debug_dump") - rescue Exception # errors from Marshal or YAML - # Object couldn't be dumped, perhaps because of singleton methods -- this is the fallback - content_tag(:code, object.inspect, :class => "debug_dump") - end - end - end -end diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb deleted file mode 100644 index 3dae1fc87a..0000000000 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ /dev/null @@ -1,1843 +0,0 @@ -require 'cgi' -require 'action_view/helpers/date_helper' -require 'action_view/helpers/tag_helper' -require 'action_view/helpers/form_tag_helper' -require 'action_view/helpers/active_model_helper' -require 'action_view/helpers/tags' -require 'action_view/model_naming' -require 'active_support/core_ext/class/attribute_accessors' -require 'active_support/core_ext/hash/slice' -require 'active_support/core_ext/string/output_safety' -require 'active_support/core_ext/string/inflections' - -module ActionView - # = Action View Form Helpers - module Helpers - # Form helpers are designed to make working with resources much easier - # compared to using vanilla HTML. - # - # Typically, a form designed to create or update a resource reflects the - # identity of the resource in several ways: (i) the url that the form is - # sent to (the form element's +action+ attribute) should result in a request - # being routed to the appropriate controller action (with the appropriate <tt>:id</tt> - # parameter in the case of an existing resource), (ii) input fields should - # be named in such a way that in the controller their values appear in the - # appropriate places within the +params+ hash, and (iii) for an existing record, - # when the form is initially displayed, input fields corresponding to attributes - # of the resource should show the current values of those attributes. - # - # In Rails, this is usually achieved by creating the form using +form_for+ and - # a number of related helper methods. +form_for+ generates an appropriate <tt>form</tt> - # tag and yields a form builder object that knows the model the form is about. - # Input fields are created by calling methods defined on the form builder, which - # means they are able to generate the appropriate names and default values - # corresponding to the model attributes, as well as convenient IDs, etc. - # Conventions in the generated field names allow controllers to receive form data - # nicely structured in +params+ with no effort on your side. - # - # For example, to create a new person you typically set up a new instance of - # +Person+ in the <tt>PeopleController#new</tt> action, <tt>@person</tt>, and - # in the view template pass that object to +form_for+: - # - # <%= form_for @person do |f| %> - # <%= f.label :first_name %>: - # <%= f.text_field :first_name %><br /> - # - # <%= f.label :last_name %>: - # <%= f.text_field :last_name %><br /> - # - # <%= f.submit %> - # <% end %> - # - # The HTML generated for this would be (modulus formatting): - # - # <form action="/people" class="new_person" id="new_person" method="post"> - # <div style="margin:0;padding:0;display:inline"> - # <input name="authenticity_token" type="hidden" value="NrOp5bsjoLRuK8IW5+dQEYjKGUJDe7TQoZVvq95Wteg=" /> - # </div> - # <label for="person_first_name">First name</label>: - # <input id="person_first_name" name="person[first_name]" type="text" /><br /> - # - # <label for="person_last_name">Last name</label>: - # <input id="person_last_name" name="person[last_name]" type="text" /><br /> - # - # <input name="commit" type="submit" value="Create Person" /> - # </form> - # - # As you see, the HTML reflects knowledge about the resource in several spots, - # like the path the form should be submitted to, or the names of the input fields. - # - # In particular, thanks to the conventions followed in the generated field names, the - # controller gets a nested hash <tt>params[:person]</tt> with the person attributes - # set in the form. That hash is ready to be passed to <tt>Person.create</tt>: - # - # if @person = Person.create(params[:person]) - # # success - # else - # # error handling - # end - # - # Interestingly, the exact same view code in the previous example can be used to edit - # a person. If <tt>@person</tt> is an existing record with name "John Smith" and ID 256, - # the code above as is would yield instead: - # - # <form action="/people/256" class="edit_person" id="edit_person_256" method="post"> - # <div style="margin:0;padding:0;display:inline"> - # <input name="_method" type="hidden" value="patch" /> - # <input name="authenticity_token" type="hidden" value="NrOp5bsjoLRuK8IW5+dQEYjKGUJDe7TQoZVvq95Wteg=" /> - # </div> - # <label for="person_first_name">First name</label>: - # <input id="person_first_name" name="person[first_name]" type="text" value="John" /><br /> - # - # <label for="person_last_name">Last name</label>: - # <input id="person_last_name" name="person[last_name]" type="text" value="Smith" /><br /> - # - # <input name="commit" type="submit" value="Update Person" /> - # </form> - # - # Note that the endpoint, default values, and submit button label are tailored for <tt>@person</tt>. - # That works that way because the involved helpers know whether the resource is a new record or not, - # and generate HTML accordingly. - # - # The controller would receive the form data again in <tt>params[:person]</tt>, ready to be - # passed to <tt>Person#update</tt>: - # - # if @person.update(params[:person]) - # # success - # else - # # error handling - # end - # - # That's how you typically work with resources. - module FormHelper - extend ActiveSupport::Concern - - include FormTagHelper - include UrlHelper - include ModelNaming - - # Creates a form that allows the user to create or update the attributes - # of a specific model object. - # - # The method can be used in several slightly different ways, depending on - # how much you wish to rely on Rails to infer automatically from the model - # how the form should be constructed. For a generic model object, a form - # can be created by passing +form_for+ a string or symbol representing - # the object we are concerned with: - # - # <%= form_for :person do |f| %> - # First name: <%= f.text_field :first_name %><br /> - # Last name : <%= f.text_field :last_name %><br /> - # Biography : <%= f.text_area :biography %><br /> - # Admin? : <%= f.check_box :admin %><br /> - # <%= f.submit %> - # <% end %> - # - # The variable +f+ yielded to the block is a FormBuilder object that - # incorporates the knowledge about the model object represented by - # <tt>:person</tt> passed to +form_for+. Methods defined on the FormBuilder - # are used to generate fields bound to this model. Thus, for example, - # - # <%= f.text_field :first_name %> - # - # will get expanded to - # - # <%= text_field :person, :first_name %> - # which results in an html <tt><input></tt> tag whose +name+ attribute is - # <tt>person[first_name]</tt>. This means that when the form is submitted, - # the value entered by the user will be available in the controller as - # <tt>params[:person][:first_name]</tt>. - # - # For fields generated in this way using the FormBuilder, - # if <tt>:person</tt> also happens to be the name of an instance variable - # <tt>@person</tt>, the default value of the field shown when the form is - # initially displayed (e.g. in the situation where you are editing an - # existing record) will be the value of the corresponding attribute of - # <tt>@person</tt>. - # - # The rightmost argument to +form_for+ is an - # optional hash of options - - # - # * <tt>:url</tt> - The URL the form is to be submitted to. This may be - # represented in the same way as values passed to +url_for+ or +link_to+. - # So for example you may use a named route directly. When the model is - # represented by a string or symbol, as in the example above, if the - # <tt>:url</tt> option is not specified, by default the form will be - # sent back to the current url (We will describe below an alternative - # resource-oriented usage of +form_for+ in which the URL does not need - # to be specified explicitly). - # * <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>:html</tt> - Optional HTML attributes for the form tag. - # - # Also note that +form_for+ doesn't create an exclusive scope. It's still - # possible to use both the stand-alone FormHelper methods and methods - # from FormTagHelper. For example: - # - # <%= form_for :person do |f| %> - # First name: <%= f.text_field :first_name %> - # Last name : <%= f.text_field :last_name %> - # Biography : <%= text_area :person, :biography %> - # Admin? : <%= check_box_tag "person[admin]", "1", @person.company.admin? %> - # <%= f.submit %> - # <% end %> - # - # This also works for the methods in FormOptionHelper and DateHelper that - # are designed to work with an object as base, like - # FormOptionHelper#collection_select and DateHelper#datetime_select. - # - # === #form_for with a model object - # - # In the examples above, the object to be created or edited was - # represented by a symbol passed to +form_for+, and we noted that - # a string can also be used equivalently. It is also possible, however, - # to pass a model object itself to +form_for+. For example, if <tt>@post</tt> - # is an existing record you wish to edit, you can create the form using - # - # <%= form_for @post do |f| %> - # ... - # <% end %> - # - # This behaves in almost the same way as outlined previously, with a - # couple of small exceptions. First, the prefix used to name the input - # elements within the form (hence the key that denotes them in the +params+ - # hash) is actually derived from the object's _class_, e.g. <tt>params[:post]</tt> - # if the object's class is +Post+. However, this can be overwritten using - # the <tt>:as</tt> option, e.g. - - # - # <%= form_for(@person, as: :client) do |f| %> - # ... - # <% end %> - # - # would result in <tt>params[:client]</tt>. - # - # Secondly, the field values shown when the form is initially displayed - # are taken from the attributes of the object passed to +form_for+, - # regardless of whether the object is an instance - # variable. So, for example, if we had a _local_ variable +post+ - # representing an existing record, - # - # <%= form_for post do |f| %> - # ... - # <% end %> - # - # would produce a form with fields whose initial state reflect the current - # values of the attributes of +post+. - # - # === Resource-oriented style - # - # In the examples just shown, although not indicated explicitly, we still - # need to use the <tt>:url</tt> option in order to specify where the - # form is going to be sent. However, further simplification is possible - # if the record passed to +form_for+ is a _resource_, i.e. it corresponds - # to a set of RESTful routes, e.g. defined using the +resources+ method - # in <tt>config/routes.rb</tt>. In this case Rails will simply infer the - # appropriate URL from the record itself. For example, - # - # <%= form_for @post do |f| %> - # ... - # <% end %> - # - # is then equivalent to something like: - # - # <%= form_for @post, as: :post, url: post_path(@post), method: :patch, html: { class: "edit_post", id: "edit_post_45" } do |f| %> - # ... - # <% end %> - # - # And for a new record - # - # <%= form_for(Post.new) do |f| %> - # ... - # <% end %> - # - # is equivalent to something like: - # - # <%= form_for @post, as: :post, url: posts_path, html: { class: "new_post", id: "new_post" } do |f| %> - # ... - # <% end %> - # - # However you can still overwrite individual conventions, such as: - # - # <%= form_for(@post, url: super_posts_path) do |f| %> - # ... - # <% end %> - # - # You can also set the answer format, like this: - # - # <%= form_for(@post, format: :json) do |f| %> - # ... - # <% end %> - # - # For namespaced routes, like +admin_post_url+: - # - # <%= form_for([:admin, @post]) do |f| %> - # ... - # <% end %> - # - # If your resource has associations defined, for example, you want to add comments - # to the document given that the routes are set correctly: - # - # <%= form_for([@document, @comment]) do |f| %> - # ... - # <% end %> - # - # Where <tt>@document = Document.find(params[:id])</tt> and - # <tt>@comment = Comment.new</tt>. - # - # === Setting the method - # - # You can force the form to use the full array of HTTP verbs by setting - # - # method: (:get|:post|:patch|:put|:delete) - # - # in the options hash. If the verb is not GET or POST, which are natively - # supported by HTML forms, the form will be set to POST and a hidden input - # called _method will carry the intended verb for the server to interpret. - # - # === Unobtrusive JavaScript - # - # Specifying: - # - # remote: true - # - # in the options hash creates a form that will allow the unobtrusive JavaScript drivers to modify its - # behavior. The expected default behavior is an XMLHttpRequest in the background instead of the regular - # POST arrangement, but ultimately the behavior is the choice of the JavaScript driver implementor. - # Even though it's using JavaScript to serialize the form elements, the form submission will work just like - # a regular submission as viewed by the receiving side (all elements available in <tt>params</tt>). - # - # Example: - # - # <%= form_for(@post, remote: true) do |f| %> - # ... - # <% end %> - # - # The HTML generated for this would be: - # - # <form action='http://www.example.com' method='post' data-remote='true'> - # <div style='margin:0;padding:0;display:inline'> - # <input name='_method' type='hidden' value='patch' /> - # </div> - # ... - # </form> - # - # === Setting HTML options - # - # You can set data attributes directly by passing in a data hash, but all other HTML options must be wrapped in - # the HTML key. Example: - # - # <%= form_for(@post, data: { behavior: "autosave" }, html: { name: "go" }) do |f| %> - # ... - # <% end %> - # - # The HTML generated for this would be: - # - # <form action='http://www.example.com' method='post' data-behavior='autosave' name='go'> - # <div style='margin:0;padding:0;display:inline'> - # <input name='_method' type='hidden' value='patch' /> - # </div> - # ... - # </form> - # - # === Removing hidden model id's - # - # The form_for method automatically includes the model id as a hidden field in the form. - # This is used to maintain the correlation between the form data and its associated model. - # Some ORM systems do not use IDs on nested models so in this case you want to be able - # to disable the hidden id. - # - # In the following example the Post model has many Comments stored within it in a NoSQL database, - # thus there is no primary key for comments. - # - # Example: - # - # <%= form_for(@post) do |f| %> - # <%= f.fields_for(:comments, include_id: false) do |cf| %> - # ... - # <% end %> - # <% end %> - # - # === Customized form builders - # - # You can also build forms using a customized FormBuilder class. Subclass - # FormBuilder and override or define some more helpers, then use your - # custom builder. For example, let's say you made a helper to - # automatically add labels to form inputs. - # - # <%= form_for @person, url: { action: "create" }, builder: LabellingFormBuilder do |f| %> - # <%= f.text_field :first_name %> - # <%= f.text_field :last_name %> - # <%= f.text_area :biography %> - # <%= f.check_box :admin %> - # <%= f.submit %> - # <% end %> - # - # In this case, if you use this: - # - # <%= render f %> - # - # The rendered template is <tt>people/_labelling_form</tt> and the local - # variable referencing the form builder is called - # <tt>labelling_form</tt>. - # - # The custom FormBuilder class is automatically merged with the options - # of a nested fields_for call, unless it's explicitly set. - # - # In many cases you will want to wrap the above in another helper, so you - # could do something like the following: - # - # def labelled_form_for(record_or_name_or_array, *args, &block) - # options = args.extract_options! - # form_for(record_or_name_or_array, *(args << options.merge(builder: LabellingFormBuilder)), &block) - # end - # - # If you don't need to attach a form to a model instance, then check out - # FormTagHelper#form_tag. - # - # === Form to external resources - # - # When you build forms to external resources sometimes you need to set an authenticity token or just render a form - # without it, for example when you submit data to a payment gateway number and types of fields could be limited. - # - # To set an authenticity token you need to pass an <tt>:authenticity_token</tt> parameter - # - # <%= form_for @invoice, url: external_url, authenticity_token: 'external_token' do |f| - # ... - # <% end %> - # - # If you don't want to an authenticity token field be rendered at all just pass <tt>false</tt>: - # - # <%= form_for @invoice, url: external_url, authenticity_token: false do |f| - # ... - # <% end %> - def form_for(record, options = {}, &block) - raise ArgumentError, "Missing block" unless block_given? - html_options = options[:html] ||= {} - - case record - when String, Symbol - object_name = record - object = nil - else - object = record.is_a?(Array) ? record.last : record - raise ArgumentError, "First argument in form cannot contain nil or be empty" unless object - object_name = options[:as] || model_name_from_record_or_class(object).param_key - apply_form_for_options!(record, object, options) - end - - 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[:authenticity_token] = options.delete(:authenticity_token) - - builder = instantiate_builder(object_name, object, options) - output = capture(builder, &block) - html_options[:multipart] = builder.multipart? - - form_tag(options[:url] || {}, html_options) { output } - end - - def apply_form_for_options!(record, object, options) #:nodoc: - object = convert_to_model(object) - - as = options[:as] - action, method = object.respond_to?(:persisted?) && object.persisted? ? [:edit, :patch] : [:new, :post] - options[:html].reverse_merge!( - class: as ? "#{action}_#{as}" : dom_class(object, action), - id: as ? "#{action}_#{as}" : [options[:namespace], dom_id(object, action)].compact.join("_").presence, - method: method - ) - - options[:url] ||= polymorphic_path(record, format: options.delete(:format)) - end - private :apply_form_for_options! - - # Creates a scope around a specific model object like form_for, but - # doesn't create the form tags themselves. This makes fields_for suitable - # for specifying additional model objects in the same form. - # - # Although the usage and purpose of +field_for+ is similar to +form_for+'s, - # its method signature is slightly different. Like +form_for+, it yields - # a FormBuilder object associated with a particular model object to a block, - # and within the block allows methods to be called on the builder to - # generate fields associated with the model object. Fields may reflect - # a model object in two ways - how they are named (hence how submitted - # values appear within the +params+ hash in the controller) and what - # default values are shown when the form the fields appear in is first - # displayed. In order for both of these features to be specified independently, - # both an object name (represented by either a symbol or string) and the - # object itself can be passed to the method separately - - # - # <%= form_for @person do |person_form| %> - # First name: <%= person_form.text_field :first_name %> - # Last name : <%= person_form.text_field :last_name %> - # - # <%= fields_for :permission, @person.permission do |permission_fields| %> - # Admin? : <%= permission_fields.check_box :admin %> - # <% end %> - # - # <%= f.submit %> - # <% end %> - # - # In this case, the checkbox field will be represented by an HTML +input+ - # tag with the +name+ attribute <tt>permission[admin]</tt>, and the submitted - # value will appear in the controller as <tt>params[:permission][:admin]</tt>. - # If <tt>@person.permission</tt> is an existing record with an attribute - # +admin+, the initial state of the checkbox when first displayed will - # reflect the value of <tt>@person.permission.admin</tt>. - # - # Often this can be simplified by passing just the name of the model - # object to +fields_for+ - - # - # <%= fields_for :permission do |permission_fields| %> - # Admin?: <%= permission_fields.check_box :admin %> - # <% end %> - # - # ...in which case, if <tt>:permission</tt> also happens to be the name of an - # instance variable <tt>@permission</tt>, the initial state of the input - # field will reflect the value of that variable's attribute <tt>@permission.admin</tt>. - # - # Alternatively, you can pass just the model object itself (if the first - # argument isn't a string or symbol +fields_for+ will realize that the - # name has been omitted) - - # - # <%= fields_for @person.permission do |permission_fields| %> - # Admin?: <%= permission_fields.check_box :admin %> - # <% end %> - # - # and +fields_for+ will derive the required name of the field from the - # _class_ of the model object, e.g. if <tt>@person.permission</tt>, is - # of class +Permission+, the field will still be named <tt>permission[admin]</tt>. - # - # Note: This also works for the methods in FormOptionHelper and - # DateHelper that are designed to work with an object as base, like - # FormOptionHelper#collection_select and DateHelper#datetime_select. - # - # === Nested Attributes Examples - # - # When the object belonging to the current scope has a nested attribute - # writer for a certain attribute, fields_for will yield a new scope - # for that attribute. This allows you to create forms that set or change - # the attributes of a parent object and its associations in one go. - # - # Nested attribute writers are normal setter methods named after an - # association. The most common way of defining these writers is either - # with +accepts_nested_attributes_for+ in a model definition or by - # defining a method with the proper name. For example: the attribute - # writer for the association <tt>:address</tt> is called - # <tt>address_attributes=</tt>. - # - # Whether a one-to-one or one-to-many style form builder will be yielded - # depends on whether the normal reader method returns a _single_ object - # or an _array_ of objects. - # - # ==== One-to-one - # - # Consider a Person class which returns a _single_ Address from the - # <tt>address</tt> reader method and responds to the - # <tt>address_attributes=</tt> writer method: - # - # class Person - # def address - # @address - # end - # - # def address_attributes=(attributes) - # # Process the attributes hash - # end - # end - # - # This model can now be used with a nested fields_for, like so: - # - # <%= form_for @person do |person_form| %> - # ... - # <%= person_form.fields_for :address do |address_fields| %> - # Street : <%= address_fields.text_field :street %> - # Zip code: <%= address_fields.text_field :zip_code %> - # <% end %> - # ... - # <% end %> - # - # When address is already an association on a Person you can use - # +accepts_nested_attributes_for+ to define the writer method for you: - # - # class Person < ActiveRecord::Base - # has_one :address - # accepts_nested_attributes_for :address - # end - # - # If you want to destroy the associated model through the form, you have - # to enable it first using the <tt>:allow_destroy</tt> option for - # +accepts_nested_attributes_for+: - # - # class Person < ActiveRecord::Base - # has_one :address - # accepts_nested_attributes_for :address, allow_destroy: true - # end - # - # Now, when you use a form element with the <tt>_destroy</tt> parameter, - # with a value that evaluates to +true+, you will destroy the associated - # model (eg. 1, '1', true, or 'true'): - # - # <%= form_for @person do |person_form| %> - # ... - # <%= person_form.fields_for :address do |address_fields| %> - # ... - # Delete: <%= address_fields.check_box :_destroy %> - # <% end %> - # ... - # <% end %> - # - # ==== One-to-many - # - # Consider a Person class which returns an _array_ of Project instances - # from the <tt>projects</tt> reader method and responds to the - # <tt>projects_attributes=</tt> writer method: - # - # class Person - # def projects - # [@project1, @project2] - # end - # - # def projects_attributes=(attributes) - # # Process the attributes hash - # end - # end - # - # Note that the <tt>projects_attributes=</tt> writer method is in fact - # required for fields_for to correctly identify <tt>:projects</tt> as a - # collection, and the correct indices to be set in the form markup. - # - # When projects is already an association on Person you can use - # +accepts_nested_attributes_for+ to define the writer method for you: - # - # class Person < ActiveRecord::Base - # has_many :projects - # accepts_nested_attributes_for :projects - # end - # - # This model can now be used with a nested fields_for. The block given to - # the nested fields_for call will be repeated for each instance in the - # collection: - # - # <%= form_for @person do |person_form| %> - # ... - # <%= person_form.fields_for :projects do |project_fields| %> - # <% if project_fields.object.active? %> - # Name: <%= project_fields.text_field :name %> - # <% end %> - # <% end %> - # ... - # <% end %> - # - # It's also possible to specify the instance to be used: - # - # <%= form_for @person do |person_form| %> - # ... - # <% @person.projects.each do |project| %> - # <% if project.active? %> - # <%= person_form.fields_for :projects, project do |project_fields| %> - # Name: <%= project_fields.text_field :name %> - # <% end %> - # <% end %> - # <% end %> - # ... - # <% end %> - # - # Or a collection to be used: - # - # <%= form_for @person do |person_form| %> - # ... - # <%= person_form.fields_for :projects, @active_projects do |project_fields| %> - # Name: <%= project_fields.text_field :name %> - # <% end %> - # ... - # <% end %> - # - # When projects is already an association on Person you can use - # +accepts_nested_attributes_for+ to define the writer method for you: - # - # class Person < ActiveRecord::Base - # has_many :projects - # accepts_nested_attributes_for :projects - # end - # - # If you want to destroy any of the associated models through the - # form, you have to enable it first using the <tt>:allow_destroy</tt> - # option for +accepts_nested_attributes_for+: - # - # class Person < ActiveRecord::Base - # has_many :projects - # accepts_nested_attributes_for :projects, allow_destroy: true - # end - # - # This will allow you to specify which models to destroy in the - # attributes hash by adding a form element for the <tt>_destroy</tt> - # parameter with a value that evaluates to +true+ - # (eg. 1, '1', true, or 'true'): - # - # <%= form_for @person do |person_form| %> - # ... - # <%= person_form.fields_for :projects do |project_fields| %> - # Delete: <%= project_fields.check_box :_destroy %> - # <% end %> - # ... - # <% end %> - # - # When a collection is used you might want to know the index of each - # object into the array. For this purpose, the <tt>index</tt> method - # is available in the FormBuilder object. - # - # <%= form_for @person do |person_form| %> - # ... - # <%= person_form.fields_for :projects do |project_fields| %> - # Project #<%= project_fields.index %> - # ... - # <% end %> - # ... - # <% end %> - # - # Note that fields_for will automatically generate a hidden field - # to store the ID of the record. There are circumstances where this - # hidden field is not needed and you can pass <tt>hidden_field_id: false</tt> - # to prevent fields_for from rendering it automatically. - def fields_for(record_name, record_object = nil, options = {}, &block) - builder = instantiate_builder(record_name, record_object, options) - capture(builder, &block) - end - - # Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object - # assigned to the template (identified by +object+). The text of label will default to the attribute name unless a translation - # is found in the current I18n locale (through helpers.label.<modelname>.<attribute>) or you specify it explicitly. - # Additional options on the label tag can be passed as a hash with +options+. These options will be tagged - # onto the HTML as an HTML element attribute as in the example shown, except for the <tt>:value</tt> option, which is designed to - # target labels for radio_button tags (where the value is used in the ID of the input tag). - # - # ==== Examples - # label(:post, :title) - # # => <label for="post_title">Title</label> - # - # You can localize your labels based on model and attribute names. - # For example you can define the following in your locale (e.g. en.yml) - # - # helpers: - # label: - # post: - # body: "Write your entire text here" - # - # Which then will result in - # - # label(:post, :body) - # # => <label for="post_body">Write your entire text here</label> - # - # Localization can also be based purely on the translation of the attribute-name - # (if you are using ActiveRecord): - # - # activerecord: - # attributes: - # post: - # cost: "Total cost" - # - # label(:post, :cost) - # # => <label for="post_cost">Total cost</label> - # - # label(:post, :title, "A short title") - # # => <label for="post_title">A short title</label> - # - # label(:post, :title, "A short title", class: "title_label") - # # => <label for="post_title" class="title_label">A short title</label> - # - # label(:post, :privacy, "Public Post", value: "public") - # # => <label for="post_privacy_public">Public Post</label> - # - # label(:post, :terms) do - # 'Accept <a href="/terms">Terms</a>.'.html_safe - # end - def label(object_name, method, content_or_options = nil, options = nil, &block) - Tags::Label.new(object_name, method, self, content_or_options, options).render(&block) - end - - # Returns an input tag of the "text" type tailored for accessing a specified attribute (identified by +method+) on an object - # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a - # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example - # shown. - # - # ==== Examples - # text_field(:post, :title, size: 20) - # # => <input type="text" id="post_title" name="post[title]" size="20" value="#{@post.title}" /> - # - # text_field(:post, :title, class: "create_input") - # # => <input type="text" id="post_title" name="post[title]" value="#{@post.title}" class="create_input" /> - # - # text_field(:session, :user, onchange: "if ($('#session_user').val() === 'admin') { alert('Your login can not be admin!'); }") - # # => <input type="text" id="session_user" name="session[user]" value="#{@session.user}" onchange="if ($('#session_user').val() === 'admin') { alert('Your login can not be admin!'); }"/> - # - # text_field(:snippet, :code, size: 20, class: 'code_input') - # # => <input type="text" id="snippet_code" name="snippet[code]" size="20" value="#{@snippet.code}" class="code_input" /> - def text_field(object_name, method, options = {}) - Tags::TextField.new(object_name, method, self, options).render - end - - # Returns an input tag of the "password" type tailored for accessing a specified attribute (identified by +method+) on an object - # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a - # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example - # shown. For security reasons this field is blank by default; pass in a value via +options+ if this is not desired. - # - # ==== Examples - # password_field(:login, :pass, size: 20) - # # => <input type="password" id="login_pass" name="login[pass]" size="20" /> - # - # password_field(:account, :secret, class: "form_input", value: @account.secret) - # # => <input type="password" id="account_secret" name="account[secret]" value="#{@account.secret}" class="form_input" /> - # - # password_field(:user, :password, onchange: "if ($('#user_password').val().length > 30) { alert('Your password needs to be shorter!'); }") - # # => <input type="password" id="user_password" name="user[password]" onchange="if ($('#user_password').val().length > 30) { alert('Your password needs to be shorter!'); }"/> - # - # password_field(:account, :pin, size: 20, class: 'form_input') - # # => <input type="password" id="account_pin" name="account[pin]" size="20" class="form_input" /> - def password_field(object_name, method, options = {}) - Tags::PasswordField.new(object_name, method, self, options).render - end - - # Returns a hidden input tag tailored for accessing a specified attribute (identified by +method+) on an object - # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a - # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example - # shown. - # - # ==== Examples - # hidden_field(:signup, :pass_confirm) - # # => <input type="hidden" id="signup_pass_confirm" name="signup[pass_confirm]" value="#{@signup.pass_confirm}" /> - # - # hidden_field(:post, :tag_list) - # # => <input type="hidden" id="post_tag_list" name="post[tag_list]" value="#{@post.tag_list}" /> - # - # hidden_field(:user, :token) - # # => <input type="hidden" id="user_token" name="user[token]" value="#{@user.token}" /> - def hidden_field(object_name, method, options = {}) - Tags::HiddenField.new(object_name, method, self, options).render - end - - # Returns a file upload input tag tailored for accessing a specified attribute (identified by +method+) on an object - # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a - # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example - # shown. - # - # Using this method inside a +form_for+ block will set the enclosing form's encoding to <tt>multipart/form-data</tt>. - # - # ==== Options - # * Creates standard HTML attributes for the tag. - # * <tt>:disabled</tt> - If set to true, the user will not be able to use this input. - # * <tt>:multiple</tt> - If set to true, *in most updated browsers* the user will be allowed to select multiple files. - # * <tt>:accept</tt> - If set to one or multiple mime-types, the user will be suggested a filter when choosing a file. You still need to set up model validations. - # - # ==== Examples - # file_field(:user, :avatar) - # # => <input type="file" id="user_avatar" name="user[avatar]" /> - # - # file_field(:post, :image, :multiple => true) - # # => <input type="file" id="post_image" name="post[image]" multiple="true" /> - # - # file_field(:post, :attached, accept: 'text/html') - # # => <input accept="text/html" type="file" id="post_attached" name="post[attached]" /> - # - # file_field(:post, :image, accept: 'image/png,image/gif,image/jpeg') - # # => <input type="file" id="post_image" name="post[image]" accept="image/png,image/gif,image/jpeg" /> - # - # file_field(:attachment, :file, class: 'file_input') - # # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" /> - def file_field(object_name, method, options = {}) - Tags::FileField.new(object_name, method, self, options).render - end - - # Returns a textarea opening and closing tag set tailored for accessing a specified attribute (identified by +method+) - # on an object assigned to the template (identified by +object+). Additional options on the input tag can be passed as a - # hash with +options+. - # - # ==== Examples - # text_area(:post, :body, cols: 20, rows: 40) - # # => <textarea cols="20" rows="40" id="post_body" name="post[body]"> - # # #{@post.body} - # # </textarea> - # - # text_area(:comment, :text, size: "20x30") - # # => <textarea cols="20" rows="30" id="comment_text" name="comment[text]"> - # # #{@comment.text} - # # </textarea> - # - # text_area(:application, :notes, cols: 40, rows: 15, class: 'app_input') - # # => <textarea cols="40" rows="15" id="application_notes" name="application[notes]" class="app_input"> - # # #{@application.notes} - # # </textarea> - # - # text_area(:entry, :body, size: "20x20", disabled: 'disabled') - # # => <textarea cols="20" rows="20" id="entry_body" name="entry[body]" disabled="disabled"> - # # #{@entry.body} - # # </textarea> - def text_area(object_name, method, options = {}) - Tags::TextArea.new(object_name, method, self, options).render - end - - # Returns a checkbox tag tailored for accessing a specified attribute (identified by +method+) on an object - # assigned to the template (identified by +object+). This object must be an instance object (@object) and not a local object. - # It's intended that +method+ returns an integer and if that integer is above zero, then the checkbox is checked. - # Additional options on the input tag can be passed as a hash with +options+. The +checked_value+ defaults to 1 - # while the default +unchecked_value+ is set to 0 which is convenient for boolean values. - # - # ==== Gotcha - # - # The HTML specification says unchecked check boxes are not successful, and - # thus web browsers do not send them. Unfortunately this introduces a gotcha: - # if an +Invoice+ model has a +paid+ flag, and in the form that edits a paid - # invoice the user unchecks its check box, no +paid+ parameter is sent. So, - # any mass-assignment idiom like - # - # @invoice.update(params[:invoice]) - # - # wouldn't update the flag. - # - # To prevent this the helper generates an auxiliary hidden field before - # the very check box. The hidden field has the same name and its - # attributes mimic an unchecked check box. - # - # This way, the client either sends only the hidden field (representing - # the check box is unchecked), or both fields. Since the HTML specification - # says key/value pairs have to be sent in the same order they appear in the - # form, and parameters extraction gets the last occurrence of any repeated - # key in the query string, that works for ordinary forms. - # - # Unfortunately that workaround does not work when the check box goes - # within an array-like parameter, as in - # - # <%= fields_for "project[invoice_attributes][]", invoice, index: nil do |form| %> - # <%= form.check_box :paid %> - # ... - # <% end %> - # - # because parameter name repetition is precisely what Rails seeks to distinguish - # the elements of the array. For each item with a checked check box you - # get an extra ghost item with only that attribute, assigned to "0". - # - # In that case it is preferable to either use +check_box_tag+ or to use - # hashes instead of arrays. - # - # # Let's say that @post.validated? is 1: - # check_box("post", "validated") - # # => <input name="post[validated]" type="hidden" value="0" /> - # # <input checked="checked" type="checkbox" id="post_validated" name="post[validated]" value="1" /> - # - # # Let's say that @puppy.gooddog is "no": - # check_box("puppy", "gooddog", {}, "yes", "no") - # # => <input name="puppy[gooddog]" type="hidden" value="no" /> - # # <input type="checkbox" id="puppy_gooddog" name="puppy[gooddog]" value="yes" /> - # - # check_box("eula", "accepted", { class: 'eula_check' }, "yes", "no") - # # => <input name="eula[accepted]" type="hidden" value="no" /> - # # <input type="checkbox" class="eula_check" id="eula_accepted" name="eula[accepted]" value="yes" /> - def check_box(object_name, method, options = {}, checked_value = "1", unchecked_value = "0") - Tags::CheckBox.new(object_name, method, self, checked_value, unchecked_value, options).render - end - - # Returns a radio button tag for accessing a specified attribute (identified by +method+) on an object - # assigned to the template (identified by +object+). If the current value of +method+ is +tag_value+ the - # radio button will be checked. - # - # To force the radio button to be checked pass <tt>checked: true</tt> in the - # +options+ hash. You may pass HTML options there as well. - # - # # Let's say that @post.category returns "rails": - # radio_button("post", "category", "rails") - # radio_button("post", "category", "java") - # # => <input type="radio" id="post_category_rails" name="post[category]" value="rails" checked="checked" /> - # # <input type="radio" id="post_category_java" name="post[category]" value="java" /> - # - # radio_button("user", "receive_newsletter", "yes") - # radio_button("user", "receive_newsletter", "no") - # # => <input type="radio" id="user_receive_newsletter_yes" name="user[receive_newsletter]" value="yes" /> - # # <input type="radio" id="user_receive_newsletter_no" name="user[receive_newsletter]" value="no" checked="checked" /> - def radio_button(object_name, method, tag_value, options = {}) - Tags::RadioButton.new(object_name, method, self, tag_value, options).render - end - - # Returns a text_field of type "color". - # - # color_field("car", "color") - # # => <input id="car_color" name="car[color]" type="color" value="#000000" /> - def color_field(object_name, method, options = {}) - Tags::ColorField.new(object_name, method, self, options).render - end - - # Returns an input of type "search" for accessing a specified attribute (identified by +method+) on an object - # assigned to the template (identified by +object_name+). Inputs of type "search" may be styled differently by - # some browsers. - # - # search_field(:user, :name) - # # => <input id="user_name" name="user[name]" type="search" /> - # search_field(:user, :name, autosave: false) - # # => <input autosave="false" id="user_name" name="user[name]" type="search" /> - # search_field(:user, :name, results: 3) - # # => <input id="user_name" name="user[name]" results="3" type="search" /> - # # Assume request.host returns "www.example.com" - # search_field(:user, :name, autosave: true) - # # => <input autosave="com.example.www" id="user_name" name="user[name]" results="10" type="search" /> - # search_field(:user, :name, onsearch: true) - # # => <input id="user_name" incremental="true" name="user[name]" onsearch="true" type="search" /> - # search_field(:user, :name, autosave: false, onsearch: true) - # # => <input autosave="false" id="user_name" incremental="true" name="user[name]" onsearch="true" type="search" /> - # search_field(:user, :name, autosave: true, onsearch: true) - # # => <input autosave="com.example.www" id="user_name" incremental="true" name="user[name]" onsearch="true" results="10" type="search" /> - def search_field(object_name, method, options = {}) - Tags::SearchField.new(object_name, method, self, options).render - end - - # Returns a text_field of type "tel". - # - # telephone_field("user", "phone") - # # => <input id="user_phone" name="user[phone]" type="tel" /> - # - def telephone_field(object_name, method, options = {}) - Tags::TelField.new(object_name, method, self, options).render - end - # aliases telephone_field - alias phone_field telephone_field - - # Returns a text_field of type "date". - # - # date_field("user", "born_on") - # # => <input id="user_born_on" name="user[born_on]" type="date" /> - # - # The default value is generated by trying to call "to_date" - # on the object's value, which makes it behave as expected for instances - # of DateTime and ActiveSupport::TimeWithZone. You can still override that - # by passing the "value" option explicitly, e.g. - # - # @user.born_on = Date.new(1984, 1, 27) - # date_field("user", "born_on", value: "1984-05-12") - # # => <input id="user_born_on" name="user[born_on]" type="date" value="1984-05-12" /> - # - def date_field(object_name, method, options = {}) - Tags::DateField.new(object_name, method, self, options).render - end - - # Returns a text_field of type "time". - # - # The default value is generated by trying to call +strftime+ with "%T.%L" - # on the objects's value. It is still possible to override that - # by passing the "value" option. - # - # === Options - # * Accepts same options as time_field_tag - # - # === Example - # time_field("task", "started_at") - # # => <input id="task_started_at" name="task[started_at]" type="time" /> - # - def time_field(object_name, method, options = {}) - Tags::TimeField.new(object_name, method, self, options).render - end - - # Returns a text_field of type "datetime". - # - # datetime_field("user", "born_on") - # # => <input id="user_born_on" name="user[born_on]" type="datetime" /> - # - # The default value is generated by trying to call +strftime+ with "%Y-%m-%dT%T.%L%z" - # on the object's value, which makes it behave as expected for instances - # of DateTime and ActiveSupport::TimeWithZone. - # - # @user.born_on = Date.new(1984, 1, 12) - # datetime_field("user", "born_on") - # # => <input id="user_born_on" name="user[born_on]" type="datetime" value="1984-01-12T00:00:00.000+0000" /> - # - def datetime_field(object_name, method, options = {}) - Tags::DatetimeField.new(object_name, method, self, options).render - end - - # Returns a text_field of type "datetime-local". - # - # datetime_local_field("user", "born_on") - # # => <input id="user_born_on" name="user[born_on]" type="datetime-local" /> - # - # The default value is generated by trying to call +strftime+ with "%Y-%m-%dT%T" - # on the object's value, which makes it behave as expected for instances - # of DateTime and ActiveSupport::TimeWithZone. - # - # @user.born_on = Date.new(1984, 1, 12) - # datetime_local_field("user", "born_on") - # # => <input id="user_born_on" name="user[born_on]" type="datetime-local" value="1984-01-12T00:00:00" /> - # - def datetime_local_field(object_name, method, options = {}) - Tags::DatetimeLocalField.new(object_name, method, self, options).render - end - - # Returns a text_field of type "month". - # - # month_field("user", "born_on") - # # => <input id="user_born_on" name="user[born_on]" type="month" /> - # - # The default value is generated by trying to call +strftime+ with "%Y-%m" - # on the object's value, which makes it behave as expected for instances - # of DateTime and ActiveSupport::TimeWithZone. - # - # @user.born_on = Date.new(1984, 1, 27) - # month_field("user", "born_on") - # # => <input id="user_born_on" name="user[born_on]" type="date" value="1984-01" /> - # - def month_field(object_name, method, options = {}) - Tags::MonthField.new(object_name, method, self, options).render - end - - # Returns a text_field of type "week". - # - # week_field("user", "born_on") - # # => <input id="user_born_on" name="user[born_on]" type="week" /> - # - # The default value is generated by trying to call +strftime+ with "%Y-W%W" - # on the object's value, which makes it behave as expected for instances - # of DateTime and ActiveSupport::TimeWithZone. - # - # @user.born_on = Date.new(1984, 5, 12) - # week_field("user", "born_on") - # # => <input id="user_born_on" name="user[born_on]" type="date" value="1984-W19" /> - # - def week_field(object_name, method, options = {}) - Tags::WeekField.new(object_name, method, self, options).render - end - - # Returns a text_field of type "url". - # - # url_field("user", "homepage") - # # => <input id="user_homepage" name="user[homepage]" type="url" /> - # - def url_field(object_name, method, options = {}) - Tags::UrlField.new(object_name, method, self, options).render - end - - # Returns a text_field of type "email". - # - # email_field("user", "address") - # # => <input id="user_address" name="user[address]" type="email" /> - # - def email_field(object_name, method, options = {}) - Tags::EmailField.new(object_name, method, self, options).render - end - - # Returns an input tag of type "number". - # - # ==== Options - # * Accepts same options as number_field_tag - def number_field(object_name, method, options = {}) - Tags::NumberField.new(object_name, method, self, options).render - end - - # Returns an input tag of type "range". - # - # ==== Options - # * Accepts same options as range_field_tag - def range_field(object_name, method, options = {}) - Tags::RangeField.new(object_name, method, self, options).render - end - - private - - def instantiate_builder(record_name, record_object, options) - case record_name - when String, Symbol - object = record_object - object_name = record_name - else - object = record_name - object_name = model_name_from_record_or_class(object).param_key - end - - builder = options[:builder] || default_form_builder - builder.new(object_name, object, self, options) - end - - def default_form_builder - builder = ActionView::Base.default_form_builder - builder.respond_to?(:constantize) ? builder.constantize : builder - end - end - - class FormBuilder - include ModelNaming - - # The methods which wrap a form helper call. - class_attribute :field_helpers - self.field_helpers = FormHelper.instance_methods - [:form_for, :convert_to_model, :model_name_from_record_or_class] - - attr_accessor :object_name, :object, :options - - attr_reader :multipart, :index - alias :multipart? :multipart - - def multipart=(multipart) - @multipart = multipart - - if parent_builder = @options[:parent_builder] - parent_builder.multipart = multipart - end - end - - def self._to_partial_path - @_to_partial_path ||= name.demodulize.underscore.sub!(/_builder$/, '') - end - - def to_partial_path - self.class._to_partial_path - end - - def to_model - self - end - - def initialize(object_name, object, template, options, block=nil) - if block - ActiveSupport::Deprecation.warn "Giving a block to FormBuilder is deprecated and has no effect anymore." - end - - @nested_child_index = {} - @object_name, @object, @template, @options = object_name, object, template, options - @default_options = @options ? @options.slice(:index, :namespace) : {} - if @object_name.to_s.match(/\[\]$/) - if object ||= @template.instance_variable_get("@#{Regexp.last_match.pre_match}") and object.respond_to?(:to_param) - @auto_index = object.to_param - else - raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}" - end - end - @multipart = nil - @index = options[:index] || options[:child_index] - end - - (field_helpers - [:label, :check_box, :radio_button, :fields_for, :hidden_field, :file_field]).each do |selector| - class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 - def #{selector}(method, options = {}) # def text_field(method, options = {}) - @template.send( # @template.send( - #{selector.inspect}, # "text_field", - @object_name, # @object_name, - method, # method, - objectify_options(options)) # objectify_options(options)) - end # end - RUBY_EVAL - end - - # Creates a scope around a specific model object like form_for, but - # doesn't create the form tags themselves. This makes fields_for suitable - # for specifying additional model objects in the same form. - # - # Although the usage and purpose of +field_for+ is similar to +form_for+'s, - # its method signature is slightly different. Like +form_for+, it yields - # a FormBuilder object associated with a particular model object to a block, - # and within the block allows methods to be called on the builder to - # generate fields associated with the model object. Fields may reflect - # a model object in two ways - how they are named (hence how submitted - # values appear within the +params+ hash in the controller) and what - # default values are shown when the form the fields appear in is first - # displayed. In order for both of these features to be specified independently, - # both an object name (represented by either a symbol or string) and the - # object itself can be passed to the method separately - - # - # <%= form_for @person do |person_form| %> - # First name: <%= person_form.text_field :first_name %> - # Last name : <%= person_form.text_field :last_name %> - # - # <%= fields_for :permission, @person.permission do |permission_fields| %> - # Admin? : <%= permission_fields.check_box :admin %> - # <% end %> - # - # <%= f.submit %> - # <% end %> - # - # In this case, the checkbox field will be represented by an HTML +input+ - # tag with the +name+ attribute <tt>permission[admin]</tt>, and the submitted - # value will appear in the controller as <tt>params[:permission][:admin]</tt>. - # If <tt>@person.permission</tt> is an existing record with an attribute - # +admin+, the initial state of the checkbox when first displayed will - # reflect the value of <tt>@person.permission.admin</tt>. - # - # Often this can be simplified by passing just the name of the model - # object to +fields_for+ - - # - # <%= fields_for :permission do |permission_fields| %> - # Admin?: <%= permission_fields.check_box :admin %> - # <% end %> - # - # ...in which case, if <tt>:permission</tt> also happens to be the name of an - # instance variable <tt>@permission</tt>, the initial state of the input - # field will reflect the value of that variable's attribute <tt>@permission.admin</tt>. - # - # Alternatively, you can pass just the model object itself (if the first - # argument isn't a string or symbol +fields_for+ will realize that the - # name has been omitted) - - # - # <%= fields_for @person.permission do |permission_fields| %> - # Admin?: <%= permission_fields.check_box :admin %> - # <% end %> - # - # and +fields_for+ will derive the required name of the field from the - # _class_ of the model object, e.g. if <tt>@person.permission</tt>, is - # of class +Permission+, the field will still be named <tt>permission[admin]</tt>. - # - # Note: This also works for the methods in FormOptionHelper and - # DateHelper that are designed to work with an object as base, like - # FormOptionHelper#collection_select and DateHelper#datetime_select. - # - # === Nested Attributes Examples - # - # When the object belonging to the current scope has a nested attribute - # writer for a certain attribute, fields_for will yield a new scope - # for that attribute. This allows you to create forms that set or change - # the attributes of a parent object and its associations in one go. - # - # Nested attribute writers are normal setter methods named after an - # association. The most common way of defining these writers is either - # with +accepts_nested_attributes_for+ in a model definition or by - # defining a method with the proper name. For example: the attribute - # writer for the association <tt>:address</tt> is called - # <tt>address_attributes=</tt>. - # - # Whether a one-to-one or one-to-many style form builder will be yielded - # depends on whether the normal reader method returns a _single_ object - # or an _array_ of objects. - # - # ==== One-to-one - # - # Consider a Person class which returns a _single_ Address from the - # <tt>address</tt> reader method and responds to the - # <tt>address_attributes=</tt> writer method: - # - # class Person - # def address - # @address - # end - # - # def address_attributes=(attributes) - # # Process the attributes hash - # end - # end - # - # This model can now be used with a nested fields_for, like so: - # - # <%= form_for @person do |person_form| %> - # ... - # <%= person_form.fields_for :address do |address_fields| %> - # Street : <%= address_fields.text_field :street %> - # Zip code: <%= address_fields.text_field :zip_code %> - # <% end %> - # ... - # <% end %> - # - # When address is already an association on a Person you can use - # +accepts_nested_attributes_for+ to define the writer method for you: - # - # class Person < ActiveRecord::Base - # has_one :address - # accepts_nested_attributes_for :address - # end - # - # If you want to destroy the associated model through the form, you have - # to enable it first using the <tt>:allow_destroy</tt> option for - # +accepts_nested_attributes_for+: - # - # class Person < ActiveRecord::Base - # has_one :address - # accepts_nested_attributes_for :address, allow_destroy: true - # end - # - # Now, when you use a form element with the <tt>_destroy</tt> parameter, - # with a value that evaluates to +true+, you will destroy the associated - # model (eg. 1, '1', true, or 'true'): - # - # <%= form_for @person do |person_form| %> - # ... - # <%= person_form.fields_for :address do |address_fields| %> - # ... - # Delete: <%= address_fields.check_box :_destroy %> - # <% end %> - # ... - # <% end %> - # - # ==== One-to-many - # - # Consider a Person class which returns an _array_ of Project instances - # from the <tt>projects</tt> reader method and responds to the - # <tt>projects_attributes=</tt> writer method: - # - # class Person - # def projects - # [@project1, @project2] - # end - # - # def projects_attributes=(attributes) - # # Process the attributes hash - # end - # end - # - # Note that the <tt>projects_attributes=</tt> writer method is in fact - # required for fields_for to correctly identify <tt>:projects</tt> as a - # collection, and the correct indices to be set in the form markup. - # - # When projects is already an association on Person you can use - # +accepts_nested_attributes_for+ to define the writer method for you: - # - # class Person < ActiveRecord::Base - # has_many :projects - # accepts_nested_attributes_for :projects - # end - # - # This model can now be used with a nested fields_for. The block given to - # the nested fields_for call will be repeated for each instance in the - # collection: - # - # <%= form_for @person do |person_form| %> - # ... - # <%= person_form.fields_for :projects do |project_fields| %> - # <% if project_fields.object.active? %> - # Name: <%= project_fields.text_field :name %> - # <% end %> - # <% end %> - # ... - # <% end %> - # - # It's also possible to specify the instance to be used: - # - # <%= form_for @person do |person_form| %> - # ... - # <% @person.projects.each do |project| %> - # <% if project.active? %> - # <%= person_form.fields_for :projects, project do |project_fields| %> - # Name: <%= project_fields.text_field :name %> - # <% end %> - # <% end %> - # <% end %> - # ... - # <% end %> - # - # Or a collection to be used: - # - # <%= form_for @person do |person_form| %> - # ... - # <%= person_form.fields_for :projects, @active_projects do |project_fields| %> - # Name: <%= project_fields.text_field :name %> - # <% end %> - # ... - # <% end %> - # - # When projects is already an association on Person you can use - # +accepts_nested_attributes_for+ to define the writer method for you: - # - # class Person < ActiveRecord::Base - # has_many :projects - # accepts_nested_attributes_for :projects - # end - # - # If you want to destroy any of the associated models through the - # form, you have to enable it first using the <tt>:allow_destroy</tt> - # option for +accepts_nested_attributes_for+: - # - # class Person < ActiveRecord::Base - # has_many :projects - # accepts_nested_attributes_for :projects, allow_destroy: true - # end - # - # This will allow you to specify which models to destroy in the - # attributes hash by adding a form element for the <tt>_destroy</tt> - # parameter with a value that evaluates to +true+ - # (eg. 1, '1', true, or 'true'): - # - # <%= form_for @person do |person_form| %> - # ... - # <%= person_form.fields_for :projects do |project_fields| %> - # Delete: <%= project_fields.check_box :_destroy %> - # <% end %> - # ... - # <% end %> - # - # When a collection is used you might want to know the index of each - # object into the array. For this purpose, the <tt>index</tt> method - # is available in the FormBuilder object. - # - # <%= form_for @person do |person_form| %> - # ... - # <%= person_form.fields_for :projects do |project_fields| %> - # Project #<%= project_fields.index %> - # ... - # <% end %> - # ... - # <% end %> - # - # Note that fields_for will automatically generate a hidden field - # to store the ID of the record. There are circumstances where this - # hidden field is not needed and you can pass <tt>hidden_field_id: false</tt> - # to prevent fields_for from rendering it automatically. - def fields_for(record_name, record_object = nil, fields_options = {}, &block) - fields_options, record_object = record_object, nil if record_object.is_a?(Hash) && record_object.extractable_options? - fields_options[:builder] ||= options[:builder] - fields_options[:namespace] = options[:namespace] - fields_options[:parent_builder] = self - - case record_name - when String, Symbol - if nested_attributes_association?(record_name) - return fields_for_with_nested_attributes(record_name, record_object, fields_options, block) - end - else - record_object = record_name.is_a?(Array) ? record_name.last : record_name - record_name = model_name_from_record_or_class(record_object).param_key - end - - index = if options.has_key?(:index) - options[:index] - elsif defined?(@auto_index) - self.object_name = @object_name.to_s.sub(/\[\]$/,"") - @auto_index - end - - record_name = index ? "#{object_name}[#{index}][#{record_name}]" : "#{object_name}[#{record_name}]" - fields_options[:child_index] = index - - @template.fields_for(record_name, record_object, fields_options, &block) - end - - # Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object - # assigned to the template (identified by +object+). The text of label will default to the attribute name unless a translation - # is found in the current I18n locale (through helpers.label.<modelname>.<attribute>) or you specify it explicitly. - # Additional options on the label tag can be passed as a hash with +options+. These options will be tagged - # onto the HTML as an HTML element attribute as in the example shown, except for the <tt>:value</tt> option, which is designed to - # target labels for radio_button tags (where the value is used in the ID of the input tag). - # - # ==== Examples - # label(:post, :title) - # # => <label for="post_title">Title</label> - # - # You can localize your labels based on model and attribute names. - # For example you can define the following in your locale (e.g. en.yml) - # - # helpers: - # label: - # post: - # body: "Write your entire text here" - # - # Which then will result in - # - # label(:post, :body) - # # => <label for="post_body">Write your entire text here</label> - # - # Localization can also be based purely on the translation of the attribute-name - # (if you are using ActiveRecord): - # - # activerecord: - # attributes: - # post: - # cost: "Total cost" - # - # label(:post, :cost) - # # => <label for="post_cost">Total cost</label> - # - # label(:post, :title, "A short title") - # # => <label for="post_title">A short title</label> - # - # label(:post, :title, "A short title", class: "title_label") - # # => <label for="post_title" class="title_label">A short title</label> - # - # label(:post, :privacy, "Public Post", value: "public") - # # => <label for="post_privacy_public">Public Post</label> - # - # label(:post, :terms) do - # 'Accept <a href="/terms">Terms</a>.'.html_safe - # end - def label(method, text = nil, options = {}, &block) - @template.label(@object_name, method, text, objectify_options(options), &block) - end - - # Returns a checkbox tag tailored for accessing a specified attribute (identified by +method+) on an object - # assigned to the template (identified by +object+). This object must be an instance object (@object) and not a local object. - # It's intended that +method+ returns an integer and if that integer is above zero, then the checkbox is checked. - # Additional options on the input tag can be passed as a hash with +options+. The +checked_value+ defaults to 1 - # while the default +unchecked_value+ is set to 0 which is convenient for boolean values. - # - # ==== Gotcha - # - # The HTML specification says unchecked check boxes are not successful, and - # thus web browsers do not send them. Unfortunately this introduces a gotcha: - # if an +Invoice+ model has a +paid+ flag, and in the form that edits a paid - # invoice the user unchecks its check box, no +paid+ parameter is sent. So, - # any mass-assignment idiom like - # - # @invoice.update(params[:invoice]) - # - # wouldn't update the flag. - # - # To prevent this the helper generates an auxiliary hidden field before - # the very check box. The hidden field has the same name and its - # attributes mimic an unchecked check box. - # - # This way, the client either sends only the hidden field (representing - # the check box is unchecked), or both fields. Since the HTML specification - # says key/value pairs have to be sent in the same order they appear in the - # form, and parameters extraction gets the last occurrence of any repeated - # key in the query string, that works for ordinary forms. - # - # Unfortunately that workaround does not work when the check box goes - # within an array-like parameter, as in - # - # <%= fields_for "project[invoice_attributes][]", invoice, index: nil do |form| %> - # <%= form.check_box :paid %> - # ... - # <% end %> - # - # because parameter name repetition is precisely what Rails seeks to distinguish - # the elements of the array. For each item with a checked check box you - # get an extra ghost item with only that attribute, assigned to "0". - # - # In that case it is preferable to either use +check_box_tag+ or to use - # hashes instead of arrays. - # - # # Let's say that @post.validated? is 1: - # check_box("post", "validated") - # # => <input name="post[validated]" type="hidden" value="0" /> - # # <input checked="checked" type="checkbox" id="post_validated" name="post[validated]" value="1" /> - # - # # Let's say that @puppy.gooddog is "no": - # check_box("puppy", "gooddog", {}, "yes", "no") - # # => <input name="puppy[gooddog]" type="hidden" value="no" /> - # # <input type="checkbox" id="puppy_gooddog" name="puppy[gooddog]" value="yes" /> - # - # check_box("eula", "accepted", { class: 'eula_check' }, "yes", "no") - # # => <input name="eula[accepted]" type="hidden" value="no" /> - # # <input type="checkbox" class="eula_check" id="eula_accepted" name="eula[accepted]" value="yes" /> - def check_box(method, options = {}, checked_value = "1", unchecked_value = "0") - @template.check_box(@object_name, method, objectify_options(options), checked_value, unchecked_value) - end - - # Returns a radio button tag for accessing a specified attribute (identified by +method+) on an object - # assigned to the template (identified by +object+). If the current value of +method+ is +tag_value+ the - # radio button will be checked. - # - # To force the radio button to be checked pass <tt>checked: true</tt> in the - # +options+ hash. You may pass HTML options there as well. - # - # # Let's say that @post.category returns "rails": - # radio_button("post", "category", "rails") - # radio_button("post", "category", "java") - # # => <input type="radio" id="post_category_rails" name="post[category]" value="rails" checked="checked" /> - # # <input type="radio" id="post_category_java" name="post[category]" value="java" /> - # - # radio_button("user", "receive_newsletter", "yes") - # radio_button("user", "receive_newsletter", "no") - # # => <input type="radio" id="user_receive_newsletter_yes" name="user[receive_newsletter]" value="yes" /> - # # <input type="radio" id="user_receive_newsletter_no" name="user[receive_newsletter]" value="no" checked="checked" /> - def radio_button(method, tag_value, options = {}) - @template.radio_button(@object_name, method, tag_value, objectify_options(options)) - end - - # Returns a hidden input tag tailored for accessing a specified attribute (identified by +method+) on an object - # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a - # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example - # shown. - # - # ==== Examples - # hidden_field(:signup, :pass_confirm) - # # => <input type="hidden" id="signup_pass_confirm" name="signup[pass_confirm]" value="#{@signup.pass_confirm}" /> - # - # hidden_field(:post, :tag_list) - # # => <input type="hidden" id="post_tag_list" name="post[tag_list]" value="#{@post.tag_list}" /> - # - # hidden_field(:user, :token) - # # => <input type="hidden" id="user_token" name="user[token]" value="#{@user.token}" /> - # - def hidden_field(method, options = {}) - @emitted_hidden_id = true if method == :id - @template.hidden_field(@object_name, method, objectify_options(options)) - end - - # Returns a file upload input tag tailored for accessing a specified attribute (identified by +method+) on an object - # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a - # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example - # shown. - # - # Using this method inside a +form_for+ block will set the enclosing form's encoding to <tt>multipart/form-data</tt>. - # - # ==== Options - # * Creates standard HTML attributes for the tag. - # * <tt>:disabled</tt> - If set to true, the user will not be able to use this input. - # * <tt>:multiple</tt> - If set to true, *in most updated browsers* the user will be allowed to select multiple files. - # * <tt>:accept</tt> - If set to one or multiple mime-types, the user will be suggested a filter when choosing a file. You still need to set up model validations. - # - # ==== Examples - # file_field(:user, :avatar) - # # => <input type="file" id="user_avatar" name="user[avatar]" /> - # - # file_field(:post, :image, :multiple => true) - # # => <input type="file" id="post_image" name="post[image]" multiple="true" /> - # - # file_field(:post, :attached, accept: 'text/html') - # # => <input accept="text/html" type="file" id="post_attached" name="post[attached]" /> - # - # file_field(:post, :image, accept: 'image/png,image/gif,image/jpeg') - # # => <input type="file" id="post_image" name="post[image]" accept="image/png,image/gif,image/jpeg" /> - # - # file_field(:attachment, :file, class: 'file_input') - # # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" /> - def file_field(method, options = {}) - self.multipart = true - @template.file_field(@object_name, method, objectify_options(options)) - end - - # Add the submit button for the given form. When no value is given, it checks - # if the object is a new resource or not to create the proper label: - # - # <%= form_for @post do |f| %> - # <%= f.submit %> - # <% end %> - # - # In the example above, if @post is a new record, it will use "Create Post" as - # submit button label, otherwise, it uses "Update Post". - # - # Those labels can be customized using I18n, under the helpers.submit key and accept - # the %{model} as translation interpolation: - # - # en: - # helpers: - # submit: - # create: "Create a %{model}" - # update: "Confirm changes to %{model}" - # - # It also searches for a key specific for the given object: - # - # en: - # helpers: - # submit: - # post: - # create: "Add %{model}" - # - def submit(value=nil, options={}) - value, options = nil, value if value.is_a?(Hash) - value ||= submit_default_value - @template.submit_tag(value, options) - end - - # Add the submit button for the given form. When no value is given, it checks - # if the object is a new resource or not to create the proper label: - # - # <%= form_for @post do |f| %> - # <%= f.button %> - # <% end %> - # - # In the example above, if @post is a new record, it will use "Create Post" as - # button label, otherwise, it uses "Update Post". - # - # Those labels can be customized using I18n, under the helpers.submit key - # (the same as submit helper) and accept the %{model} as translation interpolation: - # - # en: - # helpers: - # submit: - # create: "Create a %{model}" - # update: "Confirm changes to %{model}" - # - # It also searches for a key specific for the given object: - # - # en: - # helpers: - # submit: - # post: - # create: "Add %{model}" - # - # ==== Examples - # button("Create a post") - # # => <button name='button' type='submit'>Create post</button> - # - # button do - # content_tag(:strong, 'Ask me!') - # end - # # => <button name='button' type='submit'> - # # <strong>Ask me!</strong> - # # </button> - # - def button(value = nil, options = {}, &block) - value, options = nil, value if value.is_a?(Hash) - value ||= submit_default_value - @template.button_tag(value, options, &block) - end - - def emitted_hidden_id? - @emitted_hidden_id ||= nil - end - - private - def objectify_options(options) - @default_options.merge(options.merge(object: @object)) - end - - def submit_default_value - object = convert_to_model(@object) - key = object ? (object.persisted? ? :update : :create) : :submit - - model = if object.class.respond_to?(:model_name) - object.class.model_name.human - else - @object_name.to_s.humanize - end - - defaults = [] - defaults << :"helpers.submit.#{object_name}.#{key}" - defaults << :"helpers.submit.#{key}" - defaults << "#{key.to_s.humanize} #{model}" - - I18n.t(defaults.shift, model: model, default: defaults) - end - - def nested_attributes_association?(association_name) - @object.respond_to?("#{association_name}_attributes=") - end - - def fields_for_with_nested_attributes(association_name, association, options, block) - name = "#{object_name}[#{association_name}_attributes]" - association = convert_to_model(association) - - if association.respond_to?(:persisted?) - association = [association] if @object.send(association_name).respond_to?(:to_ary) - elsif !association.respond_to?(:to_ary) - association = @object.send(association_name) - end - - if association.respond_to?(:to_ary) - explicit_child_index = options[:child_index] - output = ActiveSupport::SafeBuffer.new - association.each do |child| - options[:child_index] = nested_child_index(name) unless explicit_child_index - output << fields_for_nested_model("#{name}[#{options[:child_index]}]", child, options, block) - end - output - elsif association - fields_for_nested_model(name, association, options, block) - end - end - - def fields_for_nested_model(name, object, fields_options, block) - object = convert_to_model(object) - emit_hidden_id = object.persisted? && fields_options.fetch(:include_id) { - options.fetch(:include_id, true) - } - - @template.fields_for(name, object, fields_options) do |f| - output = @template.capture(f, &block) - output.concat f.hidden_field(:id) if output && emit_hidden_id && !f.emitted_hidden_id? - output - end - end - - def nested_child_index(name) - @nested_child_index[name] ||= -1 - @nested_child_index[name] += 1 - end - end - end - - ActiveSupport.on_load(:action_view) do - cattr_accessor(:default_form_builder) { ::ActionView::Helpers::FormBuilder } - end -end diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb deleted file mode 100644 index 377819a80c..0000000000 --- a/actionpack/lib/action_view/helpers/form_options_helper.rb +++ /dev/null @@ -1,833 +0,0 @@ -require 'cgi' -require 'erb' -require 'action_view/helpers/form_helper' -require 'active_support/core_ext/string/output_safety' -require 'active_support/core_ext/array/extract_options' -require 'active_support/core_ext/array/wrap' - -module ActionView - # = Action View Form Option Helpers - module Helpers - # Provides a number of methods for turning different kinds of containers into a set of option tags. - # - # The <tt>collection_select</tt>, <tt>select</tt> and <tt>time_zone_select</tt> methods take an <tt>options</tt> parameter, a hash: - # - # * <tt>:include_blank</tt> - set to true or a prompt string if the first option element of the select element is a blank. Useful if there is not a default value required for the select element. - # - # select("post", "category", Post::CATEGORIES, {include_blank: true}) - # - # could become: - # - # <select name="post[category]"> - # <option></option> - # <option>joke</option> - # <option>poem</option> - # </select> - # - # Another common case is a select tag for a <tt>belongs_to</tt>-associated object. - # - # Example with @post.person_id => 2: - # - # select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, {include_blank: 'None'}) - # - # could become: - # - # <select name="post[person_id]"> - # <option value="">None</option> - # <option value="1">David</option> - # <option value="2" selected="selected">Sam</option> - # <option value="3">Tobias</option> - # </select> - # - # * <tt>:prompt</tt> - set to true or a prompt string. When the select element doesn't have a value yet, this prepends an option with a generic prompt -- "Please select" -- or the given prompt string. - # - # select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, {prompt: 'Select Person'}) - # - # could become: - # - # <select name="post[person_id]"> - # <option value="">Select Person</option> - # <option value="1">David</option> - # <option value="2">Sam</option> - # <option value="3">Tobias</option> - # </select> - # - # Like the other form helpers, +select+ can accept an <tt>:index</tt> option to manually set the ID used in the resulting output. Unlike other helpers, +select+ expects this - # option to be in the +html_options+ parameter. - # - # select("album[]", "genre", %w[rap rock country], {}, { index: nil }) - # - # becomes: - # - # <select name="album[][genre]" id="album__genre"> - # <option value="rap">rap</option> - # <option value="rock">rock</option> - # <option value="country">country</option> - # </select> - # - # * <tt>:disabled</tt> - can be a single value or an array of values that will be disabled options in the final output. - # - # select("post", "category", Post::CATEGORIES, {disabled: 'restricted'}) - # - # could become: - # - # <select name="post[category]"> - # <option></option> - # <option>joke</option> - # <option>poem</option> - # <option disabled="disabled">restricted</option> - # </select> - # - # When used with the <tt>collection_select</tt> helper, <tt>:disabled</tt> can also be a Proc that identifies those options that should be disabled. - # - # collection_select(:post, :category_id, Category.all, :id, :name, {disabled: lambda{|category| category.archived? }}) - # - # If the categories "2008 stuff" and "Christmas" return true when the method <tt>archived?</tt> is called, this would return: - # <select name="post[category_id]"> - # <option value="1" disabled="disabled">2008 stuff</option> - # <option value="2" disabled="disabled">Christmas</option> - # <option value="3">Jokes</option> - # <option value="4">Poems</option> - # </select> - # - module FormOptionsHelper - # ERB::Util can mask some helpers like textilize. Make sure to include them. - include TextHelper - - # Create a select tag and a series of contained option tags for the provided object and method. - # The option currently held by the object will be selected, provided that the object is available. - # - # There are two possible formats for the choices parameter, corresponding to other helpers' output: - # * A flat collection: see options_for_select - # * A nested collection: see grouped_options_for_select - # - # Example with @post.person_id => 1: - # select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, { include_blank: true }) - # - # could become: - # - # <select name="post[person_id]"> - # <option value=""></option> - # <option value="1" selected="selected">David</option> - # <option value="2">Sam</option> - # <option value="3">Tobias</option> - # </select> - # - # This can be used to provide a default set of options in the standard way: before rendering the create form, a - # new model instance is assigned the default options and bound to @model_name. Usually this model is not saved - # to the database. Instead, a second model object is created when the create request is received. - # This allows the user to submit a form page more than once with the expected results of creating multiple records. - # In addition, this allows a single partial to be used to generate form inputs for both edit and create forms. - # - # By default, <tt>post.person_id</tt> is the selected option. Specify <tt>selected: value</tt> to use a different selection - # or <tt>selected: nil</tt> to leave all options unselected. Similarly, you can specify values to be disabled in the option - # tags by specifying the <tt>:disabled</tt> option. This can either be a single value or an array of values to be disabled. - # - # ==== Gotcha - # - # The HTML specification says when +multiple+ parameter passed to select and all options got deselected - # web browsers do not send any value to server. Unfortunately this introduces a gotcha: - # if an +User+ model has many +roles+ and have +role_ids+ accessor, and in the form that edits roles of the user - # the user deselects all roles from +role_ids+ multiple select box, no +role_ids+ parameter is sent. So, - # any mass-assignment idiom like - # - # @user.update(params[:user]) - # - # wouldn't update roles. - # - # To prevent this the helper generates an auxiliary hidden field before - # every multiple select. The hidden field has the same name as multiple select and blank value. - # - # This way, the client either sends only the hidden field (representing - # the deselected multiple select box), or both fields. Since the HTML specification - # says key/value pairs have to be sent in the same order they appear in the - # form, and parameters extraction gets the last occurrence of any repeated - # key in the query string, that works for ordinary forms. - # - # In case if you don't want the helper to generate this hidden field you can specify - # <tt>include_hidden: false</tt> option. - # - def select(object, method, choices, options = {}, html_options = {}) - Tags::Select.new(object, method, self, choices, options, html_options).render - end - - # Returns <tt><select></tt> and <tt><option></tt> tags for the collection of existing return values of - # +method+ for +object+'s class. The value returned from calling +method+ on the instance +object+ will - # be selected. If calling +method+ returns +nil+, no selection is made without including <tt>:prompt</tt> - # or <tt>:include_blank</tt> in the +options+ hash. - # - # The <tt>:value_method</tt> and <tt>:text_method</tt> parameters are methods to be called on each member - # of +collection+. The return values are used as the +value+ attribute and contents of each - # <tt><option></tt> tag, respectively. They can also be any object that responds to +call+, such - # as a +proc+, that will be called for each member of the +collection+ to - # retrieve the value/text. - # - # Example object structure for use with this method: - # - # class Post < ActiveRecord::Base - # belongs_to :author - # end - # - # class Author < ActiveRecord::Base - # has_many :posts - # def name_with_initial - # "#{first_name.first}. #{last_name}" - # end - # end - # - # Sample usage (selecting the associated Author for an instance of Post, <tt>@post</tt>): - # - # collection_select(:post, :author_id, Author.all, :id, :name_with_initial, prompt: true) - # - # If <tt>@post.author_id</tt> is already <tt>1</tt>, this would return: - # <select name="post[author_id]"> - # <option value="">Please select</option> - # <option value="1" selected="selected">D. Heinemeier Hansson</option> - # <option value="2">D. Thomas</option> - # <option value="3">M. Clark</option> - # </select> - def collection_select(object, method, collection, value_method, text_method, options = {}, html_options = {}) - Tags::CollectionSelect.new(object, method, self, collection, value_method, text_method, options, html_options).render - end - - # Returns <tt><select></tt>, <tt><optgroup></tt> and <tt><option></tt> tags for the collection of existing return values of - # +method+ for +object+'s class. The value returned from calling +method+ on the instance +object+ will - # be selected. If calling +method+ returns +nil+, no selection is made without including <tt>:prompt</tt> - # or <tt>:include_blank</tt> in the +options+ hash. - # - # Parameters: - # * +object+ - The instance of the class to be used for the select tag - # * +method+ - The attribute of +object+ corresponding to the select tag - # * +collection+ - An array of objects representing the <tt><optgroup></tt> tags. - # * +group_method+ - The name of a method which, when called on a member of +collection+, returns an - # array of child objects representing the <tt><option></tt> tags. - # * +group_label_method+ - The name of a method which, when called on a member of +collection+, returns a - # string to be used as the +label+ attribute for its <tt><optgroup></tt> tag. - # * +option_key_method+ - The name of a method which, when called on a child object of a member of - # +collection+, returns a value to be used as the +value+ attribute for its <tt><option></tt> tag. - # * +option_value_method+ - The name of a method which, when called on a child object of a member of - # +collection+, returns a value to be used as the contents of its <tt><option></tt> tag. - # - # Example object structure for use with this method: - # - # class Continent < ActiveRecord::Base - # has_many :countries - # # attribs: id, name - # end - # - # class Country < ActiveRecord::Base - # belongs_to :continent - # # attribs: id, name, continent_id - # end - # - # class City < ActiveRecord::Base - # belongs_to :country - # # attribs: id, name, country_id - # end - # - # Sample usage: - # - # grouped_collection_select(:city, :country_id, @continents, :countries, :name, :id, :name) - # - # Possible output: - # - # <select name="city[country_id]"> - # <optgroup label="Africa"> - # <option value="1">South Africa</option> - # <option value="3">Somalia</option> - # </optgroup> - # <optgroup label="Europe"> - # <option value="7" selected="selected">Denmark</option> - # <option value="2">Ireland</option> - # </optgroup> - # </select> - # - def grouped_collection_select(object, method, collection, group_method, group_label_method, option_key_method, option_value_method, options = {}, html_options = {}) - Tags::GroupedCollectionSelect.new(object, method, self, collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options).render - end - - # Return select and option tags for the given object and method, using - # #time_zone_options_for_select to generate the list of option tags. - # - # In addition to the <tt>:include_blank</tt> option documented above, - # this method also supports a <tt>:model</tt> option, which defaults - # to ActiveSupport::TimeZone. This may be used by users to specify a - # different time zone model object. (See +time_zone_options_for_select+ - # for more information.) - # - # You can also supply an array of ActiveSupport::TimeZone objects - # as +priority_zones+, so that they will be listed above the rest of the - # (long) list. (You can use ActiveSupport::TimeZone.us_zones as a convenience - # for obtaining a list of the US time zones, or a Regexp to select the zones - # of your choice) - # - # Finally, this method supports a <tt>:default</tt> option, which selects - # a default ActiveSupport::TimeZone if the object's time zone is +nil+. - # - # time_zone_select( "user", "time_zone", nil, include_blank: true) - # - # time_zone_select( "user", "time_zone", nil, default: "Pacific Time (US & Canada)" ) - # - # time_zone_select( "user", 'time_zone', ActiveSupport::TimeZone.us_zones, default: "Pacific Time (US & Canada)") - # - # time_zone_select( "user", 'time_zone', [ ActiveSupport::TimeZone['Alaska'], ActiveSupport::TimeZone['Hawaii'] ]) - # - # time_zone_select( "user", 'time_zone', /Australia/) - # - # time_zone_select( "user", "time_zone", ActiveSupport::TimeZone.all.sort, model: ActiveSupport::TimeZone) - def time_zone_select(object, method, priority_zones = nil, options = {}, html_options = {}) - Tags::TimeZoneSelect.new(object, method, self, priority_zones, options, html_options).render - end - - # Accepts a container (hash, array, enumerable, your type) and returns a string of option tags. Given a container - # where the elements respond to first and last (such as a two-element array), the "lasts" serve as option values and - # the "firsts" as option text. Hashes are turned into this form automatically, so the keys become "firsts" and values - # become lasts. If +selected+ is specified, the matching "last" or element will get the selected option-tag. +selected+ - # may also be an array of values to be selected when using a multiple select. - # - # options_for_select([["Dollar", "$"], ["Kroner", "DKK"]]) - # # => <option value="$">Dollar</option> - # # => <option value="DKK">Kroner</option> - # - # options_for_select([ "VISA", "MasterCard" ], "MasterCard") - # # => <option>VISA</option> - # # => <option selected="selected">MasterCard</option> - # - # options_for_select({ "Basic" => "$20", "Plus" => "$40" }, "$40") - # # => <option value="$20">Basic</option> - # # => <option value="$40" selected="selected">Plus</option> - # - # options_for_select([ "VISA", "MasterCard", "Discover" ], ["VISA", "Discover"]) - # # => <option selected="selected">VISA</option> - # # => <option>MasterCard</option> - # # => <option selected="selected">Discover</option> - # - # You can optionally provide html attributes as the last element of the array. - # - # options_for_select([ "Denmark", ["USA", {class: 'bold'}], "Sweden" ], ["USA", "Sweden"]) - # # => <option value="Denmark">Denmark</option> - # # => <option value="USA" class="bold" selected="selected">USA</option> - # # => <option value="Sweden" selected="selected">Sweden</option> - # - # options_for_select([["Dollar", "$", {class: "bold"}], ["Kroner", "DKK", {onclick: "alert('HI');"}]]) - # # => <option value="$" class="bold">Dollar</option> - # # => <option value="DKK" onclick="alert('HI');">Kroner</option> - # - # If you wish to specify disabled option tags, set +selected+ to be a hash, with <tt>:disabled</tt> being either a value - # or array of values to be disabled. In this case, you can use <tt>:selected</tt> to specify selected option tags. - # - # options_for_select(["Free", "Basic", "Advanced", "Super Platinum"], disabled: "Super Platinum") - # # => <option value="Free">Free</option> - # # => <option value="Basic">Basic</option> - # # => <option value="Advanced">Advanced</option> - # # => <option value="Super Platinum" disabled="disabled">Super Platinum</option> - # - # options_for_select(["Free", "Basic", "Advanced", "Super Platinum"], disabled: ["Advanced", "Super Platinum"]) - # # => <option value="Free">Free</option> - # # => <option value="Basic">Basic</option> - # # => <option value="Advanced" disabled="disabled">Advanced</option> - # # => <option value="Super Platinum" disabled="disabled">Super Platinum</option> - # - # options_for_select(["Free", "Basic", "Advanced", "Super Platinum"], selected: "Free", disabled: "Super Platinum") - # # => <option value="Free" selected="selected">Free</option> - # # => <option value="Basic">Basic</option> - # # => <option value="Advanced">Advanced</option> - # # => <option value="Super Platinum" disabled="disabled">Super Platinum</option> - # - # NOTE: Only the option tags are returned, you have to wrap this call in a regular HTML select tag. - def options_for_select(container, selected = nil) - return container if String === container - - selected, disabled = extract_selected_and_disabled(selected).map do |r| - Array(r).map { |item| item.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 } - - html_attributes[:selected] = 'selected' if option_value_selected?(value, selected) - html_attributes[:disabled] = 'disabled' if disabled && option_value_selected?(value, disabled) - html_attributes[:value] = value - - content_tag_string(:option, text, html_attributes) - end.join("\n").html_safe - end - - # Returns a string of option tags that have been compiled by iterating over the +collection+ and assigning - # the result of a call to the +value_method+ as the option value and the +text_method+ as the option text. - # - # options_from_collection_for_select(@people, 'id', 'name') - # # => <option value="#{person.id}">#{person.name}</option> - # - # This is more often than not used inside a #select_tag like this example: - # - # select_tag 'person', options_from_collection_for_select(@people, 'id', 'name') - # - # If +selected+ is specified as a value or array of values, the element(s) returning a match on +value_method+ - # will be selected option tag(s). - # - # If +selected+ is specified as a Proc, those members of the collection that return true for the anonymous - # function are the selected values. - # - # +selected+ can also be a hash, specifying both <tt>:selected</tt> and/or <tt>:disabled</tt> values as required. - # - # Be sure to specify the same class as the +value_method+ when specifying selected or disabled options. - # Failure to do this will produce undesired results. Example: - # options_from_collection_for_select(@people, 'id', 'name', '1') - # Will not select a person with the id of 1 because 1 (an Integer) is not the same as '1' (a string) - # options_from_collection_for_select(@people, 'id', 'name', 1) - # should produce the desired results. - def options_from_collection_for_select(collection, value_method, text_method, selected = nil) - options = collection.map do |element| - [value_for_collection(element, text_method), value_for_collection(element, value_method)] - end - selected, disabled = extract_selected_and_disabled(selected) - select_deselect = { - :selected => extract_values_from_collection(collection, value_method, selected), - :disabled => extract_values_from_collection(collection, value_method, disabled) - } - - options_for_select(options, select_deselect) - end - - # Returns a string of <tt><option></tt> tags, like <tt>options_from_collection_for_select</tt>, but - # groups them by <tt><optgroup></tt> tags based on the object relationships of the arguments. - # - # Parameters: - # * +collection+ - An array of objects representing the <tt><optgroup></tt> tags. - # * +group_method+ - The name of a method which, when called on a member of +collection+, returns an - # array of child objects representing the <tt><option></tt> tags. - # * group_label_method+ - The name of a method which, when called on a member of +collection+, returns a - # string to be used as the +label+ attribute for its <tt><optgroup></tt> tag. - # * +option_key_method+ - The name of a method which, when called on a child object of a member of - # +collection+, returns a value to be used as the +value+ attribute for its <tt><option></tt> tag. - # * +option_value_method+ - The name of a method which, when called on a child object of a member of - # +collection+, returns a value to be used as the contents of its <tt><option></tt> tag. - # * +selected_key+ - A value equal to the +value+ attribute for one of the <tt><option></tt> tags, - # which will have the +selected+ attribute set. Corresponds to the return value of one of the calls - # to +option_key_method+. If +nil+, no selection is made. Can also be a hash if disabled values are - # to be specified. - # - # Example object structure for use with this method: - # - # class Continent < ActiveRecord::Base - # has_many :countries - # # attribs: id, name - # end - # - # class Country < ActiveRecord::Base - # belongs_to :continent - # # attribs: id, name, continent_id - # end - # - # Sample usage: - # option_groups_from_collection_for_select(@continents, :countries, :name, :id, :name, 3) - # - # Possible output: - # <optgroup label="Africa"> - # <option value="1">Egypt</option> - # <option value="4">Rwanda</option> - # ... - # </optgroup> - # <optgroup label="Asia"> - # <option value="3" selected="selected">China</option> - # <option value="12">India</option> - # <option value="5">Japan</option> - # ... - # </optgroup> - # - # <b>Note:</b> Only the <tt><optgroup></tt> and <tt><option></tt> tags are returned, so you still have to - # wrap the output in an appropriate <tt><select></tt> tag. - def option_groups_from_collection_for_select(collection, group_method, group_label_method, option_key_method, option_value_method, selected_key = nil) - collection.map do |group| - option_tags = options_from_collection_for_select( - group.send(group_method), option_key_method, option_value_method, selected_key) - - content_tag(:optgroup, option_tags, :label => group.send(group_label_method)) - end.join.html_safe - end - - # Returns a string of <tt><option></tt> tags, like <tt>options_for_select</tt>, but - # wraps them with <tt><optgroup></tt> tags. - # - # Parameters: - # * +grouped_options+ - Accepts a nested array or hash of strings. The first value serves as the - # <tt><optgroup></tt> label while the second value must be an array of options. The second value can be a - # nested array of text-value pairs. See <tt>options_for_select</tt> for more info. - # Ex. ["North America",[["United States","US"],["Canada","CA"]]] - # * +selected_key+ - A value equal to the +value+ attribute for one of the <tt><option></tt> tags, - # which will have the +selected+ attribute set. Note: It is possible for this value to match multiple options - # as you might have the same option in multiple groups. Each will then get <tt>selected="selected"</tt>. - # - # Options: - # * <tt>:prompt</tt> - set to true or a prompt string. When the select element doesn't have a value yet, this - # prepends an option with a generic prompt - "Please select" - or the given prompt string. - # * <tt>:divider</tt> - the divider for the options groups. - # - # grouped_options = [ - # ['North America', - # [['United States','US'],'Canada']], - # ['Europe', - # ['Denmark','Germany','France']] - # ] - # grouped_options_for_select(grouped_options) - # - # grouped_options = { - # 'North America' => [['United States','US'], 'Canada'], - # 'Europe' => ['Denmark','Germany','France'] - # } - # grouped_options_for_select(grouped_options) - # - # Possible output: - # <optgroup label="North America"> - # <option value="US">United States</option> - # <option value="Canada">Canada</option> - # </optgroup> - # <optgroup label="Europe"> - # <option value="Denmark">Denmark</option> - # <option value="Germany">Germany</option> - # <option value="France">France</option> - # </optgroup> - # - # grouped_options = [ - # [['United States','US'], 'Canada'], - # ['Denmark','Germany','France'] - # ] - # grouped_options_for_select(grouped_options, nil, divider: '---------') - # - # Possible output: - # <optgroup label="---------"> - # <option value="US">United States</option> - # <option value="Canada">Canada</option> - # </optgroup> - # <optgroup label="---------"> - # <option value="Denmark">Denmark</option> - # <option value="Germany">Germany</option> - # <option value="France">France</option> - # </optgroup> - # - # <b>Note:</b> Only the <tt><optgroup></tt> and <tt><option></tt> tags are returned, so you still have to - # wrap the output in an appropriate <tt><select></tt> tag. - def grouped_options_for_select(grouped_options, selected_key = nil, options = {}) - if options.is_a?(Hash) - prompt = options[:prompt] - divider = options[:divider] - else - prompt = options - options = {} - message = "Passing the prompt to grouped_options_for_select as an argument is deprecated. " \ - "Please use an options hash like `{ prompt: #{prompt.inspect} }`." - ActiveSupport::Deprecation.warn message - end - - body = "".html_safe - - if prompt - body.safe_concat content_tag(:option, prompt_text(prompt), :value => "") - end - - grouped_options.each do |container| - if divider - label = divider - else - label, container = container - end - body.safe_concat content_tag(:optgroup, options_for_select(container, selected_key), :label => label) - end - - body - end - - # Returns a string of option tags for pretty much any time zone in the - # world. Supply a ActiveSupport::TimeZone name as +selected+ to have it - # marked as the selected option tag. You can also supply an array of - # ActiveSupport::TimeZone objects as +priority_zones+, so that they will - # be listed above the rest of the (long) list. (You can use - # ActiveSupport::TimeZone.us_zones as a convenience for obtaining a list - # of the US time zones, or a Regexp to select the zones of your choice) - # - # The +selected+ parameter must be either +nil+, or a string that names - # a ActiveSupport::TimeZone. - # - # By default, +model+ is the ActiveSupport::TimeZone constant (which can - # be obtained in Active Record as a value object). The only requirement - # is that the +model+ parameter be an object that responds to +all+, and - # returns an array of objects that represent time zones. - # - # NOTE: Only the option tags are returned, you have to wrap this call in - # a regular HTML select tag. - def time_zone_options_for_select(selected = nil, priority_zones = nil, model = ::ActiveSupport::TimeZone) - zone_options = "".html_safe - - zones = model.all - convert_zones = lambda { |list| list.map { |z| [ z.to_s, z.name ] } } - - if priority_zones - if priority_zones.is_a?(Regexp) - priority_zones = zones.grep(priority_zones) - end - - zone_options.safe_concat options_for_select(convert_zones[priority_zones], selected) - zone_options.safe_concat content_tag(:option, '-------------', :value => '', :disabled => 'disabled') - zone_options.safe_concat "\n" - - zones = zones - priority_zones - end - - zone_options.safe_concat options_for_select(convert_zones[zones], selected) - end - - # Returns radio button tags for the collection of existing return values - # of +method+ for +object+'s class. The value returned from calling - # +method+ on the instance +object+ will be selected. If calling +method+ - # returns +nil+, no selection is made. - # - # The <tt>:value_method</tt> and <tt>:text_method</tt> parameters are - # methods to be called on each member of +collection+. The return values - # are used as the +value+ attribute and contents of each radio button tag, - # respectively. They can also be any object that responds to +call+, such - # as a +proc+, that will be called for each member of the +collection+ to - # retrieve the value/text. - # - # Example object structure for use with this method: - # class Post < ActiveRecord::Base - # belongs_to :author - # end - # class Author < ActiveRecord::Base - # has_many :posts - # def name_with_initial - # "#{first_name.first}. #{last_name}" - # end - # end - # - # Sample usage (selecting the associated Author for an instance of Post, <tt>@post</tt>): - # collection_radio_buttons(:post, :author_id, Author.all, :id, :name_with_initial) - # - # If <tt>@post.author_id</tt> is already <tt>1</tt>, this would return: - # <input id="post_author_id_1" name="post[author_id]" type="radio" value="1" checked="checked" /> - # <label for="post_author_id_1">D. Heinemeier Hansson</label> - # <input id="post_author_id_2" name="post[author_id]" type="radio" value="2" /> - # <label for="post_author_id_2">D. Thomas</label> - # <input id="post_author_id_3" name="post[author_id]" type="radio" value="3" /> - # <label for="post_author_id_3">M. Clark</label> - # - # It is also possible to customize the way the elements will be shown by - # giving a block to the method: - # collection_radio_buttons(:post, :author_id, Author.all, :id, :name_with_initial) do |b| - # b.label { b.radio_button } - # end - # - # The argument passed to the block is a special kind of builder for this - # collection, which has the ability to generate the label and radio button - # for the current item in the collection, with proper text and value. - # Using it, you can change the label and radio button display order or - # even use the label as wrapper, as in the example above. - # - # The builder methods <tt>label</tt> and <tt>radio_button</tt> also accept - # extra html options: - # collection_radio_buttons(:post, :author_id, Author.all, :id, :name_with_initial) do |b| - # b.label(class: "radio_button") { b.radio_button(class: "radio_button") } - # end - # - # There are also three special methods available: <tt>object</tt>, <tt>text</tt> and - # <tt>value</tt>, which are the current item being rendered, its text and value methods, - # respectively. You can use them like this: - # collection_radio_buttons(:post, :author_id, Author.all, :id, :name_with_initial) do |b| - # b.label(:"data-value" => b.value) { b.radio_button + b.text } - # end - def collection_radio_buttons(object, method, collection, value_method, text_method, options = {}, html_options = {}, &block) - Tags::CollectionRadioButtons.new(object, method, self, collection, value_method, text_method, options, html_options).render(&block) - end - - # Returns check box tags for the collection of existing return values of - # +method+ for +object+'s class. The value returned from calling +method+ - # on the instance +object+ will be selected. If calling +method+ returns - # +nil+, no selection is made. - # - # The <tt>:value_method</tt> and <tt>:text_method</tt> parameters are - # methods to be called on each member of +collection+. The return values - # are used as the +value+ attribute and contents of each check box tag, - # respectively. They can also be any object that responds to +call+, such - # as a +proc+, that will be called for each member of the +collection+ to - # retrieve the value/text. - # - # Example object structure for use with this method: - # class Post < ActiveRecord::Base - # has_and_belongs_to_many :author - # end - # class Author < ActiveRecord::Base - # has_and_belongs_to_many :posts - # def name_with_initial - # "#{first_name.first}. #{last_name}" - # end - # end - # - # Sample usage (selecting the associated Author for an instance of Post, <tt>@post</tt>): - # collection_check_boxes(:post, :author_ids, Author.all, :id, :name_with_initial) - # - # If <tt>@post.author_ids</tt> is already <tt>[1]</tt>, this would return: - # <input id="post_author_ids_1" name="post[author_ids][]" type="checkbox" value="1" checked="checked" /> - # <label for="post_author_ids_1">D. Heinemeier Hansson</label> - # <input id="post_author_ids_2" name="post[author_ids][]" type="checkbox" value="2" /> - # <label for="post_author_ids_2">D. Thomas</label> - # <input id="post_author_ids_3" name="post[author_ids][]" type="checkbox" value="3" /> - # <label for="post_author_ids_3">M. Clark</label> - # <input name="post[author_ids][]" type="hidden" value="" /> - # - # It is also possible to customize the way the elements will be shown by - # giving a block to the method: - # collection_check_boxes(:post, :author_ids, Author.all, :id, :name_with_initial) do |b| - # b.label { b.check_box } - # end - # - # The argument passed to the block is a special kind of builder for this - # collection, which has the ability to generate the label and check box - # for the current item in the collection, with proper text and value. - # Using it, you can change the label and check box display order or even - # use the label as wrapper, as in the example above. - # - # The builder methods <tt>label</tt> and <tt>check_box</tt> also accept - # extra html options: - # collection_check_boxes(:post, :author_ids, Author.all, :id, :name_with_initial) do |b| - # b.label(class: "check_box") { b.check_box(class: "check_box") } - # end - # - # There are also three special methods available: <tt>object</tt>, <tt>text</tt> and - # <tt>value</tt>, which are the current item being rendered, its text and value methods, - # respectively. You can use them like this: - # collection_check_boxes(:post, :author_ids, Author.all, :id, :name_with_initial) do |b| - # b.label(:"data-value" => b.value) { b.check_box + b.text } - # end - def collection_check_boxes(object, method, collection, value_method, text_method, options = {}, html_options = {}, &block) - Tags::CollectionCheckBoxes.new(object, method, self, collection, value_method, text_method, options, html_options).render(&block) - end - - private - def option_html_attributes(element) - if Array === element - element.select { |e| Hash === e }.reduce({}, :merge!) - else - {} - end - end - - def option_text_and_value(option) - # Options are [text, value] pairs or strings used for both. - if !option.is_a?(String) && option.respond_to?(:first) && option.respond_to?(:last) - option = option.reject { |e| Hash === e } if Array === option - [option.first, option.last] - else - [option, option] - end - end - - def option_value_selected?(value, selected) - Array(selected).include? value - end - - def extract_selected_and_disabled(selected) - if selected.is_a?(Proc) - [selected, nil] - else - selected = Array.wrap(selected) - options = selected.extract_options!.symbolize_keys - selected_items = options.fetch(:selected, selected) - [selected_items, options[:disabled]] - end - end - - def extract_values_from_collection(collection, value_method, selected) - if selected.is_a?(Proc) - collection.map do |element| - element.send(value_method) if selected.call(element) - end.compact - else - selected - end - end - - def value_for_collection(item, value) - value.respond_to?(:call) ? value.call(item) : item.send(value) - end - - def prompt_text(prompt) - prompt = prompt.kind_of?(String) ? prompt : I18n.translate('helpers.select.prompt', :default => 'Please select') - end - end - - class FormBuilder - # Wraps ActionView::Helpers::FormOptionsHelper#select for form builders: - # - # <%= form_for @post do |f| %> - # <%= f.select :person_id, Person.all.collect {|p| [ p.name, p.id ] }, { include_blank: true }) %> - # <%= f.submit %> - # <% end %> - # - # Please refer to the documentation of the base helper for details. - def select(method, choices, options = {}, html_options = {}) - @template.select(@object_name, method, choices, objectify_options(options), @default_options.merge(html_options)) - end - - # Wraps ActionView::Helpers::FormOptionsHelper#collection_select for form builders: - # - # <%= form_for @post do |f| %> - # <%= f.collection_select :person_id, Author.all, :id, :name_with_initial, prompt: true %> - # <%= f.submit %> - # <% end %> - # - # Please refer to the documentation of the base helper for details. - def collection_select(method, collection, value_method, text_method, options = {}, html_options = {}) - @template.collection_select(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options)) - end - - # Wraps ActionView::Helpers::FormOptionsHelper#grouped_collection_select for form builders: - # - # <%= form_for @city do |f| %> - # <%= f.grouped_collection_select :country_id, :country_id, @continents, :countries, :name, :id, :name %> - # <%= f.submit %> - # <% end %> - # - # Please refer to the documentation of the base helper for details. - def grouped_collection_select(method, collection, group_method, group_label_method, option_key_method, option_value_method, options = {}, html_options = {}) - @template.grouped_collection_select(@object_name, method, collection, group_method, group_label_method, option_key_method, option_value_method, objectify_options(options), @default_options.merge(html_options)) - end - - # Wraps ActionView::Helpers::FormOptionsHelper#time_zone_select for form builders: - # - # <%= form_for @user do |f| %> - # <%= f.time_zone_select :time_zone, nil, include_blank: true %> - # <%= f.submit %> - # <% end %> - # - # Please refer to the documentation of the base helper for details. - def time_zone_select(method, priority_zones = nil, options = {}, html_options = {}) - @template.time_zone_select(@object_name, method, priority_zones, objectify_options(options), @default_options.merge(html_options)) - end - - # Wraps ActionView::Helpers::FormOptionsHelper#collection_check_boxes for form builders: - # - # <%= form_for @post do |f| %> - # <%= f.collection_check_boxes :author_ids, Author.all, :id, :name_with_initial %> - # <%= f.submit %> - # <% end %> - # - # Please refer to the documentation of the base helper for details. - def collection_check_boxes(method, collection, value_method, text_method, options = {}, html_options = {}, &block) - @template.collection_check_boxes(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options), &block) - end - - # Wraps ActionView::Helpers::FormOptionsHelper#collection_radio_buttons for form builders: - # - # <%= form_for @post do |f| %> - # <%= f.collection_radio_buttons :author_id, Author.all, :id, :name_with_initial %> - # <%= f.submit %> - # <% end %> - # - # Please refer to the documentation of the base helper for details. - def collection_radio_buttons(method, collection, value_method, text_method, options = {}, html_options = {}, &block) - @template.collection_radio_buttons(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options), &block) - end - end - end -end diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb deleted file mode 100644 index 8abd5d6e9c..0000000000 --- a/actionpack/lib/action_view/helpers/form_tag_helper.rb +++ /dev/null @@ -1,785 +0,0 @@ -require 'cgi' -require 'action_view/helpers/tag_helper' -require 'active_support/core_ext/string/output_safety' -require 'active_support/core_ext/module/attribute_accessors' - -module ActionView - # = Action View Form Tag Helpers - module Helpers - # Provides a number of methods for creating form tags that don't rely on an Active Record object assigned to the template like - # FormHelper does. Instead, you provide the names and values manually. - # - # NOTE: The HTML options <tt>disabled</tt>, <tt>readonly</tt>, and <tt>multiple</tt> can all be treated as booleans. So specifying - # <tt>disabled: true</tt> will give <tt>disabled="disabled"</tt>. - module FormTagHelper - extend ActiveSupport::Concern - - include UrlHelper - include TextHelper - - mattr_accessor :embed_authenticity_token_in_remote_forms - self.embed_authenticity_token_in_remote_forms = false - - # Starts a form tag that points the action to an url configured with <tt>url_for_options</tt> just like - # ActionController::Base#url_for. The method for the form defaults to POST. - # - # ==== Options - # * <tt>:multipart</tt> - If set to true, the enctype is set to "multipart/form-data". - # * <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. - # * A list of parameters to feed to the URL the form will be posted to. - # * <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. - # - # ==== Examples - # form_tag('/posts') - # # => <form action="/posts" method="post"> - # - # form_tag('/posts/1', method: :put) - # # => <form action="/posts/1" method="post"> ... <input name="_method" type="hidden" value="put" /> ... - # - # form_tag('/upload', multipart: true) - # # => <form action="/upload" method="post" enctype="multipart/form-data"> - # - # <%= form_tag('/posts') do -%> - # <div><%= submit_tag 'Save' %></div> - # <% end -%> - # # => <form action="/posts" method="post"><div><input type="submit" name="commit" value="Save" /></div></form> - # - # <%= form_tag('/posts', remote: true) %> - # # => <form action="/posts" method="post" data-remote="true"> - # - # form_tag('http://far.away.com/form', authenticity_token: false) - # # form without authenticity token - # - # form_tag('http://far.away.com/form', authenticity_token: "cf50faa3fe97702ca1ae") - # # form with custom authenticity token - # - def form_tag(url_for_options = {}, options = {}, &block) - html_options = html_options_for_form(url_for_options, options) - if block_given? - form_tag_in_block(html_options, &block) - else - form_tag_html(html_options) - end - end - - # Creates a dropdown selection box, or if the <tt>:multiple</tt> option is set to true, a multiple - # choice selection box. - # - # Helpers::FormOptions can be used to create common select boxes such as countries, time zones, or - # associated records. <tt>option_tags</tt> is a string containing the option tags for the select box. - # - # ==== Options - # * <tt>:multiple</tt> - If set to true the selection will allow multiple choices. - # * <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 create - # * <tt>:prompt</tt> - Create a prompt option with blank value and the text asking user to select something - # * 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", "<option>David</option>".html_safe - # # => <select id="people" name="people"><option>David</option></select> - # - # select_tag "count", "<option>1</option><option>2</option><option>3</option><option>4</option>".html_safe - # # => <select id="count" name="count"><option>1</option><option>2</option> - # # <option>3</option><option>4</option></select> - # - # select_tag "colors", "<option>Red</option><option>Green</option><option>Blue</option>".html_safe, multiple: true - # # => <select id="colors" multiple="multiple" name="colors[]"><option>Red</option> - # # <option>Green</option><option>Blue</option></select> - # - # select_tag "locations", "<option>Home</option><option selected='selected'>Work</option><option>Out</option>".html_safe - # # => <select id="locations" name="locations"><option>Home</option><option selected='selected'>Work</option> - # # <option>Out</option></select> - # - # select_tag "access", "<option>Read</option><option>Write</option>".html_safe, multiple: true, class: 'form_input' - # # => <select class="form_input" id="access" multiple="multiple" name="access[]"><option>Read</option> - # # <option>Write</option></select> - # - # select_tag "people", options_from_collection_for_select(@people, "id", "name"), include_blank: true - # # => <select id="people" name="people"><option value=""></option><option value="1">David</option></select> - # - # select_tag "people", options_from_collection_for_select(@people, "id", "name"), prompt: "Select something" - # # => <select id="people" name="people"><option value="">Select something</option><option value="1">David</option></select> - # - # select_tag "destination", "<option>NYC</option><option>Paris</option><option>Rome</option>".html_safe, disabled: true - # # => <select disabled="disabled" id="destination" name="destination"><option>NYC</option> - # # <option>Paris</option><option>Rome</option></select> - # - # select_tag "credit_card", options_for_select([ "VISA", "MasterCard" ], "MasterCard") - # # => <select id="credit_card" name="credit_card"><option>VISA</option> - # # <option selected="selected">MasterCard</option></select> - def select_tag(name, option_tags = nil, options = {}) - 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) - end - - if prompt = options.delete(:prompt) - 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) - end - - # Creates a standard text field; use these text fields to input smaller chunks of text like a username - # or a search query. - # - # ==== Options - # * <tt>:disabled</tt> - If set to true, the user will not be able to use this input. - # * <tt>:size</tt> - The number of visible characters that will fit in the input. - # * <tt>:maxlength</tt> - The maximum number of characters that the browser will allow the user to enter. - # * <tt>:placeholder</tt> - The text contained in the field by default which is removed when the field receives focus. - # * Any other key creates standard HTML attributes for the tag. - # - # ==== Examples - # text_field_tag 'name' - # # => <input id="name" name="name" type="text" /> - # - # text_field_tag 'query', 'Enter your search query here' - # # => <input id="query" name="query" type="text" value="Enter your search query here" /> - # - # text_field_tag 'search', nil, placeholder: 'Enter search term...' - # # => <input id="search" name="search" placeholder="Enter search term..." type="text" /> - # - # text_field_tag 'request', nil, class: 'special_input' - # # => <input class="special_input" id="request" name="request" type="text" /> - # - # text_field_tag 'address', '', size: 75 - # # => <input id="address" name="address" size="75" type="text" value="" /> - # - # text_field_tag 'zip', nil, maxlength: 5 - # # => <input id="zip" maxlength="5" name="zip" type="text" /> - # - # text_field_tag 'payment_amount', '$0.00', disabled: true - # # => <input disabled="disabled" id="payment_amount" name="payment_amount" type="text" value="$0.00" /> - # - # text_field_tag 'ip', '0.0.0.0', maxlength: 15, size: 20, class: "ip-input" - # # => <input class="ip-input" id="ip" maxlength="15" name="ip" size="20" type="text" value="0.0.0.0" /> - def text_field_tag(name, value = nil, options = {}) - tag :input, { "type" => "text", "name" => name, "id" => sanitize_to_id(name), "value" => value }.update(options.stringify_keys) - end - - # Creates a label element. Accepts a block. - # - # ==== Options - # * Creates standard HTML attributes for the tag. - # - # ==== Examples - # label_tag 'name' - # # => <label for="name">Name</label> - # - # label_tag 'name', 'Your name' - # # => <label for="name">Your name</label> - # - # label_tag 'name', nil, class: 'small_label' - # # => <label for="name" class="small_label">Name</label> - def label_tag(name = nil, content_or_options = nil, options = nil, &block) - if block_given? && content_or_options.is_a?(Hash) - options = content_or_options = content_or_options.stringify_keys - else - options ||= {} - options = options.stringify_keys - end - options["for"] = sanitize_to_id(name) unless name.blank? || options.has_key?("for") - content_tag :label, content_or_options || name.to_s.humanize, options, &block - end - - # Creates a hidden form input field used to transmit data that would be lost due to HTTP's statelessness or - # data that should be hidden from the user. - # - # ==== Options - # * Creates standard HTML attributes for the tag. - # - # ==== Examples - # hidden_field_tag 'tags_list' - # # => <input id="tags_list" name="tags_list" type="hidden" /> - # - # hidden_field_tag 'token', 'VUBJKB23UIVI1UU1VOBVI@' - # # => <input id="token" name="token" type="hidden" value="VUBJKB23UIVI1UU1VOBVI@" /> - # - # hidden_field_tag 'collected_input', '', onchange: "alert('Input collected!')" - # # => <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")) - end - - # Creates a file upload field. If you are using file uploads then you will also need - # to set the multipart option for the form tag: - # - # <%= form_tag '/upload', multipart: true do %> - # <label for="file">File to Upload</label> <%= file_field_tag "file" %> - # <%= submit_tag %> - # <% end %> - # - # The specified URL will then be passed a File object containing the selected file, or if the field - # was left blank, a StringIO object. - # - # ==== Options - # * Creates standard HTML attributes for the tag. - # * <tt>:disabled</tt> - If set to true, the user will not be able to use this input. - # * <tt>:multiple</tt> - If set to true, *in most updated browsers* the user will be allowed to select multiple files. - # * <tt>:accept</tt> - If set to one or multiple mime-types, the user will be suggested a filter when choosing a file. You still need to set up model validations. - # - # ==== Examples - # file_field_tag 'attachment' - # # => <input id="attachment" name="attachment" type="file" /> - # - # file_field_tag 'avatar', class: 'profile_input' - # # => <input class="profile_input" id="avatar" name="avatar" type="file" /> - # - # file_field_tag 'picture', disabled: true - # # => <input disabled="disabled" id="picture" name="picture" type="file" /> - # - # file_field_tag 'resume', value: '~/resume.doc' - # # => <input id="resume" name="resume" type="file" value="~/resume.doc" /> - # - # file_field_tag 'user_pic', accept: 'image/png,image/gif,image/jpeg' - # # => <input accept="image/png,image/gif,image/jpeg" id="user_pic" name="user_pic" type="file" /> - # - # 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")) - end - - # Creates a password field, a masked text field that will hide the users input behind a mask character. - # - # ==== Options - # * <tt>:disabled</tt> - If set to true, the user will not be able to use this input. - # * <tt>:size</tt> - The number of visible characters that will fit in the input. - # * <tt>:maxlength</tt> - The maximum number of characters that the browser will allow the user to enter. - # * Any other key creates standard HTML attributes for the tag. - # - # ==== Examples - # password_field_tag 'pass' - # # => <input id="pass" name="pass" type="password" /> - # - # password_field_tag 'secret', 'Your secret here' - # # => <input id="secret" name="secret" type="password" value="Your secret here" /> - # - # password_field_tag 'masked', nil, class: 'masked_input_field' - # # => <input class="masked_input_field" id="masked" name="masked" type="password" /> - # - # password_field_tag 'token', '', size: 15 - # # => <input id="token" name="token" size="15" type="password" value="" /> - # - # password_field_tag 'key', nil, maxlength: 16 - # # => <input id="key" maxlength="16" name="key" type="password" /> - # - # password_field_tag 'confirm_pass', nil, disabled: true - # # => <input disabled="disabled" id="confirm_pass" name="confirm_pass" type="password" /> - # - # 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")) - end - - # Creates a text input area; use a textarea for longer text inputs such as blog posts or descriptions. - # - # ==== Options - # * <tt>:size</tt> - A string specifying the dimensions (columns by rows) of the textarea (e.g., "25x10"). - # * <tt>:rows</tt> - Specify the number of rows in the textarea - # * <tt>:cols</tt> - Specify the number of columns in the textarea - # * <tt>:disabled</tt> - If set to true, the user will not be able to use this input. - # * <tt>:escape</tt> - By default, the contents of the text input are HTML escaped. - # If you need unescaped contents, set this to false. - # * Any other key creates standard HTML attributes for the tag. - # - # ==== Examples - # text_area_tag 'post' - # # => <textarea id="post" name="post"></textarea> - # - # text_area_tag 'bio', @user.bio - # # => <textarea id="bio" name="bio">This is my biography.</textarea> - # - # text_area_tag 'body', nil, rows: 10, cols: 25 - # # => <textarea cols="25" id="body" name="body" rows="10"></textarea> - # - # text_area_tag 'body', nil, size: "25x10" - # # => <textarea name="body" id="body" cols="25" rows="10"></textarea> - # - # text_area_tag 'description', "Description goes here.", disabled: true - # # => <textarea disabled="disabled" id="description" name="description">Description goes here.</textarea> - # - # text_area_tag 'comment', nil, class: 'comment_input' - # # => <textarea class="comment_input" id="comment" name="comment"></textarea> - def text_area_tag(name, content = nil, options = {}) - options = options.stringify_keys - - if size = options.delete("size") - options["cols"], options["rows"] = size.split("x") if size.respond_to?(:split) - end - - escape = options.delete("escape") { true } - content = ERB::Util.html_escape(content) if escape - - content_tag :textarea, content.to_s.html_safe, { "name" => name, "id" => sanitize_to_id(name) }.update(options) - end - - # Creates a check box form input tag. - # - # ==== Options - # * <tt>:disabled</tt> - If set to true, the user will not be able to use this input. - # * Any other key creates standard HTML options for the tag. - # - # ==== Examples - # check_box_tag 'accept' - # # => <input id="accept" name="accept" type="checkbox" value="1" /> - # - # check_box_tag 'rock', 'rock music' - # # => <input id="rock" name="rock" type="checkbox" value="rock music" /> - # - # check_box_tag 'receive_email', 'yes', true - # # => <input checked="checked" id="receive_email" name="receive_email" type="checkbox" value="yes" /> - # - # check_box_tag 'tos', 'yes', false, class: 'accept_tos' - # # => <input class="accept_tos" id="tos" name="tos" type="checkbox" value="yes" /> - # - # check_box_tag 'eula', 'accepted', false, disabled: true - # # => <input disabled="disabled" id="eula" name="eula" type="checkbox" value="accepted" /> - def check_box_tag(name, value = "1", checked = false, options = {}) - html_options = { "type" => "checkbox", "name" => name, "id" => sanitize_to_id(name), "value" => value }.update(options.stringify_keys) - html_options["checked"] = "checked" if checked - tag :input, html_options - end - - # Creates a radio button; use groups of radio buttons named the same to allow users to - # select from a group of options. - # - # ==== Options - # * <tt>:disabled</tt> - If set to true, the user will not be able to use this input. - # * Any other key creates standard HTML options for the tag. - # - # ==== Examples - # radio_button_tag 'gender', 'male' - # # => <input id="gender_male" name="gender" type="radio" value="male" /> - # - # radio_button_tag 'receive_updates', 'no', true - # # => <input checked="checked" id="receive_updates_no" name="receive_updates" type="radio" value="no" /> - # - # radio_button_tag 'time_slot', "3:00 p.m.", false, disabled: true - # # => <input disabled="disabled" id="time_slot_300_pm" name="time_slot" type="radio" value="3:00 p.m." /> - # - # radio_button_tag 'color', "green", true, class: "color_input" - # # => <input checked="checked" class="color_input" id="color_green" name="color" type="radio" value="green" /> - def radio_button_tag(name, value, checked = false, options = {}) - html_options = { "type" => "radio", "name" => name, "id" => "#{sanitize_to_id(name)}_#{sanitize_to_id(value)}", "value" => value }.update(options.stringify_keys) - html_options["checked"] = "checked" if checked - tag :input, html_options - end - - # Creates a submit button with the text <tt>value</tt> as the caption. - # - # ==== Options - # * <tt>:data</tt> - This option can be used to add custom data attributes. - # * <tt>:disabled</tt> - If true, the user will not be able to use this input. - # * Any other key creates standard HTML options for the tag. - # - # ==== Data attributes - # - # * <tt>confirm: 'question?'</tt> - If present the unobtrusive JavaScript - # drivers will provide a prompt with the question specified. If the user accepts, - # the form is processed normally, otherwise no action is taken. - # * <tt>:disable_with</tt> - Value of this parameter will be used as the value for a - # disabled version of the submit button when the form is submitted. This feature is - # provided by the unobtrusive JavaScript driver. - # - # ==== Examples - # submit_tag - # # => <input name="commit" type="submit" value="Save changes" /> - # - # submit_tag "Edit this article" - # # => <input name="commit" type="submit" value="Edit this article" /> - # - # submit_tag "Save edits", disabled: true - # # => <input disabled="disabled" name="commit" type="submit" value="Save edits" /> - # - # submit_tag "Complete sale", data: { disable_with: "Please wait..." } - # # => <input name="commit" data-disable-with="Please wait..." type="submit" value="Complete sale" /> - # - # submit_tag nil, class: "form_submit" - # # => <input class="form_submit" name="commit" type="submit" /> - # - # submit_tag "Edit", class: "edit_button" - # # => <input class="edit_button" name="commit" type="submit" value="Edit" /> - # - # submit_tag "Save", data: { confirm: "Are you sure?" } - # # => <input name='commit' type='submit' value='Save' data-confirm="Are you sure?" /> - # - def submit_tag(value = "Save changes", options = {}) - options = options.stringify_keys - - if disable_with = options.delete("disable_with") - message = ":disable_with option is deprecated and will be removed from Rails 4.1. " \ - "Use 'data: { disable_with: \'Text\' }' instead." - ActiveSupport::Deprecation.warn message - - options["data-disable-with"] = disable_with - end - - if confirm = options.delete("confirm") - message = ":confirm option is deprecated and will be removed from Rails 4.1. " \ - "Use 'data: { confirm: \'Text\' }' instead'." - ActiveSupport::Deprecation.warn message - - options["data-confirm"] = confirm - end - - tag :input, { "type" => "submit", "name" => "commit", "value" => value }.update(options) - end - - # Creates a button element that defines a <tt>submit</tt> button, - # <tt>reset</tt>button or a generic button which can be used in - # JavaScript, for example. You can use the button tag as a regular - # submit tag but it isn't supported in legacy browsers. However, - # the button tag allows richer labels such as images and emphasis, - # so this helper will also accept a block. - # - # ==== Options - # * <tt>:data</tt> - This option can be used to add custom data attributes. - # * <tt>:disabled</tt> - If true, the user will not be able to - # use this input. - # * Any other key creates standard HTML options for the tag. - # - # ==== Data attributes - # - # * <tt>confirm: 'question?'</tt> - If present, the - # unobtrusive JavaScript drivers will provide a prompt with - # the question specified. If the user accepts, the form is - # processed normally, otherwise no action is taken. - # * <tt>:disable_with</tt> - Value of this parameter will be - # used as the value for a disabled version of the submit - # button when the form is submitted. This feature is provided - # by the unobtrusive JavaScript driver. - # - # ==== Examples - # button_tag - # # => <button name="button" type="submit">Button</button> - # - # button_tag(type: 'button') do - # content_tag(:strong, 'Ask me!') - # end - # # => <button name="button" type="button"> - # # <strong>Ask me!</strong> - # # </button> - # - # button_tag "Checkout", data: { disable_with => "Please wait..." } - # # => <button data-disable-with="Please wait..." name="button" type="submit">Checkout</button> - # - def button_tag(content_or_options = nil, options = nil, &block) - options = content_or_options if block_given? && content_or_options.is_a?(Hash) - options ||= {} - options = options.stringify_keys - - if disable_with = options.delete("disable_with") - message = ":disable_with option is deprecated and will be removed from Rails 4.1. " \ - "Use 'data: { disable_with: \'Text\' }' instead." - ActiveSupport::Deprecation.warn message - - options["data-disable-with"] = disable_with - end - - if confirm = options.delete("confirm") - message = ":confirm option is deprecated and will be removed from Rails 4.1. " \ - "Use 'data: { confirm: \'Text\' }' instead'." - ActiveSupport::Deprecation.warn message - - options["data-confirm"] = confirm - end - - options.reverse_merge! 'name' => 'button', 'type' => 'submit' - - content_tag :button, content_or_options || 'Button', options, &block - end - - # Displays an image which when clicked will submit the form. - # - # <tt>source</tt> is passed to AssetTagHelper#path_to_image - # - # ==== Options - # * <tt>:data</tt> - This option can be used to add custom data attributes. - # * <tt>:disabled</tt> - If set to true, the user will not be able to use this input. - # * Any other key creates standard HTML options for the tag. - # - # ==== Data attributes - # - # * <tt>confirm: 'question?'</tt> - This will add a JavaScript confirm - # prompt with the question specified. If the user accepts, the form is - # processed normally, otherwise no action is taken. - # - # ==== Examples - # image_submit_tag("login.png") - # # => <input alt="Login" src="/images/login.png" type="image" /> - # - # image_submit_tag("purchase.png", disabled: true) - # # => <input alt="Purchase" disabled="disabled" src="/images/purchase.png" type="image" /> - # - # image_submit_tag("search.png", class: 'search_button', alt: 'Find') - # # => <input alt="Find" class="search_button" src="/images/search.png" type="image" /> - # - # image_submit_tag("agree.png", disabled: true, class: "agree_disagree_button") - # # => <input alt="Agree" class="agree_disagree_button" disabled="disabled" src="/images/agree.png" type="image" /> - # - # image_submit_tag("save.png", data: { confirm: "Are you sure?" }) - # # => <input alt="Save" src="/images/save.png" data-confirm="Are you sure?" type="image" /> - def image_submit_tag(source, options = {}) - options = options.stringify_keys - - if confirm = options.delete("confirm") - message = ":confirm option is deprecated and will be removed from Rails 4.1. " \ - "Use 'data: { confirm: \'Text\' }' instead'." - ActiveSupport::Deprecation.warn message - - options["data-confirm"] = confirm - end - - tag :input, { "alt" => image_alt(source), "type" => "image", "src" => path_to_image(source) }.update(options) - end - - # Creates a field set for grouping HTML form elements. - # - # <tt>legend</tt> will become the fieldset's title (optional as per W3C). - # <tt>options</tt> accept the same values as tag. - # - # ==== Examples - # <%= field_set_tag do %> - # <p><%= text_field_tag 'name' %></p> - # <% end %> - # # => <fieldset><p><input id="name" name="name" type="text" /></p></fieldset> - # - # <%= field_set_tag 'Your details' do %> - # <p><%= text_field_tag 'name' %></p> - # <% end %> - # # => <fieldset><legend>Your details</legend><p><input id="name" name="name" type="text" /></p></fieldset> - # - # <%= field_set_tag nil, class: 'format' do %> - # <p><%= text_field_tag 'name' %></p> - # <% end %> - # # => <fieldset class="format"><p><input id="name" name="name" type="text" /></p></fieldset> - def field_set_tag(legend = nil, options = nil, &block) - output = tag(:fieldset, options, true) - output.safe_concat(content_tag(:legend, legend)) unless legend.blank? - output.concat(capture(&block)) if block_given? - output.safe_concat("</fieldset>") - end - - # Creates a text field of type "color". - # - # ==== Options - # * Accepts the same options as text_field_tag. - def color_field_tag(name, value = nil, options = {}) - text_field_tag(name, value, options.stringify_keys.update("type" => "color")) - end - - # Creates a text field of type "search". - # - # ==== Options - # * Accepts the same options as text_field_tag. - def search_field_tag(name, value = nil, options = {}) - text_field_tag(name, value, options.stringify_keys.update("type" => "search")) - end - - # Creates a text field of type "tel". - # - # ==== Options - # * Accepts the same options as text_field_tag. - def telephone_field_tag(name, value = nil, options = {}) - text_field_tag(name, value, options.stringify_keys.update("type" => "tel")) - end - alias phone_field_tag telephone_field_tag - - # Creates a text field of type "date". - # - # ==== Options - # * Accepts the same options as text_field_tag. - def date_field_tag(name, value = nil, options = {}) - text_field_tag(name, value, options.stringify_keys.update("type" => "date")) - end - - # Creates a text field of type "time". - # - # === Options - # * <tt>:min</tt> - The minimum acceptable value. - # * <tt>:max</tt> - The maximum acceptable value. - # * <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")) - end - - # Creates a text field of type "datetime". - # - # === Options - # * <tt>:min</tt> - The minimum acceptable value. - # * <tt>:max</tt> - The maximum acceptable value. - # * <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")) - end - - # Creates a text field of type "datetime-local". - # - # === Options - # * <tt>:min</tt> - The minimum acceptable value. - # * <tt>:max</tt> - The maximum acceptable value. - # * <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")) - end - - # Creates a text field of type "month". - # - # === Options - # * <tt>:min</tt> - The minimum acceptable value. - # * <tt>:max</tt> - The maximum acceptable value. - # * <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")) - end - - # Creates a text field of type "week". - # - # === Options - # * <tt>:min</tt> - The minimum acceptable value. - # * <tt>:max</tt> - The maximum acceptable value. - # * <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")) - end - - # Creates a text field of type "url". - # - # ==== Options - # * Accepts the same options as text_field_tag. - def url_field_tag(name, value = nil, options = {}) - text_field_tag(name, value, options.stringify_keys.update("type" => "url")) - end - - # Creates a text field of type "email". - # - # ==== Options - # * Accepts the same options as text_field_tag. - def email_field_tag(name, value = nil, options = {}) - text_field_tag(name, value, options.stringify_keys.update("type" => "email")) - end - - # Creates a number field. - # - # ==== Options - # * <tt>:min</tt> - The minimum acceptable value. - # * <tt>:max</tt> - The maximum acceptable value. - # * <tt>:in</tt> - A range specifying the <tt>:min</tt> and - # <tt>:max</tt> values. - # * <tt>:step</tt> - The acceptable value granularity. - # * Otherwise accepts the same options as text_field_tag. - # - # ==== Examples - # number_field_tag 'quantity', nil, in: 1...10 - # # => <input id="quantity" name="quantity" min="1" max="9" type="number" /> - def number_field_tag(name, value = nil, options = {}) - options = options.stringify_keys - options["type"] ||= "number" - if range = options.delete("in") || options.delete("within") - options.update("min" => range.min, "max" => range.max) - end - text_field_tag(name, value, options) - end - - # Creates a range form element. - # - # ==== 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")) - end - - # Creates the hidden UTF8 enforcer tag. Override this method in a helper - # to customize the tag. - def utf8_enforcer_tag - tag(:input, :type => "hidden", :name => "utf8", :value => "✓".html_safe) - end - - private - def html_options_for_form(url_for_options, options) - options.stringify_keys.tap do |html_options| - html_options["enctype"] = "multipart/form-data" if html_options.delete("multipart") - # The following URL is unescaped, this is just a hash of options, and it is the - # responsibility of the caller to escape all the values. - html_options["action"] = url_for(url_for_options) - html_options["accept-charset"] = "UTF-8" - - html_options["data-remote"] = true if html_options.delete("remote") - - if html_options["data-remote"] && - !embed_authenticity_token_in_remote_forms && - html_options["authenticity_token"].blank? - # The authenticity token is taken from the meta tag in this case - html_options["authenticity_token"] = false - elsif html_options["authenticity_token"] == true - # Include the default authenticity_token, which is only generated when its set to nil, - # but we needed the true value to override the default of no authenticity_token on data-remote. - html_options["authenticity_token"] = nil - end - end - end - - def extra_tags_for_form(html_options) - authenticity_token = html_options.delete("authenticity_token") - method = html_options.delete("method").to_s - - method_tag = case method - when /^get$/i # must be case-insensitive, but can't use downcase as might be nil - html_options["method"] = "get" - '' - when /^post$/i, "", nil - html_options["method"] = "post" - token_tag(authenticity_token) - else - html_options["method"] = "post" - method_tag(method) + token_tag(authenticity_token) - end - - tags = utf8_enforcer_tag << method_tag - content_tag(:div, tags, :style => 'margin:0;padding:0;display:inline') - end - - def form_tag_html(html_options) - extra_tags = extra_tags_for_form(html_options) - tag(:form, html_options, true) + extra_tags - end - - def form_tag_in_block(html_options, &block) - content = capture(&block) - output = form_tag_html(html_options) - output << content - output.safe_concat("</form>") - end - - # 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:.]/, "_") - end - end - end -end diff --git a/actionpack/lib/action_view/helpers/javascript_helper.rb b/actionpack/lib/action_view/helpers/javascript_helper.rb deleted file mode 100644 index 878d3e0eda..0000000000 --- a/actionpack/lib/action_view/helpers/javascript_helper.rb +++ /dev/null @@ -1,117 +0,0 @@ -require 'action_view/helpers/tag_helper' - -module ActionView - module Helpers - module JavaScriptHelper - JS_ESCAPE_MAP = { - '\\' => '\\\\', - '</' => '<\/', - "\r\n" => '\n', - "\n" => '\n', - "\r" => '\n', - '"' => '\\"', - "'" => "\\'" - } - - JS_ESCAPE_MAP["\342\200\250".force_encoding(Encoding::UTF_8).encode!] = '
' - JS_ESCAPE_MAP["\342\200\251".force_encoding(Encoding::UTF_8).encode!] = '
' - - # Escapes carriage returns and single and double quotes for JavaScript segments. - # - # Also available through the alias j(). This is particularly helpful in JavaScript - # responses, like: - # - # $('some_element').replaceWith('<%=j render 'some/element_template' %>'); - def escape_javascript(javascript) - if javascript - result = javascript.gsub(/(\\|<\/|\r\n|\342\200\250|\342\200\251|[\n\r"'])/u) {|match| JS_ESCAPE_MAP[match] } - javascript.html_safe? ? result.html_safe : result - else - '' - end - end - - alias_method :j, :escape_javascript - - # Returns a JavaScript tag with the +content+ inside. Example: - # javascript_tag "alert('All is good')" - # - # Returns: - # <script> - # //<![CDATA[ - # alert('All is good') - # //]]> - # </script> - # - # +html_options+ may be a hash of attributes for the <tt>\<script></tt> - # tag. - # - # javascript_tag "alert('All is good')", defer: 'defer' - # # => <script defer="defer">alert('All is good')</script> - # - # Instead of passing the content as an argument, you can also use a block - # in which case, you pass your +html_options+ as the first parameter. - # - # <%= javascript_tag defer: 'defer' do -%> - # alert('All is good') - # <% end -%> - def javascript_tag(content_or_options_with_block = nil, html_options = {}, &block) - content = - if block_given? - html_options = content_or_options_with_block if content_or_options_with_block.is_a?(Hash) - capture(&block) - else - content_or_options_with_block - end - - content_tag(:script, javascript_cdata_section(content), html_options) - end - - def javascript_cdata_section(content) #:nodoc: - "\n//#{cdata_section("\n#{content}\n//")}\n".html_safe - end - - # Returns a button whose +onclick+ handler triggers the passed JavaScript. - # - # The helper receives a name, JavaScript code, and an optional hash of HTML options. The - # name is used as button label and the JavaScript code goes into its +onclick+ attribute. - # If +html_options+ has an <tt>:onclick</tt>, that one is put before +function+. - # - # button_to_function "Greeting", "alert('Hello world!')", class: "ok" - # # => <input class="ok" onclick="alert('Hello world!');" type="button" value="Greeting" /> - # - def button_to_function(name, function=nil, html_options={}) - message = "button_to_function is deprecated and will be removed from Rails 4.1. We recomend to use Unobtrusive JavaScript instead. " + - "See http://guides.rubyonrails.org/working_with_javascript_in_rails.html#unobtrusive-javascript" - ActiveSupport::Deprecation.warn message - - onclick = "#{"#{html_options[:onclick]}; " if html_options[:onclick]}#{function};" - - tag(:input, html_options.merge(:type => 'button', :value => name, :onclick => onclick)) - end - - # Returns a link whose +onclick+ handler triggers the passed JavaScript. - # - # The helper receives a name, JavaScript code, and an optional hash of HTML options. The - # name is used as the link text and the JavaScript code goes into the +onclick+ attribute. - # If +html_options+ has an <tt>:onclick</tt>, that one is put before +function+. Once all - # the JavaScript is set, the helper appends "; return false;". - # - # The +href+ attribute of the tag is set to "#" unless +html_options+ has one. - # - # link_to_function "Greeting", "alert('Hello world!')", class: "nav_link" - # # => <a class="nav_link" href="#" onclick="alert('Hello world!'); return false;">Greeting</a> - # - def link_to_function(name, function, html_options={}) - message = "link_to_function is deprecated and will be removed from Rails 4.1. We recomend to use Unobtrusive JavaScript instead. " + - "See http://guides.rubyonrails.org/working_with_javascript_in_rails.html#unobtrusive-javascript" - ActiveSupport::Deprecation.warn message - - onclick = "#{"#{html_options[:onclick]}; " if html_options[:onclick]}#{function}; return false;" - href = html_options[:href] || '#' - - content_tag(:a, name, html_options.merge(:href => href, :onclick => onclick)) - end - end - end -end diff --git a/actionpack/lib/action_view/helpers/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb deleted file mode 100644 index 9e1be65b1a..0000000000 --- a/actionpack/lib/action_view/helpers/number_helper.rb +++ /dev/null @@ -1,416 +0,0 @@ -# encoding: utf-8 - -require 'active_support/core_ext/hash/keys' -require 'active_support/core_ext/string/output_safety' -require 'active_support/number_helper' - -module ActionView - # = Action View Number Helpers - module Helpers #:nodoc: - - # Provides methods for converting numbers into formatted strings. - # Methods are provided for phone numbers, currency, percentage, - # precision, positional notation, file size and pretty printing. - # - # Most methods expect a +number+ argument, and will return it - # unchanged if can't be converted into a valid number. - module NumberHelper - - # Raised when argument +number+ param given to the helpers is invalid and - # the option :raise is set to +true+. - class InvalidNumberError < StandardError - attr_accessor :number - def initialize(number) - @number = number - end - end - - # Formats a +number+ into a US phone number (e.g., (555) - # 123-9876). You can customize the format in the +options+ hash. - # - # * <tt>:area_code</tt> - Adds parentheses around the area code. - # * <tt>:delimiter</tt> - Specifies the delimiter to use - # (defaults to "-"). - # * <tt>:extension</tt> - Specifies an extension to add to the - # end of the generated number. - # * <tt>:country_code</tt> - Sets the country code for the phone - # number. - # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when - # the argument is invalid. - # - # number_to_phone(5551234) # => 555-1234 - # number_to_phone("5551234") # => 555-1234 - # number_to_phone(1235551234) # => 123-555-1234 - # number_to_phone(1235551234, area_code: true) # => (123) 555-1234 - # number_to_phone(1235551234, delimiter: " ") # => 123 555 1234 - # number_to_phone(1235551234, area_code: true, extension: 555) # => (123) 555-1234 x 555 - # number_to_phone(1235551234, country_code: 1) # => +1-123-555-1234 - # number_to_phone("123a456") # => 123a456 - # number_to_phone("1234a567", raise: true) # => InvalidNumberError - # - # number_to_phone(1235551234, country_code: 1, extension: 1343, delimiter: ".") - # # => +1.123.555.1234 x 1343 - def number_to_phone(number, options = {}) - return unless number - options = options.symbolize_keys - - parse_float(number, true) if options.delete(:raise) - ERB::Util.html_escape(ActiveSupport::NumberHelper.number_to_phone(number, options)) - end - - # Formats a +number+ into a currency string (e.g., $13.65). You - # can customize the format in the +options+ hash. - # - # * <tt>:locale</tt> - Sets the locale to be used for formatting - # (defaults to current locale). - # * <tt>:precision</tt> - Sets the level of precision (defaults - # to 2). - # * <tt>:unit</tt> - Sets the denomination of the currency - # (defaults to "$"). - # * <tt>:separator</tt> - Sets the separator between the units - # (defaults to "."). - # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults - # to ","). - # * <tt>:format</tt> - Sets the format for non-negative numbers - # (defaults to "%u%n"). Fields are <tt>%u</tt> for the - # currency, and <tt>%n</tt> for the number. - # * <tt>:negative_format</tt> - Sets the format for negative - # numbers (defaults to prepending an hyphen to the formatted - # number given by <tt>:format</tt>). Accepts the same fields - # than <tt>:format</tt>, except <tt>%n</tt> is here the - # absolute value of the number. - # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when - # the argument is invalid. - # - # number_to_currency(1234567890.50) # => $1,234,567,890.50 - # number_to_currency(1234567890.506) # => $1,234,567,890.51 - # number_to_currency(1234567890.506, precision: 3) # => $1,234,567,890.506 - # number_to_currency(1234567890.506, locale: :fr) # => 1 234 567 890,51 € - # number_to_currency("123a456") # => $123a456 - # - # number_to_currency("123a456", raise: true) # => InvalidNumberError - # - # number_to_currency(-1234567890.50, negative_format: "(%u%n)") - # # => ($1,234,567,890.50) - # number_to_currency(1234567890.50, unit: "£", separator: ",", delimiter: "") - # # => £1234567890,50 - # number_to_currency(1234567890.50, unit: "£", separator: ",", delimiter: "", format: "%n %u") - # # => 1234567890,50 £ - def number_to_currency(number, options = {}) - return unless number - options = escape_unsafe_delimiters_and_separators(options.symbolize_keys) - - wrap_with_output_safety_handling(number, options.delete(:raise)) { - ActiveSupport::NumberHelper.number_to_currency(number, options) - } - end - - # Formats a +number+ as a percentage string (e.g., 65%). You can - # customize the format in the +options+ hash. - # - # - # * <tt>:locale</tt> - Sets the locale to be used for formatting - # (defaults to current locale). - # * <tt>:precision</tt> - Sets the precision of the number - # (defaults to 3). - # * <tt>:significant</tt> - If +true+, precision will be the # - # of significant_digits. If +false+, the # of fractional - # digits (defaults to +false+). - # * <tt>:separator</tt> - Sets the separator between the - # fractional and integer digits (defaults to "."). - # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults - # to ""). - # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes - # insignificant zeros after the decimal separator (defaults to - # +false+). - # * <tt>:format</tt> - Specifies the format of the percentage - # string The number field is <tt>%n</tt> (defaults to "%n%"). - # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when - # the argument is invalid. - # - # number_to_percentage(100) # => 100.000% - # number_to_percentage("98") # => 98.000% - # number_to_percentage(100, precision: 0) # => 100% - # number_to_percentage(1000, delimiter: '.', separator: ',') # => 1.000,000% - # number_to_percentage(302.24398923423, precision: 5) # => 302.24399% - # number_to_percentage(1000, locale: :fr) # => 1 000,000% - # number_to_percentage("98a") # => 98a% - # number_to_percentage(100, format: "%n %") # => 100 % - # - # number_to_percentage("98a", raise: true) # => InvalidNumberError - def number_to_percentage(number, options = {}) - return unless number - options = escape_unsafe_delimiters_and_separators(options.symbolize_keys) - - wrap_with_output_safety_handling(number, options.delete(:raise)) { - ActiveSupport::NumberHelper.number_to_percentage(number, options) - } - end - - # Formats a +number+ with grouped thousands using +delimiter+ - # (e.g., 12,324). You can customize the format in the +options+ - # hash. - # - # * <tt>:locale</tt> - Sets the locale to be used for formatting - # (defaults to current locale). - # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults - # to ","). - # * <tt>:separator</tt> - Sets the separator between the - # fractional and integer digits (defaults to "."). - # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when - # the argument is invalid. - # - # number_with_delimiter(12345678) # => 12,345,678 - # number_with_delimiter("123456") # => 123,456 - # number_with_delimiter(12345678.05) # => 12,345,678.05 - # number_with_delimiter(12345678, delimiter: ".") # => 12.345.678 - # number_with_delimiter(12345678, delimiter: ",") # => 12,345,678 - # number_with_delimiter(12345678.05, separator: " ") # => 12,345,678 05 - # number_with_delimiter(12345678.05, locale: :fr) # => 12 345 678,05 - # number_with_delimiter("112a") # => 112a - # number_with_delimiter(98765432.98, delimiter: " ", separator: ",") - # # => 98 765 432,98 - # - # number_with_delimiter("112a", raise: true) # => raise InvalidNumberError - def number_with_delimiter(number, options = {}) - options = escape_unsafe_delimiters_and_separators(options.symbolize_keys) - - wrap_with_output_safety_handling(number, options.delete(:raise)) { - ActiveSupport::NumberHelper.number_to_delimited(number, options) - } - end - - # Formats a +number+ with the specified level of - # <tt>:precision</tt> (e.g., 112.32 has a precision of 2 if - # +:significant+ is +false+, and 5 if +:significant+ is +true+). - # You can customize the format in the +options+ hash. - # - # * <tt>:locale</tt> - Sets the locale to be used for formatting - # (defaults to current locale). - # * <tt>:precision</tt> - Sets the precision of the number - # (defaults to 3). - # * <tt>:significant</tt> - If +true+, precision will be the # - # of significant_digits. If +false+, the # of fractional - # digits (defaults to +false+). - # * <tt>:separator</tt> - Sets the separator between the - # fractional and integer digits (defaults to "."). - # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults - # to ""). - # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes - # insignificant zeros after the decimal separator (defaults to - # +false+). - # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when - # the argument is invalid. - # - # number_with_precision(111.2345) # => 111.235 - # number_with_precision(111.2345, precision: 2) # => 111.23 - # number_with_precision(13, precision: 5) # => 13.00000 - # number_with_precision(389.32314, precision: 0) # => 389 - # number_with_precision(111.2345, significant: true) # => 111 - # number_with_precision(111.2345, precision: 1, significant: true) # => 100 - # number_with_precision(13, precision: 5, significant: true) # => 13.000 - # number_with_precision(111.234, locale: :fr) # => 111,234 - # - # number_with_precision(13, precision: 5, significant: true, strip_insignificant_zeros: true) - # # => 13 - # - # number_with_precision(389.32314, precision: 4, significant: true) # => 389.3 - # number_with_precision(1111.2345, precision: 2, separator: ',', delimiter: '.') - # # => 1.111,23 - def number_with_precision(number, options = {}) - options = escape_unsafe_delimiters_and_separators(options.symbolize_keys) - - wrap_with_output_safety_handling(number, options.delete(:raise)) { - ActiveSupport::NumberHelper.number_to_rounded(number, options) - } - end - - # Formats the bytes in +number+ into a more understandable - # representation (e.g., giving it 1500 yields 1.5 KB). This - # method is useful for reporting file sizes to users. You can - # customize the format in the +options+ hash. - # - # See <tt>number_to_human</tt> if you want to pretty-print a - # generic number. - # - # * <tt>:locale</tt> - Sets the locale to be used for formatting - # (defaults to current locale). - # * <tt>:precision</tt> - Sets the precision of the number - # (defaults to 3). - # * <tt>:significant</tt> - If +true+, precision will be the # - # of significant_digits. If +false+, the # of fractional - # digits (defaults to +true+) - # * <tt>:separator</tt> - Sets the separator between the - # fractional and integer digits (defaults to "."). - # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults - # to ""). - # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes - # insignificant zeros after the decimal separator (defaults to - # +true+) - # * <tt>:prefix</tt> - If +:si+ formats the number using the SI - # prefix (defaults to :binary) - # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when - # the argument is invalid. - # - # number_to_human_size(123) # => 123 Bytes - # number_to_human_size(1234) # => 1.21 KB - # number_to_human_size(12345) # => 12.1 KB - # number_to_human_size(1234567) # => 1.18 MB - # number_to_human_size(1234567890) # => 1.15 GB - # number_to_human_size(1234567890123) # => 1.12 TB - # number_to_human_size(1234567, precision: 2) # => 1.2 MB - # number_to_human_size(483989, precision: 2) # => 470 KB - # number_to_human_size(1234567, precision: 2, separator: ',') # => 1,2 MB - # - # Non-significant zeros after the fractional separator are - # stripped out by default (set - # <tt>:strip_insignificant_zeros</tt> to +false+ to change - # that): - # - # number_to_human_size(1234567890123, precision: 5) # => "1.1229 TB" - # number_to_human_size(524288000, precision: 5) # => "500 MB" - def number_to_human_size(number, options = {}) - options = escape_unsafe_delimiters_and_separators(options.symbolize_keys) - - wrap_with_output_safety_handling(number, options.delete(:raise)) { - ActiveSupport::NumberHelper.number_to_human_size(number, options) - } - end - - # Pretty prints (formats and approximates) a number in a way it - # is more readable by humans (eg.: 1200000000 becomes "1.2 - # Billion"). This is useful for numbers that can get very large - # (and too hard to read). - # - # See <tt>number_to_human_size</tt> if you want to print a file - # size. - # - # You can also define you own unit-quantifier names if you want - # to use other decimal units (eg.: 1500 becomes "1.5 - # kilometers", 0.150 becomes "150 milliliters", etc). You may - # define a wide range of unit quantifiers, even fractional ones - # (centi, deci, mili, etc). - # - # ==== Options - # - # * <tt>:locale</tt> - Sets the locale to be used for formatting - # (defaults to current locale). - # * <tt>:precision</tt> - Sets the precision of the number - # (defaults to 3). - # * <tt>:significant</tt> - If +true+, precision will be the # - # of significant_digits. If +false+, the # of fractional - # digits (defaults to +true+) - # * <tt>:separator</tt> - Sets the separator between the - # fractional and integer digits (defaults to "."). - # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults - # to ""). - # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes - # insignificant zeros after the decimal separator (defaults to - # +true+) - # * <tt>:units</tt> - A Hash of unit quantifier names. Or a - # 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> - # * *fractionals*: <tt>:deci</tt>, <tt>:centi</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') - # * %n - The number - # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when - # the argument is invalid. - # - # number_to_human(123) # => "123" - # number_to_human(1234) # => "1.23 Thousand" - # number_to_human(12345) # => "12.3 Thousand" - # number_to_human(1234567) # => "1.23 Million" - # number_to_human(1234567890) # => "1.23 Billion" - # number_to_human(1234567890123) # => "1.23 Trillion" - # number_to_human(1234567890123456) # => "1.23 Quadrillion" - # number_to_human(1234567890123456789) # => "1230 Quadrillion" - # number_to_human(489939, precision: 2) # => "490 Thousand" - # number_to_human(489939, precision: 4) # => "489.9 Thousand" - # number_to_human(1234567, precision: 4, - # significant: false) # => "1.2346 Million" - # number_to_human(1234567, precision: 1, - # separator: ',', - # significant: false) # => "1,2 Million" - # - # Non-significant zeros after the decimal separator are stripped - # out by default (set <tt>:strip_insignificant_zeros</tt> to - # +false+ to change that): - # number_to_human(12345012345, significant_digits: 6) # => "12.345 Billion" - # number_to_human(500000000, precision: 5) # => "500 Million" - # - # ==== Custom Unit Quantifiers - # - # You can also use your own custom unit quantifiers: - # number_to_human(500000, units: {unit: "ml", thousand: "lt"}) # => "500 lt" - # - # If in your I18n locale you have: - # distance: - # centi: - # one: "centimeter" - # other: "centimeters" - # unit: - # one: "meter" - # other: "meters" - # thousand: - # one: "kilometer" - # other: "kilometers" - # billion: "gazillion-distance" - # - # Then you could do: - # - # number_to_human(543934, units: :distance) # => "544 kilometers" - # number_to_human(54393498, units: :distance) # => "54400 kilometers" - # number_to_human(54393498000, units: :distance) # => "54.4 gazillion-distance" - # number_to_human(343, units: :distance, precision: 1) # => "300 meters" - # number_to_human(1, units: :distance) # => "1 meter" - # number_to_human(0.34, units: :distance) # => "34 centimeters" - # - def number_to_human(number, options = {}) - options = escape_unsafe_delimiters_and_separators(options.symbolize_keys) - - wrap_with_output_safety_handling(number, options.delete(:raise)) { - ActiveSupport::NumberHelper.number_to_human(number, options) - } - end - - private - - def escape_unsafe_delimiters_and_separators(options) - options[:separator] = ERB::Util.html_escape(options[:separator]) if options[:separator] && !options[:separator].html_safe? - options[:delimiter] = ERB::Util.html_escape(options[:delimiter]) if options[:delimiter] && !options[:delimiter].html_safe? - options - end - - def wrap_with_output_safety_handling(number, raise_on_invalid, &block) - valid_float = valid_float?(number) - raise InvalidNumberError, number if raise_on_invalid && !valid_float - - formatted_number = yield - - if valid_float || number.html_safe? - formatted_number.html_safe - else - formatted_number - end - end - - def valid_float?(number) - !parse_float(number, false).nil? - end - - def parse_float(number, raise_error) - Float(number) - rescue ArgumentError, TypeError - raise InvalidNumberError, number if raise_error - end - end - end -end diff --git a/actionpack/lib/action_view/helpers/output_safety_helper.rb b/actionpack/lib/action_view/helpers/output_safety_helper.rb deleted file mode 100644 index 60a4478c26..0000000000 --- a/actionpack/lib/action_view/helpers/output_safety_helper.rb +++ /dev/null @@ -1,38 +0,0 @@ -require 'active_support/core_ext/string/output_safety' - -module ActionView #:nodoc: - # = Action View Raw Output Helper - module Helpers #:nodoc: - module OutputSafetyHelper - # This method outputs without escaping a string. Since escaping tags is - # now default, this can be used when you don't want Rails to automatically - # escape tags. This is not recommended if the data is coming from the user's - # input. - # - # For example: - # - # raw @user.name - # # => 'Jimmy <alert>Tables</alert>' - def raw(stringish) - stringish.to_s.html_safe - end - - # This method returns a html safe string similar to what <tt>Array#join</tt> - # would return. All items in the array, including the supplied separator, are - # html escaped unless they are html safe, and the returned string is marked - # as html safe. - # - # safe_join(["<p>foo</p>".html_safe, "<p>bar</p>"], "<br />") - # # => "<p>foo</p><br /><p>bar</p>" - # - # safe_join(["<p>foo</p>".html_safe, "<p>bar</p>".html_safe], "<br />".html_safe) - # # => "<p>foo</p><br /><p>bar</p>" - # - def safe_join(array, sep=$,) - sep = ERB::Util.html_escape(sep) - - array.map { |i| ERB::Util.html_escape(i) }.join(sep).html_safe - end - end - end -end diff --git a/actionpack/lib/action_view/helpers/record_tag_helper.rb b/actionpack/lib/action_view/helpers/record_tag_helper.rb deleted file mode 100644 index f767957fa9..0000000000 --- a/actionpack/lib/action_view/helpers/record_tag_helper.rb +++ /dev/null @@ -1,106 +0,0 @@ -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) - 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 - 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/actionpack/lib/action_view/helpers/rendering_helper.rb b/actionpack/lib/action_view/helpers/rendering_helper.rb deleted file mode 100644 index 458086de96..0000000000 --- a/actionpack/lib/action_view/helpers/rendering_helper.rb +++ /dev/null @@ -1,90 +0,0 @@ -module ActionView - module Helpers - # = Action View Rendering - # - # Implements methods that allow rendering from a view context. - # In order to use this module, all you need is to implement - # view_renderer that returns an ActionView::Renderer object. - module RenderingHelper - # Returns the result of a render that's dictated by the options hash. The primary options are: - # - # * <tt>:partial</tt> - See <tt>ActionView::PartialRenderer</tt>. - # * <tt>:file</tt> - Renders an explicit template file (this used to be the old default), add :locals to pass in those. - # * <tt>:inline</tt> - Renders an inline template similar to how it's done in the controller. - # * <tt>:text</tt> - Renders the text passed in out. - # - # If no options hash is passed or :update specified, the default is to render a partial and use the second parameter - # as the locals hash. - def render(options = {}, locals = {}, &block) - case options - when Hash - if block_given? - view_renderer.render_partial(self, options.merge(:partial => options[:layout]), &block) - else - view_renderer.render(self, options) - end - else - view_renderer.render_partial(self, :partial => options, :locals => locals) - end - end - - # Overwrites _layout_for in the context object so it supports the case a block is - # passed to a partial. Returns the contents that are yielded to a layout, given a - # name or a block. - # - # You can think of a layout as a method that is called with a block. If the user calls - # <tt>yield :some_name</tt>, the block, by default, returns <tt>content_for(:some_name)</tt>. - # If the user calls simply +yield+, the default block returns <tt>content_for(:layout)</tt>. - # - # The user can override this default by passing a block to the layout: - # - # # The template - # <%= render layout: "my_layout" do %> - # Content - # <% end %> - # - # # The layout - # <html> - # <%= yield %> - # </html> - # - # In this case, instead of the default block, which would return <tt>content_for(:layout)</tt>, - # this method returns the block that was passed in to <tt>render :layout</tt>, and the response - # would be - # - # <html> - # Content - # </html> - # - # Finally, the block can take block arguments, which can be passed in by +yield+: - # - # # The template - # <%= render layout: "my_layout" do |customer| %> - # Hello <%= customer.name %> - # <% end %> - # - # # The layout - # <html> - # <%= yield Struct.new(:name).new("David") %> - # </html> - # - # In this case, the layout would receive the block passed into <tt>render :layout</tt>, - # and the struct specified would be passed into the block as an argument. The result - # would be - # - # <html> - # Hello David - # </html> - # - def _layout_for(*args, &block) - name = args.first - - if block && !name.is_a?(Symbol) - capture(*args, &block) - else - super - end - end - end - end -end diff --git a/actionpack/lib/action_view/helpers/sanitize_helper.rb b/actionpack/lib/action_view/helpers/sanitize_helper.rb deleted file mode 100644 index e5cb843670..0000000000 --- a/actionpack/lib/action_view/helpers/sanitize_helper.rb +++ /dev/null @@ -1,256 +0,0 @@ -require 'active_support/core_ext/object/try' -require 'action_view/vendor/html-scanner' - -module ActionView - # = Action View Sanitize Helpers - module Helpers - # The SanitizeHelper module provides a set of methods for scrubbing text of undesired HTML elements. - # 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. - # - # 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. - # - # <%= sanitize @article.body %> - # - # 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: - # - # Normal Use - # - # <%= sanitize @article.body %> - # - # Custom Use (only the mentioned tags and attributes are allowed, nothing else) - # - # <%= sanitize @article.body, tags: %w(table tr td), attributes: %w(id class style) %> - # - # Add table tags to the default allowed tags - # - # class Application < Rails::Application - # config.action_view.sanitized_allowed_tags = 'table', 'tr', 'td' - # end - # - # Remove tags to the default allowed tags - # - # class Application < Rails::Application - # config.after_initialize do - # ActionView::Base.sanitized_allowed_tags.delete 'div' - # end - # end - # - # Change allowed default attributes - # - # class Application < Rails::Application - # config.action_view.sanitized_allowed_attributes = 'id', 'class', 'style' - # 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. - # - def sanitize(html, options = {}) - self.class.white_list_sanitizer.sanitize(html, options).try(:html_safe) - end - - # Sanitizes a block of CSS code. Used by +sanitize+ when it comes across a style attribute. - def sanitize_css(style) - self.class.white_list_sanitizer.sanitize_css(style) - end - - # Strips all HTML tags from the +html+, including comments. This uses the - # html-scanner tokenizer and so its HTML parsing ability is limited by - # that of html-scanner. - # - # strip_tags("Strip <i>these</i> tags!") - # # => Strip these tags! - # - # strip_tags("<b>Bold</b> no more! <a href='more.html'>See more here</a>...") - # # => Bold no more! See more here... - # - # strip_tags("<div id='top-bar'>Welcome to my website!</div>") - # # => Welcome to my website! - def strip_tags(html) - self.class.full_sanitizer.sanitize(html) - end - - # Strips all link tags from +text+ leaving just the link text. - # - # strip_links('<a href="http://www.rubyonrails.org">Ruby on Rails</a>') - # # => Ruby on Rails - # - # strip_links('Please e-mail me at <a href="mailto:me@email.com">me@email.com</a>.') - # # => Please e-mail me at me@email.com. - # - # strip_links('Blog: <a href="http://www.myblog.com/" class="nav" target=\"_blank\">Visit</a>.') - # # => Blog: Visit. - def strip_links(html) - self.class.link_sanitizer.sanitize(html) - end - - module ClassMethods #:nodoc: - attr_writer :full_sanitizer, :link_sanitizer, :white_list_sanitizer - - def sanitized_protocol_separator - white_list_sanitizer.protocol_separator - end - - def sanitized_uri_attributes - white_list_sanitizer.uri_attributes - end - - def sanitized_bad_tags - white_list_sanitizer.bad_tags - end - - def sanitized_allowed_tags - white_list_sanitizer.allowed_tags - end - - def sanitized_allowed_attributes - white_list_sanitizer.allowed_attributes - end - - def sanitized_allowed_css_properties - white_list_sanitizer.allowed_css_properties - end - - def sanitized_allowed_css_keywords - white_list_sanitizer.allowed_css_keywords - end - - def sanitized_shorthand_css_properties - white_list_sanitizer.shorthand_css_properties - end - - def sanitized_allowed_protocols - white_list_sanitizer.allowed_protocols - end - - def sanitized_protocol_separator=(value) - white_list_sanitizer.protocol_separator = value - end - - # Gets the HTML::FullSanitizer instance used by +strip_tags+. Replace with - # any object that responds to +sanitize+. - # - # class Application < Rails::Application - # config.action_view.full_sanitizer = MySpecialSanitizer.new - # end - # - def full_sanitizer - @full_sanitizer ||= HTML::FullSanitizer.new - end - - # Gets the HTML::LinkSanitizer instance used by +strip_links+. Replace with - # any object that responds to +sanitize+. - # - # class Application < Rails::Application - # config.action_view.link_sanitizer = MySpecialSanitizer.new - # end - # - def link_sanitizer - @link_sanitizer ||= HTML::LinkSanitizer.new - end - - # Gets the HTML::WhiteListSanitizer instance used by sanitize and +sanitize_css+. - # Replace with any object that responds to +sanitize+. - # - # class Application < Rails::Application - # config.action_view.white_list_sanitizer = MySpecialSanitizer.new - # end - # - def white_list_sanitizer - @white_list_sanitizer ||= HTML::WhiteListSanitizer.new - end - - # Adds valid HTML attributes that the +sanitize+ helper checks for URIs. - # - # class Application < Rails::Application - # config.action_view.sanitized_uri_attributes = 'lowsrc', 'target' - # end - # - def sanitized_uri_attributes=(attributes) - HTML::WhiteListSanitizer.uri_attributes.merge(attributes) - end - - # Adds to the Set of 'bad' tags for the +sanitize+ helper. - # - # class Application < Rails::Application - # config.action_view.sanitized_bad_tags = 'embed', 'object' - # end - # - def sanitized_bad_tags=(attributes) - HTML::WhiteListSanitizer.bad_tags.merge(attributes) - end - - # Adds to the Set of allowed tags for the +sanitize+ helper. - # - # class Application < Rails::Application - # config.action_view.sanitized_allowed_tags = 'table', 'tr', 'td' - # end - # - def sanitized_allowed_tags=(attributes) - HTML::WhiteListSanitizer.allowed_tags.merge(attributes) - end - - # Adds to the Set of allowed HTML attributes for the +sanitize+ helper. - # - # class Application < Rails::Application - # config.action_view.sanitized_allowed_attributes = 'onclick', 'longdesc' - # end - # - def sanitized_allowed_attributes=(attributes) - HTML::WhiteListSanitizer.allowed_attributes.merge(attributes) - end - - # Adds to the Set of allowed CSS properties for the #sanitize and +sanitize_css+ helpers. - # - # class Application < Rails::Application - # config.action_view.sanitized_allowed_css_properties = 'expression' - # end - # - def sanitized_allowed_css_properties=(attributes) - HTML::WhiteListSanitizer.allowed_css_properties.merge(attributes) - end - - # Adds to the Set of allowed CSS keywords for the +sanitize+ and +sanitize_css+ helpers. - # - # class Application < Rails::Application - # config.action_view.sanitized_allowed_css_keywords = 'expression' - # end - # - def sanitized_allowed_css_keywords=(attributes) - HTML::WhiteListSanitizer.allowed_css_keywords.merge(attributes) - end - - # Adds to the Set of allowed shorthand CSS properties for the +sanitize+ and +sanitize_css+ helpers. - # - # class Application < Rails::Application - # config.action_view.sanitized_shorthand_css_properties = 'expression' - # end - # - def sanitized_shorthand_css_properties=(attributes) - HTML::WhiteListSanitizer.shorthand_css_properties.merge(attributes) - end - - # Adds to the Set of allowed protocols for the +sanitize+ helper. - # - # class Application < Rails::Application - # config.action_view.sanitized_allowed_protocols = 'ssh', 'feed' - # end - # - def sanitized_allowed_protocols=(attributes) - HTML::WhiteListSanitizer.allowed_protocols.merge(attributes) - end - end - end - end -end diff --git a/actionpack/lib/action_view/helpers/tag_helper.rb b/actionpack/lib/action_view/helpers/tag_helper.rb deleted file mode 100644 index 8c7524e795..0000000000 --- a/actionpack/lib/action_view/helpers/tag_helper.rb +++ /dev/null @@ -1,173 +0,0 @@ -require 'active_support/core_ext/string/output_safety' -require 'set' - -module ActionView - # = Action View Tag Helpers - module Helpers #:nodoc: - # Provides methods to generate HTML tags programmatically when you can't use - # a Builder. By default, they output XHTML compliant tags. - module TagHelper - extend ActiveSupport::Concern - include CaptureHelper - - BOOLEAN_ATTRIBUTES = %w(disabled readonly multiple checked autobuffer - autoplay controls loop selected hidden scoped async - defer reversed ismap seemless muted required - autofocus novalidate formnovalidate open pubdate itemscope).to_set - BOOLEAN_ATTRIBUTES.merge(BOOLEAN_ATTRIBUTES.map {|attribute| attribute.to_sym }) - - PRE_CONTENT_STRINGS = { - :textarea => "\n" - } - - # Returns an empty HTML tag of type +name+ which by default is XHTML - # compliant. Set +open+ to true to create an open tag compatible - # with HTML 4.0 and below. Add HTML attributes by passing an attributes - # hash to +options+. Set +escape+ to false to disable attribute value - # escaping. - # - # ==== Options - # You can use symbols or strings for the attribute names. - # - # Use +true+ with boolean attributes that can render with no value, like - # +disabled+ and +readonly+. - # - # HTML5 <tt>data-*</tt> attributes can be set with a single +data+ key - # pointing to a hash of sub-attributes. - # - # To play nicely with JavaScript conventions sub-attributes are dasherized. - # For example, a key +user_id+ would render as <tt>data-user-id</tt> and - # thus accessed as <tt>dataset.userId</tt>. - # - # Values are encoded to JSON, with the exception of strings and symbols. - # This may come in handy when using jQuery's HTML5-aware <tt>.data()</tt> - # from 1.4.3. - # - # ==== Examples - # tag("br") - # # => <br /> - # - # tag("br", nil, true) - # # => <br> - # - # tag("input", type: 'text', disabled: true) - # # => <input type="text" disabled="disabled" /> - # - # tag("img", src: "open & shut.png") - # # => <img src="open & shut.png" /> - # - # tag("img", {src: "open & shut.png"}, false, false) - # # => <img src="open & shut.png" /> - # - # tag("div", data: {name: 'Stephen', city_state: %w(Chicago IL)}) - # # => <div data-name="Stephen" data-city-state="["Chicago","IL"]" /> - def tag(name, options = nil, open = false, escape = true) - "<#{name}#{tag_options(options, escape) if options}#{open ? ">" : " />"}".html_safe - end - - # Returns an HTML block tag of type +name+ surrounding the +content+. Add - # HTML attributes by passing an attributes hash to +options+. - # Instead of passing the content as an argument, you can also use a block - # in which case, you pass your +options+ as the second parameter. - # Set escape to false to disable attribute value escaping. - # - # ==== Options - # The +options+ hash is used with attributes with no value like (<tt>disabled</tt> and - # <tt>readonly</tt>), which you can give a value of true in the +options+ hash. You can use - # symbols or strings for the attribute names. - # - # ==== Examples - # content_tag(:p, "Hello world!") - # # => <p>Hello world!</p> - # content_tag(:div, content_tag(:p, "Hello world!"), class: "strong") - # # => <div class="strong"><p>Hello world!</p></div> - # content_tag("select", options, multiple: true) - # # => <select multiple="multiple">...options...</select> - # - # <%= content_tag :div, class: "strong" do -%> - # Hello world! - # <% end -%> - # # => <div class="strong">Hello world!</div> - def content_tag(name, content_or_options_with_block = nil, options = nil, escape = true, &block) - if block_given? - options = content_or_options_with_block if content_or_options_with_block.is_a?(Hash) - content_tag_string(name, capture(&block), options, escape) - else - content_tag_string(name, content_or_options_with_block, options, escape) - end - end - - # Returns a CDATA section with the given +content+. CDATA sections - # are used to escape blocks of text containing characters which would - # otherwise be recognized as markup. CDATA sections begin with the string - # <tt><![CDATA[</tt> and end with (and may not contain) the string <tt>]]></tt>. - # - # cdata_section("<hello world>") - # # => <![CDATA[<hello world>]]> - # - # cdata_section(File.read("hello_world.txt")) - # # => <![CDATA[<hello from a text file]]> - # - # cdata_section("hello]]>world") - # # => <![CDATA[hello]]]]><![CDATA[>world]]> - def cdata_section(content) - splitted = content.gsub(']]>', ']]]]><![CDATA[>') - "<![CDATA[#{splitted}]]>".html_safe - end - - # Returns an escaped version of +html+ without affecting existing escaped entities. - # - # escape_once("1 < 2 & 3") - # # => "1 < 2 & 3" - # - # escape_once("<< Accept & Checkout") - # # => "<< Accept & Checkout" - def escape_once(html) - ERB::Util.html_escape_once(html) - end - - private - - def content_tag_string(name, content, options, escape = true) - tag_options = tag_options(options, escape) if options - content = ERB::Util.h(content) if escape - "<#{name}#{tag_options}>#{PRE_CONTENT_STRINGS[name.to_sym]}#{content}</#{name}>".html_safe - end - - def tag_options(options, escape = true) - return if options.blank? - attrs = [] - options.each_pair do |key, value| - if key.to_s == 'data' && value.is_a?(Hash) - value.each_pair do |k, v| - attrs << data_tag_option(k, v, escape) - end - elsif BOOLEAN_ATTRIBUTES.include?(key) - attrs << boolean_tag_option(key) if value - elsif !value.nil? - attrs << tag_option(key, value, escape) - end - end - " #{attrs.sort * ' '}".html_safe unless attrs.empty? - end - - def data_tag_option(key, value, escape) - key = "data-#{key.to_s.dasherize}" - unless value.is_a?(String) || value.is_a?(Symbol) || value.is_a?(BigDecimal) - value = value.to_json - end - tag_option(key, value, escape) - end - - def boolean_tag_option(key) - %(#{key}="#{key}") - end - - def tag_option(key, value, escape) - value = value.join(" ") if value.is_a?(Array) - value = ERB::Util.h(value) if escape - %(#{key}="#{value}") - end - end - end -end diff --git a/actionpack/lib/action_view/helpers/tags.rb b/actionpack/lib/action_view/helpers/tags.rb deleted file mode 100644 index a05e16979a..0000000000 --- a/actionpack/lib/action_view/helpers/tags.rb +++ /dev/null @@ -1,39 +0,0 @@ -module ActionView - module Helpers - module Tags #:nodoc: - extend ActiveSupport::Autoload - - autoload :Base - autoload :CheckBox - autoload :CollectionCheckBoxes - autoload :CollectionRadioButtons - autoload :CollectionSelect - autoload :ColorField - autoload :DateField - autoload :DateSelect - autoload :DatetimeField - autoload :DatetimeLocalField - autoload :DatetimeSelect - autoload :EmailField - autoload :FileField - autoload :GroupedCollectionSelect - autoload :HiddenField - autoload :Label - autoload :MonthField - autoload :NumberField - autoload :PasswordField - autoload :RadioButton - autoload :RangeField - autoload :SearchField - autoload :Select - autoload :TelField - autoload :TextArea - autoload :TextField - autoload :TimeField - autoload :TimeSelect - autoload :TimeZoneSelect - autoload :UrlField - autoload :WeekField - end - end -end diff --git a/actionpack/lib/action_view/helpers/tags/base.rb b/actionpack/lib/action_view/helpers/tags/base.rb deleted file mode 100644 index aef1572290..0000000000 --- a/actionpack/lib/action_view/helpers/tags/base.rb +++ /dev/null @@ -1,148 +0,0 @@ -module ActionView - module Helpers - module Tags - class Base #:nodoc: - include Helpers::ActiveModelInstanceTag, Helpers::TagHelper, Helpers::FormTagHelper - include FormOptionsHelper - - attr_reader :object - - def initialize(object_name, method_name, template_object, options = {}) - @object_name, @method_name = object_name.to_s.dup, method_name.to_s.dup - @template_object = template_object - - @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 - end - - # This is what child classes implement. - def render - raise NotImplementedError, "Subclasses must implement a render method" - end - - private - - def value(object) - object.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) : - value(object) - end - end - - def retrieve_object(object) - if object - object - elsif @template_object.instance_variable_defined?("@#{@object_name}") - @template_object.instance_variable_get("@#{@object_name}") - end - rescue NameError - # As @object_name may contain the nested syntax (item[subobject]) we need to fallback to nil. - nil - end - - def retrieve_autoindex(pre_match) - object = self.object || @template_object.instance_variable_get("@#{pre_match}") - if object && object.respond_to?(:to_param) - object.to_param - else - raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}" - end - end - - def add_default_name_and_id_for_value(tag_value, options) - if tag_value.nil? - add_default_name_and_id(options) - else - specified_id = options["id"] - add_default_name_and_id(options) - - if specified_id.blank? && options["id"].present? - options["id"] += "_#{sanitized_value(tag_value)}" - end - end - 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["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["id"] = options.fetch("id"){ tag_id_with_index(@auto_index) } - else - options["name"] ||= options.fetch("name"){ tag_name } - options["id"] = options.fetch("id"){ tag_id } - end - - options["name"] += "[]" if options["multiple"] && !options["name"].ends_with?("[]") - options["id"] = [options.delete('namespace'), options["id"]].compact.join("_").presence - end - - def tag_name - "#{@object_name}[#{sanitized_method_name}]" - end - - def tag_name_with_index(index) - "#{@object_name}[#{index}][#{sanitized_method_name}]" - end - - def tag_id - "#{sanitized_object_name}_#{sanitized_method_name}" - end - - def tag_id_with_index(index) - "#{sanitized_object_name}_#{index}_#{sanitized_method_name}" - end - - def sanitized_object_name - @sanitized_object_name ||= @object_name.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "") - end - - def sanitized_method_name - @sanitized_method_name ||= @method_name.sub(/\?$/,"") - end - - def sanitized_value(value) - value.to_s.gsub(/\s/, "_").gsub(/[^-\w]/, "").downcase - end - - def select_content_tag(option_tags, options, html_options) - html_options = html_options.stringify_keys - add_default_name_and_id(html_options) - options[:include_blank] ||= true unless options[:prompt] || select_not_required?(html_options) - select = content_tag("select", add_options(option_tags, options, value(object)), html_options) - - if html_options["multiple"] && options.fetch(:include_hidden, true) - tag("input", :disabled => html_options["disabled"], :name => html_options["name"], :type => "hidden", :value => "") + select - else - select - end - end - - def select_not_required?(html_options) - !html_options["required"] || html_options["multiple"] || html_options["size"].to_i > 1 - end - - def add_options(option_tags, options, value = nil) - if options[:include_blank] - option_tags = content_tag_string('option', options[:include_blank].kind_of?(String) ? options[:include_blank] : nil, :value => '') + "\n" + option_tags - end - if value.blank? && options[:prompt] - option_tags = content_tag_string('option', prompt_text(options[:prompt]), :value => '') + "\n" + option_tags - end - option_tags - end - end - end - end -end diff --git a/actionpack/lib/action_view/helpers/tags/check_box.rb b/actionpack/lib/action_view/helpers/tags/check_box.rb deleted file mode 100644 index e21cc07746..0000000000 --- a/actionpack/lib/action_view/helpers/tags/check_box.rb +++ /dev/null @@ -1,64 +0,0 @@ -require 'action_view/helpers/tags/checkable' - -module ActionView - module Helpers - module Tags - class CheckBox < Base #:nodoc: - include Checkable - - def initialize(object_name, method_name, template_object, checked_value, unchecked_value, options) - @checked_value = checked_value - @unchecked_value = unchecked_value - super(object_name, method_name, template_object, options) - end - - def render - options = @options.stringify_keys - options["type"] = "checkbox" - options["value"] = @checked_value - options["checked"] = "checked" if input_checked?(object, options) - - if options["multiple"] - add_default_name_and_id_for_value(@checked_value, options) - options.delete("multiple") - else - add_default_name_and_id(options) - end - - include_hidden = options.delete("include_hidden") { true } - checkbox = tag("input", options) - - if include_hidden - hidden = hidden_field_for_checkbox(options) - hidden + checkbox - else - checkbox - end - end - - private - - def checked?(value) - case value - when TrueClass, FalseClass - value == !!@checked_value - when NilClass - false - when String - value == @checked_value - else - if value.respond_to?(:include?) - value.include?(@checked_value) - else - value.to_i == @checked_value.to_i - end - end - end - - def hidden_field_for_checkbox(options) - @unchecked_value ? tag("input", options.slice("name", "disabled", "form").merge!("type" => "hidden", "value" => @unchecked_value)) : "".html_safe - end - end - end - end -end diff --git a/actionpack/lib/action_view/helpers/tags/checkable.rb b/actionpack/lib/action_view/helpers/tags/checkable.rb deleted file mode 100644 index b97c0c68d7..0000000000 --- a/actionpack/lib/action_view/helpers/tags/checkable.rb +++ /dev/null @@ -1,16 +0,0 @@ -module ActionView - module Helpers - module Tags - module Checkable - def input_checked?(object, options) - if options.has_key?("checked") - checked = options.delete "checked" - checked == true || checked == "checked" - else - checked?(value(object)) - end - end - end - end - end -end diff --git a/actionpack/lib/action_view/helpers/tags/collection_check_boxes.rb b/actionpack/lib/action_view/helpers/tags/collection_check_boxes.rb deleted file mode 100644 index 9655008fe2..0000000000 --- a/actionpack/lib/action_view/helpers/tags/collection_check_boxes.rb +++ /dev/null @@ -1,43 +0,0 @@ -require 'action_view/helpers/tags/collection_helpers' - -module ActionView - module Helpers - module Tags - class CollectionCheckBoxes < Base - include CollectionHelpers - - class CheckBoxBuilder < Builder - def check_box(extra_html_options={}) - html_options = extra_html_options.merge(@input_html_options) - @template_object.check_box(@object_name, @method_name, html_options, @value, nil) - end - end - - def render(&block) - rendered_collection = render_collection do |item, value, text, default_html_options| - default_html_options[:multiple] = true - builder = instantiate_builder(CheckBoxBuilder, item, value, text, default_html_options) - - if block_given? - @template_object.capture(builder, &block) - else - render_component(builder) - end - end - - # Append a hidden field to make sure something will be sent back to the - # server if all check boxes are unchecked. - hidden = @template_object.hidden_field_tag("#{tag_name}[]", "", :id => nil) - - rendered_collection + hidden - end - - private - - def render_component(builder) - builder.check_box + builder.label - end - end - end - end -end diff --git a/actionpack/lib/action_view/helpers/tags/collection_helpers.rb b/actionpack/lib/action_view/helpers/tags/collection_helpers.rb deleted file mode 100644 index e92a318c73..0000000000 --- a/actionpack/lib/action_view/helpers/tags/collection_helpers.rb +++ /dev/null @@ -1,83 +0,0 @@ -module ActionView - module Helpers - module Tags - module CollectionHelpers - class Builder - attr_reader :object, :text, :value - - def initialize(template_object, object_name, method_name, object, - sanitized_attribute_name, text, value, input_html_options) - @template_object = template_object - @object_name = object_name - @method_name = method_name - @object = object - @sanitized_attribute_name = sanitized_attribute_name - @text = text - @value = value - @input_html_options = input_html_options - end - - def label(label_html_options={}, &block) - @template_object.label(@object_name, @sanitized_attribute_name, @text, label_html_options, &block) - end - end - - def initialize(object_name, method_name, template_object, collection, value_method, text_method, options, html_options) - @collection = collection - @value_method = value_method - @text_method = text_method - @html_options = html_options - - super(object_name, method_name, template_object, options) - end - - private - - def instantiate_builder(builder_class, item, value, text, html_options) - builder_class.new(@template_object, @object_name, @method_name, item, - sanitize_attribute_name(value), text, value, html_options) - end - - # Generate default options for collection helpers, such as :checked and - # :disabled. - def default_html_options_for_collection(item, value) #:nodoc: - html_options = @html_options.dup - - [:checked, :selected, :disabled].each do |option| - current_value = @options[option] - next if current_value.nil? - - accept = if current_value.respond_to?(:call) - current_value.call(item) - else - Array(current_value).map(&:to_s).include?(value.to_s) - end - - if accept - html_options[option] = true - elsif option == :checked - html_options[option] = false - end - end - - html_options[:object] = @object - html_options - end - - def sanitize_attribute_name(value) #:nodoc: - "#{sanitized_method_name}_#{sanitized_value(value)}" - end - - def render_collection #:nodoc: - @collection.map do |item| - value = value_for_collection(item, @value_method) - text = value_for_collection(item, @text_method) - default_html_options = default_html_options_for_collection(item, value) - - yield item, value, text, default_html_options - end.join.html_safe - end - end - end - end -end diff --git a/actionpack/lib/action_view/helpers/tags/collection_radio_buttons.rb b/actionpack/lib/action_view/helpers/tags/collection_radio_buttons.rb deleted file mode 100644 index 893f4411e7..0000000000 --- a/actionpack/lib/action_view/helpers/tags/collection_radio_buttons.rb +++ /dev/null @@ -1,36 +0,0 @@ -require 'action_view/helpers/tags/collection_helpers' - -module ActionView - module Helpers - module Tags - class CollectionRadioButtons < Base - include CollectionHelpers - - class RadioButtonBuilder < Builder - def radio_button(extra_html_options={}) - html_options = extra_html_options.merge(@input_html_options) - @template_object.radio_button(@object_name, @method_name, @value, html_options) - end - end - - def render(&block) - render_collection do |item, value, text, default_html_options| - builder = instantiate_builder(RadioButtonBuilder, item, value, text, default_html_options) - - if block_given? - @template_object.capture(builder, &block) - else - render_component(builder) - end - end - end - - private - - def render_component(builder) - builder.radio_button + builder.label - end - end - end - end -end diff --git a/actionpack/lib/action_view/helpers/tags/collection_select.rb b/actionpack/lib/action_view/helpers/tags/collection_select.rb deleted file mode 100644 index ec78e6e5f9..0000000000 --- a/actionpack/lib/action_view/helpers/tags/collection_select.rb +++ /dev/null @@ -1,28 +0,0 @@ -module ActionView - module Helpers - module Tags - class CollectionSelect < Base #:nodoc: - def initialize(object_name, method_name, template_object, collection, value_method, text_method, options, html_options) - @collection = collection - @value_method = value_method - @text_method = text_method - @html_options = html_options - - super(object_name, method_name, template_object, options) - end - - def render - option_tags_options = { - :selected => @options.fetch(:selected) { value(@object) }, - :disabled => @options[:disabled] - } - - select_content_tag( - options_from_collection_for_select(@collection, @value_method, @text_method, option_tags_options), - @options, @html_options - ) - end - end - end - end -end diff --git a/actionpack/lib/action_view/helpers/tags/color_field.rb b/actionpack/lib/action_view/helpers/tags/color_field.rb deleted file mode 100644 index 6f08f8483a..0000000000 --- a/actionpack/lib/action_view/helpers/tags/color_field.rb +++ /dev/null @@ -1,25 +0,0 @@ -module ActionView - module Helpers - module Tags - class ColorField < TextField #:nodoc: - def render - options = @options.stringify_keys - options["value"] = @options.fetch("value") { validate_color_string(value(object)) } - @options = options - super - end - - private - - def validate_color_string(string) - regex = /#[0-9a-fA-F]{6}/ - if regex.match(string) - string.downcase - else - "#000000" - end - end - end - end - end -end diff --git a/actionpack/lib/action_view/helpers/tags/date_field.rb b/actionpack/lib/action_view/helpers/tags/date_field.rb deleted file mode 100644 index 64c29dea3d..0000000000 --- a/actionpack/lib/action_view/helpers/tags/date_field.rb +++ /dev/null @@ -1,13 +0,0 @@ -module ActionView - module Helpers - module Tags - class DateField < DatetimeField #:nodoc: - private - - def format_date(value) - value.try(:strftime, "%Y-%m-%d") - end - end - end - end -end diff --git a/actionpack/lib/action_view/helpers/tags/date_select.rb b/actionpack/lib/action_view/helpers/tags/date_select.rb deleted file mode 100644 index 734591394b..0000000000 --- a/actionpack/lib/action_view/helpers/tags/date_select.rb +++ /dev/null @@ -1,72 +0,0 @@ -require 'active_support/core_ext/time/calculations' - -module ActionView - module Helpers - module Tags - class DateSelect < Base #:nodoc: - def initialize(object_name, method_name, template_object, options, html_options) - @html_options = html_options - - super(object_name, method_name, template_object, options) - end - - def render - error_wrapping(datetime_selector(@options, @html_options).send("select_#{select_type}").html_safe) - end - - class << self - def select_type - @select_type ||= self.name.split("::").last.sub("Select", "").downcase - end - end - - private - - def select_type - self.class.select_type - end - - def datetime_selector(options, html_options) - datetime = options.fetch(:selected) { value(object) || default_datetime(options) } - @auto_index ||= nil - - options = options.dup - options[:field_name] = @method_name - options[:include_position] = true - options[:prefix] ||= @object_name - options[:index] = @auto_index if @auto_index && !options.has_key?(:index) - - DateTimeSelector.new(datetime, options, html_options) - end - - def default_datetime(options) - return if options[:include_blank] || options[:prompt] - - case options[:default] - when nil - Time.current - when Date, Time - options[:default] - else - default = options[:default].dup - - # Rename :minute and :second to :min and :sec - default[:min] ||= default[:minute] - default[:sec] ||= default[:second] - - time = Time.current - - [:year, :month, :day, :hour, :min, :sec].each do |key| - default[key] ||= time.send(key) - end - - Time.utc( - default[:year], default[:month], default[:day], - default[:hour], default[:min], default[:sec] - ) - end - end - end - end - end -end diff --git a/actionpack/lib/action_view/helpers/tags/datetime_field.rb b/actionpack/lib/action_view/helpers/tags/datetime_field.rb deleted file mode 100644 index e407146e96..0000000000 --- a/actionpack/lib/action_view/helpers/tags/datetime_field.rb +++ /dev/null @@ -1,22 +0,0 @@ -module ActionView - module Helpers - module Tags - class DatetimeField < TextField #:nodoc: - def render - options = @options.stringify_keys - options["value"] = @options.fetch("value") { format_date(value(object)) } - options["min"] = format_date(options["min"]) - options["max"] = format_date(options["max"]) - @options = options - super - end - - private - - def format_date(value) - value.try(:strftime, "%Y-%m-%dT%T.%L%z") - end - end - end - end -end diff --git a/actionpack/lib/action_view/helpers/tags/datetime_local_field.rb b/actionpack/lib/action_view/helpers/tags/datetime_local_field.rb deleted file mode 100644 index 6668d6d718..0000000000 --- a/actionpack/lib/action_view/helpers/tags/datetime_local_field.rb +++ /dev/null @@ -1,19 +0,0 @@ -module ActionView - module Helpers - module Tags - class DatetimeLocalField < DatetimeField #:nodoc: - class << self - def field_type - @field_type ||= "datetime-local" - end - end - - private - - def format_date(value) - value.try(:strftime, "%Y-%m-%dT%T") - end - end - end - end -end diff --git a/actionpack/lib/action_view/helpers/tags/datetime_select.rb b/actionpack/lib/action_view/helpers/tags/datetime_select.rb deleted file mode 100644 index a32c840bce..0000000000 --- a/actionpack/lib/action_view/helpers/tags/datetime_select.rb +++ /dev/null @@ -1,8 +0,0 @@ -module ActionView - module Helpers - module Tags - class DatetimeSelect < DateSelect #:nodoc: - end - end - end -end diff --git a/actionpack/lib/action_view/helpers/tags/email_field.rb b/actionpack/lib/action_view/helpers/tags/email_field.rb deleted file mode 100644 index 45cde507d7..0000000000 --- a/actionpack/lib/action_view/helpers/tags/email_field.rb +++ /dev/null @@ -1,8 +0,0 @@ -module ActionView - module Helpers - module Tags - class EmailField < TextField #:nodoc: - end - end - end -end diff --git a/actionpack/lib/action_view/helpers/tags/file_field.rb b/actionpack/lib/action_view/helpers/tags/file_field.rb deleted file mode 100644 index 59f2ff71b4..0000000000 --- a/actionpack/lib/action_view/helpers/tags/file_field.rb +++ /dev/null @@ -1,8 +0,0 @@ -module ActionView - module Helpers - module Tags - class FileField < TextField #:nodoc: - end - end - end -end diff --git a/actionpack/lib/action_view/helpers/tags/grouped_collection_select.rb b/actionpack/lib/action_view/helpers/tags/grouped_collection_select.rb deleted file mode 100644 index 507ba8835f..0000000000 --- a/actionpack/lib/action_view/helpers/tags/grouped_collection_select.rb +++ /dev/null @@ -1,29 +0,0 @@ -module ActionView - module Helpers - module Tags - class GroupedCollectionSelect < Base #:nodoc: - def initialize(object_name, method_name, template_object, collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options) - @collection = collection - @group_method = group_method - @group_label_method = group_label_method - @option_key_method = option_key_method - @option_value_method = option_value_method - @html_options = html_options - - super(object_name, method_name, template_object, options) - end - - def render - option_tags_options = { - :selected => @options.fetch(:selected) { value(@object) }, - :disabled => @options[:disabled] - } - - select_content_tag( - option_groups_from_collection_for_select(@collection, @group_method, @group_label_method, @option_key_method, @option_value_method, option_tags_options), @options, @html_options - ) - end - end - end - end -end diff --git a/actionpack/lib/action_view/helpers/tags/hidden_field.rb b/actionpack/lib/action_view/helpers/tags/hidden_field.rb deleted file mode 100644 index a8d13dc1b1..0000000000 --- a/actionpack/lib/action_view/helpers/tags/hidden_field.rb +++ /dev/null @@ -1,8 +0,0 @@ -module ActionView - module Helpers - module Tags - class HiddenField < TextField #:nodoc: - end - end - end -end diff --git a/actionpack/lib/action_view/helpers/tags/label.rb b/actionpack/lib/action_view/helpers/tags/label.rb deleted file mode 100644 index 16135fcd5a..0000000000 --- a/actionpack/lib/action_view/helpers/tags/label.rb +++ /dev/null @@ -1,65 +0,0 @@ -module ActionView - module Helpers - module Tags - class Label < Base #:nodoc: - def initialize(object_name, method_name, template_object, content_or_options = nil, options = nil) - options ||= {} - - content_is_options = content_or_options.is_a?(Hash) - if content_is_options - options.merge! content_or_options - @content = nil - else - @content = content_or_options - end - - super(object_name, method_name, template_object, options) - end - - def render(&block) - options = @options.stringify_keys - tag_value = options.delete("value") - name_and_id = options.dup - - if name_and_id["for"] - name_and_id["id"] = name_and_id["for"] - else - name_and_id.delete("id") - end - - add_default_name_and_id_for_value(tag_value, name_and_id) - options.delete("index") - options.delete("namespace") - options["for"] = name_and_id["id"] unless options.key?("for") - - if block_given? - content = @template_object.capture(&block) - else - content = if @content.blank? - @object_name.gsub!(/\[(.*)_attributes\]\[\d\]/, '.\1') - method_and_value = tag_value.present? ? "#{@method_name}.#{tag_value}" : @method_name - - if object.respond_to?(:to_model) - key = object.class.model_name.i18n_key - i18n_default = ["#{key}.#{method_and_value}".to_sym, ""] - end - - i18n_default ||= "" - I18n.t("#{@object_name}.#{method_and_value}", :default => i18n_default, :scope => "helpers.label").presence - else - @content.to_s - end - - content ||= if object && object.class.respond_to?(:human_attribute_name) - object.class.human_attribute_name(@method_name) - end - - content ||= @method_name.humanize - end - - label_tag(name_and_id["id"], content, options) - end - end - end - end -end diff --git a/actionpack/lib/action_view/helpers/tags/month_field.rb b/actionpack/lib/action_view/helpers/tags/month_field.rb deleted file mode 100644 index 3d3c32d847..0000000000 --- a/actionpack/lib/action_view/helpers/tags/month_field.rb +++ /dev/null @@ -1,13 +0,0 @@ -module ActionView - module Helpers - module Tags - class MonthField < DatetimeField #:nodoc: - private - - def format_date(value) - value.try(:strftime, "%Y-%m") - end - end - end - end -end diff --git a/actionpack/lib/action_view/helpers/tags/number_field.rb b/actionpack/lib/action_view/helpers/tags/number_field.rb deleted file mode 100644 index 9cd04434f0..0000000000 --- a/actionpack/lib/action_view/helpers/tags/number_field.rb +++ /dev/null @@ -1,18 +0,0 @@ -module ActionView - module Helpers - module Tags - class NumberField < TextField #:nodoc: - def render - options = @options.stringify_keys - - if range = options.delete("in") || options.delete("within") - options.update("min" => range.min, "max" => range.max) - end - - @options = options - super - end - end - end - end -end diff --git a/actionpack/lib/action_view/helpers/tags/password_field.rb b/actionpack/lib/action_view/helpers/tags/password_field.rb deleted file mode 100644 index 6e7a4d3c36..0000000000 --- a/actionpack/lib/action_view/helpers/tags/password_field.rb +++ /dev/null @@ -1,12 +0,0 @@ -module ActionView - module Helpers - module Tags - class PasswordField < TextField #:nodoc: - def render - @options = {:value => nil}.merge!(@options) - super - end - end - end - end -end diff --git a/actionpack/lib/action_view/helpers/tags/radio_button.rb b/actionpack/lib/action_view/helpers/tags/radio_button.rb deleted file mode 100644 index 8a0421f061..0000000000 --- a/actionpack/lib/action_view/helpers/tags/radio_button.rb +++ /dev/null @@ -1,31 +0,0 @@ -require 'action_view/helpers/tags/checkable' - -module ActionView - module Helpers - module Tags - class RadioButton < Base #:nodoc: - include Checkable - - def initialize(object_name, method_name, template_object, tag_value, options) - @tag_value = tag_value - super(object_name, method_name, template_object, options) - end - - def render - options = @options.stringify_keys - options["type"] = "radio" - options["value"] = @tag_value - options["checked"] = "checked" if input_checked?(object, options) - add_default_name_and_id_for_value(@tag_value, options) - tag("input", options) - end - - private - - def checked?(value) - value.to_s == @tag_value.to_s - end - end - end - end -end diff --git a/actionpack/lib/action_view/helpers/tags/range_field.rb b/actionpack/lib/action_view/helpers/tags/range_field.rb deleted file mode 100644 index 47db4680e7..0000000000 --- a/actionpack/lib/action_view/helpers/tags/range_field.rb +++ /dev/null @@ -1,8 +0,0 @@ -module ActionView - module Helpers - module Tags - class RangeField < NumberField #:nodoc: - end - end - end -end diff --git a/actionpack/lib/action_view/helpers/tags/search_field.rb b/actionpack/lib/action_view/helpers/tags/search_field.rb deleted file mode 100644 index 818fd4b887..0000000000 --- a/actionpack/lib/action_view/helpers/tags/search_field.rb +++ /dev/null @@ -1,24 +0,0 @@ -module ActionView - module Helpers - module Tags - class SearchField < TextField #:nodoc: - def render - options = @options.stringify_keys - - if options["autosave"] - if options["autosave"] == true - options["autosave"] = request.host.split(".").reverse.join(".") - end - options["results"] ||= 10 - end - - if options["onsearch"] - options["incremental"] = true unless options.has_key?("incremental") - end - - super - end - end - end - end -end diff --git a/actionpack/lib/action_view/helpers/tags/select.rb b/actionpack/lib/action_view/helpers/tags/select.rb deleted file mode 100644 index 53a108b7e6..0000000000 --- a/actionpack/lib/action_view/helpers/tags/select.rb +++ /dev/null @@ -1,41 +0,0 @@ -module ActionView - module Helpers - module Tags - class Select < Base #:nodoc: - def initialize(object_name, method_name, template_object, choices, options, html_options) - @choices = choices - @choices = @choices.to_a if @choices.is_a?(Range) - @html_options = html_options - - super(object_name, method_name, template_object, options) - end - - def render - option_tags_options = { - :selected => @options.fetch(:selected) { value(@object) }, - :disabled => @options[:disabled] - } - - option_tags = if grouped_choices? - grouped_options_for_select(@choices, option_tags_options) - else - options_for_select(@choices, option_tags_options) - end - - select_content_tag(option_tags, @options, @html_options) - end - - private - - # Grouped choices look like this: - # - # [nil, []] - # { nil => [] } - # - def grouped_choices? - !@choices.empty? && @choices.first.respond_to?(:last) && Array === @choices.first.last - end - end - end - end -end diff --git a/actionpack/lib/action_view/helpers/tags/tel_field.rb b/actionpack/lib/action_view/helpers/tags/tel_field.rb deleted file mode 100644 index 87c1f6b6b6..0000000000 --- a/actionpack/lib/action_view/helpers/tags/tel_field.rb +++ /dev/null @@ -1,8 +0,0 @@ -module ActionView - module Helpers - module Tags - class TelField < TextField #:nodoc: - end - end - end -end diff --git a/actionpack/lib/action_view/helpers/tags/text_area.rb b/actionpack/lib/action_view/helpers/tags/text_area.rb deleted file mode 100644 index f74652c5e7..0000000000 --- a/actionpack/lib/action_view/helpers/tags/text_area.rb +++ /dev/null @@ -1,18 +0,0 @@ -module ActionView - module Helpers - module Tags - class TextArea < Base #:nodoc: - def render - options = @options.stringify_keys - add_default_name_and_id(options) - - if size = options.delete("size") - options["cols"], options["rows"] = size.split("x") if size.respond_to?(:split) - end - - content_tag("textarea", options.delete('value') || value_before_type_cast(object), options) - end - end - end - end -end diff --git a/actionpack/lib/action_view/helpers/tags/text_field.rb b/actionpack/lib/action_view/helpers/tags/text_field.rb deleted file mode 100644 index 024a1a8af2..0000000000 --- a/actionpack/lib/action_view/helpers/tags/text_field.rb +++ /dev/null @@ -1,29 +0,0 @@ -module ActionView - module Helpers - module Tags - class TextField < Base #:nodoc: - def render - options = @options.stringify_keys - options["size"] = options["maxlength"] unless options.key?("size") - options["type"] ||= field_type - options["value"] = options.fetch("value"){ value_before_type_cast(object) } unless field_type == "file" - options["value"] &&= ERB::Util.html_escape(options["value"]) - add_default_name_and_id(options) - tag("input", options) - end - - class << self - def field_type - @field_type ||= self.name.split("::").last.sub("Field", "").downcase - end - end - - private - - def field_type - self.class.field_type - end - end - end - end -end diff --git a/actionpack/lib/action_view/helpers/tags/time_field.rb b/actionpack/lib/action_view/helpers/tags/time_field.rb deleted file mode 100644 index a3941860c9..0000000000 --- a/actionpack/lib/action_view/helpers/tags/time_field.rb +++ /dev/null @@ -1,13 +0,0 @@ -module ActionView - module Helpers - module Tags - class TimeField < DatetimeField #:nodoc: - private - - def format_date(value) - value.try(:strftime, "%T.%L") - end - end - end - end -end diff --git a/actionpack/lib/action_view/helpers/tags/time_select.rb b/actionpack/lib/action_view/helpers/tags/time_select.rb deleted file mode 100644 index 9e97deb706..0000000000 --- a/actionpack/lib/action_view/helpers/tags/time_select.rb +++ /dev/null @@ -1,8 +0,0 @@ -module ActionView - module Helpers - module Tags - class TimeSelect < DateSelect #:nodoc: - end - end - end -end diff --git a/actionpack/lib/action_view/helpers/tags/time_zone_select.rb b/actionpack/lib/action_view/helpers/tags/time_zone_select.rb deleted file mode 100644 index 0a176157c3..0000000000 --- a/actionpack/lib/action_view/helpers/tags/time_zone_select.rb +++ /dev/null @@ -1,20 +0,0 @@ -module ActionView - module Helpers - module Tags - class TimeZoneSelect < Base #:nodoc: - def initialize(object_name, method_name, template_object, priority_zones, options, html_options) - @priority_zones = priority_zones - @html_options = html_options - - super(object_name, method_name, template_object, options) - end - - def render - select_content_tag( - time_zone_options_for_select(value(@object) || @options[:default], @priority_zones, @options[:model] || ActiveSupport::TimeZone), @options, @html_options - ) - end - end - end - end -end diff --git a/actionpack/lib/action_view/helpers/tags/url_field.rb b/actionpack/lib/action_view/helpers/tags/url_field.rb deleted file mode 100644 index 1ffdfe0b3c..0000000000 --- a/actionpack/lib/action_view/helpers/tags/url_field.rb +++ /dev/null @@ -1,8 +0,0 @@ -module ActionView - module Helpers - module Tags - class UrlField < TextField #:nodoc: - end - end - end -end diff --git a/actionpack/lib/action_view/helpers/tags/week_field.rb b/actionpack/lib/action_view/helpers/tags/week_field.rb deleted file mode 100644 index 1e13939a0a..0000000000 --- a/actionpack/lib/action_view/helpers/tags/week_field.rb +++ /dev/null @@ -1,13 +0,0 @@ -module ActionView - module Helpers - module Tags - class WeekField < DatetimeField #:nodoc: - private - - def format_date(value) - value.try(:strftime, "%Y-W%W") - end - end - end - end -end diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb deleted file mode 100644 index 2e124cf085..0000000000 --- a/actionpack/lib/action_view/helpers/text_helper.rb +++ /dev/null @@ -1,439 +0,0 @@ -require 'active_support/core_ext/string/filters' -require 'active_support/core_ext/array/extract_options' - -module ActionView - # = Action View Text Helpers - module Helpers #:nodoc: - # The TextHelper module provides a set of methods for filtering, formatting - # and transforming strings, which can reduce the amount of inline Ruby code in - # your views. These helper methods extend Action View making them callable - # within your template files. - # - # ==== Sanitization - # - # Most text helpers by default sanitize the given content, but do not escape it. - # This means HTML tags will appear in the page but all malicious code will be removed. - # Let's look at some examples using the +simple_format+ method: - # - # simple_format('<a href="http://example.com/">Example</a>') - # # => "<p><a href=\"http://example.com/\">Example</a></p>" - # - # simple_format('<a href="javascript:alert(\'no!\')">Example</a>') - # # => "<p><a>Example</a></p>" - # - # If you want to escape all content, you should invoke the +h+ method before - # calling the text helper. - # - # simple_format h('<a href="http://example.com/">Example</a>') - # # => "<p><a href=\"http://example.com/\">Example</a></p>" - module TextHelper - extend ActiveSupport::Concern - - include SanitizeHelper - include TagHelper - # The preferred method of outputting text in your views is to use the - # <%= "text" %> eRuby syntax. The regular _puts_ and _print_ methods - # do not operate as expected in an eRuby code block. If you absolutely must - # output text within a non-output code block (i.e., <% %>), you can use the concat method. - # - # <% - # concat "hello" - # # is the equivalent of <%= "hello" %> - # - # if logged_in - # concat "Logged in!" - # else - # concat link_to('login', action: :login) - # end - # # will either display "Logged in!" or a login link - # %> - def concat(string) - output_buffer << string - end - - def safe_concat(string) - output_buffer.respond_to?(:safe_concat) ? output_buffer.safe_concat(string) : concat(string) - end - - # Truncates a given +text+ after a given <tt>:length</tt> if +text+ is longer than <tt>:length</tt> - # (defaults to 30). The last characters will be replaced with the <tt>:omission</tt> (defaults to "...") - # for a total length not exceeding <tt>:length</tt>. - # - # Pass a <tt>:separator</tt> to truncate +text+ at a natural break. - # - # Pass a block if you want to show extra content when the text is truncated. - # - # The result is marked as HTML-safe, but it is escaped by default, unless <tt>:escape</tt> is - # +false+. Care should be taken if +text+ contains HTML tags or entities, because truncation - # may produce invalid HTML (such as unbalanced or incomplete tags). - # - # truncate("Once upon a time in a world far far away") - # # => "Once upon a time in a world..." - # - # truncate("Once upon a time in a world far far away", length: 17) - # # => "Once upon a ti..." - # - # truncate("Once upon a time in a world far far away", length: 17, separator: ' ') - # # => "Once upon a..." - # - # truncate("And they found that many people were sleeping better.", length: 25, omission: '... (continued)') - # # => "And they f... (continued)" - # - # truncate("<p>Once upon a time in a world far far away</p>") - # # => "<p>Once upon a time in a wo..." - # - # truncate("Once upon a time in a world far far away") { link_to "Continue", "#" } - # # => "Once upon a time in a wo...<a href="#">Continue</a>" - def truncate(text, options = {}, &block) - if text - length = options.fetch(:length, 30) - - content = text.truncate(length, options) - content = options[:escape] == false ? content.html_safe : ERB::Util.html_escape(content) - content << capture(&block) if block_given? && text.length > length - content - end - end - - # Highlights one or more +phrases+ everywhere in +text+ by inserting it into - # a <tt>:highlighter</tt> string. The highlighter can be specialized by passing <tt>:highlighter</tt> - # as a single-quoted string with <tt>\1</tt> where the phrase is to be inserted (defaults to - # '<mark>\1</mark>') - # - # highlight('You searched for: rails', 'rails') - # # => You searched for: <mark>rails</mark> - # - # highlight('You searched for: ruby, rails, dhh', 'actionpack') - # # => You searched for: ruby, rails, dhh - # - # highlight('You searched for: rails', ['for', 'rails'], highlighter: '<em>\1</em>') - # # => You searched <em>for</em>: <em>rails</em> - # - # highlight('You searched for: rails', 'rails', highlighter: '<a href="search?q=\1">\1</a>') - # # => You searched for: <a href="search?q=rails">rails</a> - def highlight(text, phrases, options = {}) - text = sanitize(text) if options.fetch(:sanitize, true) - - if text.blank? || phrases.blank? - text - else - highlighter = options.fetch(:highlighter, '<mark>\1</mark>') - match = Array(phrases).map { |p| Regexp.escape(p) }.join('|') - text.gsub(/(#{match})(?![^<]*?>)/i, highlighter) - end.html_safe - end - - # Extracts an excerpt from +text+ that matches the first instance of +phrase+. - # The <tt>:radius</tt> option expands the excerpt on each side of the first occurrence of +phrase+ by the number of characters - # defined in <tt>:radius</tt> (which defaults to 100). If the excerpt radius overflows the beginning or end of the +text+, - # then the <tt>:omission</tt> option (which defaults to "...") will be prepended/appended accordingly. The - # <tt>:separator</tt> enable to choose the delimation. The resulting string will be stripped in any case. If the +phrase+ - # isn't found, nil is returned. - # - # excerpt('This is an example', 'an', radius: 5) - # # => ...s is an exam... - # - # excerpt('This is an example', 'is', radius: 5) - # # => This is a... - # - # excerpt('This is an example', 'is') - # # => This is an example - # - # excerpt('This next thing is an example', 'ex', radius: 2) - # # => ...next... - # - # excerpt('This is also an example', 'an', radius: 8, omission: '<chop> ') - # # => <chop> is also an example - # - # excerpt('This is a very beautiful morning', 'very', separator: ' ', radius: 1) - # # => ...a very beautiful... - def excerpt(text, phrase, options = {}) - return unless text && phrase - - separator = options.fetch(:separator, "") - phrase = Regexp.escape(phrase) - regex = /#{phrase}/i - - return unless matches = text.match(regex) - phrase = matches[0] - - text.split(separator).each do |value| - if value.match(regex) - regex = phrase = value - break - end - end - - first_part, second_part = text.split(regex, 2) - - prefix, first_part = cut_excerpt_part(:first, first_part, separator, options) - postfix, second_part = cut_excerpt_part(:second, second_part, separator, options) - - prefix + (first_part + separator + phrase + separator + second_part).strip + postfix - end - - # Attempts to pluralize the +singular+ word unless +count+ is 1. If - # +plural+ is supplied, it will use that when count is > 1, otherwise - # it will use the Inflector to determine the plural form. - # - # pluralize(1, 'person') - # # => 1 person - # - # pluralize(2, 'person') - # # => 2 people - # - # pluralize(3, 'person', 'users') - # # => 3 users - # - # pluralize(0, 'person') - # # => 0 people - def pluralize(count, singular, plural = nil) - word = if (count == 1 || count =~ /^1(\.0+)?$/) - singular - else - plural || singular.pluralize - end - - "#{count || 0} #{word}" - end - - # Wraps the +text+ into lines no longer than +line_width+ width. This method - # breaks on the first whitespace character that does not exceed +line_width+ - # (which is 80 by default). - # - # word_wrap('Once upon a time') - # # => Once upon a time - # - # word_wrap('Once upon a time, in a kingdom called Far Far Away, a king fell ill, and finding a successor to the throne turned out to be more trouble than anyone could have imagined...') - # # => Once upon a time, in a kingdom called Far Far Away, a king fell ill, and finding\na successor to the throne turned out to be more trouble than anyone could have\nimagined... - # - # word_wrap('Once upon a time', line_width: 8) - # # => Once\nupon a\ntime - # - # word_wrap('Once upon a time', line_width: 1) - # # => Once\nupon\na\ntime - def word_wrap(text, options = {}) - line_width = options.fetch(:line_width, 80) - - text.split("\n").collect do |line| - line.length > line_width ? line.gsub(/(.{1,#{line_width}})(\s+|$)/, "\\1\n").strip : line - end * "\n" - end - - # Returns +text+ transformed into HTML using simple formatting rules. - # Two or more consecutive newlines(<tt>\n\n</tt>) are considered as a - # paragraph and wrapped in <tt><p></tt> tags. One newline (<tt>\n</tt>) is - # considered as a linebreak and a <tt><br /></tt> tag is appended. This - # method does not remove the newlines from the +text+. - # - # You can pass any HTML attributes into <tt>html_options</tt>. These - # will be added to all created paragraphs. - # - # ==== Options - # * <tt>:sanitize</tt> - If +false+, does not sanitize +text+. - # * <tt>:wrapper_tag</tt> - String representing the wrapper tag, defaults to <tt>"p"</tt> - # - # ==== Examples - # my_text = "Here is some basic text...\n...with a line break." - # - # simple_format(my_text) - # # => "<p>Here is some basic text...\n<br />...with a line break.</p>" - # - # simple_format(my_text, {}, wrapper_tag: "div") - # # => "<div>Here is some basic text...\n<br />...with a line break.</div>" - # - # more_text = "We want to put a paragraph...\n\n...right there." - # - # simple_format(more_text) - # # => "<p>We want to put a paragraph...</p>\n\n<p>...right there.</p>" - # - # simple_format("Look ma! A class!", class: 'description') - # # => "<p class='description'>Look ma! A class!</p>" - # - # simple_format("<span>I'm allowed!</span> It's true.", {}, sanitize: false) - # # => "<p><span>I'm allowed!</span> It's true.</p>" - def simple_format(text, html_options = {}, options = {}) - wrapper_tag = options.fetch(:wrapper_tag, :p) - - text = sanitize(text) if options.fetch(:sanitize, true) - paragraphs = split_paragraphs(text) - - if paragraphs.empty? - content_tag(wrapper_tag, nil, html_options) - else - paragraphs.map { |paragraph| - content_tag(wrapper_tag, paragraph, html_options, options[:sanitize]) - }.join("\n\n").html_safe - end - end - - # Creates a Cycle object whose _to_s_ method cycles through elements of an - # array every time it is called. This can be used for example, to alternate - # classes for table rows. You can use named cycles to allow nesting in loops. - # Passing a Hash as the last parameter with a <tt>:name</tt> key will create a - # named cycle. The default name for a cycle without a +:name+ key is - # <tt>"default"</tt>. You can manually reset a cycle by calling reset_cycle - # and passing the name of the cycle. The current cycle string can be obtained - # anytime using the current_cycle method. - # - # # Alternate CSS classes for even and odd numbers... - # @items = [1,2,3,4] - # <table> - # <% @items.each do |item| %> - # <tr class="<%= cycle("odd", "even") -%>"> - # <td>item</td> - # </tr> - # <% end %> - # </table> - # - # - # # Cycle CSS classes for rows, and text colors for values within each row - # @items = x = [{first: 'Robert', middle: 'Daniel', last: 'James'}, - # {first: 'Emily', middle: 'Shannon', maiden: 'Pike', last: 'Hicks'}, - # {first: 'June', middle: 'Dae', last: 'Jones'}] - # <% @items.each do |item| %> - # <tr class="<%= cycle("odd", "even", name: "row_class") -%>"> - # <td> - # <% item.values.each do |value| %> - # <%# Create a named cycle "colors" %> - # <span style="color:<%= cycle("red", "green", "blue", name: "colors") -%>"> - # <%= value %> - # </span> - # <% end %> - # <% reset_cycle("colors") %> - # </td> - # </tr> - # <% end %> - def cycle(first_value, *values) - options = values.extract_options! - name = options.fetch(:name, 'default') - - values.unshift(first_value) - - cycle = get_cycle(name) - unless cycle && cycle.values == values - cycle = set_cycle(name, Cycle.new(*values)) - end - cycle.to_s - end - - # Returns the current cycle string after a cycle has been started. Useful - # for complex table highlighting or any other design need which requires - # the current cycle string in more than one place. - # - # # Alternate background colors - # @items = [1,2,3,4] - # <% @items.each do |item| %> - # <div style="background-color:<%= cycle("red","white","blue") %>"> - # <span style="background-color:<%= current_cycle %>"><%= item %></span> - # </div> - # <% end %> - def current_cycle(name = "default") - cycle = get_cycle(name) - cycle.current_value if cycle - end - - # Resets a cycle so that it starts from the first element the next time - # it is called. Pass in +name+ to reset a named cycle. - # - # # Alternate CSS classes for even and odd numbers... - # @items = [[1,2,3,4], [5,6,3], [3,4,5,6,7,4]] - # <table> - # <% @items.each do |item| %> - # <tr class="<%= cycle("even", "odd") -%>"> - # <% item.each do |value| %> - # <span style="color:<%= cycle("#333", "#666", "#999", name: "colors") -%>"> - # <%= value %> - # </span> - # <% end %> - # - # <% reset_cycle("colors") %> - # </tr> - # <% end %> - # </table> - def reset_cycle(name = "default") - cycle = get_cycle(name) - cycle.reset if cycle - end - - class Cycle #:nodoc: - attr_reader :values - - def initialize(first_value, *values) - @values = values.unshift(first_value) - reset - end - - def reset - @index = 0 - end - - def current_value - @values[previous_index].to_s - end - - def to_s - value = @values[@index].to_s - @index = next_index - return value - end - - private - - def next_index - step_index(1) - end - - def previous_index - step_index(-1) - end - - def step_index(n) - (@index + n) % @values.size - end - end - - private - # The cycle helpers need to store the cycles in a place that is - # guaranteed to be reset every time a page is rendered, so it - # uses an instance variable of ActionView::Base. - def get_cycle(name) - @_cycles = Hash.new unless defined?(@_cycles) - return @_cycles[name] - end - - def set_cycle(name, cycle_object) - @_cycles = Hash.new unless defined?(@_cycles) - @_cycles[name] = cycle_object - end - - def split_paragraphs(text) - return [] if text.blank? - - text.to_str.gsub(/\r\n?/, "\n").split(/\n\n+/).map! do |t| - t.gsub!(/([^\n]\n)(?=[^\n])/, '\1<br />') || t - end - end - - def cut_excerpt_part(part_position, part, separator, options) - return "", "" unless part - - radius = options.fetch(:radius, 100) - omission = options.fetch(:omission, "...") - - part = part.split(separator) - part.delete("") - affix = part.size > radius ? omission : "" - - part = if part_position == :first - drop_index = [part.length - radius, 0].max - part.drop(drop_index) - else - part.first(radius) - end - - return affix, part.join(separator) - end - end - end -end diff --git a/actionpack/lib/action_view/helpers/translation_helper.rb b/actionpack/lib/action_view/helpers/translation_helper.rb deleted file mode 100644 index ad8eb47f1f..0000000000 --- a/actionpack/lib/action_view/helpers/translation_helper.rb +++ /dev/null @@ -1,107 +0,0 @@ -require 'action_view/helpers/tag_helper' -require 'i18n/exceptions' - -module I18n - class ExceptionHandler - include Module.new { - def call(exception, locale, key, options) - exception.is_a?(MissingTranslation) && options[:rescue_format] == :html ? super.html_safe : super - end - } - end -end - -module ActionView - # = Action View Translation Helpers - module Helpers - module TranslationHelper - # Delegates to <tt>I18n#translate</tt> but also performs three additional functions. - # - # First, it'll pass the <tt>rescue_format: :html</tt> option to I18n so that any - # thrown +MissingTranslation+ messages will be turned into inline spans that - # - # * have a "translation-missing" class set, - # * contain the missing key as a title attribute and - # * a titleized version of the last key segment as a text. - # - # E.g. the value returned for a missing translation key :"blog.post.title" will be - # <span class="translation_missing" title="translation missing: en.blog.post.title">Title</span>. - # This way your views will display rather reasonable strings but it will still - # be easy to spot missing translations. - # - # Second, it'll scope the key by the current partial if the key starts - # with a period. So if you call <tt>translate(".foo")</tt> from the - # <tt>people/index.html.erb</tt> template, you'll actually be calling - # <tt>I18n.translate("people.index.foo")</tt>. This makes it less repetitive - # to translate many keys within the same partials and gives you a simple framework - # for scoping them consistently. If you don't prepend the key with a period, - # nothing is converted. - # - # 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 "html". For example, - # calling translate("footer_html") or translate("footer.html") will return - # a safe HTML string that won't be escaped by other HTML helper methods. This - # naming convention helps to identify translations that include HTML tags so that - # you know what kind of output to expect when you call translate in a template. - def translate(key, options = {}) - options.merge!(:rescue_format => :html) unless options.key?(:rescue_format) - options[:default] = wrap_translate_defaults(options[:default]) if options[:default] - if html_safe_translation_key?(key) - html_safe_options = options.dup - options.except(*I18n::RESERVED_KEYS).each do |name, value| - unless name == :count && value.is_a?(Numeric) - html_safe_options[name] = ERB::Util.html_escape(value.to_s) - end - end - translation = I18n.translate(scope_key_by_partial(key), html_safe_options) - - translation.respond_to?(:html_safe) ? translation.html_safe : translation - else - I18n.translate(scope_key_by_partial(key), options) - end - end - alias :t :translate - - # Delegates to <tt>I18n.localize</tt> with no additional functionality. - # - # See http://rubydoc.info/github/svenfuchs/i18n/master/I18n/Backend/Base:localize - # for more information. - def localize(*args) - I18n.localize(*args) - end - alias :l :localize - - private - def scope_key_by_partial(key) - if key.to_s.first == "." - if @virtual_path - @virtual_path.gsub(%r{/_?}, ".") + key.to_s - else - raise "Cannot use t(#{key.inspect}) shortcut because path is not available" - end - else - key - end - end - - 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/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb deleted file mode 100644 index 775d93ed39..0000000000 --- a/actionpack/lib/action_view/helpers/url_helper.rb +++ /dev/null @@ -1,622 +0,0 @@ -require 'action_view/helpers/javascript_helper' -require 'active_support/core_ext/array/access' -require 'active_support/core_ext/hash/keys' -require 'active_support/core_ext/string/output_safety' - -module ActionView - # = Action View URL Helpers - module Helpers #:nodoc: - # Provides a set of methods for making links and getting URLs that - # depend on the routing subsystem (see ActionDispatch::Routing). - # This allows you to use the same format for links in views - # and controllers. - module UrlHelper - # This helper may be included in any class that includes the - # URL helpers of a routes (routes.url_helpers). Some methods - # provided here will only work in the context of a request - # (link_to_unless_current, for instance), which must be provided - # as a method called #request on the context. - - extend ActiveSupport::Concern - - include TagHelper - - module ClassMethods - def _url_for_modules - ActionView::RoutingUrlFor - end - end - - # Basic implementation of url_for to allow use helpers without routes existence - def url_for(options = nil) # :nodoc: - case options - when String - options - when :back - _back_url - else - raise ArgumentError, "arguments passed to url_for can't be handled. Please require " + - "routes or provide your own implementation" - end - end - - def _back_url # :nodoc: - referrer = controller.respond_to?(:request) && controller.request.env["HTTP_REFERER"] - referrer || 'javascript:history.back()' - end - protected :_back_url - - # Creates a link tag 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 - # 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 - # the value of the link itself will become the name. - # - # ==== Signatures - # - # link_to(body, url, html_options = {}) - # # url is a String; you can use URL helpers like - # # posts_path - # - # link_to(body, url_options = {}, html_options = {}) - # # url_options, except :method, is passed to url_for - # - # link_to(options = {}, html_options = {}) do - # # name - # end - # - # link_to(url, html_options = {}) do - # # name - # end - # - # ==== Options - # * <tt>:data</tt> - This option can be used to add custom data attributes. - # * <tt>method: symbol of HTTP verb</tt> - This modifier will dynamically - # create an HTML form and immediately submit the form for processing using - # the HTTP verb specified. Useful for having links perform a POST operation - # in dangerous actions like deleting a record (which search bots can follow - # while spidering your site). Supported verbs are <tt>:post</tt>, <tt>:delete</tt>, <tt>:patch</tt>, and <tt>:put</tt>. - # Note that if the user has JavaScript disabled, the request will fall back - # to using GET. If <tt>href: '#'</tt> is used and the user has JavaScript - # disabled clicking the link will have no effect. If you are relying on the - # POST behavior, you should check for it in your controller's action by using - # the request object's methods for <tt>post?</tt>, <tt>delete?</tt>, <tt>:patch</tt>, or <tt>put?</tt>. - # * <tt>remote: true</tt> - This will allow the unobtrusive JavaScript - # driver to make an Ajax request to the URL in question instead of following - # the link. The drivers each provide mechanisms for listening for the - # completion of the Ajax request and performing JavaScript operations once - # they're complete - # - # ==== Data attributes - # - # * <tt>confirm: 'question?'</tt> - This will allow the unobtrusive JavaScript - # driver to prompt with the question specified. If the user accepts, the link is - # processed normally, otherwise no action is taken. - # * <tt>:disable_with</tt> - Value of this parameter will be - # used as the value for a disabled version of the submit - # button when the form is submitted. This feature is provided - # by the unobtrusive JavaScript driver. - # - # ==== Examples - # Because it relies on +url_for+, +link_to+ supports both older-style controller/action/id arguments - # and newer RESTful routes. Current Rails style favors RESTful routes whenever possible, so base - # your application on resources and use - # - # link_to "Profile", profile_path(@profile) - # # => <a href="/profiles/1">Profile</a> - # - # or the even pithier - # - # link_to "Profile", @profile - # # => <a href="/profiles/1">Profile</a> - # - # in place of the older more verbose, non-resource-oriented - # - # link_to "Profile", controller: "profiles", action: "show", id: @profile - # # => <a href="/profiles/show/1">Profile</a> - # - # Similarly, - # - # link_to "Profiles", profiles_path - # # => <a href="/profiles">Profiles</a> - # - # is better than - # - # link_to "Profiles", controller: "profiles" - # # => <a href="/profiles">Profiles</a> - # - # You can use a block as well if your link target is hard to fit into the name parameter. ERB example: - # - # <%= link_to(@profile) do %> - # <strong><%= @profile.name %></strong> -- <span>Check it out!</span> - # <% end %> - # # => <a href="/profiles/1"> - # <strong>David</strong> -- <span>Check it out!</span> - # </a> - # - # Classes and ids for CSS are easy to produce: - # - # link_to "Articles", articles_path, id: "news", class: "article" - # # => <a href="/articles" class="article" id="news">Articles</a> - # - # Be careful when using the older argument style, as an extra literal hash is needed: - # - # link_to "Articles", { controller: "articles" }, id: "news", class: "article" - # # => <a href="/articles" class="article" id="news">Articles</a> - # - # Leaving the hash off gives the wrong link: - # - # link_to "WRONG!", controller: "articles", id: "news", class: "article" - # # => <a href="/articles/index/news?class=article">WRONG!</a> - # - # +link_to+ can also produce links with anchors or query strings: - # - # link_to "Comment wall", profile_path(@profile, anchor: "wall") - # # => <a href="/profiles/1#wall">Comment wall</a> - # - # link_to "Ruby on Rails search", controller: "searches", query: "ruby on rails" - # # => <a href="/searches?query=ruby+on+rails">Ruby on Rails search</a> - # - # link_to "Nonsense search", searches_path(foo: "bar", baz: "quux") - # # => <a href="/searches?foo=bar&baz=quux">Nonsense search</a> - # - # The only option specific to +link_to+ (<tt>:method</tt>) is used as follows: - # - # link_to("Destroy", "http://www.example.com", method: :delete) - # # => <a href='http://www.example.com' rel="nofollow" data-method="delete">Destroy</a> - # - # You can also use custom data attributes using the <tt>:data</tt> option: - # - # link_to "Visit Other Site", "http://www.rubyonrails.org/", data: { confirm: "Are you sure?" } - # # => <a href="http://www.rubyonrails.org/" data-confirm="Are you sure?">Visit Other Site</a> - def link_to(name = nil, options = nil, html_options = nil, &block) - html_options, options = options, name if block_given? - options ||= {} - - html_options = convert_options_to_data_attributes(options, html_options) - - url = url_for(options) - html_options['href'] ||= url - - content_tag(:a, name || url, html_options, &block) - end - - # Generates a form containing a single button that submits to the URL created - # by the set of +options+. This is the safest method to ensure links that - # cause changes to your data are not triggered by search bots or accelerators. - # If the HTML button does not work with your layout, you can also consider - # using the +link_to+ method with the <tt>:method</tt> modifier as described in - # the +link_to+ documentation. - # - # By default, the generated form element has a class name of <tt>button_to</tt> - # to allow styling of the form itself and its children. This can be changed - # using the <tt>:form_class</tt> modifier within +html_options+. You can control - # the form submission and input element behavior using +html_options+. - # This method accepts the <tt>:method</tt> modifier described in the +link_to+ documentation. - # If no <tt>:method</tt> modifier is given, it will default to performing a POST operation. - # You can also disable the button by passing <tt>disabled: true</tt> in +html_options+. - # If you are using RESTful routes, you can pass the <tt>:method</tt> - # to change the HTTP verb used to submit the form. - # - # ==== Options - # The +options+ hash accepts the same options as +url_for+. - # - # There are a few special +html_options+: - # * <tt>:method</tt> - Symbol of HTTP verb. Supported verbs are <tt>:post</tt>, <tt>:get</tt>, - # <tt>:delete</tt>, <tt>:patch</tt>, and <tt>:put</tt>. By default it will be <tt>:post</tt>. - # * <tt>:disabled</tt> - If set to true, it will generate a disabled button. - # * <tt>:data</tt> - This option can be used to add custom data attributes. - # * <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>:form</tt> - This hash will be form attributes - # * <tt>:form_class</tt> - This controls the class of the form within which the submit button will - # be placed - # - # ==== Data attributes - # - # * <tt>:confirm</tt> - This will use the unobtrusive JavaScript driver to - # prompt with the question specified. If the user accepts, the link is - # processed normally, otherwise no action is taken. - # * <tt>:disable_with</tt> - Value of this parameter will be - # used as the value for a disabled version of the submit - # button when the form is submitted. This feature is provided - # by the unobtrusive JavaScript driver. - # - # ==== Examples - # <%= button_to "New", action: "new" %> - # # => "<form method="post" action="/controller/new" class="button_to"> - # # <div><input value="New" type="submit" /></div> - # # </form>" - # - # <%= button_to [:make_happy, @user] do %> - # Make happy <strong><%= @user.name %></strong> - # <% end %> - # # => "<form method="post" action="/users/1/make_happy" class="button_to"> - # # <div> - # # <button type="submit"> - # # Make happy <strong><%= @user.name %></strong> - # # </button> - # # </div> - # # </form>" - # - # <%= button_to "New", { action: "new" }, form_class: "new-thing" %> - # # => "<form method="post" action="/controller/new" class="new-thing"> - # # <div><input value="New" type="submit" /></div> - # # </form>" - # - # - # <%= button_to "Create", { action: "create" }, remote: true, form: { "data-type" => "json" } %> - # # => "<form method="post" action="/images/create" class="button_to" data-remote="true" data-type="json"> - # # <div> - # # <input value="Create" type="submit" /> - # # <input name="authenticity_token" type="hidden" value="10f2163b45388899ad4d5ae948988266befcb6c3d1b2451cf657a0c293d605a6"/> - # # </div> - # # </form>" - # - # - # <%= button_to "Delete Image", { action: "delete", id: @image.id }, - # method: :delete, data: { confirm: "Are you sure?" } %> - # # => "<form method="post" action="/images/delete/1" class="button_to"> - # # <div> - # # <input type="hidden" name="_method" value="delete" /> - # # <input data-confirm='Are you sure?' value="Delete Image" type="submit" /> - # # <input name="authenticity_token" type="hidden" value="10f2163b45388899ad4d5ae948988266befcb6c3d1b2451cf657a0c293d605a6"/> - # # </div> - # # </form>" - # - # - # <%= button_to('Destroy', 'http://www.example.com', - # method: "delete", remote: true, data: { confirm: 'Are you sure?', disable_with: 'loading...' }) %> - # # => "<form class='button_to' method='post' action='http://www.example.com' data-remote='true'> - # # <div> - # # <input name='_method' value='delete' type='hidden' /> - # # <input value='Destroy' type='submit' data-disable-with='loading...' data-confirm='Are you sure?' /> - # # <input name="authenticity_token" type="hidden" value="10f2163b45388899ad4d5ae948988266befcb6c3d1b2451cf657a0c293d605a6"/> - # # </div> - # # </form>" - # # - def button_to(name = nil, options = nil, html_options = nil, &block) - html_options, options = options, name if block_given? - options ||= {} - html_options ||= {} - - html_options = html_options.stringify_keys - convert_boolean_attributes!(html_options, %w(disabled)) - - url = options.is_a?(String) ? options : url_for(options) - remote = html_options.delete('remote') - - method = html_options.delete('method').to_s - method_tag = %w{patch put delete}.include?(method) ? method_tag(method) : ''.html_safe - - form_method = method == 'get' ? 'get' : 'post' - form_options = html_options.delete('form') || {} - form_options[:class] ||= html_options.delete('form_class') || 'button_to' - form_options.merge!(method: form_method, action: url) - form_options.merge!("data-remote" => "true") if remote - - request_token_tag = form_method == 'post' ? token_tag : '' - - html_options = convert_options_to_data_attributes(options, html_options) - html_options['type'] = 'submit' - - button = if block_given? - content_tag('button', html_options, &block) - else - html_options['value'] = name || url - tag('input', html_options) - end - - inner_tags = method_tag.safe_concat(button).safe_concat(request_token_tag) - content_tag('form', content_tag('div', inner_tags), form_options) - end - - # Creates a link tag of the given +name+ using a URL created by the set of - # +options+ unless the current request URI is the same as the links, in - # which case only the name is returned (or the given block is yielded, if - # one exists). You can give +link_to_unless_current+ a block which will - # specialize the default behavior (e.g., show a "Start Here" link rather - # than the link's text). - # - # ==== Examples - # Let's say you have a navigation menu... - # - # <ul id="navbar"> - # <li><%= link_to_unless_current("Home", { action: "index" }) %></li> - # <li><%= link_to_unless_current("About Us", { action: "about" }) %></li> - # </ul> - # - # If in the "about" action, it will render... - # - # <ul id="navbar"> - # <li><a href="/controller/index">Home</a></li> - # <li>About Us</li> - # </ul> - # - # ...but if in the "index" action, it will render: - # - # <ul id="navbar"> - # <li>Home</li> - # <li><a href="/controller/about">About Us</a></li> - # </ul> - # - # The implicit block given to +link_to_unless_current+ is evaluated if the current - # action is the action given. So, if we had a comments page and wanted to render a - # "Go Back" link instead of a link to the comments page, we could do something like this... - # - # <%= - # link_to_unless_current("Comment", { controller: "comments", action: "new" }) do - # link_to("Go back", { controller: "posts", action: "index" }) - # end - # %> - def link_to_unless_current(name, options = {}, html_options = {}, &block) - link_to_unless current_page?(options), name, options, html_options, &block - end - - # Creates a link tag of the given +name+ using a URL created by the set of - # +options+ unless +condition+ is true, in which case only the name is - # returned. To specialize the default behavior (i.e., show a login link rather - # than just the plaintext link text), you can pass a block that - # accepts the name or the full argument list for +link_to_unless+. - # - # ==== Examples - # <%= link_to_unless(@current_user.nil?, "Reply", { action: "reply" }) %> - # # If the user is logged in... - # # => <a href="/controller/reply/">Reply</a> - # - # <%= - # link_to_unless(@current_user.nil?, "Reply", { action: "reply" }) do |name| - # link_to(name, { controller: "accounts", action: "signup" }) - # end - # %> - # # If the user is logged in... - # # => <a href="/controller/reply/">Reply</a> - # # If not... - # # => <a href="/accounts/signup">Reply</a> - def link_to_unless(condition, name, options = {}, html_options = {}, &block) - if condition - if block_given? - block.arity <= 1 ? capture(name, &block) : capture(name, options, html_options, &block) - else - name - end - else - link_to(name, options, html_options) - end - end - - # Creates a link tag of the given +name+ using a URL created by the set of - # +options+ if +condition+ is true, otherwise only the name is - # returned. To specialize the default behavior, you can pass a block that - # accepts the name or the full argument list for +link_to_unless+ (see the examples - # in +link_to_unless+). - # - # ==== Examples - # <%= link_to_if(@current_user.nil?, "Login", { controller: "sessions", action: "new" }) %> - # # If the user isn't logged in... - # # => <a href="/sessions/new/">Login</a> - # - # <%= - # link_to_if(@current_user.nil?, "Login", { controller: "sessions", action: "new" }) do - # link_to(@current_user.login, { controller: "accounts", action: "show", id: @current_user }) - # end - # %> - # # If the user isn't logged in... - # # => <a href="/sessions/new/">Login</a> - # # If they are logged in... - # # => <a href="/accounts/show/3">my_username</a> - def link_to_if(condition, name, options = {}, html_options = {}, &block) - link_to_unless !condition, name, options, html_options, &block - end - - # Creates a mailto link tag to the specified +email_address+, which is - # also used as the name of the link unless +name+ is specified. Additional - # HTML attributes for the link can be passed in +html_options+. - # - # +mail_to+ has several methods for customizing the email itself by - # passing special keys to +html_options+. - # - # ==== Options - # * <tt>:subject</tt> - Preset the subject line of the email. - # * <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. - # - # ==== Obfuscation - # Prior to Rails 4.0, +mail_to+ provided options for encoding the address - # in order to hinder email harvesters. To take advantage of these options, - # install the +actionview-encoded_mail_to+ gem. - # - # ==== Examples - # mail_to "me@domain.com" - # # => <a href="mailto:me@domain.com">me@domain.com</a> - # - # mail_to "me@domain.com", "My email" - # # => <a href="mailto:me@domain.com">My email</a> - # - # mail_to "me@domain.com", "My email", cc: "ccaddress@domain.com", - # subject: "This is an example email" - # # => <a href="mailto:me@domain.com?cc=ccaddress@domain.com&subject=This%20is%20an%20example%20email">My email</a> - def mail_to(email_address, name = nil, html_options = {}) - email_address = ERB::Util.html_escape(email_address) - - html_options.stringify_keys! - - extras = %w{ cc bcc body subject }.map { |item| - option = html_options.delete(item) || next - "#{item}=#{Rack::Utils.escape_path(option)}" - }.compact - extras = extras.empty? ? '' : '?' + ERB::Util.html_escape(extras.join('&')) - - content_tag "a", name || email_address.html_safe, html_options.merge("href" => "mailto:#{email_address}#{extras}".html_safe) - end - - # True if the current request URI was generated by the given +options+. - # - # ==== Examples - # Let's say we're in the <tt>http://www.example.com/shop/checkout?order=desc</tt> action. - # - # current_page?(action: 'process') - # # => false - # - # current_page?(controller: 'shop', action: 'checkout') - # # => true - # - # current_page?(controller: 'shop', action: 'checkout', order: 'asc') - # # => false - # - # current_page?(action: 'checkout') - # # => true - # - # current_page?(controller: 'library', action: 'checkout') - # # => false - # - # current_page?('http://www.example.com/shop/checkout') - # # => true - # - # current_page?('/shop/checkout') - # # => true - # - # Let's say we're in the <tt>http://www.example.com/shop/checkout?order=desc&page=1</tt> action. - # - # current_page?(action: 'process') - # # => false - # - # current_page?(controller: 'shop', action: 'checkout') - # # => true - # - # current_page?(controller: 'shop', action: 'checkout', order: 'desc', page: '1') - # # => true - # - # current_page?(controller: 'shop', action: 'checkout', order: 'desc', page: '2') - # # => false - # - # current_page?(controller: 'shop', action: 'checkout', order: 'desc') - # # => false - # - # current_page?(action: 'checkout') - # # => true - # - # current_page?(controller: 'library', action: 'checkout') - # # => false - # - # Let's say we're in the <tt>http://www.example.com/products</tt> action with method POST in case of invalid product. - # - # current_page?(controller: 'product', action: 'index') - # # => false - # - def current_page?(options) - unless request - raise "You cannot use helpers that need to determine the current " \ - "page unless your view context provides a Request object " \ - "in a #request method" - end - - return false unless request.get? || request.head? - - url_string = url_for(options) - - # We ignore any extra parameters in the request_uri if the - # submitted url doesn't have any either. This lets the function - # work with things like ?order=asc - request_uri = url_string.index("?") ? request.fullpath : request.path - - if url_string =~ /^\w+:\/\// - url_string == "#{request.protocol}#{request.host_with_port}#{request_uri}" - else - url_string == request_uri - end - end - - private - def convert_options_to_data_attributes(options, html_options) - if html_options - html_options = html_options.stringify_keys - html_options['data-remote'] = 'true' if link_to_remote_options?(options) || link_to_remote_options?(html_options) - - disable_with = html_options.delete("disable_with") - confirm = html_options.delete('confirm') - method = html_options.delete('method') - - if confirm - message = ":confirm option is deprecated and will be removed from Rails 4.1. " \ - "Use 'data: { confirm: \'Text\' }' instead." - ActiveSupport::Deprecation.warn message - - html_options["data-confirm"] = confirm - end - - add_method_to_attributes!(html_options, method) if method - - if disable_with - message = ":disable_with option is deprecated and will be removed from Rails 4.1. " \ - "Use 'data: { disable_with: \'Text\' }' instead." - ActiveSupport::Deprecation.warn message - - html_options["data-disable-with"] = disable_with - end - - html_options - else - link_to_remote_options?(options) ? {'data-remote' => 'true'} : {} - end - end - - def link_to_remote_options?(options) - if options.is_a?(Hash) - options.delete('remote') || options.delete(:remote) - end - end - - def add_method_to_attributes!(html_options, method) - if method && method.to_s.downcase != "get" && html_options["rel"] !~ /nofollow/ - html_options["rel"] = "#{html_options["rel"]} nofollow".lstrip - end - html_options["data-method"] = method - end - - # Processes the +html_options+ hash, converting the boolean - # attributes from true/false form into the form required by - # HTML/XHTML. (An attribute is considered to be boolean if - # its name is listed in the given +bool_attrs+ array.) - # - # More specifically, for each boolean attribute in +html_options+ - # given as: - # - # "attr" => bool_value - # - # if the associated +bool_value+ evaluates to true, it is - # replaced with the attribute's name; otherwise the attribute is - # removed from the +html_options+ hash. (See the XHTML 1.0 spec, - # section 4.5 "Attribute Minimization" for more: - # http://www.w3.org/TR/xhtml1/#h-4.5) - # - # Returns the updated +html_options+ hash, which is also modified - # in place. - # - # Example: - # - # convert_boolean_attributes!( html_options, - # %w( checked disabled readonly ) ) - def convert_boolean_attributes!(html_options, bool_attrs) - bool_attrs.each { |x| html_options[x] = x if html_options.delete(x) } - html_options - end - - def token_tag(token=nil) - if token != false && protect_against_forgery? - token ||= form_authenticity_token - tag(:input, type: "hidden", name: request_forgery_protection_token.to_s, value: token) - else - '' - end - end - - def method_tag(method) - tag('input', type: 'hidden', name: '_method', value: method.to_s) - end - end - end -end diff --git a/actionpack/lib/action_view/locale/en.yml b/actionpack/lib/action_view/locale/en.yml deleted file mode 100644 index 8a56f147b8..0000000000 --- a/actionpack/lib/action_view/locale/en.yml +++ /dev/null @@ -1,56 +0,0 @@ -"en": - # Used in distance_of_time_in_words(), distance_of_time_in_words_to_now(), time_ago_in_words() - datetime: - distance_in_words: - half_a_minute: "half a minute" - less_than_x_seconds: - one: "less than 1 second" - other: "less than %{count} seconds" - x_seconds: - one: "1 second" - other: "%{count} seconds" - less_than_x_minutes: - one: "less than a minute" - other: "less than %{count} minutes" - x_minutes: - one: "1 minute" - other: "%{count} minutes" - about_x_hours: - one: "about 1 hour" - other: "about %{count} hours" - x_days: - one: "1 day" - other: "%{count} days" - about_x_months: - one: "about 1 month" - other: "about %{count} months" - x_months: - one: "1 month" - other: "%{count} months" - about_x_years: - one: "about 1 year" - other: "about %{count} years" - over_x_years: - one: "over 1 year" - other: "over %{count} years" - almost_x_years: - one: "almost 1 year" - other: "almost %{count} years" - prompts: - year: "Year" - month: "Month" - day: "Day" - hour: "Hour" - minute: "Minute" - second: "Seconds" - - helpers: - select: - # Default value for :prompt => true in FormOptionsHelper - prompt: "Please select" - - # Default translation keys for submit and button FormHelper - submit: - create: 'Create %{model}' - update: 'Update %{model}' - submit: 'Save %{model}' diff --git a/actionpack/lib/action_view/log_subscriber.rb b/actionpack/lib/action_view/log_subscriber.rb deleted file mode 100644 index fd9a543e0a..0000000000 --- a/actionpack/lib/action_view/log_subscriber.rb +++ /dev/null @@ -1,30 +0,0 @@ -module ActionView - # = Action View Log Subscriber - # - # Provides functionality so that Rails can output logs from Action View. - class LogSubscriber < ActiveSupport::LogSubscriber - VIEWS_PATTERN = /^app\/views\//.freeze - - def render_template(event) - return unless logger.info? - message = " Rendered #{from_rails_root(event.payload[:identifier])}" - message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout] - message << " (#{event.duration.round(1)}ms)" - info(message) - end - alias :render_partial :render_template - alias :render_collection :render_template - - def logger - ActionView::Base.logger - end - - protected - - def from_rails_root(string) - string.sub("#{Rails.root}/", "").sub(VIEWS_PATTERN, "") - end - end -end - -ActionView::LogSubscriber.attach_to :action_view diff --git a/actionpack/lib/action_view/lookup_context.rb b/actionpack/lib/action_view/lookup_context.rb deleted file mode 100644 index 4e4816d983..0000000000 --- a/actionpack/lib/action_view/lookup_context.rb +++ /dev/null @@ -1,234 +0,0 @@ -require 'thread_safe' -require 'active_support/core_ext/module/remove_method' - -module ActionView - # = Action View Lookup Context - # - # LookupContext is the object responsible to hold all information required to lookup - # templates, i.e. view paths and details. The LookupContext is also responsible to - # generate a key, given to view paths, used in the resolver cache lookup. Since - # this key is generated just once during the request, it speeds up all cache accesses. - class LookupContext #:nodoc: - attr_accessor :prefixes, :rendered_format - - mattr_accessor :fallbacks - @@fallbacks = FallbackFileSystemResolver.instances - - mattr_accessor :registered_details - self.registered_details = [] - - def self.register_detail(name, options = {}, &block) - self.registered_details << name - initialize = registered_details.map { |n| "@details[:#{n}] = details[:#{n}] || default_#{n}" } - - Accessors.send :define_method, :"default_#{name}", &block - Accessors.module_eval <<-METHOD, __FILE__, __LINE__ + 1 - def #{name} - @details.fetch(:#{name}, []) - end - - def #{name}=(value) - value = value.present? ? Array(value) : default_#{name} - _set_detail(:#{name}, value) if value != @details[:#{name}] - end - - remove_possible_method :initialize_details - def initialize_details(details) - #{initialize.join("\n")} - end - METHOD - end - - # Holds accessors for the registered details. - module Accessors #:nodoc: - end - - register_detail(:locale) { [I18n.locale, I18n.default_locale].uniq } - register_detail(:formats) { ActionView::Base.default_formats || [:html, :text, :js, :css, :xml, :json] } - register_detail(:handlers){ Template::Handlers.extensions } - - class DetailsKey #:nodoc: - alias :eql? :equal? - alias :object_hash :hash - - attr_reader :hash - @details_keys = ThreadSafe::Cache.new - - def self.get(details) - @details_keys[details] ||= new - end - - def self.clear - @details_keys.clear - end - - def initialize - @hash = object_hash - end - end - - # Add caching behavior on top of Details. - module DetailsCache - attr_accessor :cache - - # Calculate the details key. Remove the handlers from calculation to improve performance - # since the user cannot modify it explicitly. - def details_key #:nodoc: - @details_key ||= DetailsKey.get(@details) if @cache - end - - # Temporary skip passing the details_key forward. - def disable_cache - old_value, @cache = @cache, false - yield - ensure - @cache = old_value - end - - protected - - def _set_detail(key, value) - @details = @details.dup if @details_key - @details_key = nil - @details[key] = value - end - end - - # Helpers related to template lookup using the lookup context information. - module ViewPaths - attr_reader :view_paths, :html_fallback_for_js - - # Whenever setting view paths, makes a copy so we can manipulate then in - # instance objects as we wish. - def view_paths=(paths) - @view_paths = ActionView::PathSet.new(Array(paths)) - end - - def find(name, prefixes = [], partial = false, keys = [], options = {}) - @view_paths.find(*args_for_lookup(name, prefixes, partial, keys, options)) - end - alias :find_template :find - - def find_all(name, prefixes = [], partial = false, keys = [], options = {}) - @view_paths.find_all(*args_for_lookup(name, prefixes, partial, keys, options)) - end - - def exists?(name, prefixes = [], partial = false, keys = [], options = {}) - @view_paths.exists?(*args_for_lookup(name, prefixes, partial, keys, options)) - end - alias :template_exists? :exists? - - # Add fallbacks to the view paths. Useful in cases you are rendering a :file. - def with_fallbacks - added_resolvers = 0 - self.class.fallbacks.each do |resolver| - next if view_paths.include?(resolver) - view_paths.push(resolver) - added_resolvers += 1 - end - yield - ensure - added_resolvers.times { view_paths.pop } - end - - protected - - def args_for_lookup(name, prefixes, partial, keys, details_options) #:nodoc: - name, prefixes = normalize_name(name, prefixes) - details, details_key = detail_args_for(details_options) - [name, prefixes, partial || false, details, details_key, keys] - end - - # Compute details hash and key according to user options (e.g. passed from #render). - def detail_args_for(options) - return @details, details_key if options.empty? # most common path. - user_details = @details.merge(options) - [user_details, DetailsKey.get(user_details)] - end - - # Support legacy foo.erb names even though we now ignore .erb - # as well as incorrectly putting part of the path in the template - # name instead of the prefix. - def normalize_name(name, prefixes) #:nodoc: - prefixes = prefixes.presence - parts = name.to_s.split('/') - parts.shift if parts.first.empty? - name = parts.pop - - return name, prefixes || [""] if parts.empty? - - parts = parts.join('/') - prefixes = prefixes ? prefixes.map { |p| "#{p}/#{parts}" } : [parts] - - return name, prefixes - end - end - - include Accessors - include DetailsCache - include ViewPaths - - def initialize(view_paths, details = {}, prefixes = []) - @details, @details_key = {}, nil - @skip_default_locale = false - @cache = true - @prefixes = prefixes - @rendered_format = nil - - self.view_paths = view_paths - initialize_details(details) - end - - # Override formats= to expand ["*/*"] values and automatically - # add :html as fallback to :js. - def formats=(values) - if values - values.concat(default_formats) if values.delete "*/*" - if values == [:js] - values << :html - @html_fallback_for_js = true - end - end - 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 - end - - # Overload locale= to also set the I18n.locale. If the current I18n.config object responds - # to original_config, it means that it's has a copy of the original I18n configuration and it's - # acting as proxy, which we need to skip. - def locale=(value) - if value - config = I18n.config.respond_to?(:original_config) ? I18n.config.original_config : I18n.config - config.locale = value - end - - super(@skip_default_locale ? I18n.locale : default_locale) - end - - # A method which only uses the first format in the formats array for layout lookup. - def with_layout_format - if formats.size == 1 - yield - else - old_formats = formats - _set_detail(:formats, formats[0,1]) - - begin - yield - ensure - _set_detail(:formats, old_formats) - end - end - end - end -end diff --git a/actionpack/lib/action_view/model_naming.rb b/actionpack/lib/action_view/model_naming.rb deleted file mode 100644 index e09ebd60df..0000000000 --- a/actionpack/lib/action_view/model_naming.rb +++ /dev/null @@ -1,12 +0,0 @@ -module ActionView - module ModelNaming - # Converts the given object to an ActiveModel compliant one. - def convert_to_model(object) - object.respond_to?(:to_model) ? object.to_model : object - end - - def model_name_from_record_or_class(record_or_class) - (record_or_class.is_a?(Class) ? record_or_class : convert_to_model(record_or_class).class).model_name - end - end -end diff --git a/actionpack/lib/action_view/path_set.rb b/actionpack/lib/action_view/path_set.rb deleted file mode 100644 index d9c76366f8..0000000000 --- a/actionpack/lib/action_view/path_set.rb +++ /dev/null @@ -1,71 +0,0 @@ -module ActionView #:nodoc: - # = Action View PathSet - class PathSet #:nodoc: - include Enumerable - - attr_reader :paths - - delegate :[], :include?, :pop, :size, :each, to: :paths - - def initialize(paths = []) - @paths = typecast paths - end - - def initialize_copy(other) - @paths = other.paths.dup - self - end - - def to_ary - paths.dup - end - - def compact - PathSet.new paths.compact - end - - def +(array) - PathSet.new(paths + array) - end - - %w(<< concat push insert unshift).each do |method| - class_eval <<-METHOD, __FILE__, __LINE__ + 1 - def #{method}(*args) - paths.#{method}(*typecast(args)) - end - METHOD - end - - def find(*args) - find_all(*args).first || raise(MissingTemplate.new(self, *args)) - end - - def find_all(path, prefixes = [], *args) - prefixes = [prefixes] if String === prefixes - prefixes.each do |prefix| - paths.each do |resolver| - templates = resolver.find_all(path, prefix, *args) - return templates unless templates.empty? - end - end - [] - end - - def exists?(path, prefixes, *args) - find_all(path, prefixes, *args).any? - end - - private - - def typecast(paths) - paths.map do |path| - case path - when Pathname, String - OptimizedFileSystemResolver.new path.to_s - else - path - end - end - end - end -end diff --git a/actionpack/lib/action_view/railtie.rb b/actionpack/lib/action_view/railtie.rb deleted file mode 100644 index e80e0ed9b0..0000000000 --- a/actionpack/lib/action_view/railtie.rb +++ /dev/null @@ -1,39 +0,0 @@ -require "action_view" -require "rails" - -module ActionView - # = Action View Railtie - class Railtie < Rails::Railtie # :nodoc: - config.action_view = ActiveSupport::OrderedOptions.new - config.action_view.embed_authenticity_token_in_remote_forms = false - - config.eager_load_namespaces << ActionView - - initializer "action_view.embed_authenticity_token_in_remote_forms" do |app| - ActiveSupport.on_load(:action_view) do - ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms = - app.config.action_view.delete(:embed_authenticity_token_in_remote_forms) - end - end - - initializer "action_view.logger" do - ActiveSupport.on_load(:action_view) { self.logger ||= Rails.logger } - end - - initializer "action_view.set_configs" do |app| - ActiveSupport.on_load(:action_view) do - app.config.action_view.each do |k,v| - send "#{k}=", v - end - end - end - - initializer "action_view.caching" do |app| - ActiveSupport.on_load(:action_view) do - if app.config.action_view.cache_template_loading.nil? - ActionView::Resolver.caching = app.config.cache_classes - end - end - end - end -end diff --git a/actionpack/lib/action_view/record_identifier.rb b/actionpack/lib/action_view/record_identifier.rb deleted file mode 100644 index 63f645431a..0000000000 --- a/actionpack/lib/action_view/record_identifier.rb +++ /dev/null @@ -1,84 +0,0 @@ -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. - # - # # routes - # resources :posts - # - # # view - # <%= div_for(post) do %> <div id="post_45" class="post"> - # <%= post.body %> What a wonderful world! - # <% end %> </div> - # - # # controller - # def update - # post = Post.find(params[:id]) - # post.update(params[:post]) - # - # redirect_to(post) # Calls polymorphic_url(post) which in turn calls post_url(post) - # end - # - # 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. - module RecordIdentifier - extend self - extend ModelNaming - - include ModelNaming - - JOIN = '_'.freeze - NEW = 'new'.freeze - - # The DOM class convention is to use the singular form of an object or class. - # - # dom_class(post) # => "post" - # dom_class(Person) # => "person" - # - # If you need to address multiple instances of the same class in the same view, you can prefix the dom_class: - # - # dom_class(post, :edit) # => "edit_post" - # dom_class(Person, :edit) # => "edit_person" - def dom_class(record_or_class, prefix = nil) - singular = model_name_from_record_or_class(record_or_class).param_key - prefix ? "#{prefix}#{JOIN}#{singular}" : singular - end - - # The DOM id convention is to use the singular form of an object or class with the id following an underscore. - # If no id is found, prefix with "new_" instead. - # - # dom_id(Post.find(45)) # => "post_45" - # dom_id(Post.new) # => "new_post" - # - # If you need to address multiple instances of the same class in the same view, you can prefix the dom_id: - # - # dom_id(Post.find(45), :edit) # => "edit_post_45" - # dom_id(Post.new, :custom) # => "custom_post" - def dom_id(record, prefix = nil) - if record_id = record_key_for_dom_id(record) - "#{dom_class(record, prefix)}#{JOIN}#{record_id}" - else - dom_class(record, prefix || NEW) - end - end - - protected - - # Returns a string representation of the key attribute(s) that is suitable for use in an HTML DOM id. - # This can be overwritten to customize the default generated string representation if desired. - # If you need to read back a key from a dom_id in order to query for the underlying database record, - # you should write a helper like 'person_record_from_dom_id' that will extract the key either based - # on the default implementation (which just joins all key attributes with '_') or on your own - # overwritten version of the method. By default, this implementation passes the key string through a - # method that replaces all characters that are invalid inside DOM ids, with valid ones. You need to - # 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 - end - end -end diff --git a/actionpack/lib/action_view/renderer/abstract_renderer.rb b/actionpack/lib/action_view/renderer/abstract_renderer.rb deleted file mode 100644 index 6fb8cbb46c..0000000000 --- a/actionpack/lib/action_view/renderer/abstract_renderer.rb +++ /dev/null @@ -1,32 +0,0 @@ -module ActionView - class AbstractRenderer #:nodoc: - delegate :find_template, :template_exists?, :with_fallbacks, :with_layout_format, :formats, :to => :@lookup_context - - def initialize(lookup_context) - @lookup_context = lookup_context - end - - def render - raise NotImplementedError - end - - protected - - def extract_details(options) - @lookup_context.registered_details.each_with_object({}) do |key, details| - next unless value = options[key] - details[key] = Array(value) - end - end - - def instrument(name, options={}) - ActiveSupport::Notifications.instrument("render_#{name}.action_view", options){ yield } - end - - def prepend_formats(formats) - formats = Array(formats) - return if formats.empty? || @lookup_context.html_fallback_for_js - @lookup_context.formats = formats | @lookup_context.formats - end - end -end diff --git a/actionpack/lib/action_view/renderer/partial_renderer.rb b/actionpack/lib/action_view/renderer/partial_renderer.rb deleted file mode 100644 index 43a88b0623..0000000000 --- a/actionpack/lib/action_view/renderer/partial_renderer.rb +++ /dev/null @@ -1,478 +0,0 @@ -require 'thread_safe' - -module ActionView - # = Action View Partials - # - # There's also a convenience method for rendering sub templates within the current controller that depends on a - # single object (we call this kind of sub templates for partials). It relies on the fact that partials should - # follow the naming convention of being prefixed with an underscore -- as to separate them from regular - # templates that could be rendered on their own. - # - # In a template for Advertiser#account: - # - # <%= render partial: "account" %> - # - # This would render "advertiser/_account.html.erb". - # - # In another template for Advertiser#buy, we could have: - # - # <%= render partial: "account", locals: { account: @buyer } %> - # - # <% @advertisements.each do |ad| %> - # <%= render partial: "ad", locals: { ad: ad } %> - # <% end %> - # - # This would first render "advertiser/_account.html.erb" with @buyer passed in as the local variable +account+, then - # render "advertiser/_ad.html.erb" and pass the local variable +ad+ to the template for display. - # - # == The :as and :object options - # - # By default <tt>ActionView::PartialRenderer</tt> doesn't have any local variables. - # The <tt>:object</tt> option can be used to pass an object to the partial. For instance: - # - # <%= render partial: "account", object: @buyer %> - # - # would provide the <tt>@buyer</tt> object to the partial, available under the local variable +account+ and is - # equivalent to: - # - # <%= render partial: "account", locals: { account: @buyer } %> - # - # With the <tt>:as</tt> option we can specify a different name for said local variable. For example, if we - # wanted it to be +user+ instead of +account+ we'd do: - # - # <%= render partial: "account", object: @buyer, as: 'user' %> - # - # This is equivalent to - # - # <%= render partial: "account", locals: { user: @buyer } %> - # - # == 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 - # accepts an array and renders a partial by the same name as the elements contained within. So the three-lined - # example in "Using partials" can be rewritten with a single line: - # - # <%= render partial: "ad", collection: @advertisements %> - # - # This will render "advertiser/_ad.html.erb" and pass the local variable +ad+ to the template for display. An - # iteration counter will automatically be made available to the template with a name of the form - # +partial_name_counter+. In the case of the example above, the template would be fed +ad_counter+. - # - # The <tt>:as</tt> option may be used when rendering partials. - # - # You can specify a partial to be rendered between elements via the <tt>:spacer_template</tt> option. - # The following example will render <tt>advertiser/_ad_divider.html.erb</tt> between each ad partial: - # - # <%= render partial: "ad", collection: @advertisements, spacer_template: "ad_divider" %> - # - # If the given <tt>:collection</tt> is nil or empty, <tt>render</tt> will return nil. This will allow you - # to specify a text which will displayed instead by using this form: - # - # <%= render(partial: "ad", collection: @advertisements) || "There's no ad to be displayed" %> - # - # 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 - # - # Two controllers can share a set of partials and render them like this: - # - # <%= render partial: "advertisement/ad", locals: { ad: @advertisement } %> - # - # 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` - # - # 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. - # - # # @account.to_partial_path returns 'accounts/account', so it can be used to replace: - # # <%= render partial: "accounts/account", locals: { account: @account} %> - # <%= render partial: @account %> - # - # # @posts is an array of Post instances, so every post record returns 'posts/post' on `to_partial_path`, - # # that's why we can replace: - # # <%= render partial: "posts/post", collection: @posts %> - # <%= render partial: @posts %> - # - # == 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: - # - # # Instead of <%= render partial: "account" %> - # <%= render "account" %> - # - # # Instead of <%= render partial: "account", locals: { account: @buyer } %> - # <%= render "account", account: @buyer %> - # - # # @account.to_partial_path returns 'accounts/account', so it can be used to replace: - # # <%= render partial: "accounts/account", locals: { account: @account} %> - # <%= render @account %> - # - # # @posts is an array of Post instances, so every post record returns 'posts/post' on `to_partial_path`, - # # that's why we can replace: - # # <%= render partial: "posts/post", collection: @posts %> - # <%= render @posts %> - # - # == 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 - # of users: - # - # <%# app/views/users/index.html.erb &> - # Here's the administrator: - # <%= render partial: "user", layout: "administrator", locals: { user: administrator } %> - # - # Here's the editor: - # <%= render partial: "user", layout: "editor", locals: { user: editor } %> - # - # <%# app/views/users/_user.html.erb &> - # Name: <%= user.name %> - # - # <%# app/views/users/_administrator.html.erb &> - # <div id="administrator"> - # Budget: $<%= user.budget %> - # <%= yield %> - # </div> - # - # <%# app/views/users/_editor.html.erb &> - # <div id="editor"> - # Deadline: <%= user.deadline %> - # <%= yield %> - # </div> - # - # ...this will return: - # - # Here's the administrator: - # <div id="administrator"> - # Budget: $<%= user.budget %> - # Name: <%= user.name %> - # </div> - # - # Here's the editor: - # <div id="editor"> - # Deadline: <%= user.deadline %> - # Name: <%= user.name %> - # </div> - # - # If a collection is given, the layout will be rendered once for each item in - # the collection. Just think these two snippets have the same output: - # - # <%# app/views/users/_user.html.erb %> - # Name: <%= user.name %> - # - # <%# app/views/users/index.html.erb %> - # <%# This does not use layouts %> - # <ul> - # <% users.each do |user| -%> - # <li> - # <%= render partial: "user", locals: { user: user } %> - # </li> - # <% end -%> - # </ul> - # - # <%# app/views/users/_li_layout.html.erb %> - # <li> - # <%= yield %> - # </li> - # - # <%# app/views/users/index.html.erb %> - # <ul> - # <%= render partial: "user", layout: "li_layout", collection: users %> - # </ul> - # - # Given two users whose names are Alice and Bob, these snippets return: - # - # <ul> - # <li> - # Name: Alice - # </li> - # <li> - # Name: Bob - # </li> - # </ul> - # - # The current object being rendered, as well as the object_counter, will be - # available as local variables inside the layout template under the same names - # as available in the partial. - # - # You can also apply a layout to a block within any template: - # - # <%# app/views/users/_chief.html.erb &> - # <%= render(layout: "administrator", locals: { user: chief }) do %> - # Title: <%= chief.title %> - # <% end %> - # - # ...this will return: - # - # <div id="administrator"> - # Budget: $<%= user.budget %> - # Title: <%= chief.name %> - # </div> - # - # As you can see, the <tt>:locals</tt> hash is shared between both the partial and its layout. - # - # If you pass arguments to "yield" then this will be passed to the block. One way to use this is to pass - # an array to layout and treat it as an enumerable. - # - # <%# app/views/users/_user.html.erb &> - # <div class="user"> - # Budget: $<%= user.budget %> - # <%= yield user %> - # </div> - # - # <%# app/views/users/index.html.erb &> - # <%= render layout: @users do |user| %> - # Title: <%= user.title %> - # <% end %> - # - # This will render the layout for each user and yield to the block, passing the user, each time. - # - # You can also yield multiple times in one layout and use block arguments to differentiate the sections. - # - # <%# app/views/users/_user.html.erb &> - # <div class="user"> - # <%= yield user, :header %> - # Budget: $<%= user.budget %> - # <%= yield user, :footer %> - # </div> - # - # <%# app/views/users/index.html.erb &> - # <%= render layout: @users do |user, section| %> - # <%- case section when :header -%> - # Title: <%= user.title %> - # <%- when :footer -%> - # Deadline: <%= user.deadline %> - # <%- end -%> - # <% end %> - class PartialRenderer < AbstractRenderer - PREFIXED_PARTIAL_NAMES = ThreadSafe::Cache.new do |h, k| - h[k] = ThreadSafe::Cache.new - end - - def initialize(*) - super - @context_prefix = @lookup_context.prefixes.first - end - - def render(context, options, block) - setup(context, options, block) - identifier = (@template = find_partial) ? @template.identifier : @path - - @lookup_context.rendered_format ||= begin - if @template && @template.formats.present? - @template.formats.first - else - formats.first - end - end - - if @collection - instrument(:collection, :identifier => identifier || "collection", :count => @collection.size) do - render_collection - end - else - instrument(:partial, :identifier => identifier) do - render_partial - end - end - end - - def render_collection - return nil if @collection.blank? - - if @options.key?(:spacer_template) - spacer = find_template(@options[:spacer_template], @locals.keys).render(@view, @locals) - end - - result = @template ? collection_with_template : collection_without_template - result.join(spacer).html_safe - end - - def render_partial - view, locals, block = @view, @locals, @block - object, as = @object, @variable - - if !block && (layout = @options[:layout]) - layout = find_template(layout.to_s, @template_keys) - end - - object ||= locals[as] - locals[as] = object - - content = @template.render(view, locals) do |*name| - view._layout_for(*name, &block) - end - - content = layout.render(view, locals){ content } if layout - content - end - - private - - def setup(context, options, block) - @view = context - partial = options[:partial] - - @options = options - @locals = options[:locals] || {} - @block = block - @details = extract_details(options) - - prepend_formats(options[:formats]) - - if String === partial - @object = options[:object] - @path = partial - @collection = collection - else - @object = partial - - if @collection = collection_from_object || collection - paths = @collection_data = @collection.map { |o| partial_path(o) } - @path = paths.uniq.size == 1 ? paths.first : nil - else - @path = partial_path - end - end - - if as = options[:as] - raise_invalid_identifier(as) unless as.to_s =~ /\A[a-z_]\w*\z/ - as = as.to_sym - end - - if @path - @variable, @variable_counter = retrieve_variable(@path, as) - @template_keys = retrieve_template_keys - else - paths.map! { |path| retrieve_variable(path, as).unshift(path) } - end - - self - end - - def collection - if @options.key?(:collection) - collection = @options[:collection] - collection.respond_to?(:to_ary) ? collection.to_ary : [] - end - end - - def collection_from_object - @object.to_ary if @object.respond_to?(:to_ary) - end - - def find_partial - if path = @path - find_template(path, @template_keys) - end - end - - def find_template(path, locals) - prefixes = path.include?(?/) ? [] : @lookup_context.prefixes - @lookup_context.find_template(path, prefixes, true, locals, @details) - end - - def collection_with_template - view, locals, template = @view, @locals, @template - as, counter = @variable, @variable_counter - - if layout = @options[:layout] - layout = find_template(layout, @template_keys) - end - - index = -1 - @collection.map do |object| - locals[as] = object - locals[counter] = (index += 1) - - content = template.render(view, locals) - content = layout.render(view, locals) { content } if layout - content - end - end - - def collection_without_template - view, locals, collection_data = @view, @locals, @collection_data - cache = {} - keys = @locals.keys - - index = -1 - @collection.map do |object| - index += 1 - path, as, counter = collection_data[index] - - locals[as] = object - locals[counter] = index - - template = (cache[path] ||= find_template(path, keys + [as, counter])) - template.render(view, locals) - end - end - - def partial_path(object = @object) - object = object.to_model if object.respond_to?(:to_model) - - path = if object.respond_to?(:to_partial_path) - object.to_partial_path - else - raise ArgumentError.new("'#{object.inspect}' is not an ActiveModel-compatible object. It must implement :to_partial_path.") - end - - if @view.prefix_partial_path_with_controller_namespace - prefixed_partial_names[path] ||= merge_prefix_into_object_path(@context_prefix, path.dup) - else - path - end - end - - def prefixed_partial_names - @prefixed_partial_names ||= PREFIXED_PARTIAL_NAMES[@context_prefix] - end - - def merge_prefix_into_object_path(prefix, object_path) - if prefix.include?(?/) && object_path.include?(?/) - prefixes = [] - prefix_array = File.dirname(prefix).split('/') - object_path_array = object_path.split('/')[0..-3] # skip model dir & partial - - prefix_array.each_with_index do |dir, index| - break if dir == object_path_array[index] - prefixes << dir - end - - (prefixes << object_path).join("/") - else - object_path - end - end - - def retrieve_template_keys - keys = @locals.keys - keys << @variable if @object || @collection - keys << @variable_counter if @collection - keys - end - - 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/ - $1.to_sym - end - variable_counter = :"#{variable}_counter" if @collection - [variable, variable_counter] - 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, " + - "and is followed by any combination of letters, numbers and underscores." - - def raise_invalid_identifier(path) - raise ArgumentError.new(IDENTIFIER_ERROR_MESSAGE % (path)) - end - end -end diff --git a/actionpack/lib/action_view/renderer/renderer.rb b/actionpack/lib/action_view/renderer/renderer.rb deleted file mode 100644 index 30a0c4be70..0000000000 --- a/actionpack/lib/action_view/renderer/renderer.rb +++ /dev/null @@ -1,44 +0,0 @@ -module ActionView - # This is the main entry point for rendering. It basically delegates - # to other objects like TemplateRenderer and PartialRenderer which - # actually renders the template. - class Renderer - attr_accessor :lookup_context - - def initialize(lookup_context) - @lookup_context = lookup_context - end - - # Main render entry point shared by AV and AC. - def render(context, options) - if options.key?(:partial) - render_partial(context, options) - else - render_template(context, options) - end - end - - # Render but returns a valid Rack body. If fibers are defined, we return - # a streaming body that renders the template piece by piece. - # - # Note that partials are not supported to be rendered with streaming, - # so in such cases, we just wrap them in an array. - def render_body(context, options) - if options.key?(:partial) - [render_partial(context, options)] - else - StreamingTemplateRenderer.new(@lookup_context).render(context, options) - end - end - - # Direct accessor to template rendering. - def render_template(context, options) #:nodoc: - TemplateRenderer.new(@lookup_context).render(context, options) - end - - # Direct access to partial rendering. - def render_partial(context, options, &block) #:nodoc: - PartialRenderer.new(@lookup_context).render(context, options, block) - end - end -end diff --git a/actionpack/lib/action_view/renderer/streaming_template_renderer.rb b/actionpack/lib/action_view/renderer/streaming_template_renderer.rb deleted file mode 100644 index 9cf6eb0c65..0000000000 --- a/actionpack/lib/action_view/renderer/streaming_template_renderer.rb +++ /dev/null @@ -1,103 +0,0 @@ -require 'fiber' - -module ActionView - # == TODO - # - # * Support streaming from child templates, partials and so on. - # * Integrate exceptions with exceptron - # * Rack::Cache needs to support streaming bodies - class StreamingTemplateRenderer < TemplateRenderer #:nodoc: - # A valid Rack::Body (i.e. it responds to each). - # It is initialized with a block that, when called, starts - # rendering the template. - class Body #:nodoc: - def initialize(&start) - @start = start - end - - def each(&block) - begin - @start.call(block) - rescue Exception => exception - log_error(exception) - block.call ActionView::Base.streaming_completion_on_exception - end - self - end - - private - - # This is the same logging logic as in ShowExceptions middleware. - # TODO Once "exceptron" is in, refactor this piece to simply re-use exceptron. - def log_error(exception) #:nodoc: - logger = ActionView::Base.logger - return unless logger - - message = "\n#{exception.class} (#{exception.message}):\n" - message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code) - message << " " << exception.backtrace.join("\n ") - logger.fatal("#{message}\n\n") - end - end - - # For streaming, instead of rendering a given a template, we return a Body - # object that responds to each. This object is initialized with a block - # that knows how to render the template. - def render_template(template, layout_name = nil, locals = {}) #:nodoc: - return [super] unless layout_name && template.supports_streaming? - - locals ||= {} - layout = layout_name && find_layout(layout_name, locals.keys) - - Body.new do |buffer| - delayed_render(buffer, template, layout, @view, locals) - end - end - - private - - def delayed_render(buffer, template, layout, view, locals) - # Wrap the given buffer in the StreamingBuffer and pass it to the - # underlying template handler. Now, everytime something is concatenated - # to the buffer, it is not appended to an array, but streamed straight - # to the client. - output = ActionView::StreamingBuffer.new(buffer) - yielder = lambda { |*name| view._layout_for(*name) } - - instrument(:template, :identifier => template.identifier, :layout => layout.try(:virtual_path)) do - fiber = Fiber.new do - if layout - layout.render(view, locals, output, &yielder) - else - # If you don't have a layout, just render the thing - # and concatenate the final result. This is the same - # as a layout with just <%= yield %> - output.safe_concat view._layout_for - end - end - - # Set the view flow to support streaming. It will be aware - # when to stop rendering the layout because it needs to search - # something in the template and vice-versa. - view.view_flow = StreamingFlow.new(view, fiber) - - # Yo! Start the fiber! - fiber.resume - - # If the fiber is still alive, it means we need something - # from the template, so start rendering it. If not, it means - # the layout exited without requiring anything from the template. - if fiber.alive? - content = template.render(view, locals, &yielder) - - # Once rendering the template is done, sets its content in the :layout key. - view.view_flow.set(:layout, content) - - # In case the layout continues yielding, we need to resume - # the fiber until all yields are handled. - fiber.resume while fiber.alive? - end - end - end - end -end diff --git a/actionpack/lib/action_view/renderer/template_renderer.rb b/actionpack/lib/action_view/renderer/template_renderer.rb deleted file mode 100644 index 4d5c5db80c..0000000000 --- a/actionpack/lib/action_view/renderer/template_renderer.rb +++ /dev/null @@ -1,96 +0,0 @@ -require 'active_support/core_ext/object/try' - -module ActionView - class TemplateRenderer < AbstractRenderer #:nodoc: - def render(context, options) - @view = context - @details = extract_details(options) - template = determine_template(options) - context = @lookup_context - - prepend_formats(template.formats) - - unless context.rendered_format - context.rendered_format = template.formats.first || formats.last - end - - render_template(template, options[:layout], options[:locals]) - end - - # Determine the template to be rendered using the given options. - def determine_template(options) #:nodoc: - keys = options.fetch(:locals, {}).keys - - if options.key?(:text) - Template::Text.new(options[:text], formats.first) - elsif options.key?(:file) - with_fallbacks { find_template(options[:file], nil, false, keys, @details) } - elsif options.key?(:inline) - handler = Template.handler_for_extension(options[:type] || "erb") - Template.new(options[:inline], "inline template", handler, :locals => keys) - elsif options.key?(:template) - if options[:template].respond_to?(:render) - options[:template] - else - find_template(options[:template], options[:prefixes], false, keys, @details) - end - else - raise ArgumentError, "You invoked render but did not give any of :partial, :template, :inline, :file or :text option." - end - end - - # Renders the given template. A string representing the layout can be - # supplied as well. - def render_template(template, layout_name = nil, locals = nil) #:nodoc: - view, locals = @view, locals || {} - - render_with_layout(layout_name, locals) do |layout| - instrument(:template, :identifier => template.identifier, :layout => layout.try(:virtual_path)) do - template.render(view, locals) { |*name| view._layout_for(*name) } - end - end - end - - def render_with_layout(path, locals) #:nodoc: - layout = path && find_layout(path, locals.keys) - content = yield(layout) - - if layout - view = @view - view.view_flow.set(:layout, content) - layout.render(view, locals){ |*name| view._layout_for(*name) } - else - content - end - end - - # This is the method which actually finds the layout using details in the lookup - # context object. If no layout is found, it checks if at least a layout with - # the given name exists across all details before raising the error. - def find_layout(layout, keys) - with_layout_format { resolve_layout(layout, keys) } - end - - def resolve_layout(layout, keys) - case layout - when String - begin - if layout =~ /^\// - with_fallbacks { find_template(layout, nil, false, keys, @details) } - else - find_template(layout, nil, false, keys, @details) - end - rescue ActionView::MissingTemplate - all_details = @details.merge(:formats => @lookup_context.default_formats) - raise unless template_exists?(layout, nil, false, keys, all_details) - end - when Proc - resolve_layout(layout.call, keys) - when FalseClass - nil - else - layout - end - end - end -end diff --git a/actionpack/lib/action_view/routing_url_for.rb b/actionpack/lib/action_view/routing_url_for.rb deleted file mode 100644 index f10e7e88ba..0000000000 --- a/actionpack/lib/action_view/routing_url_for.rb +++ /dev/null @@ -1,107 +0,0 @@ -module ActionView - module RoutingUrlFor - - # Returns the URL for the set of +options+ provided. This takes the - # same options as +url_for+ in Action Controller (see the - # documentation for <tt>ActionController::Base#url_for</tt>). Note that by default - # <tt>:only_path</tt> is <tt>true</tt> so you'll get the relative "/controller/action" - # instead of the fully qualified URL like "http://example.com/controller/action". - # - # ==== Options - # * <tt>:anchor</tt> - Specifies the anchor name to be appended to the path. - # * <tt>:only_path</tt> - If true, returns the relative URL (omitting the protocol, host name, and port) (<tt>true</tt> by default unless <tt>:host</tt> is specified). - # * <tt>:trailing_slash</tt> - If true, adds a trailing slash, as in "/archive/2005/". Note that this - # is currently not recommended since it breaks caching. - # * <tt>:host</tt> - Overrides the default (current) host if provided. - # * <tt>:protocol</tt> - Overrides the default (current) protocol if provided. - # * <tt>:user</tt> - Inline HTTP authentication (only plucked out if <tt>:password</tt> is also present). - # * <tt>:password</tt> - Inline HTTP authentication (only plucked out if <tt>:user</tt> is also present). - # - # ==== Relying on named routes - # - # Passing a record (like an Active Record) instead of a hash as the options parameter will - # trigger the named route for that record. The lookup will happen on the name of the class. So passing a - # Workshop object will attempt to use the +workshop_path+ route. If you have a nested route, such as - # +admin_workshop_path+ you'll have to call that explicitly (it's impossible for +url_for+ to guess that route). - # - # ==== Implicit Controller Namespacing - # - # Controllers passed in using the +:controller+ option will retain their namespace unless it is an absolute one. - # - # ==== Examples - # <%= url_for(action: 'index') %> - # # => /blog/ - # - # <%= url_for(action: 'find', controller: 'books') %> - # # => /books/find - # - # <%= url_for(action: 'login', controller: 'members', only_path: false, protocol: 'https') %> - # # => https://www.example.com/members/login/ - # - # <%= url_for(action: 'play', anchor: 'player') %> - # # => /messages/play/#player - # - # <%= url_for(action: 'jump', anchor: 'tax&ship') %> - # # => /testing/jump/#tax&ship - # - # <%= url_for(Workshop.new) %> - # # relies on Workshop answering a persisted? call (and in this case returning false) - # # => /workshops - # - # <%= url_for(@workshop) %> - # # calls @workshop.to_param which by default returns the id - # # => /workshops/5 - # - # # to_param can be re-defined in a model to provide different URL names: - # # => /workshops/1-workshop-name - # - # <%= url_for("http://www.example.com") %> - # # => http://www.example.com - # - # <%= url_for(:back) %> - # # if request.env["HTTP_REFERER"] is set to "http://www.example.com" - # # => http://www.example.com - # - # <%= url_for(:back) %> - # # if request.env["HTTP_REFERER"] is not set or is blank - # # => javascript:history.back() - # - # <%= url_for(action: 'index', controller: 'users') %> - # # Assuming an "admin" namespace - # # => /admin/users - # - # <%= url_for(action: 'index', controller: '/users') %> - # # Specify absolute path with beginning slash - # # => /users - def url_for(options = nil) - case options - when String - options - when nil, Hash - options ||= {} - options = { :only_path => options[:host].nil? }.merge!(options.symbolize_keys) - super - when :back - _back_url - else - polymorphic_path(options) - end - end - - def url_options #:nodoc: - return super unless controller.respond_to?(:url_options) - controller.url_options - end - - def _routes_context #:nodoc: - controller - end - protected :_routes_context - - def optimize_routes_generation? #:nodoc: - controller.respond_to?(:optimize_routes_generation?, true) ? - controller.optimize_routes_generation? : super - end - protected :optimize_routes_generation? - end -end diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb deleted file mode 100644 index 720890eeb9..0000000000 --- a/actionpack/lib/action_view/template.rb +++ /dev/null @@ -1,338 +0,0 @@ -require 'active_support/core_ext/object/try' -require 'active_support/core_ext/kernel/singleton_class' -require 'thread' - -module ActionView - # = Action View Template - class Template - extend ActiveSupport::Autoload - - # === Encodings in ActionView::Template - # - # ActionView::Template is one of a few sources of potential - # encoding issues in Rails. This is because the source for - # templates are usually read from disk, and Ruby (like most - # encoding-aware programming languages) assumes that the - # String retrieved through File IO is encoded in the - # <tt>default_external</tt> encoding. In Rails, the default - # <tt>default_external</tt> encoding is UTF-8. - # - # As a result, if a user saves their template as ISO-8859-1 - # (for instance, using a non-Unicode-aware text editor), - # and uses characters outside of the ASCII range, their - # users will see diamonds with question marks in them in - # the browser. - # - # For the rest of this documentation, when we say "UTF-8", - # we mean "UTF-8 or whatever the default_internal encoding - # is set to". By default, it will be UTF-8. - # - # To mitigate this problem, we use a few strategies: - # 1. If the source is not valid UTF-8, we raise an exception - # when the template is compiled to alert the user - # to the problem. - # 2. The user can specify the encoding using Ruby-style - # encoding comments in any template engine. If such - # a comment is supplied, Rails will apply that encoding - # to the resulting compiled source returned by the - # template handler. - # 3. In all cases, we transcode the resulting String to - # the UTF-8. - # - # This means that other parts of Rails can always assume - # that templates are encoded in UTF-8, even if the original - # source of the template was not UTF-8. - # - # From a user's perspective, the easiest thing to do is - # to save your templates as UTF-8. If you do this, you - # do not need to do anything else for things to "just work". - # - # === Instructions for template handlers - # - # The easiest thing for you to do is to simply ignore - # encodings. Rails will hand you the template source - # as the default_internal (generally UTF-8), raising - # an exception for the user before sending the template - # to you if it could not determine the original encoding. - # - # For the greatest simplicity, you can support only - # UTF-8 as the <tt>default_internal</tt>. This means - # that from the perspective of your handler, the - # entire pipeline is just UTF-8. - # - # === Advanced: Handlers with alternate metadata sources - # - # If you want to provide an alternate mechanism for - # specifying encodings (like ERB does via <%# encoding: ... %>), - # you may indicate that you will handle encodings yourself - # by implementing <tt>self.handles_encoding?</tt> - # on your handler. - # - # If you do, Rails will not try to encode the String - # into the default_internal, passing you the unaltered - # bytes tagged with the assumed encoding (from - # default_external). - # - # In this case, make sure you return a String from - # your handler encoded in the default_internal. Since - # you are handling out-of-band metadata, you are - # also responsible for alerting the user to any - # problems with converting the user's data to - # the <tt>default_internal</tt>. - # - # To do so, simply raise +WrongEncodingError+ as follows: - # - # raise WrongEncodingError.new( - # problematic_string, - # expected_encoding - # ) - - eager_autoload do - autoload :Error - autoload :Handlers - autoload :Text - autoload :Types - end - - extend Template::Handlers - - attr_accessor :locals, :formats, :virtual_path - - attr_reader :source, :identifier, :handler, :original_encoding, :updated_at - - # This finalizer is needed (and exactly with a proc inside another proc) - # otherwise templates leak in development. - Finalizer = proc do |method_name, mod| - proc do - mod.module_eval do - remove_possible_method method_name - end - end - end - - def initialize(source, identifier, handler, details) - format = details[:format] || (handler.default_format if handler.respond_to?(:default_format)) - - @source = source - @identifier = identifier - @handler = handler - @compiled = false - @original_encoding = nil - @locals = details[:locals] || [] - @virtual_path = details[:virtual_path] - @updated_at = details[:updated_at] || Time.now - @formats = Array(format).map { |f| f.respond_to?(:ref) ? f.ref : f } - @compile_mutex = Mutex.new - end - - # Returns if the underlying handler supports streaming. If so, - # a streaming buffer *may* be passed when it start rendering. - def supports_streaming? - handler.respond_to?(:supports_streaming?) && handler.supports_streaming? - end - - # Render a template. If the template was not compiled yet, it is done - # exactly before rendering. - # - # This method is instrumented as "!render_template.action_view". Notice that - # we use a bang in this instrumentation because you don't want to - # consume this in production. This is only slow if it's being listened to. - def render(view, locals, buffer=nil, &block) - ActiveSupport::Notifications.instrument("!render_template.action_view", virtual_path: @virtual_path, identifier: @identifier) do - compile!(view) - view.send(method_name, locals, buffer, &block) - end - rescue Exception => e - handle_render_error(view, e) - end - - def mime_type - message = 'Template#mime_type is deprecated and will be removed in Rails 4.1. Please use type method instead.' - ActiveSupport::Deprecation.warn message - @mime_type ||= Mime::Type.lookup_by_extension(@formats.first.to_s) if @formats.first - end - - def type - @type ||= Types[@formats.first] if @formats.first - end - - # Receives a view object and return a template similar to self by using @virtual_path. - # - # This method is useful if you have a template object but it does not contain its source - # anymore since it was already compiled. In such cases, all you need to do is to call - # refresh passing in the view object. - # - # Notice this method raises an error if the template to be refreshed does not have a - # virtual path set (true just for inline templates). - def refresh(view) - raise "A template needs to have a virtual path in order to be refreshed" unless @virtual_path - lookup = view.lookup_context - pieces = @virtual_path.split("/") - name = pieces.pop - partial = !!name.sub!(/^_/, "") - lookup.disable_cache do - lookup.find_template(name, [ pieces.join('/') ], partial, @locals) - end - end - - def inspect - @inspect ||= defined?(Rails.root) ? identifier.sub("#{Rails.root}/", '') : identifier - end - - # This method is responsible for properly setting the encoding of the - # source. Until this point, we assume that the source is BINARY data. - # If no additional information is supplied, we assume the encoding is - # the same as <tt>Encoding.default_external</tt>. - # - # The user can also specify the encoding via a comment on the first - # line of the template (# encoding: NAME-OF-ENCODING). This will work - # with any template engine, as we process out the encoding comment - # before passing the source on to the template engine, leaving a - # blank line in its stead. - def encode! - return unless source.encoding == Encoding::BINARY - - # Look for # encoding: *. If we find one, we'll encode the - # String in that encoding, otherwise, we'll use the - # default external encoding. - if source.sub!(/\A#{ENCODING_FLAG}/, '') - encoding = magic_encoding = $1 - else - encoding = Encoding.default_external - end - - # Tag the source with the default external encoding - # or the encoding specified in the file - source.force_encoding(encoding) - - # If the user didn't specify an encoding, and the handler - # handles encodings, we simply pass the String as is to - # the handler (with the default_external tag) - if !magic_encoding && @handler.respond_to?(:handles_encoding?) && @handler.handles_encoding? - source - # Otherwise, if the String is valid in the encoding, - # encode immediately to default_internal. This means - # that if a handler doesn't handle encodings, it will - # always get Strings in the default_internal - elsif source.valid_encoding? - source.encode! - # Otherwise, since the String is invalid in the encoding - # specified, raise an exception - else - raise WrongEncodingError.new(source, encoding) - end - end - - protected - - # Compile a template. This method ensures a template is compiled - # just once and removes the source after it is compiled. - def compile!(view) #:nodoc: - return if @compiled - - # Templates can be used concurrently in threaded environments - # so compilation and any instance variable modification must - # be synchronized - @compile_mutex.synchronize do - # Any thread holding this lock will be compiling the template needed - # by the threads waiting. So re-check the @compiled flag to avoid - # re-compilation - return if @compiled - - if view.is_a?(ActionView::CompiledTemplates) - mod = ActionView::CompiledTemplates - else - mod = view.singleton_class - end - - compile(view, mod) - - # Just discard the source if we have a virtual path. This - # means we can get the template back. - @source = nil if @virtual_path - @compiled = true - end - end - - # Among other things, this method is responsible for properly setting - # the encoding of the compiled template. - # - # If the template engine handles encodings, we send the encoded - # String to the engine without further processing. This allows - # the template engine to support additional mechanisms for - # specifying the encoding. For instance, ERB supports <%# encoding: %> - # - # Otherwise, after we figure out the correct encoding, we then - # encode the source into <tt>Encoding.default_internal</tt>. - # In general, this means that templates will be UTF-8 inside of Rails, - # regardless of the original source encoding. - def compile(view, mod) #:nodoc: - encode! - method_name = self.method_name - code = @handler.call(self) - - # Make sure that the resulting String to be evalled is in the - # encoding of the code - source = <<-end_src - def #{method_name}(local_assigns, output_buffer) - _old_virtual_path, @virtual_path = @virtual_path, #{@virtual_path.inspect};_old_output_buffer = @output_buffer;#{locals_code};#{code} - ensure - @virtual_path, @output_buffer = _old_virtual_path, _old_output_buffer - end - end_src - - # Make sure the source is in the encoding of the returned code - source.force_encoding(code.encoding) - - # In case we get back a String from a handler that is not in - # BINARY or the default_internal, encode it to the default_internal - source.encode! - - # Now, validate that the source we got back from the template - # handler is valid in the default_internal. This is for handlers - # that handle encoding but screw up - unless source.valid_encoding? - raise WrongEncodingError.new(@source, Encoding.default_internal) - end - - begin - mod.module_eval(source, identifier, 0) - ObjectSpace.define_finalizer(self, Finalizer[method_name, mod]) - rescue Exception => e # errors from template code - if logger = (view && view.logger) - logger.debug "ERROR: compiling #{method_name} RAISED #{e}" - logger.debug "Function body: #{source}" - logger.debug "Backtrace: #{e.backtrace.join("\n")}" - end - - raise ActionView::Template::Error.new(self, e) - end - end - - def handle_render_error(view, e) #:nodoc: - if e.is_a?(Template::Error) - e.sub_template_of(self) - raise e - else - template = self - unless template.source - template = refresh(view) - template.encode! - end - raise Template::Error.new(template, e) - end - end - - def locals_code #:nodoc: - @locals.map { |key| "#{key} = local_assigns[:#{key}];" }.join - end - - def method_name #:nodoc: - @method_name ||= "_#{identifier_method_name}__#{@identifier.hash}_#{__id__}".gsub('-', "_") - end - - def identifier_method_name #:nodoc: - inspect.gsub(/[^a-z_]/, '_') - end - end -end diff --git a/actionpack/lib/action_view/template/error.rb b/actionpack/lib/action_view/template/error.rb deleted file mode 100644 index a89d51221e..0000000000 --- a/actionpack/lib/action_view/template/error.rb +++ /dev/null @@ -1,138 +0,0 @@ -require "active_support/core_ext/enumerable" - -module ActionView - # = Action View Errors - class ActionViewError < StandardError #:nodoc: - end - - class EncodingError < StandardError #:nodoc: - end - - class MissingRequestError < StandardError #:nodoc: - end - - class WrongEncodingError < EncodingError #:nodoc: - def initialize(string, encoding) - @string, @encoding = string, encoding - end - - def message - @string.force_encoding(Encoding::ASCII_8BIT) - "Your template was not saved as valid #{@encoding}. Please " \ - "either specify #{@encoding} as the encoding for your template " \ - "in your text editor, or mark the template with its " \ - "encoding by inserting the following as the first line " \ - "of the template:\n\n# encoding: <name of correct encoding>.\n\n" \ - "The source of your template was:\n\n#{@string}" - end - end - - class MissingTemplate < ActionViewError #:nodoc: - attr_reader :path - - def initialize(paths, path, prefixes, partial, details, *) - @path = path - prefixes = Array(prefixes) - template_type = if partial - "partial" - elsif path =~ /layouts/i - 'layout' - else - 'template' - end - - searched_paths = prefixes.map { |prefix| [prefix, path].join("/") } - - out = "Missing #{template_type} #{searched_paths.join(", ")} with #{details.inspect}. Searched in:\n" - out += paths.compact.map { |p| " * #{p.to_s.inspect}\n" }.join - super out - end - end - - class Template - # The Template::Error exception is raised when the compilation or rendering of the template - # fails. This exception then gathers a bunch of intimate details and uses it to report a - # precise exception message. - class Error < ActionViewError #:nodoc: - SOURCE_CODE_RADIUS = 3 - - attr_reader :original_exception, :backtrace - - def initialize(template, original_exception) - super(original_exception.message) - @template, @original_exception = template, original_exception - @sub_templates = nil - @backtrace = original_exception.backtrace - end - - def file_name - @template.identifier - end - - def sub_template_message - if @sub_templates - "Trace of template inclusion: " + - @sub_templates.collect { |template| template.inspect }.join(", ") - else - "" - end - end - - def source_extract(indentation = 0, output = :console) - return unless num = line_number - num = num.to_i - - source_code = @template.source.split("\n") - - start_on_line = [ num - SOURCE_CODE_RADIUS - 1, 0 ].max - end_on_line = [ num + SOURCE_CODE_RADIUS - 1, source_code.length].min - - indent = end_on_line.to_s.size + indentation - return unless source_code = source_code[start_on_line..end_on_line] - - formatted_code_for(source_code, start_on_line, indent, output) - end - - def sub_template_of(template_path) - @sub_templates ||= [] - @sub_templates << template_path - end - - def line_number - @line_number ||= - if file_name - regexp = /#{Regexp.escape File.basename(file_name)}:(\d+)/ - $1 if message =~ regexp || backtrace.find { |line| line =~ regexp } - end - end - - def annoted_source_code - source_extract(4) - end - - private - - def source_location - if line_number - "on line ##{line_number} of " - else - 'in ' - end + file_name - end - - def formatted_code_for(source_code, line_counter, indent, output) - start_value = (output == :html) ? {} : "" - source_code.inject(start_value) do |result, line| - line_counter += 1 - if output == :html - result.update(line_counter.to_s => "%#{indent}s %s\n" % ["", line]) - else - result << "%#{indent}s: %s\n" % [line_counter, line] - end - end - end - end - end - - TemplateError = Template::Error -end diff --git a/actionpack/lib/action_view/template/handlers.rb b/actionpack/lib/action_view/template/handlers.rb deleted file mode 100644 index d9cddc0040..0000000000 --- a/actionpack/lib/action_view/template/handlers.rb +++ /dev/null @@ -1,53 +0,0 @@ -module ActionView #:nodoc: - # = Action View Template Handlers - class Template - module Handlers #:nodoc: - autoload :ERB, 'action_view/template/handlers/erb' - autoload :Builder, 'action_view/template/handlers/builder' - autoload :Raw, 'action_view/template/handlers/raw' - - def self.extended(base) - base.register_default_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 - - @@template_handlers = {} - @@default_template_handlers = nil - - def self.extensions - @@template_extensions ||= @@template_handlers.keys - end - - # 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 - # and should return the rendered template as a String. - def register_template_handler(*extensions, handler) - raise(ArgumentError, "Extension is required") if extensions.empty? - extensions.each do |extension| - @@template_handlers[extension.to_sym] = handler - end - @@template_extensions = nil - end - - def template_handler_extensions - @@template_handlers.keys.map {|key| key.to_s }.sort - end - - def registered_template_handler(extension) - extension && @@template_handlers[extension.to_sym] - end - - def register_default_template_handler(extension, klass) - register_template_handler(extension, klass) - @@default_template_handlers = klass - end - - def handler_for_extension(extension) - registered_template_handler(extension) || @@default_template_handlers - end - end - end -end diff --git a/actionpack/lib/action_view/template/handlers/builder.rb b/actionpack/lib/action_view/template/handlers/builder.rb deleted file mode 100644 index d90b0c6378..0000000000 --- a/actionpack/lib/action_view/template/handlers/builder.rb +++ /dev/null @@ -1,26 +0,0 @@ -module ActionView - module Template::Handlers - class Builder - # Default format used by Builder. - class_attribute :default_format - self.default_format = :xml - - def call(template) - require_engine - "xml = ::Builder::XmlMarkup.new(:indent => 2);" + - "self.output_buffer = xml.target!;" + - template.source + - ";xml.target!;" - end - - protected - - def require_engine - @required ||= begin - require "builder" - true - end - end - end - end -end diff --git a/actionpack/lib/action_view/template/handlers/erb.rb b/actionpack/lib/action_view/template/handlers/erb.rb deleted file mode 100644 index 5aaafc15c1..0000000000 --- a/actionpack/lib/action_view/template/handlers/erb.rb +++ /dev/null @@ -1,120 +0,0 @@ -require 'action_dispatch/http/mime_type' -require 'erubis' - -module ActionView - class Template - module Handlers - class Erubis < ::Erubis::Eruby - def add_preamble(src) - src << "@output_buffer = output_buffer || ActionView::OutputBuffer.new;" - end - - def add_text(src, text) - return if text.empty? - src << "@output_buffer.safe_concat('" << escape_text(text) << "');" - end - - # Erubis toggles <%= and <%== behavior when escaping is enabled. - # We override to always treat <%== as escaped. - def add_expr(src, code, indicator) - case indicator - when '==' - add_expr_escaped(src, code) - else - super - end - end - - BLOCK_EXPR = /\s+(do|\{)(\s*\|[^|]*\|)?\s*\Z/ - - def add_expr_literal(src, code) - if code =~ BLOCK_EXPR - src << '@output_buffer.append= ' << code - else - src << '@output_buffer.append= (' << code << ');' - end - end - - def add_expr_escaped(src, code) - if code =~ BLOCK_EXPR - src << "@output_buffer.safe_append= " << code - else - src << "@output_buffer.safe_concat((" << code << ").to_s);" - end - end - - def add_postamble(src) - src << '@output_buffer.to_s' - end - end - - class ERB - # Specify trim mode for the ERB compiler. Defaults to '-'. - # See ERB documentation for suitable values. - class_attribute :erb_trim_mode - self.erb_trim_mode = '-' - - # Default implementation used. - class_attribute :erb_implementation - self.erb_implementation = Erubis - - # Do not escape templates of these mime types. - class_attribute :escape_whitelist - self.escape_whitelist = ["text/plain"] - - ENCODING_TAG = Regexp.new("\\A(<%#{ENCODING_FLAG}-?%>)[ \\t]*") - - def self.call(template) - new.call(template) - end - - def supports_streaming? - true - end - - def handles_encoding? - true - end - - def call(template) - # First, convert to BINARY, so in case the encoding is - # wrong, we can still find an encoding tag - # (<%# encoding %>) inside the String using a regular - # expression - template_source = template.source.dup.force_encoding(Encoding::ASCII_8BIT) - - erb = template_source.gsub(ENCODING_TAG, '') - encoding = $2 - - erb.force_encoding valid_encoding(template.source.dup, encoding) - - # Always make sure we return a String in the default_internal - erb.encode! - - self.class.erb_implementation.new( - erb, - :escape => (self.class.escape_whitelist.include? template.type), - :trim => (self.class.erb_trim_mode == "-") - ).src - end - - private - - def valid_encoding(string, encoding) - # If a magic encoding comment was found, tag the - # String with this encoding. This is for a case - # where the original String was assumed to be, - # for instance, UTF-8, but a magic comment - # proved otherwise - string.force_encoding(encoding) if encoding - - # If the String is valid, return the encoding we found - return string.encoding if string.valid_encoding? - - # Otherwise, raise an exception - raise WrongEncodingError.new(string, string.encoding) - end - end - end - end -end diff --git a/actionpack/lib/action_view/template/handlers/raw.rb b/actionpack/lib/action_view/template/handlers/raw.rb deleted file mode 100644 index 0c0d1fffcb..0000000000 --- a/actionpack/lib/action_view/template/handlers/raw.rb +++ /dev/null @@ -1,11 +0,0 @@ -module ActionView - module Template::Handlers - class Raw - def call(template) - escaped = template.source.gsub(':', '\:') - - '%q:' + escaped + ':;' - end - end - end -end diff --git a/actionpack/lib/action_view/template/resolver.rb b/actionpack/lib/action_view/template/resolver.rb deleted file mode 100644 index 47bd011d5e..0000000000 --- a/actionpack/lib/action_view/template/resolver.rb +++ /dev/null @@ -1,326 +0,0 @@ -require "pathname" -require "active_support/core_ext/class" -require "active_support/core_ext/class/attribute_accessors" -require "action_view/template" -require "thread" -require "thread_safe" - -module ActionView - # = Action View Resolver - class Resolver - # Keeps all information about view path and builds virtual path. - class Path - attr_reader :name, :prefix, :partial, :virtual - alias_method :partial?, :partial - - def self.build(name, prefix, partial) - virtual = "" - virtual << "#{prefix}/" unless prefix.empty? - virtual << (partial ? "_#{name}" : name) - new name, prefix, partial, virtual - end - - def initialize(name, prefix, partial, virtual) - @name = name - @prefix = prefix - @partial = partial - @virtual = virtual - end - - def to_str - @virtual - end - alias :to_s :to_str - end - - # Threadsafe template cache - class Cache #:nodoc: - class SmallCache < ThreadSafe::Cache - def initialize(options = {}) - super(options.merge(:initial_capacity => 2)) - end - end - - # preallocate all the default blocks for performance/memory consumption reasons - PARTIAL_BLOCK = lambda {|cache, partial| cache[partial] = SmallCache.new} - PREFIX_BLOCK = lambda {|cache, prefix| cache[prefix] = SmallCache.new(&PARTIAL_BLOCK)} - NAME_BLOCK = lambda {|cache, name| cache[name] = SmallCache.new(&PREFIX_BLOCK)} - KEY_BLOCK = lambda {|cache, key| cache[key] = SmallCache.new(&NAME_BLOCK)} - - # usually a majority of template look ups return nothing, use this canonical preallocated array to safe memory - NO_TEMPLATES = [].freeze - - def initialize - @data = SmallCache.new(&KEY_BLOCK) - end - - # Cache the templates returned by the block - def cache(key, name, prefix, partial, locals) - if Resolver.caching? - @data[key][name][prefix][partial][locals] ||= canonical_no_templates(yield) - else - fresh_templates = yield - cached_templates = @data[key][name][prefix][partial][locals] - - if templates_have_changed?(cached_templates, fresh_templates) - @data[key][name][prefix][partial][locals] = canonical_no_templates(fresh_templates) - else - cached_templates || NO_TEMPLATES - end - end - end - - def clear - @data.clear - end - - private - - def canonical_no_templates(templates) - templates.empty? ? NO_TEMPLATES : templates - end - - def templates_have_changed?(cached_templates, fresh_templates) - # if either the old or new template list is empty, we don't need to (and can't) - # compare modification times, and instead just check whether the lists are different - if cached_templates.blank? || fresh_templates.blank? - return fresh_templates.blank? != cached_templates.blank? - end - - cached_templates_max_updated_at = cached_templates.map(&:updated_at).max - - # if a template has changed, it will be now be newer than all the cached templates - fresh_templates.any? { |t| t.updated_at > cached_templates_max_updated_at } - end - end - - cattr_accessor :caching - self.caching = true - - class << self - alias :caching? :caching - end - - def initialize - @cache = Cache.new - end - - def clear_cache - @cache.clear - end - - # Normalizes the arguments and passes it on to find_templates. - def find_all(name, prefix=nil, partial=false, details={}, key=nil, locals=[]) - cached(key, [name, prefix, partial], details, locals) do - find_templates(name, prefix, partial, details) - end - end - - private - - delegate :caching?, to: :class - - # This is what child classes implement. No defaults are needed - # because Resolver guarantees that the arguments are present and - # normalized. - def find_templates(name, prefix, partial, details) - raise NotImplementedError, "Subclasses must implement a find_templates(name, prefix, partial, details) method" - end - - # Helpers that builds a path. Useful for building virtual paths. - def build_path(name, prefix, partial) - Path.build(name, prefix, partial) - end - - # Handles templates caching. If a key is given and caching is on - # always check the cache before hitting the resolver. Otherwise, - # it always hits the resolver but if the key is present, check if the - # 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! - - if key - @cache.cache(key, name, prefix, partial, locals) do - decorate(yield, path_info, details, locals) - end - else - decorate(yield, path_info, details, locals) - end - end - - # Ensures all the resolver information is set in the template. - def decorate(templates, path_info, details, locals) #:nodoc: - cached = nil - templates.each do |t| - t.locals = locals - t.formats = details[:formats] || [:html] if t.formats.empty? - t.virtual_path ||= (cached ||= build_path(*path_info)) - end - end - end - - # An abstract class that implements a Resolver with path semantics. - class PathResolver < Resolver #:nodoc: - EXTENSIONS = [:locale, :formats, :handlers] - DEFAULT_PATTERN = ":prefix/:action{.:locale,}{.:formats,}{.:handlers,}" - - def initialize(pattern=nil) - @pattern = pattern || DEFAULT_PATTERN - super() - end - - private - - def find_templates(name, prefix, partial, details) - path = Path.build(name, prefix, partial) - query(path, details, details[:formats]) - end - - def query(path, details, formats) - query = build_query(path, details) - - # deals with case-insensitive file systems. - sanitizer = Hash.new { |h,dir| h[dir] = Dir["#{dir}/*"] } - - template_paths = Dir[query].reject { |filename| - File.directory?(filename) || - !sanitizer[File.dirname(filename)].include?(filename) - } - - template_paths.map { |template| - handler, format = extract_handler_and_format(template, formats) - contents = File.binread template - - Template.new(contents, File.expand_path(template), handler, - :virtual_path => path.virtual, - :format => format, - :updated_at => mtime(template)) - } - end - - # Helper for building query glob string based on resolver's pattern. - def build_query(path, details) - query = @pattern.dup - - prefix = path.prefix.empty? ? "" : "#{escape_entry(path.prefix)}\\1" - query.gsub!(/\:prefix(\/)?/, prefix) - - partial = escape_entry(path.partial? ? "_#{path.name}" : path.name) - query.gsub!(/\:action/, partial) - - details.each do |ext, variants| - query.gsub!(/\:#{ext}/, "{#{variants.compact.uniq.join(',')}}") - end - - File.expand_path(query, @path) - end - - def escape_entry(entry) - entry.gsub(/[*?{}\[\]]/, '\\\\\\&') - end - - # Returns the file mtime from the filesystem. - def mtime(p) - File.mtime(p) - end - - # Extract handler and formats from path. If a format cannot be a found neither - # from the path, or the handler, we should return the array of formats given - # to the resolver. - def extract_handler_and_format(path, default_formats) - pieces = File.basename(path).split(".") - 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 = pieces.last && Template::Types[pieces.last] - [handler, format] - end - end - - # A resolver that loads files from the filesystem. It allows setting your own - # resolving pattern. Such pattern can be a glob string supported by some variables. - # - # ==== Examples - # - # Default pattern, loads views the same way as previous versions of rails, eg. when you're - # looking for `users/new` it will produce query glob: `users/new{.{en},}{.{html,js},}{.{erb,haml},}` - # - # FileSystemResolver.new("/path/to/views", ":prefix/:action{.:locale,}{.:formats,}{.:handlers,}") - # - # This one allows you to keep files with different formats in seperated subdirectories, - # eg. `users/new.html` will be loaded from `users/html/new.erb` or `users/new.html.erb`, - # `users/new.js` from `users/js/new.erb` or `users/new.js.erb`, etc. - # - # FileSystemResolver.new("/path/to/views", ":prefix/{:formats/,}:action{.:locale,}{.:formats,}{.:handlers,}") - # - # If you don't specify a pattern then the default will be used. - # - # In order to use any of the customized resolvers above in a Rails application, you just need - # to configure ActionController::Base.view_paths in an initializer, for example: - # - # ActionController::Base.view_paths = FileSystemResolver.new( - # Rails.root.join("app/views"), - # ":prefix{/:locale}/:action{.:formats,}{.:handlers,}" - # ) - # - # ==== Pattern format and variables - # - # Pattern has to be a valid glob string, and it allows you to use the - # following variables: - # - # * <tt>:prefix</tt> - usually the controller path - # * <tt>:action</tt> - name of the action - # * <tt>:locale</tt> - possible locale versions - # * <tt>:formats</tt> - possible request formats (for example html, json, xml...) - # * <tt>:handlers</tt> - possible handlers (for example erb, haml, builder...) - # - class FileSystemResolver < PathResolver - def initialize(path, pattern=nil) - raise ArgumentError, "path already is a Resolver class" if path.is_a?(Resolver) - super(pattern) - @path = File.expand_path(path) - end - - def to_s - @path.to_s - end - alias :to_path :to_s - - def eql?(resolver) - self.class.equal?(resolver.class) && to_path == resolver.to_path - end - alias :== :eql? - end - - # An Optimized resolver for Rails' most common case. - class OptimizedFileSystemResolver < FileSystemResolver #:nodoc: - def build_query(path, details) - exts = EXTENSIONS.map { |ext| details[ext] } - query = escape_entry(File.join(@path, path)) - - query + exts.map { |ext| - "{#{ext.compact.uniq.map { |e| ".#{e}," }.join}}" - }.join - end - end - - # The same as FileSystemResolver but does not allow templates to store - # a virtual path since it is invalid for such resolvers. - class FallbackFileSystemResolver < FileSystemResolver #:nodoc: - def self.instances - [new(""), new("/")] - end - - def decorate(*) - super.each { |t| t.virtual_path = nil } - end - end -end diff --git a/actionpack/lib/action_view/template/text.rb b/actionpack/lib/action_view/template/text.rb deleted file mode 100644 index 859c7bc3ce..0000000000 --- a/actionpack/lib/action_view/template/text.rb +++ /dev/null @@ -1,34 +0,0 @@ -module ActionView #:nodoc: - # = Action View Text Template - class Template - class Text #:nodoc: - attr_accessor :type - - def initialize(string, type = nil) - @string = string.to_s - @type = Types[type] || type if type - @type ||= Types[:text] - end - - def identifier - 'text template' - end - - def inspect - 'text template' - end - - def to_str - @string - end - - def render(*args) - to_str - end - - def formats - [@type.to_sym] - end - end - end -end diff --git a/actionpack/lib/action_view/template/types.rb b/actionpack/lib/action_view/template/types.rb deleted file mode 100644 index db77cb5d19..0000000000 --- a/actionpack/lib/action_view/template/types.rb +++ /dev/null @@ -1,57 +0,0 @@ -require 'set' -require 'active_support/core_ext/class/attribute_accessors' - -module ActionView - class Template - class Types - class Type - cattr_accessor :types - self.types = Set.new - - def self.register(*t) - types.merge(t.map { |type| type.to_s }) - end - - register :html, :text, :js, :css, :xml, :json - - def self.[](type) - return type if type.is_a?(self) - - if type.is_a?(Symbol) || types.member?(type.to_s) - new(type) - end - end - - attr_reader :symbol - - def initialize(symbol) - @symbol = symbol.to_sym - end - - delegate :to_s, :to_sym, :to => :symbol - alias to_str to_s - - def ref - to_sym || to_s - end - - def ==(type) - return false if type.blank? - symbol.to_sym == type.to_sym - end - end - - cattr_accessor :type_klass - - def self.delegate_to(klass) - self.type_klass = klass - end - - delegate_to Type - - def self.[](type) - type_klass[type] - end - end - end -end diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb deleted file mode 100644 index 10b487f37a..0000000000 --- a/actionpack/lib/action_view/test_case.rb +++ /dev/null @@ -1,270 +0,0 @@ -require 'active_support/core_ext/module/remove_method' -require 'action_controller' -require 'action_controller/test_case' -require 'action_view' - -module ActionView - # = Action View Test Case - class TestCase < ActiveSupport::TestCase - class TestController < ActionController::Base - include ActionDispatch::TestProcess - - attr_accessor :request, :response, :params - - class << self - attr_writer :controller_path - end - - def controller_path=(path) - self.class.controller_path=(path) - end - - def initialize - super - self.class.controller_path = "" - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - - @request.env.delete('PATH_INFO') - @params = {} - end - end - - module Behavior - extend ActiveSupport::Concern - - include ActionDispatch::Assertions, ActionDispatch::TestProcess - include ActionController::TemplateAssertions - include ActionView::Context - - include ActionDispatch::Routing::PolymorphicRoutes - - include AbstractController::Helpers - include ActionView::Helpers - include ActionView::RecordIdentifier - include ActionView::RoutingUrlFor - - include ActiveSupport::Testing::ConstantLookup - - delegate :lookup_context, :to => :controller - attr_accessor :controller, :output_buffer, :rendered - - module ClassMethods - def tests(helper_class) - case helper_class - when String, Symbol - self.helper_class = "#{helper_class.to_s.underscore}_helper".camelize.safe_constantize - when Module - self.helper_class = helper_class - end - end - - def determine_default_helper_class(name) - determine_constant_from_test_name(name) do |constant| - Module === constant && !(Class === constant) - end - end - - def helper_method(*methods) - # Almost a duplicate from ActionController::Helpers - methods.flatten.each do |method| - _helpers.module_eval <<-end_eval - def #{method}(*args, &block) # def current_user(*args, &block) - _test_case.send(%(#{method}), *args, &block) # _test_case.send(%(current_user), *args, &block) - end # end - end_eval - end - end - - attr_writer :helper_class - - def helper_class - @helper_class ||= determine_default_helper_class(name) - end - - def new(*) - include_helper_modules! - super - end - - private - - def include_helper_modules! - helper(helper_class) if helper_class - include _helpers - end - - end - - def setup_with_controller - @controller = ActionView::TestCase::TestController.new - @request = @controller.request - @output_buffer = ActiveSupport::SafeBuffer.new - @rendered = '' - - make_test_case_available_to_view! - say_no_to_protect_against_forgery! - end - - def config - @controller.config if @controller.respond_to?(:config) - end - - def render(options = {}, local_assigns = {}, &block) - view.assign(view_assigns) - @rendered << output = view.render(options, local_assigns, &block) - output - end - - def rendered_views - @_rendered_views ||= RenderedViewsCollection.new - end - - class RenderedViewsCollection - def initialize - @rendered_views ||= Hash.new { |hash, key| hash[key] = [] } - end - - def add(view, locals) - @rendered_views[view] ||= [] - @rendered_views[view] << locals - end - - def locals_for(view) - @rendered_views[view] - end - - def rendered_views - @rendered_views.keys - end - - def view_rendered?(view, expected_locals) - locals_for(view).any? do |actual_locals| - expected_locals.all? {|key, value| value == actual_locals[key] } - end - end - end - - included do - setup :setup_with_controller - end - - private - - # Support the selector assertions - # - # Need to experiment if this priority is the best one: rendered => output_buffer - def response_from_page - HTML::Document.new(@rendered.blank? ? @output_buffer : @rendered).root - end - - def say_no_to_protect_against_forgery! - _helpers.module_eval do - remove_possible_method :protect_against_forgery? - def protect_against_forgery? - false - end - end - end - - def make_test_case_available_to_view! - test_case_instance = self - _helpers.module_eval do - unless private_method_defined?(:_test_case) - define_method(:_test_case) { test_case_instance } - private :_test_case - end - end - end - - module Locals - attr_accessor :rendered_views - - def render(options = {}, local_assigns = {}) - case options - when Hash - if block_given? - rendered_views.add options[:layout], options[:locals] - elsif options.key?(:partial) - rendered_views.add options[:partial], options[:locals] - end - else - rendered_views.add options, local_assigns - end - - super - end - end - - # The instance of ActionView::Base that is used by +render+. - def view - @view ||= begin - view = @controller.view_context - view.singleton_class.send :include, _helpers - view.extend(Locals) - view.rendered_views = self.rendered_views - view.output_buffer = self.output_buffer - view - end - end - - alias_method :_view, :view - - INTERNAL_IVARS = [ - :@__name__, - :@__io__, - :@_assertion_wrapped, - :@_assertions, - :@_result, - :@_routes, - :@controller, - :@_layouts, - :@_files, - :@_rendered_views, - :@method_name, - :@output_buffer, - :@_partials, - :@passed, - :@rendered, - :@request, - :@routes, - :@tagged_logger, - :@_templates, - :@options, - :@test_passed, - :@view, - :@view_context_class - ] - - def _user_defined_ivars - instance_variables - INTERNAL_IVARS - end - - # Returns a Hash of instance variables and their values, as defined by - # the user in the test case, which are then assigned to the view being - # rendered. This is generally intended for internal use and extension - # frameworks. - def view_assigns - Hash[_user_defined_ivars.map do |ivar| - [ivar[1..-1].to_sym, instance_variable_get(ivar)] - end] - end - - def _routes - @controller._routes if @controller.respond_to?(:_routes) - end - - def method_missing(selector, *args) - if @controller.respond_to?(:_routes) && - ( @controller._routes.named_routes.helpers.include?(selector) || - @controller._routes.mounted_helpers.method_defined?(selector) ) - @controller.__send__(selector, *args) - else - super - end - end - end - - include Behavior - end -end diff --git a/actionpack/lib/action_view/testing/resolvers.rb b/actionpack/lib/action_view/testing/resolvers.rb deleted file mode 100644 index 7afa2fa613..0000000000 --- a/actionpack/lib/action_view/testing/resolvers.rb +++ /dev/null @@ -1,50 +0,0 @@ -require 'action_view/template/resolver' - -module ActionView #:nodoc: - # Use FixtureResolver in your tests to simulate the presence of files on the - # file system. This is used internally by Rails' own test suite, and is - # useful for testing extensions that have no way of knowing what the file - # system will look like at runtime. - class FixtureResolver < PathResolver - attr_reader :hash - - def initialize(hash = {}, pattern=nil) - super(pattern) - @hash = hash - end - - def to_s - @hash.keys.join(', ') - end - - private - - def query(path, exts, formats) - query = "" - EXTENSIONS.each do |ext| - query << '(' << exts[ext].map {|e| e && Regexp.escape(".#{e}") }.join('|') << '|)' - end - query = /^(#{Regexp.escape(path)})#{query}$/ - - templates = [] - @hash.each do |_path, array| - source, updated_at = array - next unless _path =~ query - handler, format = extract_handler_and_format(_path, formats) - templates << Template.new(source, _path, handler, - :virtual_path => path.virtual, :format => format, :updated_at => updated_at) - end - - templates.sort_by {|t| -t.identifier.match(/^#{query}$/).captures.reject(&:blank?).size } - end - end - - class NullResolver < PathResolver - def query(path, exts, formats) - handler, format = extract_handler_and_format(path, formats) - [ActionView::Template.new("Template generated by Null Resolver", path, handler, :virtual_path => path, :format => format)] - end - end - -end - diff --git a/actionpack/lib/action_view/vendor/html-scanner.rb b/actionpack/lib/action_view/vendor/html-scanner.rb deleted file mode 100644 index 775b827529..0000000000 --- a/actionpack/lib/action_view/vendor/html-scanner.rb +++ /dev/null @@ -1,20 +0,0 @@ -$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/html-scanner" - -module HTML - extend ActiveSupport::Autoload - - eager_autoload do - autoload :CDATA, 'html/node' - autoload :Document, 'html/document' - autoload :FullSanitizer, 'html/sanitizer' - autoload :LinkSanitizer, 'html/sanitizer' - autoload :Node, 'html/node' - autoload :Sanitizer, 'html/sanitizer' - autoload :Selector, 'html/selector' - autoload :Tag, 'html/node' - autoload :Text, 'html/node' - autoload :Tokenizer, 'html/tokenizer' - autoload :Version, 'html/version' - autoload :WhiteListSanitizer, 'html/sanitizer' - end -end diff --git a/actionpack/lib/action_view/vendor/html-scanner/html/document.rb b/actionpack/lib/action_view/vendor/html-scanner/html/document.rb deleted file mode 100644 index 386820300a..0000000000 --- a/actionpack/lib/action_view/vendor/html-scanner/html/document.rb +++ /dev/null @@ -1,68 +0,0 @@ -require 'html/tokenizer' -require 'html/node' -require 'html/selector' -require 'html/sanitizer' - -module HTML #:nodoc: - # A top-level HTML document. You give it a body of text, and it will parse that - # text into a tree of nodes. - class Document #:nodoc: - - # The root of the parsed document. - attr_reader :root - - # Create a new Document from the given text. - def initialize(text, strict=false, xml=false) - tokenizer = Tokenizer.new(text) - @root = Node.new(nil) - node_stack = [ @root ] - while token = tokenizer.next - node = Node.parse(node_stack.last, tokenizer.line, tokenizer.position, token, strict) - - node_stack.last.children << node unless node.tag? && node.closing == :close - if node.tag? - if node_stack.length > 1 && node.closing == :close - if node_stack.last.name == node.name - if node_stack.last.children.empty? - node_stack.last.children << Text.new(node_stack.last, node.line, node.position, "") - end - node_stack.pop - else - open_start = node_stack.last.position - 20 - open_start = 0 if open_start < 0 - close_start = node.position - 20 - close_start = 0 if close_start < 0 - msg = <<EOF.strip -ignoring attempt to close #{node_stack.last.name} with #{node.name} - opened at byte #{node_stack.last.position}, line #{node_stack.last.line} - closed at byte #{node.position}, line #{node.line} - attributes at open: #{node_stack.last.attributes.inspect} - text around open: #{text[open_start,40].inspect} - text around close: #{text[close_start,40].inspect} -EOF - strict ? raise(msg) : warn(msg) - end - elsif !node.childless?(xml) && node.closing != :close - node_stack.push node - end - end - end - end - - # Search the tree for (and return) the first node that matches the given - # conditions. The conditions are interpreted differently for different node - # types, see HTML::Text#find and HTML::Tag#find. - def find(conditions) - @root.find(conditions) - end - - # Search the tree for (and return) all nodes that match the given - # conditions. The conditions are interpreted differently for different node - # types, see HTML::Text#find and HTML::Tag#find. - def find_all(conditions) - @root.find_all(conditions) - end - - end - -end diff --git a/actionpack/lib/action_view/vendor/html-scanner/html/node.rb b/actionpack/lib/action_view/vendor/html-scanner/html/node.rb deleted file mode 100644 index 7e7cd4f7b6..0000000000 --- a/actionpack/lib/action_view/vendor/html-scanner/html/node.rb +++ /dev/null @@ -1,532 +0,0 @@ -require 'strscan' - -module HTML #:nodoc: - - class Conditions < Hash #:nodoc: - def initialize(hash) - super() - hash = { :content => hash } unless Hash === hash - hash = keys_to_symbols(hash) - hash.each do |k,v| - case k - when :tag, :content then - # keys are valid, and require no further processing - when :attributes then - hash[k] = keys_to_strings(v) - when :parent, :child, :ancestor, :descendant, :sibling, :before, - :after - hash[k] = Conditions.new(v) - when :children - hash[k] = v = keys_to_symbols(v) - v.each do |key,value| - case key - when :count, :greater_than, :less_than - # keys are valid, and require no further processing - when :only - v[key] = Conditions.new(value) - else - raise "illegal key #{key.inspect} => #{value.inspect}" - end - end - else - raise "illegal key #{k.inspect} => #{v.inspect}" - end - end - update hash - end - - private - - def keys_to_strings(hash) - Hash[hash.keys.map {|k| [k.to_s, hash[k]]}] - end - - def keys_to_symbols(hash) - Hash[hash.keys.map do |k| - raise "illegal key #{k.inspect}" unless k.respond_to?(:to_sym) - [k.to_sym, hash[k]] - end] - end - end - - # The base class of all nodes, textual and otherwise, in an HTML document. - class Node #:nodoc: - # The array of children of this node. Not all nodes have children. - attr_reader :children - - # The parent node of this node. All nodes have a parent, except for the - # root node. - attr_reader :parent - - # The line number of the input where this node was begun - attr_reader :line - - # The byte position in the input where this node was begun - attr_reader :position - - # Create a new node as a child of the given parent. - def initialize(parent, line=0, pos=0) - @parent = parent - @children = [] - @line, @position = line, pos - end - - # Return a textual representation of the node. - def to_s - @children.join() - end - - # Return false (subclasses must override this to provide specific matching - # behavior.) +conditions+ may be of any type. - def match(conditions) - false - end - - # Search the children of this node for the first node for which #find - # returns non +nil+. Returns the result of the #find call that succeeded. - def find(conditions) - conditions = validate_conditions(conditions) - @children.each do |child| - node = child.find(conditions) - return node if node - end - nil - end - - # Search for all nodes that match the given conditions, and return them - # as an array. - def find_all(conditions) - conditions = validate_conditions(conditions) - - matches = [] - matches << self if match(conditions) - @children.each do |child| - matches.concat child.find_all(conditions) - end - matches - end - - # Returns +false+. Subclasses may override this if they define a kind of - # tag. - def tag? - false - end - - def validate_conditions(conditions) - Conditions === conditions ? conditions : Conditions.new(conditions) - end - - def ==(node) - return false unless self.class == node.class && children.size == node.children.size - - equivalent = true - - children.size.times do |i| - equivalent &&= children[i] == node.children[i] - end - - equivalent - end - - class <<self - def parse(parent, line, pos, content, strict=true) - if content !~ /^<\S/ - Text.new(parent, line, pos, content) - else - scanner = StringScanner.new(content) - - unless scanner.skip(/</) - if strict - raise "expected <" - else - return Text.new(parent, line, pos, content) - end - end - - if scanner.skip(/!\[CDATA\[/) - unless scanner.skip_until(/\]\]>/) - if strict - raise "expected ]]> (got #{scanner.rest.inspect} for #{content})" - else - scanner.skip_until(/\Z/) - end - end - - return CDATA.new(parent, line, pos, scanner.pre_match.gsub(/<!\[CDATA\[/, '')) - end - - closing = ( scanner.scan(/\//) ? :close : nil ) - return Text.new(parent, line, pos, content) unless name = scanner.scan(/[^\s!>\/]+/) - name.downcase! - - unless closing - scanner.skip(/\s*/) - attributes = {} - while attr = scanner.scan(/[-\w:]+/) - value = true - if scanner.scan(/\s*=\s*/) - if delim = scanner.scan(/['"]/) - value = "" - while text = scanner.scan(/[^#{delim}\\]+|./) - case text - when "\\" then - value << text - break if scanner.eos? - value << scanner.getch - when delim - break - else value << text - end - end - else - value = scanner.scan(/[^\s>\/]+/) - end - end - attributes[attr.downcase] = value - scanner.skip(/\s*/) - end - - closing = ( scanner.scan(/\//) ? :self : nil ) - end - - unless scanner.scan(/\s*>/) - if strict - raise "expected > (got #{scanner.rest.inspect} for #{content}, #{attributes.inspect})" - else - # throw away all text until we find what we're looking for - scanner.skip_until(/>/) or scanner.terminate - end - end - - Tag.new(parent, line, pos, name, attributes, closing) - end - end - end - end - - # A node that represents text, rather than markup. - class Text < Node #:nodoc: - - attr_reader :content - - # Creates a new text node as a child of the given parent, with the given - # content. - def initialize(parent, line, pos, content) - super(parent, line, pos) - @content = content - end - - # Returns the content of this node. - def to_s - @content - end - - # Returns +self+ if this node meets the given conditions. Text nodes support - # conditions of the following kinds: - # - # * if +conditions+ is a string, it must be a substring of the node's - # content - # * if +conditions+ is a regular expression, it must match the node's - # content - # * if +conditions+ is a hash, it must contain a <tt>:content</tt> key that - # is either a string or a regexp, and which is interpreted as described - # above. - def find(conditions) - match(conditions) && self - end - - # Returns non-+nil+ if this node meets the given conditions, or +nil+ - # otherwise. See the discussion of #find for the valid conditions. - def match(conditions) - case conditions - when String - @content == conditions - when Regexp - @content =~ conditions - when Hash - conditions = validate_conditions(conditions) - - # Text nodes only have :content, :parent, :ancestor - unless (conditions.keys - [:content, :parent, :ancestor]).empty? - return false - end - - match(conditions[:content]) - else - nil - end - end - - def ==(node) - return false unless super - content == node.content - end - end - - # A CDATA node is simply a text node with a specialized way of displaying - # itself. - class CDATA < Text #:nodoc: - def to_s - "<![CDATA[#{super}]]>" - end - end - - # A Tag is any node that represents markup. It may be an opening tag, a - # closing tag, or a self-closing tag. It has a name, and may have a hash of - # attributes. - class Tag < Node #:nodoc: - - # Either +nil+, <tt>:close</tt>, or <tt>:self</tt> - attr_reader :closing - - # Either +nil+, or a hash of attributes for this node. - attr_reader :attributes - - # The name of this tag. - attr_reader :name - - # Create a new node as a child of the given parent, using the given content - # to describe the node. It will be parsed and the node name, attributes and - # closing status extracted. - def initialize(parent, line, pos, name, attributes, closing) - super(parent, line, pos) - @name = name - @attributes = attributes - @closing = closing - end - - # A convenience for obtaining an attribute of the node. Returns +nil+ if - # the node has no attributes. - def [](attr) - @attributes ? @attributes[attr] : nil - end - - # Returns non-+nil+ if this tag can contain child nodes. - def childless?(xml = false) - return false if xml && @closing.nil? - !@closing.nil? || - @name =~ /^(img|br|hr|link|meta|area|base|basefont| - col|frame|input|isindex|param)$/ox - end - - # Returns a textual representation of the node - def to_s - if @closing == :close - "</#{@name}>" - else - s = "<#{@name}" - @attributes.each do |k,v| - s << " #{k}" - s << "=\"#{v}\"" if String === v - end - s << " /" if @closing == :self - s << ">" - @children.each { |child| s << child.to_s } - s << "</#{@name}>" if @closing != :self && !@children.empty? - s - end - end - - # If either the node or any of its children meet the given conditions, the - # matching node is returned. Otherwise, +nil+ is returned. (See the - # description of the valid conditions in the +match+ method.) - def find(conditions) - match(conditions) && self || super - end - - # Returns +true+, indicating that this node represents an HTML tag. - def tag? - true - end - - # Returns +true+ if the node meets any of the given conditions. The - # +conditions+ parameter must be a hash of any of the following keys - # (all are optional): - # - # * <tt>:tag</tt>: the node name must match the corresponding value - # * <tt>:attributes</tt>: a hash. The node's values must match the - # corresponding values in the hash. - # * <tt>:parent</tt>: a hash. The node's parent must match the - # corresponding hash. - # * <tt>:child</tt>: a hash. At least one of the node's immediate children - # must meet the criteria described by the hash. - # * <tt>:ancestor</tt>: a hash. At least one of the node's ancestors must - # meet the criteria described by the hash. - # * <tt>:descendant</tt>: a hash. At least one of the node's descendants - # must meet the criteria described by the hash. - # * <tt>:sibling</tt>: a hash. At least one of the node's siblings must - # meet the criteria described by the hash. - # * <tt>:after</tt>: a hash. The node must be after any sibling meeting - # the criteria described by the hash, and at least one sibling must match. - # * <tt>:before</tt>: a hash. The node must be before any sibling meeting - # the criteria described by the hash, and at least one sibling must match. - # * <tt>:children</tt>: a hash, for counting children of a node. Accepts the - # keys: - # ** <tt>:count</tt>: either a number or a range which must equal (or - # include) the number of children that match. - # ** <tt>:less_than</tt>: the number of matching children must be less than - # this number. - # ** <tt>:greater_than</tt>: the number of matching children must be - # greater than this number. - # ** <tt>:only</tt>: another hash consisting of the keys to use - # to match on the children, and only matching children will be - # counted. - # - # Conditions are matched using the following algorithm: - # - # * if the condition is a string, it must be a substring of the value. - # * if the condition is a regexp, it must match the value. - # * if the condition is a number, the value must match number.to_s. - # * if the condition is +true+, the value must not be +nil+. - # * if the condition is +false+ or +nil+, the value must be +nil+. - # - # Usage: - # - # # test if the node is a "span" tag - # node.match tag: "span" - # - # # test if the node's parent is a "div" - # node.match parent: { tag: "div" } - # - # # test if any of the node's ancestors are "table" tags - # node.match ancestor: { tag: "table" } - # - # # test if any of the node's immediate children are "em" tags - # node.match child: { tag: "em" } - # - # # test if any of the node's descendants are "strong" tags - # node.match descendant: { tag: "strong" } - # - # # test if the node has between 2 and 4 span tags as immediate children - # node.match children: { count: 2..4, only: { tag: "span" } } - # - # # get funky: test to see if the node is a "div", has a "ul" ancestor - # # and an "li" parent (with "class" = "enum"), and whether or not it has - # # a "span" descendant that contains # text matching /hello world/: - # node.match tag: "div", - # ancestor: { tag: "ul" }, - # parent: { tag: "li", - # attributes: { class: "enum" } }, - # descendant: { tag: "span", - # child: /hello world/ } - def match(conditions) - conditions = validate_conditions(conditions) - # check content of child nodes - if conditions[:content] - if children.empty? - return false unless match_condition("", conditions[:content]) - else - return false unless children.find { |child| child.match(conditions[:content]) } - end - end - - # test the name - return false unless match_condition(@name, conditions[:tag]) if conditions[:tag] - - # test attributes - (conditions[:attributes] || {}).each do |key, value| - return false unless match_condition(self[key], value) - end - - # test parent - return false unless parent.match(conditions[:parent]) if conditions[:parent] - - # test children - return false unless children.find { |child| child.match(conditions[:child]) } if conditions[:child] - - # test ancestors - if conditions[:ancestor] - return false unless catch :found do - p = self - throw :found, true if p.match(conditions[:ancestor]) while p = p.parent - end - end - - # test descendants - if conditions[:descendant] - return false unless children.find do |child| - # test the child - child.match(conditions[:descendant]) || - # test the child's descendants - child.match(:descendant => conditions[:descendant]) - end - end - - # count children - if opts = conditions[:children] - matches = children.select do |c| - (c.kind_of?(HTML::Tag) and (c.closing == :self or ! c.childless?)) - end - - matches = matches.select { |c| c.match(opts[:only]) } if opts[:only] - opts.each do |key, value| - next if key == :only - case key - when :count - if Integer === value - return false if matches.length != value - else - return false unless value.include?(matches.length) - end - when :less_than - return false unless matches.length < value - when :greater_than - return false unless matches.length > value - else raise "unknown count condition #{key}" - end - end - end - - # test siblings - if conditions[:sibling] || conditions[:before] || conditions[:after] - siblings = parent ? parent.children : [] - self_index = siblings.index(self) - - if conditions[:sibling] - return false unless siblings.detect do |s| - s != self && s.match(conditions[:sibling]) - end - end - - if conditions[:before] - return false unless siblings[self_index+1..-1].detect do |s| - s != self && s.match(conditions[:before]) - end - end - - if conditions[:after] - return false unless siblings[0,self_index].detect do |s| - s != self && s.match(conditions[:after]) - end - end - end - - true - end - - def ==(node) - return false unless super - return false unless closing == node.closing && self.name == node.name - attributes == node.attributes - end - - private - # Match the given value to the given condition. - def match_condition(value, condition) - case condition - when String - value && value == condition - when Regexp - value && value.match(condition) - when Numeric - value == condition.to_s - when true - !value.nil? - when false, nil - value.nil? - else - false - end - end - end -end diff --git a/actionpack/lib/action_view/vendor/html-scanner/html/sanitizer.rb b/actionpack/lib/action_view/vendor/html-scanner/html/sanitizer.rb deleted file mode 100644 index 6b4ececda2..0000000000 --- a/actionpack/lib/action_view/vendor/html-scanner/html/sanitizer.rb +++ /dev/null @@ -1,188 +0,0 @@ -require 'set' -require 'cgi' -require 'active_support/core_ext/class/attribute_accessors' - -module HTML - class Sanitizer - def sanitize(text, options = {}) - validate_options(options) - return text unless sanitizeable?(text) - tokenize(text, options).join - end - - def sanitizeable?(text) - !(text.nil? || text.empty? || !text.index("<")) - end - - protected - def tokenize(text, options) - tokenizer = HTML::Tokenizer.new(text) - result = [] - while token = tokenizer.next - node = Node.parse(nil, 0, 0, token, false) - process_node node, result, options - end - result - end - - def process_node(node, result, options) - result << node.to_s - end - - def validate_options(options) - if options[:tags] && !options[:tags].is_a?(Enumerable) - raise ArgumentError, "You should pass :tags as an Enumerable" - end - - if options[:attributes] && !options[:attributes].is_a?(Enumerable) - raise ArgumentError, "You should pass :attributes as an Enumerable" - end - end - end - - class FullSanitizer < Sanitizer - def sanitize(text, options = {}) - result = super - # strip any comments, and if they have a newline at the end (ie. line with - # only a comment) strip that too - result = result.gsub(/<!--(.*?)-->[\n]?/m, "") if (result && result =~ /<!--(.*?)-->[\n]?/m) - # Recurse - handle all dirty nested tags - result == text ? result : sanitize(result, options) - end - - def process_node(node, result, options) - result << node.to_s if node.class == HTML::Text - end - end - - class LinkSanitizer < FullSanitizer - cattr_accessor :included_tags, :instance_writer => false - self.included_tags = Set.new(%w(a href)) - - def sanitizeable?(text) - !(text.nil? || text.empty? || !((text.index("<a") || text.index("<href")) && text.index(">"))) - end - - protected - def process_node(node, result, options) - result << node.to_s unless node.is_a?(HTML::Tag) && included_tags.include?(node.name) - end - end - - class WhiteListSanitizer < Sanitizer - [:protocol_separator, :uri_attributes, :allowed_attributes, :allowed_tags, :allowed_protocols, :bad_tags, - :allowed_css_properties, :allowed_css_keywords, :shorthand_css_properties].each do |attr| - class_attribute attr, :instance_writer => false - end - - # A regular expression of the valid characters used to separate protocols like - # the ':' in 'http://foo.com' - self.protocol_separator = /:|(�*58)|(p)|(%|%)3A/ - - # Specifies a Set of HTML attributes that can have URIs. - self.uri_attributes = Set.new(%w(href src cite action longdesc xlink:href lowsrc)) - - # Specifies a Set of 'bad' tags that the #sanitize helper will remove completely, as opposed - # to just escaping harmless tags like <font> - self.bad_tags = Set.new(%w(script)) - - # Specifies the default Set of tags that the #sanitize helper will allow unscathed. - self.allowed_tags = Set.new(%w(strong em b i p code pre tt samp kbd var sub - sup dfn cite big small address hr br div span h1 h2 h3 h4 h5 h6 ul ol li dl dt dd abbr - acronym a img blockquote del ins)) - - # Specifies the default Set of html attributes that the #sanitize helper will leave - # in the allowed tag. - self.allowed_attributes = Set.new(%w(href src width height alt cite datetime title class name xml:lang abbr)) - - # Specifies the default Set of acceptable css properties that #sanitize and #sanitize_css will accept. - self.allowed_protocols = Set.new(%w(ed2k ftp http https irc mailto news gopher nntp telnet webcal xmpp callto - feed svn urn aim rsync tag ssh sftp rtsp afs)) - - # Specifies the default Set of acceptable css properties that #sanitize and #sanitize_css will accept. - self.allowed_css_properties = Set.new(%w(azimuth background-color border-bottom-color border-collapse - border-color border-left-color border-right-color border-top-color clear color cursor direction display - elevation float font font-family font-size font-style font-variant font-weight height letter-spacing line-height - overflow pause pause-after pause-before pitch pitch-range richness speak speak-header speak-numeral speak-punctuation - speech-rate stress text-align text-decoration text-indent unicode-bidi vertical-align voice-family volume white-space - width)) - - # Specifies the default Set of acceptable css keywords that #sanitize and #sanitize_css will accept. - self.allowed_css_keywords = Set.new(%w(auto aqua black block blue bold both bottom brown center - collapse dashed dotted fuchsia gray green !important italic left lime maroon medium none navy normal - nowrap olive pointer purple red right solid silver teal top transparent underline white yellow)) - - # Specifies the default Set of allowed shorthand css properties for the #sanitize and #sanitize_css helpers. - self.shorthand_css_properties = Set.new(%w(background border margin padding)) - - # Sanitizes a block of css code. Used by #sanitize when it comes across a style attribute - def sanitize_css(style) - # disallow urls - style = style.to_s.gsub(/url\s*\(\s*[^\s)]+?\s*\)\s*/, ' ') - - # gauntlet - if style !~ /^([:,;#%.\sa-zA-Z0-9!]|\w-\w|\'[\s\w]+\'|\"[\s\w]+\"|\([\d,\s]+\))*$/ || - style !~ /^(\s*[-\w]+\s*:\s*[^:;]*(;|$)\s*)*$/ - return '' - end - - clean = [] - style.scan(/([-\w]+)\s*:\s*([^:;]*)/) do |prop,val| - if allowed_css_properties.include?(prop.downcase) - clean << prop + ': ' + val + ';' - elsif shorthand_css_properties.include?(prop.split('-')[0].downcase) - unless val.split().any? do |keyword| - !allowed_css_keywords.include?(keyword) && - keyword !~ /^(#[0-9a-f]+|rgb\(\d+%?,\d*%?,?\d*%?\)?|\d{0,2}\.?\d{0,2}(cm|em|ex|in|mm|pc|pt|px|%|,|\))?)$/ - end - clean << prop + ': ' + val + ';' - end - end - end - clean.join(' ') - end - - protected - def tokenize(text, options) - options[:parent] = [] - options[:attributes] ||= allowed_attributes - options[:tags] ||= allowed_tags - super - end - - def process_node(node, result, options) - result << case node - when HTML::Tag - if node.closing == :close - options[:parent].shift - else - options[:parent].unshift node.name - end - - process_attributes_for node, options - - options[:tags].include?(node.name) ? node : nil - else - bad_tags.include?(options[:parent].first) ? nil : node.to_s.gsub(/</, "<") - end - end - - def process_attributes_for(node, options) - return unless node.attributes - node.attributes.keys.each do |attr_name| - value = node.attributes[attr_name].to_s - - if !options[:attributes].include?(attr_name) || contains_bad_protocols?(attr_name, value) - node.attributes.delete(attr_name) - else - node.attributes[attr_name] = attr_name == 'style' ? sanitize_css(value) : CGI::escapeHTML(CGI::unescapeHTML(value)) - end - end - end - - def contains_bad_protocols?(attr_name, value) - uri_attributes.include?(attr_name) && - (value =~ /(^[^\/:]*):|(�*58)|(p)|(%|%)3A/ && !allowed_protocols.include?(value.split(protocol_separator).first.downcase.strip)) - end - end -end diff --git a/actionpack/lib/action_view/vendor/html-scanner/html/selector.rb b/actionpack/lib/action_view/vendor/html-scanner/html/selector.rb deleted file mode 100644 index 60b6783b19..0000000000 --- a/actionpack/lib/action_view/vendor/html-scanner/html/selector.rb +++ /dev/null @@ -1,830 +0,0 @@ -#-- -# Copyright (c) 2006 Assaf Arkin (http://labnotes.org) -# Under MIT and/or CC By license. -#++ - -module HTML - - # Selects HTML elements using CSS 2 selectors. - # - # The +Selector+ class uses CSS selector expressions to match and select - # HTML elements. - # - # For example: - # selector = HTML::Selector.new "form.login[action=/login]" - # creates a new selector that matches any +form+ element with the class - # +login+ and an attribute +action+ with the value <tt>/login</tt>. - # - # === Matching Elements - # - # Use the #match method to determine if an element matches the selector. - # - # For simple selectors, the method returns an array with that element, - # or +nil+ if the element does not match. For complex selectors (see below) - # the method returns an array with all matched elements, of +nil+ if no - # match found. - # - # For example: - # if selector.match(element) - # puts "Element is a login form" - # end - # - # === Selecting Elements - # - # Use the #select method to select all matching elements starting with - # one element and going through all children in depth-first order. - # - # This method returns an array of all matching elements, an empty array - # if no match is found - # - # For example: - # selector = HTML::Selector.new "input[type=text]" - # matches = selector.select(element) - # matches.each do |match| - # puts "Found text field with name #{match.attributes['name']}" - # end - # - # === Expressions - # - # Selectors can match elements using any of the following criteria: - # * <tt>name</tt> -- Match an element based on its name (tag name). - # For example, <tt>p</tt> to match a paragraph. You can use <tt>*</tt> - # to match any element. - # * <tt>#</tt><tt>id</tt> -- Match an element based on its identifier (the - # <tt>id</tt> attribute). For example, <tt>#</tt><tt>page</tt>. - # * <tt>.class</tt> -- Match an element based on its class name, all - # class names if more than one specified. - # * <tt>[attr]</tt> -- Match an element that has the specified attribute. - # * <tt>[attr=value]</tt> -- Match an element that has the specified - # attribute and value. (More operators are supported see below) - # * <tt>:pseudo-class</tt> -- Match an element based on a pseudo class, - # such as <tt>:nth-child</tt> and <tt>:empty</tt>. - # * <tt>:not(expr)</tt> -- Match an element that does not match the - # negation expression. - # - # When using a combination of the above, the element name comes first - # followed by identifier, class names, attributes, pseudo classes and - # negation in any order. Do not separate these parts with spaces! - # Space separation is used for descendant selectors. - # - # For example: - # selector = HTML::Selector.new "form.login[action=/login]" - # The matched element must be of type +form+ and have the class +login+. - # It may have other classes, but the class +login+ is required to match. - # It must also have an attribute called +action+ with the value - # <tt>/login</tt>. - # - # This selector will match the following element: - # <form class="login form" method="post" action="/login"> - # but will not match the element: - # <form method="post" action="/logout"> - # - # === Attribute Values - # - # Several operators are supported for matching attributes: - # * <tt>name</tt> -- The element must have an attribute with that name. - # * <tt>name=value</tt> -- The element must have an attribute with that - # name and value. - # * <tt>name^=value</tt> -- The attribute value must start with the - # specified value. - # * <tt>name$=value</tt> -- The attribute value must end with the - # specified value. - # * <tt>name*=value</tt> -- The attribute value must contain the - # specified value. - # * <tt>name~=word</tt> -- The attribute value must contain the specified - # word (space separated). - # * <tt>name|=word</tt> -- The attribute value must start with specified - # word. - # - # For example, the following two selectors match the same element: - # #my_id - # [id=my_id] - # and so do the following two selectors: - # .my_class - # [class~=my_class] - # - # === Alternatives, siblings, children - # - # Complex selectors use a combination of expressions to match elements: - # * <tt>expr1 expr2</tt> -- Match any element against the second expression - # if it has some parent element that matches the first expression. - # * <tt>expr1 > expr2</tt> -- Match any element against the second expression - # if it is the child of an element that matches the first expression. - # * <tt>expr1 + expr2</tt> -- Match any element against the second expression - # if it immediately follows an element that matches the first expression. - # * <tt>expr1 ~ expr2</tt> -- Match any element against the second expression - # that comes after an element that matches the first expression. - # * <tt>expr1, expr2</tt> -- Match any element against the first expression, - # or against the second expression. - # - # Since children and sibling selectors may match more than one element given - # the first element, the #match method may return more than one match. - # - # === Pseudo classes - # - # Pseudo classes were introduced in CSS 3. They are most often used to select - # elements in a given position: - # * <tt>:root</tt> -- Match the element only if it is the root element - # (no parent element). - # * <tt>:empty</tt> -- Match the element only if it has no child elements, - # and no text content. - # * <tt>:content(string)</tt> -- Match the element only if it has <tt>string</tt> - # as its text content (ignoring leading and trailing whitespace). - # * <tt>:only-child</tt> -- Match the element if it is the only child (element) - # of its parent element. - # * <tt>:only-of-type</tt> -- Match the element if it is the only child (element) - # of its parent element and its type. - # * <tt>:first-child</tt> -- Match the element if it is the first child (element) - # of its parent element. - # * <tt>:first-of-type</tt> -- Match the element if it is the first child (element) - # of its parent element of its type. - # * <tt>:last-child</tt> -- Match the element if it is the last child (element) - # of its parent element. - # * <tt>:last-of-type</tt> -- Match the element if it is the last child (element) - # of its parent element of its type. - # * <tt>:nth-child(b)</tt> -- Match the element if it is the b-th child (element) - # of its parent element. The value <tt>b</tt> specifies its index, starting with 1. - # * <tt>:nth-child(an+b)</tt> -- Match the element if it is the b-th child (element) - # in each group of <tt>a</tt> child elements of its parent element. - # * <tt>:nth-child(-an+b)</tt> -- Match the element if it is the first child (element) - # in each group of <tt>a</tt> child elements, up to the first <tt>b</tt> child - # elements of its parent element. - # * <tt>:nth-child(odd)</tt> -- Match element in the odd position (i.e. first, third). - # Same as <tt>:nth-child(2n+1)</tt>. - # * <tt>:nth-child(even)</tt> -- Match element in the even position (i.e. second, - # fourth). Same as <tt>:nth-child(2n+2)</tt>. - # * <tt>:nth-of-type(..)</tt> -- As above, but only counts elements of its type. - # * <tt>:nth-last-child(..)</tt> -- As above, but counts from the last child. - # * <tt>:nth-last-of-type(..)</tt> -- As above, but counts from the last child and - # only elements of its type. - # * <tt>:not(selector)</tt> -- Match the element only if the element does not - # match the simple selector. - # - # As you can see, <tt>:nth-child</tt> pseudo class and its variant can get quite - # tricky and the CSS specification doesn't do a much better job explaining it. - # But after reading the examples and trying a few combinations, it's easy to - # figure out. - # - # For example: - # table tr:nth-child(odd) - # Selects every second row in the table starting with the first one. - # - # div p:nth-child(4) - # Selects the fourth paragraph in the +div+, but not if the +div+ contains - # other elements, since those are also counted. - # - # div p:nth-of-type(4) - # Selects the fourth paragraph in the +div+, counting only paragraphs, and - # ignoring all other elements. - # - # div p:nth-of-type(-n+4) - # Selects the first four paragraphs, ignoring all others. - # - # And you can always select an element that matches one set of rules but - # not another using <tt>:not</tt>. For example: - # p:not(.post) - # Matches all paragraphs that do not have the class <tt>.post</tt>. - # - # === Substitution Values - # - # You can use substitution with identifiers, class names and element values. - # A substitution takes the form of a question mark (<tt>?</tt>) and uses the - # next value in the argument list following the CSS expression. - # - # The substitution value may be a string or a regular expression. All other - # values are converted to strings. - # - # For example: - # selector = HTML::Selector.new "#?", /^\d+$/ - # matches any element whose identifier consists of one or more digits. - # - # See http://www.w3.org/TR/css3-selectors/ - class Selector - - - # An invalid selector. - class InvalidSelectorError < StandardError #:nodoc: - end - - - class << self - - # :call-seq: - # Selector.for_class(cls) => selector - # - # Creates a new selector for the given class name. - def for_class(cls) - self.new([".?", cls]) - end - - - # :call-seq: - # Selector.for_id(id) => selector - # - # Creates a new selector for the given id. - def for_id(id) - self.new(["#?", id]) - end - - end - - - # :call-seq: - # Selector.new(string, [values ...]) => selector - # - # Creates a new selector from a CSS 2 selector expression. - # - # The first argument is the selector expression. All other arguments - # are used for value substitution. - # - # Throws InvalidSelectorError is the selector expression is invalid. - def initialize(selector, *values) - raise ArgumentError, "CSS expression cannot be empty" if selector.empty? - @source = "" - values = values[0] if values.size == 1 && values[0].is_a?(Array) - - # We need a copy to determine if we failed to parse, and also - # preserve the original pass by-ref statement. - statement = selector.strip.dup - - # Create a simple selector, along with negation. - simple_selector(statement, values).each { |name, value| instance_variable_set("@#{name}", value) } - - @alternates = [] - @depends = nil - - # Alternative selector. - if statement.sub!(/^\s*,\s*/, "") - second = Selector.new(statement, values) - @alternates << second - # If there are alternate selectors, we group them in the top selector. - if alternates = second.instance_variable_get(:@alternates) - second.instance_variable_set(:@alternates, []) - @alternates.concat alternates - end - @source << " , " << second.to_s - # Sibling selector: create a dependency into second selector that will - # match element immediately following this one. - elsif statement.sub!(/^\s*\+\s*/, "") - second = next_selector(statement, values) - @depends = lambda do |element, first| - if element = next_element(element) - second.match(element, first) - end - end - @source << " + " << second.to_s - # Adjacent selector: create a dependency into second selector that will - # match all elements following this one. - elsif statement.sub!(/^\s*~\s*/, "") - second = next_selector(statement, values) - @depends = lambda do |element, first| - matches = [] - while element = next_element(element) - if subset = second.match(element, first) - if first && !subset.empty? - matches << subset.first - break - else - matches.concat subset - end - end - end - matches.empty? ? nil : matches - end - @source << " ~ " << second.to_s - # Child selector: create a dependency into second selector that will - # match a child element of this one. - elsif statement.sub!(/^\s*>\s*/, "") - second = next_selector(statement, values) - @depends = lambda do |element, first| - matches = [] - element.children.each do |child| - if child.tag? && subset = second.match(child, first) - if first && !subset.empty? - matches << subset.first - break - else - matches.concat subset - end - end - end - matches.empty? ? nil : matches - end - @source << " > " << second.to_s - # Descendant selector: create a dependency into second selector that - # will match all descendant elements of this one. Note, - elsif statement =~ /^\s+\S+/ && statement != selector - second = next_selector(statement, values) - @depends = lambda do |element, first| - matches = [] - stack = element.children.reverse - while node = stack.pop - next unless node.tag? - if subset = second.match(node, first) - if first && !subset.empty? - matches << subset.first - break - else - matches.concat subset - end - elsif children = node.children - stack.concat children.reverse - end - end - matches.empty? ? nil : matches - end - @source << " " << second.to_s - else - # The last selector is where we check that we parsed - # all the parts. - unless statement.empty? || statement.strip.empty? - raise ArgumentError, "Invalid selector: #{statement}" - end - end - end - - - # :call-seq: - # match(element, first?) => array or nil - # - # Matches an element against the selector. - # - # For a simple selector this method returns an array with the - # element if the element matches, nil otherwise. - # - # For a complex selector (sibling and descendant) this method - # returns an array with all matching elements, nil if no match is - # found. - # - # Use +first_only=true+ if you are only interested in the first element. - # - # For example: - # if selector.match(element) - # puts "Element is a login form" - # end - def match(element, first_only = false) - # Match element if no element name or element name same as element name - if matched = (!@tag_name || @tag_name == element.name) - # No match if one of the attribute matches failed - for attr in @attributes - if element.attributes[attr[0]] !~ attr[1] - matched = false - break - end - end - end - - # Pseudo class matches (nth-child, empty, etc). - if matched - for pseudo in @pseudo - unless pseudo.call(element) - matched = false - break - end - end - end - - # Negation. Same rules as above, but we fail if a match is made. - if matched && @negation - for negation in @negation - if negation[:tag_name] == element.name - matched = false - else - for attr in negation[:attributes] - if element.attributes[attr[0]] =~ attr[1] - matched = false - break - end - end - end - if matched - for pseudo in negation[:pseudo] - if pseudo.call(element) - matched = false - break - end - end - end - break unless matched - end - end - - # If element matched but depends on another element (child, - # sibling, etc), apply the dependent matches instead. - if matched && @depends - matches = @depends.call(element, first_only) - else - matches = matched ? [element] : nil - end - - # If this selector is part of the group, try all the alternative - # selectors (unless first_only). - if !first_only || !matches - @alternates.each do |alternate| - break if matches && first_only - if subset = alternate.match(element, first_only) - if matches - matches.concat subset - else - matches = subset - end - end - end - end - - matches - end - - - # :call-seq: - # select(root) => array - # - # Selects and returns an array with all matching elements, beginning - # with one node and traversing through all children depth-first. - # Returns an empty array if no match is found. - # - # The root node may be any element in the document, or the document - # itself. - # - # For example: - # selector = HTML::Selector.new "input[type=text]" - # matches = selector.select(element) - # matches.each do |match| - # puts "Found text field with name #{match.attributes['name']}" - # end - def select(root) - matches = [] - stack = [root] - while node = stack.pop - if node.tag? && subset = match(node, false) - subset.each do |match| - matches << match unless matches.any? { |item| item.equal?(match) } - end - elsif children = node.children - stack.concat children.reverse - end - end - matches - end - - - # Similar to #select but returns the first matching element. Returns +nil+ - # if no element matches the selector. - def select_first(root) - stack = [root] - while node = stack.pop - if node.tag? && subset = match(node, true) - return subset.first if !subset.empty? - elsif children = node.children - stack.concat children.reverse - end - end - nil - end - - - def to_s #:nodoc: - @source - end - - - # Return the next element after this one. Skips sibling text nodes. - # - # With the +name+ argument, returns the next element with that name, - # skipping other sibling elements. - def next_element(element, name = nil) - if siblings = element.parent.children - found = false - siblings.each do |node| - if node.equal?(element) - found = true - elsif found && node.tag? - return node if (name.nil? || node.name == name) - end - end - end - nil - end - - - protected - - - # Creates a simple selector given the statement and array of - # substitution values. - # - # Returns a hash with the values +tag_name+, +attributes+, - # +pseudo+ (classes) and +negation+. - # - # Called the first time with +can_negate+ true to allow - # negation. Called a second time with false since negation - # cannot be negated. - def simple_selector(statement, values, can_negate = true) - tag_name = nil - attributes = [] - pseudo = [] - negation = [] - - # Element name. (Note that in negation, this can come at - # any order, but for simplicity we allow if only first). - statement.sub!(/^(\*|[[:alpha:]][\w\-]*)/) do |match| - match.strip! - tag_name = match.downcase unless match == "*" - @source << match - "" # Remove - end - - # Get identifier, class, attribute name, pseudo or negation. - while true - # Element identifier. - next if statement.sub!(/^#(\?|[\w\-]+)/) do |match| - id = $1 - if id == "?" - id = values.shift - end - @source << "##{id}" - id = Regexp.new("^#{Regexp.escape(id.to_s)}$") unless id.is_a?(Regexp) - attributes << ["id", id] - "" # Remove - end - - # Class name. - next if statement.sub!(/^\.([\w\-]+)/) do |match| - class_name = $1 - @source << ".#{class_name}" - class_name = Regexp.new("(^|\s)#{Regexp.escape(class_name)}($|\s)") unless class_name.is_a?(Regexp) - attributes << ["class", class_name] - "" # Remove - end - - # Attribute value. - next if statement.sub!(/^\[\s*([[:alpha:]][\w\-:]*)\s*((?:[~|^$*])?=)?\s*('[^']*'|"[^*]"|[^\]]*)\s*\]/) do |match| - name, equality, value = $1, $2, $3 - if value == "?" - value = values.shift - else - # Handle single and double quotes. - value.strip! - if (value[0] == ?" || value[0] == ?') && value[0] == value[-1] - value = value[1..-2] - end - end - @source << "[#{name}#{equality}'#{value}']" - attributes << [name.downcase.strip, attribute_match(equality, value)] - "" # Remove - end - - # Root element only. - next if statement.sub!(/^:root/) do |match| - pseudo << lambda do |element| - element.parent.nil? || !element.parent.tag? - end - @source << ":root" - "" # Remove - end - - # Nth-child including last and of-type. - next if statement.sub!(/^:nth-(last-)?(child|of-type)\((odd|even|(\d+|\?)|(-?\d*|\?)?n([+\-]\d+|\?)?)\)/) do |match| - reverse = $1 == "last-" - of_type = $2 == "of-type" - @source << ":nth-#{$1}#{$2}(" - case $3 - when "odd" - pseudo << nth_child(2, 1, of_type, reverse) - @source << "odd)" - when "even" - pseudo << nth_child(2, 2, of_type, reverse) - @source << "even)" - when /^(\d+|\?)$/ # b only - b = ($1 == "?" ? values.shift : $1).to_i - pseudo << nth_child(0, b, of_type, reverse) - @source << "#{b})" - when /^(-?\d*|\?)?n([+\-]\d+|\?)?$/ - a = ($1 == "?" ? values.shift : - $1 == "" ? 1 : $1 == "-" ? -1 : $1).to_i - b = ($2 == "?" ? values.shift : $2).to_i - pseudo << nth_child(a, b, of_type, reverse) - @source << (b >= 0 ? "#{a}n+#{b})" : "#{a}n#{b})") - else - raise ArgumentError, "Invalid nth-child #{match}" - end - "" # Remove - end - # First/last child (of type). - next if statement.sub!(/^:(first|last)-(child|of-type)/) do |match| - reverse = $1 == "last" - of_type = $2 == "of-type" - pseudo << nth_child(0, 1, of_type, reverse) - @source << ":#{$1}-#{$2}" - "" # Remove - end - # Only child (of type). - next if statement.sub!(/^:only-(child|of-type)/) do |match| - of_type = $1 == "of-type" - pseudo << only_child(of_type) - @source << ":only-#{$1}" - "" # Remove - end - - # Empty: no child elements or meaningful content (whitespaces - # are ignored). - next if statement.sub!(/^:empty/) do |match| - pseudo << lambda do |element| - empty = true - for child in element.children - if child.tag? || !child.content.strip.empty? - empty = false - break - end - end - empty - end - @source << ":empty" - "" # Remove - end - # Content: match the text content of the element, stripping - # leading and trailing spaces. - next if statement.sub!(/^:content\(\s*(\?|'[^']*'|"[^"]*"|[^)]*)\s*\)/) do |match| - content = $1 - if content == "?" - content = values.shift - elsif (content[0] == ?" || content[0] == ?') && content[0] == content[-1] - content = content[1..-2] - end - @source << ":content('#{content}')" - content = Regexp.new("^#{Regexp.escape(content.to_s)}$") unless content.is_a?(Regexp) - pseudo << lambda do |element| - text = "" - for child in element.children - unless child.tag? - text << child.content - end - end - text.strip =~ content - end - "" # Remove - end - - # Negation. Create another simple selector to handle it. - if statement.sub!(/^:not\(\s*/, "") - raise ArgumentError, "Double negatives are not missing feature" unless can_negate - @source << ":not(" - negation << simple_selector(statement, values, false) - raise ArgumentError, "Negation not closed" unless statement.sub!(/^\s*\)/, "") - @source << ")" - next - end - - # No match: moving on. - break - end - - # Return hash. The keys are mapped to instance variables. - {:tag_name=>tag_name, :attributes=>attributes, :pseudo=>pseudo, :negation=>negation} - end - - - # Create a regular expression to match an attribute value based - # on the equality operator (=, ^=, |=, etc). - def attribute_match(equality, value) - regexp = value.is_a?(Regexp) ? value : Regexp.escape(value.to_s) - case equality - when "=" then - # Match the attribute value in full - Regexp.new("^#{regexp}$") - when "~=" then - # Match a space-separated word within the attribute value - Regexp.new("(^|\s)#{regexp}($|\s)") - when "^=" - # Match the beginning of the attribute value - Regexp.new("^#{regexp}") - when "$=" - # Match the end of the attribute value - Regexp.new("#{regexp}$") - when "*=" - # Match substring of the attribute value - regexp.is_a?(Regexp) ? regexp : Regexp.new(regexp) - when "|=" then - # Match the first space-separated item of the attribute value - Regexp.new("^#{regexp}($|\s)") - else - raise InvalidSelectorError, "Invalid operation/value" unless value.empty? - # Match all attributes values (existence check) - // - end - end - - - # Returns a lambda that can match an element against the nth-child - # pseudo class, given the following arguments: - # * +a+ -- Value of a part. - # * +b+ -- Value of b part. - # * +of_type+ -- True to test only elements of this type (of-type). - # * +reverse+ -- True to count in reverse order (last-). - def nth_child(a, b, of_type, reverse) - # a = 0 means select at index b, if b = 0 nothing selected - return lambda { |element| false } if a == 0 && b == 0 - # a < 0 and b < 0 will never match against an index - return lambda { |element| false } if a < 0 && b < 0 - b = a + b + 1 if b < 0 # b < 0 just picks last element from each group - b -= 1 unless b == 0 # b == 0 is same as b == 1, otherwise zero based - lambda do |element| - # Element must be inside parent element. - return false unless element.parent && element.parent.tag? - index = 0 - # Get siblings, reverse if counting from last. - siblings = element.parent.children - siblings = siblings.reverse if reverse - # Match element name if of-type, otherwise ignore name. - name = of_type ? element.name : nil - found = false - for child in siblings - # Skip text nodes/comments. - if child.tag? && (name == nil || child.name == name) - if a == 0 - # Shortcut when a == 0 no need to go past count - if index == b - found = child.equal?(element) - break - end - elsif a < 0 - # Only look for first b elements - break if index > b - if child.equal?(element) - found = (index % a) == 0 - break - end - else - # Otherwise, break if child found and count == an+b - if child.equal?(element) - found = (index % a) == b - break - end - end - index += 1 - end - end - found - end - end - - - # Creates a only child lambda. Pass +of-type+ to only look at - # elements of its type. - def only_child(of_type) - lambda do |element| - # Element must be inside parent element. - return false unless element.parent && element.parent.tag? - name = of_type ? element.name : nil - other = false - for child in element.parent.children - # Skip text nodes/comments. - if child.tag? && (name == nil || child.name == name) - unless child.equal?(element) - other = true - break - end - end - end - !other - end - end - - - # Called to create a dependent selector (sibling, descendant, etc). - # Passes the remainder of the statement that will be reduced to zero - # eventually, and array of substitution values. - # - # This method is called from four places, so it helps to put it here - # for reuse. The only logic deals with the need to detect comma - # separators (alternate) and apply them to the selector group of the - # top selector. - def next_selector(statement, values) - second = Selector.new(statement, values) - # If there are alternate selectors, we group them in the top selector. - if alternates = second.instance_variable_get(:@alternates) - second.instance_variable_set(:@alternates, []) - @alternates.concat alternates - end - second - end - - end - - - # See HTML::Selector.new - def self.selector(statement, *values) - Selector.new(statement, *values) - end - - - class Tag - - def select(selector, *values) - selector = HTML::Selector.new(selector, values) - selector.select(self) - end - - end - -end diff --git a/actionpack/lib/action_view/vendor/html-scanner/html/tokenizer.rb b/actionpack/lib/action_view/vendor/html-scanner/html/tokenizer.rb deleted file mode 100644 index 8ac8d34430..0000000000 --- a/actionpack/lib/action_view/vendor/html-scanner/html/tokenizer.rb +++ /dev/null @@ -1,107 +0,0 @@ -require 'strscan' - -module HTML #:nodoc: - - # A simple HTML tokenizer. It simply breaks a stream of text into tokens, where each - # token is a string. Each string represents either "text", or an HTML element. - # - # This currently assumes valid XHTML, which means no free < or > characters. - # - # Usage: - # - # tokenizer = HTML::Tokenizer.new(text) - # while token = tokenizer.next - # p token - # end - class Tokenizer #:nodoc: - - # The current (byte) position in the text - attr_reader :position - - # The current line number - attr_reader :line - - # Create a new Tokenizer for the given text. - def initialize(text) - text.encode! - @scanner = StringScanner.new(text) - @position = 0 - @line = 0 - @current_line = 1 - end - - # Return the next token in the sequence, or +nil+ if there are no more tokens in - # the stream. - def next - return nil if @scanner.eos? - @position = @scanner.pos - @line = @current_line - if @scanner.check(/<\S/) - update_current_line(scan_tag) - else - update_current_line(scan_text) - end - end - - private - - # Treat the text at the current position as a tag, and scan it. Supports - # comments, doctype tags, and regular tags, and ignores less-than and - # greater-than characters within quoted strings. - def scan_tag - tag = @scanner.getch - if @scanner.scan(/!--/) # comment - tag << @scanner.matched - tag << (@scanner.scan_until(/--\s*>/) || @scanner.scan_until(/\Z/)) - elsif @scanner.scan(/!\[CDATA\[/) - tag << @scanner.matched - tag << (@scanner.scan_until(/\]\]>/) || @scanner.scan_until(/\Z/)) - elsif @scanner.scan(/!/) # doctype - tag << @scanner.matched - tag << consume_quoted_regions - else - tag << consume_quoted_regions - end - tag - end - - # Scan all text up to the next < character and return it. - def scan_text - "#{@scanner.getch}#{@scanner.scan(/[^<]*/)}" - end - - # Counts the number of newlines in the text and updates the current line - # accordingly. - def update_current_line(text) - text.scan(/\r?\n/) { @current_line += 1 } - end - - # Skips over quoted strings, so that less-than and greater-than characters - # within the strings are ignored. - def consume_quoted_regions - text = "" - loop do - match = @scanner.scan_until(/['"<>]/) or break - - delim = @scanner.matched - if delim == "<" - match = match.chop - @scanner.pos -= 1 - end - - text << match - break if delim == "<" || delim == ">" - - # consume the quoted region - while match = @scanner.scan_until(/[\\#{delim}]/) - text << match - break if @scanner.matched == delim - break if @scanner.eos? - text << @scanner.getch # skip the escaped character - end - end - text - end - end - -end diff --git a/actionpack/lib/action_view/vendor/html-scanner/html/version.rb b/actionpack/lib/action_view/vendor/html-scanner/html/version.rb deleted file mode 100644 index 6d645c3e14..0000000000 --- a/actionpack/lib/action_view/vendor/html-scanner/html/version.rb +++ /dev/null @@ -1,11 +0,0 @@ -module HTML #:nodoc: - module Version #:nodoc: - - MAJOR = 0 - MINOR = 5 - TINY = 3 - - STRING = [ MAJOR, MINOR, TINY ].join(".") - - end -end |