diff options
author | Aaron Patterson <aaron.patterson@gmail.com> | 2014-01-11 17:28:43 -0800 |
---|---|---|
committer | Aaron Patterson <aaron.patterson@gmail.com> | 2014-01-11 17:28:43 -0800 |
commit | 11e8badb16876e6bd72c874631d25aec41dad293 (patch) | |
tree | 5ec87baa3a8e7652c46d6804b174b81b8f9c7745 /actionpack/lib | |
parent | 474ebc55bd13ad58626a49dfc44c8e6407813935 (diff) | |
parent | caa981d88112f019ade868f75af6b5f399c244a4 (diff) | |
download | rails-11e8badb16876e6bd72c874631d25aec41dad293.tar.gz rails-11e8badb16876e6bd72c874631d25aec41dad293.tar.bz2 rails-11e8badb16876e6bd72c874631d25aec41dad293.zip |
Merge branch 'master' into set_binds
* master: (2794 commits)
doc, API example on how to use `Model#exists?` with multiple IDs. [ci skip]
Restore DATABASE_URL even if it's nil in connection_handler test
[ci skip] - error_messages_for has been deprecated since 2.3.8 - lets reduce any confusion for users
Ensure Active Record connection consistency
Revert "ask the fixture set for the sql statements"
Check `respond_to` before delegation due to: https://github.com/ruby/ruby/commit/d781caaf313b8649948c107bba277e5ad7307314
Adding Hash#compact and Hash#compact! methods
MySQL version 4.1 was EOL on December 31, 2009 We should at least recommend modern versions of MySQL to users.
clear cache on body close so that cache remains during rendering
add a more restricted codepath for templates fixes #13390
refactor generator tests to use block form of Tempfile
Fix typo [ci skip]
Move finish_template as the last public method in the generator
Minor typos fix [ci skip]
make `change_column_null` reversible. Closes #13576.
create/drop test and development databases only if RAILS_ENV is nil
Revert "Speedup String#to"
typo fix in test name. [ci skip].
`core_ext/string/access.rb` test what we are documenting.
Fix typo in image_tag documentation
...
Conflicts:
activerecord/lib/active_record/associations/join_dependency/join_association.rb
activerecord/lib/active_record/relation/query_methods.rb
Diffstat (limited to 'actionpack/lib')
183 files changed, 1071 insertions, 15924 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/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb index 19cfd7dae1..d6c941832f 100644 --- a/actionpack/lib/abstract_controller/callbacks.rb +++ b/actionpack/lib/abstract_controller/callbacks.rb @@ -38,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 @@ -71,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/collector.rb b/actionpack/lib/abstract_controller/collector.rb index 09b9e7ddf0..ddd56b354a 100644 --- a/actionpack/lib/abstract_controller/collector.rb +++ b/actionpack/lib/abstract_controller/collector.rb @@ -23,7 +23,17 @@ module AbstractController protected def method_missing(symbol, &block) - mime_constant = Mime.const_get(symbol.upcase) + const_name = symbol.upcase + + unless Mime.const_defined?(const_name) + raise NoMethodError, "To respond to a custom format, register it as a MIME type first: " \ + "http://guides.rubyonrails.org/action_controller_overview.html#restful-downloads. " \ + "If you meant to respond to a variant like :tablet or :phone, not a custom format, " \ + "be sure to nest your variant response within a format response: " \ + "format.html { |html| html.tablet { ... } }" + end + + mime_constant = Mime.const_get(const_name) if Mime::SET.include?(mime_constant) AbstractController::Collector.generate_method_for_mime(mime_constant) diff --git a/actionpack/lib/abstract_controller/helpers.rb b/actionpack/lib/abstract_controller/helpers.rb index 5ae8c6c3b0..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. @@ -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 8e7bdf620e..0000000000 --- a/actionpack/lib/abstract_controller/layouts.rb +++ /dev/null @@ -1,423 +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"] - default_behavior = "lookup_context.find_all('#{_implied_layout_name}', #{prefixes.inspect}).first || super" - name_clause = if name - default_behavior - else - <<-RUBY - super - RUBY - end - - layout_definition = case _layout - when String - _layout.inspect - when Symbol - <<-RUBY - #{_layout}.tap do |layout| - return #{default_behavior} if layout.nil? - 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 - protected :_layout_from_proc - <<-RUBY - result = _layout_from_proc(#{_layout.arity == 0 ? '' : 'self'}) - return #{default_behavior} if result.nil? - result - RUBY - 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 3f34add790..7be61d94c9 100644 --- a/actionpack/lib/abstract_controller/rendering.rb +++ b/actionpack/lib/abstract_controller/rendering.rb @@ -1,5 +1,7 @@ -require "abstract_controller/base" -require "action_view" +require 'active_support/concern' +require 'active_support/core_ext/class/attribute' +require 'action_view/view_paths' +require 'set' module AbstractController class DoubleRenderError < Error @@ -10,91 +12,18 @@ 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 + include ActionView::ViewPaths # 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) if rendered_format + self.response_body end # Raw rendering of a template to a string. @@ -113,84 +42,76 @@ module AbstractController 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) + # Returns 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 = Set.new %w( + @_action_name @_response_body @_formats @_prefixes @_config + @_view_context_class @_view_renderer @_lookup_context + @_routes @_db_runtime + ).map(&:to_sym) # This method should return a hash with assigns. # You can overwrite this configuration per controller. # :api: public def view_assigns - hash = {} - variables = instance_variables - variables -= protected_instance_variables - variables -= DEFAULT_PROTECTED_INSTANCE_VARIABLES - variables.each { |name| hash[name[1..-1]] = instance_variable_get(name) } - hash - end - - private + protected_vars = _protected_ivars + variables = instance_variables - # Normalize args and options. - # :api: private - def _normalize_render(*args, &block) - options = _normalize_args(*args, &block) - _normalize_options(options) - options + variables.reject! { |s| protected_vars.include? s } + variables.each_with_object({}) { |name, hash| + hash[name.slice(1, name.length)] = instance_variable_get(name) + } 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) + #TODO: remove defined? when we restore AP <=> AV dependency + options[:variant] = request.variant if defined?(request) && request.variant.present? + _normalize_options(options) + options + end + + def _protected_ivars # :nodoc: + DEFAULT_PROTECTED_INSTANCE_VARIABLES 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..50bc26a80f 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -40,33 +40,17 @@ 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/module/attribute_accessors' require 'active_support/core_ext/load_error' require 'active_support/core_ext/module/attr_internal' require 'active_support/core_ext/name_error' diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 971c4189c8..c0f10da23a 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -1,9 +1,10 @@ +require 'action_view' require "action_controller/log_subscriber" require "action_controller/metal/params_wrapper" module ActionController # 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 +60,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 @@ -85,7 +86,7 @@ module ActionController # or you can remove the entire session with +reset_session+. # # Sessions are stored by default in a browser cookie that's cryptographically signed, but unencrypted. - # This prevents the user from tampering with the session but also allows him to see its contents. + # This prevents the user from tampering with the session but also allows them to see its contents. # # Do not put secret information in cookie-based sessions! # @@ -200,7 +201,7 @@ module ActionController end MODULES = [ - AbstractController::Layouts, + AbstractController::Rendering, AbstractController::Translation, AbstractController::AssetPaths, @@ -208,6 +209,7 @@ module ActionController HideActions, UrlFor, Redirecting, + ActionView::Layouts, Rendering, Renderers::All, ConditionalGet, @@ -223,7 +225,6 @@ module ActionController ForceSSL, Streaming, DataStreaming, - RecordIdentifier, HttpAuthentication::Basic::ControllerMethods, HttpAuthentication::Digest::ControllerMethods, HttpAuthentication::Token::ControllerMethods, @@ -249,10 +250,17 @@ module ActionController end # Define some internal variables that should not be propagated to the view. - self.protected_instance_variables = [ + PROTECTED_IVARS = AbstractController::Rendering::DEFAULT_PROTECTED_INSTANCE_VARIABLES + [ :@_status, :@_headers, :@_params, :@_env, :@_response, :@_request, - :@_view_runtime, :@_stream, :@_url_options, :@_action_has_layout - ] + :@_view_runtime, :@_stream, :@_url_options, :@_action_has_layout ] + + def _protected_ivars # :nodoc: + PROTECTED_IVARS + end + + def self.protected_instance_variables + PROTECTED_IVARS + end ActiveSupport.run_load_hooks(:action_controller, self) end 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/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 b8afce42c9..a2cb6d1e66 100644 --- a/actionpack/lib/action_controller/metal/force_ssl.rb +++ b/actionpack/lib/action_controller/metal/force_ssl.rb @@ -48,7 +48,7 @@ module ActionController # 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 a alert message when redirecting + # * <tt>alert</tt> - Set an alert message when redirecting # * <tt>notice</tt> - Set a notice message when redirecting # # ==== Action Options diff --git a/actionpack/lib/action_controller/metal/head.rb b/actionpack/lib/action_controller/metal/head.rb index 8237db15ca..43407f5b78 100644 --- a/actionpack/lib/action_controller/metal/head.rb +++ b/actionpack/lib/action_controller/metal/head.rb @@ -1,8 +1,6 @@ module ActionController module Head - extend ActiveSupport::Concern - - # Return a response that has no content (merely headers). The options + # Returns 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 # significant headers: diff --git a/actionpack/lib/action_controller/metal/helpers.rb b/actionpack/lib/action_controller/metal/helpers.rb index 243fd40a7e..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 diff --git a/actionpack/lib/action_controller/metal/instrumentation.rb b/actionpack/lib/action_controller/metal/instrumentation.rb index d3aa8f90c5..b0e164bc57 100644 --- a/actionpack/lib/action_controller/metal/instrumentation.rb +++ b/actionpack/lib/action_controller/metal/instrumentation.rb @@ -67,7 +67,7 @@ module ActionController private - # A hook invoked everytime a before callback is halted. + # A hook invoked every time a before callback is halted. def halted_callback_hook(filter) ActiveSupport::Notifications.instrument("halted_callback.action_controller", :filter => filter) end diff --git a/actionpack/lib/action_controller/metal/live.rb b/actionpack/lib/action_controller/metal/live.rb index 8092fd639f..33014b97ca 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,6 +33,79 @@ 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 across 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 @@ -160,7 +234,7 @@ module ActionController def response_body=(body) super - response.stream.close if response + response.close if response end def set_response!(request) diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb index 834d44f045..d5e08b7034 100644 --- a/actionpack/lib/action_controller/metal/mime_responds.rb +++ b/actionpack/lib/action_controller/metal/mime_responds.rb @@ -181,6 +181,61 @@ module ActionController #:nodoc: # end # end # + # Formats can have different variants. + # + # The request variant is a specialization of the request format, like <tt>:tablet</tt>, + # <tt>:phone</tt>, or <tt>:desktop</tt>. + # + # We often want to render different html/json/xml templates for phones, + # tablets, and desktop browsers. Variants make it easy. + # + # You can set the variant in a +before_action+: + # + # request.variant = :tablet if request.user_agent =~ /iPad/ + # + # Respond to variants in the action just like you respond to formats: + # + # respond_to do |format| + # format.html do |variant| + # variant.tablet # renders app/views/projects/show.html+tablet.erb + # variant.phone { extra_setup; render ... } + # variant.none { special_setup } # executed only if there is no variant set + # end + # end + # + # Provide separate templates for each format and variant: + # + # app/views/projects/show.html.erb + # app/views/projects/show.html+tablet.erb + # app/views/projects/show.html+phone.erb + # + # When you're not sharing any code within the format, you can simplify defining variants + # using the inline syntax: + # + # respond_to do |format| + # format.js { render "trash" } + # format.html.phone { redirect_to progress_path } + # format.html.none { render "trash" } + # end + # + # Variants also support common `any`/`all` block that formats have. + # + # It works for both inline: + # + # respond_to do |format| + # format.html.any { render text: "any" } + # format.html.phone { render text: "phone" } + # end + # + # and block syntax: + # + # respond_to do |format| + # format.html do |variant| + # variant.any(:tablet, :phablet){ render text: "any" } + # variant.phone { render text: "phone" } + # end + # end + # # Be sure to check the documentation of +respond_with+ and # <tt>ActionController::MimeResponds.respond_to</tt> for more examples. def respond_to(*mimes, &block) @@ -260,7 +315,7 @@ module ActionController #:nodoc: # * for other requests - i.e. data formats such as xml, json, csv etc, if # the resource passed to +respond_with+ responds to <code>to_<format></code>, # the method attempts to render the resource in the requested format - # directly, e.g. for an xml request, the response is equivalent to calling + # directly, e.g. for an xml request, the response is equivalent to calling # <code>render xml: resource</code>. # # === Nested resources @@ -321,11 +376,14 @@ module ActionController #:nodoc: # 2. <tt>:action</tt> - overwrites the default render action used after an # unsuccessful html +post+ request. def respond_with(*resources, &block) - raise "In order to use respond_with, first you need to declare the formats your " \ - "controller responds to in the class level" if self.class.mimes_for_respond_to.empty? + if self.class.mimes_for_respond_to.empty? + raise "In order to use respond_with, first you need to declare the " \ + "formats your controller responds to in the class level." + end 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 @@ -359,14 +417,12 @@ module ActionController #:nodoc: # is available. def retrieve_collector_from_mimes(mimes=nil, &block) #:nodoc: mimes ||= collect_mimes_from_class_level - collector = Collector.new(mimes) + collector = Collector.new(mimes, request.variant) block.call(collector) if block_given? 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 @@ -397,11 +453,13 @@ module ActionController #:nodoc: # request, with this response then being accessible by calling #response. class Collector include AbstractController::Collector - attr_accessor :order, :format + attr_accessor :format + + def initialize(mimes, variant = nil) + @responses = {} + @variant = variant - def initialize(mimes) - @order, @responses = [], {} - mimes.each { |mime| send(mime) } + mimes.each { |mime| @responses["Mime::#{mime.upcase}".constantize] = nil } end def any(*args, &block) @@ -415,16 +473,62 @@ module ActionController #:nodoc: def custom(mime_type, &block) mime_type = Mime::Type.lookup(mime_type.to_s) unless mime_type.is_a?(Mime::Type) - @order << mime_type - @responses[mime_type] ||= block + @responses[mime_type] ||= if block_given? + block + else + VariantCollector.new(@variant) + end end def response - @responses.fetch(format, @responses[Mime::ALL]) + response = @responses.fetch(format, @responses[Mime::ALL]) + if response.is_a?(VariantCollector) # `format.html.phone` - variant inline syntax + response.variant + elsif response.nil? || response.arity == 0 # `format.html` - just a format, call its block + response + else # `format.html{ |variant| variant.phone }` - variant block syntax + variant_collector = VariantCollector.new(@variant) + response.call(variant_collector) #call format block with variants collector + variant_collector.variant + end end def negotiate_format(request) - @format = request.negotiate_mime(order) + @format = request.negotiate_mime(@responses.keys) + end + + class VariantCollector #:nodoc: + def initialize(variant = nil) + @variant = variant + @variants = {} + end + + def any(*args, &block) + if block_given? + if args.any? && args.none?{ |a| a == @variant } + args.each{ |v| @variants[v] = block } + else + @variants[:any] = block + end + end + end + alias :all :any + + def method_missing(name, *args, &block) + @variants[name] = block if block_given? + end + + def variant + key = if @variant.nil? + :none + elsif @variants.has_key?(@variant) + @variant + else + :any + end + + @variants[key] + end end end end diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb index e9031f3fac..2812038938 100644 --- a/actionpack/lib/action_controller/metal/redirecting.rb +++ b/actionpack/lib/action_controller/metal/redirecting.rb @@ -58,7 +58,7 @@ module ActionController # redirect_to post_url(@post), alert: "Watch it, mister!" # redirect_to post_url(@post), status: :found, notice: "Pay attention to the road" # redirect_to post_url(@post), status: 301, flash: { updated_post_id: @post.id } - # redirect_to { action: 'atom' }, alert: "Something serious happened" + # redirect_to({ action: 'atom' }, alert: "Something serious happened") # # When using <tt>redirect_to :back</tt>, if there is no referrer, ActionController::RedirectBackError will be raised. You may specify some fallback # behavior for this case by rescuing ActionController::RedirectBackError. @@ -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..6c7b4652d4 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 @@ -37,7 +43,7 @@ module ActionController end # Hash of available renderers, mapping a renderer name to its proc. - # Default keys are :json, :js, :xml. + # Default keys are <tt>:json</tt>, <tt>:js</tt>, <tt>:xml</tt>. RENDERERS = Set.new # Adds a new renderer to call within controller actions. diff --git a/actionpack/lib/action_controller/metal/rendering.rb b/actionpack/lib/action_controller/metal/rendering.rb index bea6b88f91..5c48b4ab98 100644 --- a/actionpack/lib/action_controller/metal/rendering.rb +++ b/actionpack/lib/action_controller/metal/rendering.rb @@ -2,8 +2,6 @@ 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(&:ref).compact @@ -12,29 +10,33 @@ module ActionController # 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 || options[:text].presence || ' ' 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 573c739da4..c88074d4c6 100644 --- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb +++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb @@ -5,14 +5,24 @@ module ActionController #:nodoc: class InvalidAuthenticityToken < ActionControllerError #:nodoc: end + class InvalidCrossOriginRequest < ActionControllerError #:nodoc: + end + # Controller actions are protected from Cross-Site Request Forgery (CSRF) attacks # by including a token in the rendered html for your application. This token is # stored as a random string in the session, to which an attacker does not have # access. When a request reaches your application, \Rails verifies the received # token with the token in the session. Only HTML and JavaScript requests are checked, # so this will not protect your XML API (presumably you'll have a different - # authentication scheme there anyway). Also, GET requests are not protected as these - # should be idempotent. + # authentication scheme there anyway). + # + # GET requests are not protected since they don't have side effects like writing + # to the database and don't leak sensitive information. JavaScript requests are + # an exception: a third-party site can use a <script> tag to reference a JavaScript + # URL on your site. When your JavaScript response loads on their site, it executes. + # With carefully crafted JavaScript on their end, sensitive data in your JavaScript + # response may be extracted. To prevent this, only XmlHttpRequest (known as XHR or + # Ajax) requests are allowed to make GET requests for JavaScript responses. # # It's important to remember that XML or JSON requests are also affected and if # you're building an API you'll need something like: @@ -65,17 +75,16 @@ module ActionController #:nodoc: module ClassMethods # Turn on request forgery protection. Bear in mind that only non-GET, HTML/JavaScript requests are checked. # + # class ApplicationController < ActionController::Base + # protect_from_forgery + # end + # # class FooController < ApplicationController # protect_from_forgery except: :index # - # You can disable csrf protection on controller-by-controller basis: - # + # You can disable CSRF protection on controller by skipping the verification before_action: # skip_before_action :verify_authenticity_token # - # It can also be disabled for specific controller actions: - # - # skip_before_action :verify_authenticity_token, except: [:create] - # # Valid Options: # # * <tt>:only/:except</tt> - Passed to the <tt>before_action</tt> call. Set which actions are verified. @@ -89,6 +98,7 @@ module ActionController #:nodoc: self.forgery_protection_strategy = protection_method_class(options[:with] || :null_session) self.request_forgery_protection_token ||= :authenticity_token prepend_before_action :verify_authenticity_token, options + append_after_action :verify_same_origin_request end private @@ -124,6 +134,9 @@ module ActionController #:nodoc: @loaded = true end + # no-op + def destroy; end + def exists? true end @@ -166,18 +179,61 @@ module ActionController #:nodoc: end protected + # The actual before_action that is used to verify the CSRF token. + # Don't override this directly. Provide your own forgery protection + # strategy instead. If you override, you'll disable same-origin + # `<script>` verification. + # + # Lean on the protect_from_forgery declaration to mark which actions are + # due for same-origin request verification. If protect_from_forgery is + # enabled on an action, this before_action flags its after_action to + # verify that JavaScript responses are for XHR requests, ensuring they + # follow the browser's same-origin policy. + def verify_authenticity_token + mark_for_same_origin_verification! + + if !verified_request? + logger.warn "Can't verify CSRF token authenticity" if logger + handle_unverified_request + end + end + def handle_unverified_request forgery_protection_strategy.new(self).handle_unverified_request end - # The actual before_action that is used. Modify this to change how you handle unverified requests. - def verify_authenticity_token - unless verified_request? - logger.warn "Can't verify CSRF token authenticity" if logger - handle_unverified_request + CROSS_ORIGIN_JAVASCRIPT_WARNING = "Security warning: an embedded " \ + "<script> tag on another site requested protected JavaScript. " \ + "If you know what you're doing, go ahead and disable forgery " \ + "protection on this action to permit cross-origin JavaScript embedding." + private_constant :CROSS_ORIGIN_JAVASCRIPT_WARNING + + # If `verify_authenticity_token` was run (indicating that we have + # forgery protection enabled for this request) then also verify that + # we aren't serving an unauthorized cross-origin response. + def verify_same_origin_request + if marked_for_same_origin_verification? && non_xhr_javascript_response? + logger.warn CROSS_ORIGIN_JAVASCRIPT_WARNING if logger + raise ActionController::InvalidCrossOriginRequest, CROSS_ORIGIN_JAVASCRIPT_WARNING end end + # GET requests are checked for cross-origin JavaScript after rendering. + def mark_for_same_origin_verification! + @marked_for_same_origin_verification = request.get? + end + + # If the `verify_authenticity_token` before_action ran, verify that + # JavaScript responses are only served to same-origin GET requests. + def marked_for_same_origin_verification? + @marked_for_same_origin_verification ||= false + end + + # Check for cross-origin JavaScript responses. + def non_xhr_javascript_response? + content_type =~ %r(\Atext/javascript) && !request.xhr? + end + # Returns true or false if a request is verified. Checks: # # * is it a GET or HEAD request? Gets should be safe and idempotent diff --git a/actionpack/lib/action_controller/metal/responder.rb b/actionpack/lib/action_controller/metal/responder.rb index 891819968b..e24b56fa91 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 necessary 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 44703221f3..48a916f2b1 100644 --- a/actionpack/lib/action_controller/metal/strong_parameters.rb +++ b/actionpack/lib/action_controller/metal/strong_parameters.rb @@ -3,6 +3,7 @@ require 'active_support/core_ext/array/wrap' require 'active_support/rescuable' require 'action_dispatch/http/upload' require 'stringio' +require 'set' module ActionController # Raised when a required parameter is missing. @@ -17,7 +18,7 @@ module ActionController def initialize(param) # :nodoc: @param = param - super("param not found: #{param}") + super("param is missing or the value is empty: #{param}") end end @@ -125,6 +126,13 @@ module ActionController @permitted = self.class.permit_all_parameters end + # Attribute that keeps track of converted arrays, if any, to avoid double + # looping in the common use case permit + mass-assignment. Defined in a + # method to instantiate it only if needed. + def converted_arrays + @converted_arrays ||= Set.new + end + # Returns +true+ if the parameter is permitted, +false+ otherwise. # # params = ActionController::Parameters.new @@ -149,8 +157,10 @@ module ActionController # Person.new(params) # => #<Person id: nil, name: "Francesco"> def permit! each_pair do |key, value| - convert_hashes_to_parameters(key, value) - self[key].permit! if self[key].respond_to? :permit! + value = convert_hashes_to_parameters(key, value) + Array.wrap(value).each do |_| + _.permit! if _.respond_to? :permit! + end end @permitted = true @@ -201,6 +211,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: @@ -283,7 +294,7 @@ module ActionController # params.fetch(:none, 'Francesco') # => "Francesco" # params.fetch(:none) { 'Francesco' } # => "Francesco" def fetch(key, *args) - convert_hashes_to_parameters(key, super) + convert_hashes_to_parameters(key, super, false) rescue KeyError raise ActionController::ParameterMissing.new(key) end @@ -297,7 +308,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 @@ -311,24 +322,38 @@ 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) + def convert_hashes_to_parameters(key, value, assign_if_converted=true) + converted = convert_value_to_parameters(value) + self[key] = converted if assign_if_converted && !converted.equal?(value) + converted + end + + def convert_value_to_parameters(value) + if value.is_a?(Array) && !converted_arrays.member?(value) + converted = value.map { |_| convert_value_to_parameters(_) } + converted_arrays << converted + converted + elsif value.is_a?(Parameters) || !value.is_a?(Hash) value else - # Convert to Parameters on first access - self[key] = self.class.new(value) + self.class.new(value) end end 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 @@ -337,6 +362,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? @@ -415,7 +444,7 @@ 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: [] }. diff --git a/actionpack/lib/action_controller/railtie.rb b/actionpack/lib/action_controller/railtie.rb index 5379547c57..a2fc814221 100644 --- a/actionpack/lib/action_controller/railtie.rb +++ b/actionpack/lib/action_controller/railtie.rb @@ -1,9 +1,9 @@ require "rails" require "action_controller" require "action_dispatch/railtie" -require "action_view/railtie" require "abstract_controller/railties/routes_helpers" require "action_controller/railties/helpers" +require "action_view/railtie" module ActionController class Railtie < Rails::Railtie #:nodoc: 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 0cbbbbe1fd..5ed3d2ebc1 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -524,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 @@ -628,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 24a3d4741e..920e651b08 100644 --- a/actionpack/lib/action_dispatch.rb +++ b/actionpack/lib/action_dispatch.rb @@ -1,5 +1,5 @@ #-- -# Copyright (c) 2004-2013 David Heinemeier Hansson +# Copyright (c) 2004-2014 David Heinemeier Hansson # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb index 40bb060d52..c33ba201e1 100644 --- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb +++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb @@ -10,6 +10,8 @@ module ActionDispatch self.ignore_accept_header = false end + attr_reader :variant + # The MIME type of the HTTP request, such as Mime::XML. # # For backward compatibility, the post \format is extracted from the @@ -48,7 +50,7 @@ module ActionDispatch # GET /posts/5 | request.format => Mime::HTML or MIME::JS, or request.accepts.first # def format(view_path = []) - formats.first + formats.first || Mime::NullType.instance end def formats @@ -64,6 +66,18 @@ module ActionDispatch end end + # Sets the \variant for template. + def variant=(variant) + if variant.is_a? Symbol + @variant = variant + else + raise ArgumentError, "request.variant must be set to a Symbol, not a #{variant.class}. " \ + "For security reasons, never directly set the variant to a user-provided value, " \ + "like params[:variant].to_sym. Check user-provided value against a whitelist first, " \ + "then set the variant: request.variant = :tablet if params[:variant] == 'tablet'" + end + end + # Sets the \format by string extension, which can be used to force custom formats # that are not controlled by the extension. # diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb index f29ad359ac..3d2dd2d632 100644 --- a/actionpack/lib/action_dispatch/http/mime_type.rb +++ b/actionpack/lib/action_dispatch/http/mime_type.rb @@ -1,5 +1,6 @@ require 'set' -require 'active_support/core_ext/class/attribute_accessors' +require 'singleton' +require 'active_support/core_ext/module/attribute_accessors' require 'active_support/core_ext/string/starts_ends_with' module Mime @@ -27,7 +28,7 @@ module Mime class << self def [](type) return type if type.is_a?(Type) - Type.lookup_by_extension(type) || NullType.new + Type.lookup_by_extension(type) end def fetch(type) @@ -53,10 +54,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 +176,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| @@ -272,18 +269,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 @@ -308,13 +293,13 @@ module Mime end class NullType + include Singleton + def nil? true end - def ref - nil - end + def ref; end def respond_to_missing?(method, include_private = false) method.to_s.ends_with? '?' diff --git a/actionpack/lib/action_dispatch/http/mime_types.rb b/actionpack/lib/action_dispatch/http/mime_types.rb index a6b3aee5e7..0e4da36038 100644 --- a/actionpack/lib/action_dispatch/http/mime_types.rb +++ b/actionpack/lib/action_dispatch/http/mime_types.rb @@ -7,6 +7,7 @@ Mime::Type.register "text/javascript", :js, %w( application/javascript applicati Mime::Type.register "text/css", :css Mime::Type.register "text/calendar", :ics Mime::Type.register "text/csv", :csv +Mime::Type.register "text/vcard", :vcf Mime::Type.register "image/png", :png, [], %w(png) Mime::Type.register "image/jpeg", :jpeg, [], %w(jpg jpeg jpe pjpeg) diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb index 20c24ddd85..dcb299ed03 100644 --- a/actionpack/lib/action_dispatch/http/parameters.rb +++ b/actionpack/lib/action_dispatch/http/parameters.rb @@ -57,26 +57,25 @@ module ActionDispatch # you'll get a weird error down the road, but our form handling # should really prevent that from happening def normalize_encode_params(params) - if params.is_a?(String) - return params.force_encoding(Encoding::UTF_8).encode! - elsif !params.is_a?(Hash) - return params - end - - new_hash = {} - params.each do |k, v| - new_key = k.is_a?(String) ? k.dup.force_encoding(Encoding::UTF_8).encode! : k - new_hash[new_key] = - case v - when Hash - normalize_encode_params(v) - when Array - v.map! {|el| normalize_encode_params(el) } - else - normalize_encode_params(v) - end + case params + when String + params.force_encoding(Encoding::UTF_8).encode! + when Hash + if params.has_key?(:tempfile) + UploadedFile.new(params) + else + 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 + else + params end - new_hash.with_indifferent_access end end end diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index ebd87c40b5..1318c62fbe 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(%.*)?$/] @@ -225,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'] @@ -271,7 +271,7 @@ module ActionDispatch # Override Rack's GET method to support indifferent access def GET - @env["action_dispatch.request.query_parameters"] ||= (normalize_encode_params(super) || {}) + @env["action_dispatch.request.query_parameters"] ||= Utils.deep_munge((normalize_encode_params(super) || {})) rescue TypeError => e raise ActionController::BadRequest.new(:query, e) end @@ -279,7 +279,7 @@ module ActionDispatch # Override Rack's POST method to support indifferent access def POST - @env["action_dispatch.request.request_parameters"] ||= (normalize_encode_params(super) || {}) + @env["action_dispatch.request.request_parameters"] ||= Utils.deep_munge((normalize_encode_params(super) || {})) rescue TypeError => e raise ActionController::BadRequest.new(:request, e) end @@ -299,33 +299,24 @@ module ActionDispatch LOCALHOST =~ remote_addr && LOCALHOST =~ remote_ip end - # Remove nils from the params hash + # Extracted into ActionDispatch::Request::Utils.deep_munge, but kept here for backwards compatibility. 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 + ActiveSupport::Deprecation.warn( + "This method has been extracted into ActionDispatch::Request::Utils.deep_munge. Please start using that instead." + ) - hash + Utils.deep_munge(hash) end protected - - def parse_query(qs) - deep_munge(super) - end + def parse_query(qs) + Utils.deep_munge(super) + end private - - def check_method(name) - HTTP_METHOD_LOOKUP[name] || raise(ActionController::UnknownHttpMethod, "#{name}, accepted HTTP methods are #{HTTP_METHODS.to_sentence(:locale => :en)}") - name - end + def check_method(name) + HTTP_METHOD_LOOKUP[name] || raise(ActionController::UnknownHttpMethod, "#{name}, accepted HTTP methods are #{HTTP_METHODS.to_sentence(:locale => :en)}") + name + end end end diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb index 60a2cccdc5..7b2655b2d8 100644 --- a/actionpack/lib/action_dispatch/http/response.rb +++ b/actionpack/lib/action_dispatch/http/response.rb @@ -1,4 +1,4 @@ -require 'active_support/core_ext/class/attribute_accessors' +require 'active_support/core_ext/module/attribute_accessors' require 'monitor' module ActionDispatch # :nodoc: @@ -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,9 +56,12 @@ 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 @@ -93,6 +103,7 @@ module ActionDispatch # :nodoc: end end + # The underlying body, as a streamable object. attr_reader :stream def initialize(status = 200, header = {}, body = []) @@ -142,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 @@ -169,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 @@ -198,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 @@ -216,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 @@ -229,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 # diff --git a/actionpack/lib/action_dispatch/http/upload.rb b/actionpack/lib/action_dispatch/http/upload.rb index b57c84dec8..a8d2dc3950 100644 --- a/actionpack/lib/action_dispatch/http/upload.rb +++ b/actionpack/lib/action_dispatch/http/upload.rb @@ -73,18 +73,5 @@ module ActionDispatch filename.force_encoding(Encoding::UTF_8).encode! if filename end end - - module Upload # :nodoc: - # Replace file upload hash with UploadedFile objects - # when normalize and encode parameters. - def normalize_encode_params(value) - if Hash === value && value.has_key?(:tempfile) - UploadedFile.new(value) - else - super - end - end - private :normalize_encode_params - end end end diff --git a/actionpack/lib/action_dispatch/journey/formatter.rb b/actionpack/lib/action_dispatch/journey/formatter.rb index e288f026c7..4410c1b5d5 100644 --- a/actionpack/lib/action_dispatch/journey/formatter.rb +++ b/actionpack/lib/action_dispatch/journey/formatter.rb @@ -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? @@ -29,8 +33,8 @@ module ActionDispatch return [route.format(parameterized_parts), params] end - message = "No route matches #{constraints.inspect}" - message << " missing required keys: #{missing_keys.inspect}" if name + message = "No route matches #{Hash[constraints.sort].inspect}" + message << " missing required keys: #{missing_keys.sort.inspect}" if name raise ActionController::UrlGenerationError, message 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..5a79059ed6 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 @@ -43,9 +43,7 @@ module ActionDispatch move_string(t, a).concat(move_regexp(t, a)) end - def to_json - require 'json' - + def as_json(options = nil) simple_regexp = Hash.new { |h,k| h[k] = {} } @regexp_states.each do |from, hash| @@ -54,11 +52,11 @@ module ActionDispatch end end - JSON.dump({ + { regexp_states: simple_regexp, string_states: @string_states, accepting: @accepting - }) + } end def to_svg @@ -111,14 +109,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 +129,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.rb b/actionpack/lib/action_dispatch/journey/parser.rb index bb4cbb00e2..430812fafe 100644 --- a/actionpack/lib/action_dispatch/journey/parser.rb +++ b/actionpack/lib/action_dispatch/journey/parser.rb @@ -1,7 +1,7 @@ # # DO NOT MODIFY!!!! # This file is automatically generated by Racc 1.4.9 -# from Racc grammer file "". +# from Racc grammar file "". # require 'racc/parser.rb' diff --git a/actionpack/lib/action_dispatch/journey/route.rb b/actionpack/lib/action_dispatch/journey/route.rb index 50e1853094..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) diff --git a/actionpack/lib/action_dispatch/journey/router.rb b/actionpack/lib/action_dispatch/journey/router.rb index 419e665d12..da32f1bfe7 100644 --- a/actionpack/lib/action_dispatch/journey/router.rb +++ b/actionpack/lib/action_dispatch/journey/router.rb @@ -54,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', @@ -103,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..daade5bb74 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) @@ -74,54 +77,73 @@ module ActionDispatch end end - class OptimizedPath < String # :nodoc: + class OptimizedPath < Visitor # :nodoc: + def accept(node) + Array(visit(node)) + end + private - def visit_GROUP(node) - "" - end + def visit_CAT(node) + [visit(node.left), visit(node.right)].flatten + end + + def visit_SYMBOL(node) + node.left[1..-1].to_sym + end + + def visit_STAR(node) + visit(node.left) + end + + def visit_GROUP(node) + [] + end + + %w{ LITERAL SLASH DOT }.each do |t| + class_eval %{ def visit_#{t}(n); n.left; end }, __FILE__, __LINE__ + end end # 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/callbacks.rb b/actionpack/lib/action_dispatch/middleware/callbacks.rb index 852f1cf6f5..baf9d5779e 100644 --- a/actionpack/lib/action_dispatch/middleware/callbacks.rb +++ b/actionpack/lib/action_dispatch/middleware/callbacks.rb @@ -8,14 +8,14 @@ module ActionDispatch class << self delegate :to_prepare, :to_cleanup, :to => "ActionDispatch::Reloader" - end - def self.before(*args, &block) - set_callback(:call, :before, *args, &block) - end + def before(*args, &block) + set_callback(:call, :before, *args, &block) + end - def self.after(*args, &block) - set_callback(:call, :after, *args, &block) + def after(*args, &block) + set_callback(:call, :after, *args, &block) + end end def initialize(app) diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb index 5b914f293d..fe110d7938 100644 --- a/actionpack/lib/action_dispatch/middleware/cookies.rb +++ b/actionpack/lib/action_dispatch/middleware/cookies.rb @@ -30,7 +30,7 @@ module ActionDispatch # cookies[:login] = { value: "XJ-122", expires: 1.hour.from_now } # # # 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. + # # The cookie is signed by your app's <tt>secrets.secret_key_base</tt> value. # # It can be read using the signed method <tt>cookies.signed[:name]</tt> # cookies.signed[:user_id] = current_user.id # @@ -77,7 +77,7 @@ module ActionDispatch # 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+. @@ -117,10 +117,10 @@ module ActionDispatch # 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, + # If +secrets.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+. + # This jar requires that you set a suitable secret for the verification on your app's +secrets.secret_key_base+. # # Example: # @@ -140,10 +140,10 @@ module ActionDispatch # 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, + # If +secrets.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+. + # This jar requires that you set a suitable secret for the verification on your app's +secrets.secret_key_base+. # # Example: # @@ -160,7 +160,7 @@ module ActionDispatch end end - # Returns the +signed+ or +encrypted jar, preferring +encrypted+ if +secret_key_base+ is set. + # 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 ||= @@ -409,7 +409,7 @@ module ActionDispatch end # UpgradeLegacySignedCookieJar is used instead of SignedCookieJar if - # config.secret_token and config.secret_key_base are both set. It reads + # config.secret_token and secrets.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: @@ -427,7 +427,7 @@ module ActionDispatch def initialize(parent_jar, key_generator, options = {}) if ActiveSupport::LegacyKeyGenerator === key_generator - raise "You didn't set config.secret_key_base, which is required for this cookie jar. " + + raise "You didn't set secrets.secret_key_base, which is required for this cookie jar. " + "Read the upgrade documentation to learn more about this new config option." end @@ -465,7 +465,7 @@ module ActionDispatch end # UpgradeLegacyEncryptedCookieJar is used by ActionDispatch::Session::CookieStore - # instead of EncryptedCookieJar if config.secret_token and config.secret_key_base + # instead of EncryptedCookieJar if config.secret_token and secrets.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: 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 1de3d14530..377f05c982 100644 --- a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb +++ b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb @@ -1,5 +1,5 @@ require 'action_controller/metal/exceptions' -require 'active_support/core_ext/class/attribute_accessors' +require 'active_support/core_ext/module/attribute_accessors' module ActionDispatch class ExceptionWrapper @@ -96,7 +96,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/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/remote_ip.rb b/actionpack/lib/action_dispatch/middleware/remote_ip.rb index 8879291dbd..57bc6d5cd0 100644 --- a/actionpack/lib/action_dispatch/middleware/remote_ip.rb +++ b/actionpack/lib/action_dispatch/middleware/remote_ip.rb @@ -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/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb index b9eb8036e9..1ebc189c28 100644 --- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb @@ -15,8 +15,8 @@ module ActionDispatch # best possible option given your application's configuration. # # 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 + # not encrypted. This means a user cannot alter their +user_id+ without + # knowing your app's secret key, but can easily read their +user_id+. This # was the default for Rails 3 apps. # # If you have secret_key_base set, your cookies will be encrypted. This @@ -31,9 +31,10 @@ module ActionDispatch # # Myapp::Application.config.session_store :cookie_store, key: '_your_app_session' # - # Configure your secret key in config/initializers/secret_token.rb: + # Configure your secret key in config/secrets.yml: # - # Myapp::Application.config.secret_key_base 'secret key' + # development: + # secret_key_base: 'secret key' # # To generate a secret key for an existing application, run `rake secret`. # 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/static.rb b/actionpack/lib/action_dispatch/middleware/static.rb index c6a7d9c415..2764584fe9 100644 --- a/actionpack/lib/action_dispatch/middleware/static.rb +++ b/actionpack/lib/action_dispatch/middleware/static.rb @@ -11,9 +11,10 @@ module ActionDispatch end def match?(path) - path = path.dup + path = unescape_path(path) + return false unless path.valid_encoding? - full_path = path.empty? ? @root : File.join(@root, escape_glob_chars(unescape_path(path))) + full_path = path.empty? ? @root : File.join(@root, escape_glob_chars(path)) paths = "#{full_path}#{ext}" matches = Dir[paths] @@ -40,7 +41,6 @@ module ActionDispatch end def escape_glob_chars(path) - path.force_encoding('binary') if path.respond_to? :force_encoding path.gsub(/[*?{}\[\]]/, "\\\\\\&") end end 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 db219c8fa9..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 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 b181909bff..b181909bff 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb 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/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 31f46ee340..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 @@ -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/middleware/templates/routes/_table.html.erb b/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb index 95461fa693..323873ba4b 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb @@ -89,8 +89,8 @@ } // takes an array of elements with a data-regexp attribute and - // passes their their parent <tr> into the callback function - // if the regexp matchs a given path + // passes their parent <tr> into the callback function + // if the regexp matches a given path function eachElemsForPath(elems, path, func) { each(elems, function(e){ var reg = e.getAttribute("data-regexp"); diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb index edf37bb9a5..ddeea24bb3 100644 --- a/actionpack/lib/action_dispatch/railtie.rb +++ b/actionpack/lib/action_dispatch/railtie.rb @@ -16,12 +16,12 @@ module ActionDispatch config.action_dispatch.signed_cookie_salt = 'signed cookie' config.action_dispatch.encrypted_cookie_salt = 'encrypted cookie' config.action_dispatch.encrypted_signed_cookie_salt = 'signed encrypted cookie' + config.action_dispatch.perform_deep_munge = true 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 @@ -29,6 +29,7 @@ module ActionDispatch initializer "action_dispatch.configure" do |app| ActionDispatch::Http::URL.tld_length = app.config.action_dispatch.tld_length ActionDispatch::Request.ignore_accept_header = app.config.action_dispatch.ignore_accept_header + ActionDispatch::Request::Utils.perform_deep_munge = app.config.action_dispatch.perform_deep_munge ActionDispatch::Response.default_charset = app.config.action_dispatch.default_charset || app.config.encoding ActionDispatch::Response.default_headers = app.config.action_dispatch.default_headers diff --git a/actionpack/lib/action_dispatch/request/session.rb b/actionpack/lib/action_dispatch/request/session.rb index 7bc812fd22..973627f106 100644 --- a/actionpack/lib/action_dispatch/request/session.rb +++ b/actionpack/lib/action_dispatch/request/session.rb @@ -7,6 +7,9 @@ module ActionDispatch ENV_SESSION_KEY = Rack::Session::Abstract::ENV_SESSION_KEY # :nodoc: ENV_SESSION_OPTIONS_KEY = Rack::Session::Abstract::ENV_SESSION_OPTIONS_KEY # :nodoc: + # Singleton object used to determine if an optional param wasn't specified + Unspecified = Object.new + def self.create(store, env, default_options) session_was = find env session = Request::Session.new(store, env) @@ -127,6 +130,15 @@ module ActionDispatch @delegate.delete key.to_s end + def fetch(key, default=Unspecified, &block) + load_for_read! + if default == Unspecified + @delegate.fetch(key.to_s, &block) + else + @delegate.fetch(key.to_s, default, &block) + 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..a6dca9741c --- /dev/null +++ b/actionpack/lib/action_dispatch/request/utils.rb @@ -0,0 +1,30 @@ +module ActionDispatch + class Request < Rack::Request + class Utils # :nodoc: + + mattr_accessor :perform_deep_munge + self.perform_deep_munge = true + + class << self + # Remove nils from the params hash + def deep_munge(hash) + return hash unless perform_deep_munge + + 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 550c7d0e7b..a9ac2bce1d 100644 --- a/actionpack/lib/action_dispatch/routing.rb +++ b/actionpack/lib/action_dispatch/routing.rb @@ -246,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..f612e91aef 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|mailers|welcome)} || path =~ %r{\A#{Rails.application.config.assets.prefix}\z} end def engine? @@ -179,7 +179,8 @@ module ActionDispatch private def draw_section(routes) - name_width, verb_width, path_width = widths(routes) + header_lengths = ['Prefix', 'Verb', 'URI Pattern'].map(&:length) + name_width, verb_width, path_width = widths(routes).zip(header_lengths).map(&:max) routes.map do |r| "#{r[:name].rjust(name_width)} #{r[:verb].ljust(verb_width)} #{r[:path].ljust(path_width)} #{r[:reqs]}" diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 3c58a2cfc3..18f37dc732 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -3,6 +3,7 @@ require 'active_support/core_ext/hash/reverse_merge' require 'active_support/core_ext/hash/slice' require 'active_support/core_ext/enumerable' require 'active_support/core_ext/array/extract_options' +require 'active_support/core_ext/module/remove_method' require 'active_support/inflector' require 'action_dispatch/routing/redirection' @@ -11,8 +12,8 @@ module ActionDispatch class Mapper URL_OPTIONS = [:protocol, :subdomain, :domain, :host, :port] SCOPE_OPTIONS = [:path, :shallow_path, :as, :shallow_prefix, :module, - :controller, :path_names, :constraints, :defaults, - :shallow, :blocks, :options] + :controller, :action, :path_names, :constraints, + :shallow, :blocks, :defaults, :options] class Constraints #:nodoc: def self.new(app, constraints, request = Rack::Request) @@ -147,14 +148,16 @@ module ActionDispatch @defaults.merge!(options[:defaults]) if options[:defaults] options.each do |key, default| - next if Regexp === default || IGNORE_OPTIONS.include?(key) - @defaults[key] = default + unless Regexp === default || IGNORE_OPTIONS.include?(key) + @defaults[key] = default + end end if options[:constraints].is_a?(Hash) options[:constraints].each do |key, default| - next unless URL_OPTIONS.include?(key) && (String === default || Fixnum === default) - @defaults[key] ||= default + if URL_OPTIONS.include?(key) && (String === default || Fixnum === default) + @defaults[key] ||= default + end end end @@ -166,19 +169,21 @@ module ActionDispatch end def normalize_conditions! - @conditions.merge!(:path_info => path) + @conditions[:path_info] = path constraints.each do |key, condition| - next if segment_keys.include?(key) || key == :controller - @conditions[key] = condition + unless segment_keys.include?(key) || key == :controller + @conditions[key] = condition + end end - @conditions[:required_defaults] = [] + required_defaults = [] options.each do |key, required_default| - next if segment_keys.include?(key) || IGNORE_OPTIONS.include?(key) - next if Regexp === required_default - @conditions[:required_defaults] << key + unless segment_keys.include?(key) || IGNORE_OPTIONS.include?(key) || Regexp === required_default + required_defaults << key + end end + @conditions[:required_defaults] = required_defaults via_all = options.delete(:via) if options[:via] == :all @@ -192,8 +197,7 @@ module ActionDispatch end if via = options[:via] - list = Array(via).map { |m| m.to_s.dasherize.upcase } - @conditions.merge!(:request_method => list) + @conditions[:request_method] = Array(via).map { |m| m.to_s.dasherize.upcase } end end @@ -214,8 +218,12 @@ module ActionDispatch controller ||= default_controller action ||= default_action - unless controller.is_a?(Regexp) - controller = [@scope[:module], controller].compact.join("/").presence + if @scope[:module] && !controller.is_a?(Regexp) + if controller =~ %r{\A/} + controller = controller[1..-1] + else + controller = [@scope[:module], controller].compact.join("/").presence + end end if controller.is_a?(String) && controller =~ %r{\A/} @@ -226,11 +234,13 @@ module ActionDispatch action = action.to_s unless action.is_a?(Regexp) if controller.blank? && segment_keys.exclude?(:controller) - raise ArgumentError, "missing :controller" + message = "Missing :controller key on routes definition, please check your routes." + raise ArgumentError, message end if action.blank? && segment_keys.exclude?(:action) - raise ArgumentError, "missing :action" + message = "Missing :action key on routes definition, please check your routes." + raise ArgumentError, message end if controller.is_a?(String) && controller !~ /\A[a-z_0-9\/]*\z/ @@ -362,8 +372,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 @@ -383,7 +394,7 @@ module ActionDispatch # The namespace for :controller. # # match 'path', to: 'c#a', module: 'sekret', controller: 'posts' - # #=> Sekret::PostsController + # # => Sekret::PostsController # # See <tt>Scoping#namespace</tt> for its scope equivalent. # @@ -432,10 +443,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. @@ -496,11 +507,12 @@ module ActionDispatch raise "A rack application must be specified" unless path options[:as] ||= app_name(app) + target_as = name_for_action(options[:as], path) options[:via] ||= :all match(path, options.merge(:to => app, :anchor => false, :format => false)) - define_generate_prefix(app, options[:as]) + define_generate_prefix(app, target_as) self end @@ -539,11 +551,11 @@ module ActionDispatch _routes = @set app.routes.define_mounted_helper(name) app.routes.singleton_class.class_eval do - define_method :mounted? do + redefine_method :mounted? do true end - define_method :_generate_prefix do |options| + redefine_method :_generate_prefix do |options| prefix_options = options.slice(*_route.segment_keys) # we must actually delete prefix segment keys to avoid passing them to next url_for _route.segment_keys.each { |k| options.delete(k) } @@ -874,6 +886,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 @@ -1062,18 +1078,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+. @@ -1383,6 +1399,10 @@ module ActionDispatch raise ArgumentError, "Unknown scope #{on.inspect} given to :on" end + 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) 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..b08e62543b 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? @@ -26,6 +26,15 @@ module ActionDispatch end uri = URI.parse(path(req.symbolized_path_parameters, req)) + + unless uri.host + if relative_path?(uri.path) + uri.path = "#{req.script_name}/#{uri.path}" + elsif uri.path.empty? + uri.path = req.script_name.empty? ? "/" : req.script_name + end + end + uri.scheme ||= req.scheme uri.host ||= req.host uri.port ||= req.port unless req.standard_port? @@ -48,11 +57,38 @@ module ActionDispatch def inspect "redirect(#{status})" end + + private + def relative_path?(path) + path && !path.empty? && path[0] != '/' + end + + def escape(params) + Hash[params.map{ |k,v| [k, Rack::Utils.escape(v)] }] + end + + def escape_fragment(params) + Hash[params.map{ |k,v| [k, Journey::Router::Utils.escape_fragment(v)] }] + end + + def escape_path(params) + Hash[params.map{ |k,v| [k, Journey::Router::Utils.escape_path(v)] }] + end end class PathRedirect < Redirect + URL_PARTS = /\A([^?]+)?(\?[^#]+)?(#.+)?\z/ + def path(params, request) - (params.empty? || !block.match(/%\{\w*\}/)) ? block : (block % escape(params)) + if block.match(URL_PARTS) + path = interpolation_required?($1, params) ? $1 % escape_path(params) : $1 + query = interpolation_required?($2, params) ? $2 % escape(params) : $2 + fragment = interpolation_required?($3, params) ? $3 % escape_fragment(params) : $3 + + "#{path}#{query}#{fragment}" + else + interpolation_required?(block, params) ? block % escape(params) : block + end end def inspect @@ -60,8 +96,8 @@ module ActionDispatch end private - def escape(params) - Hash[params.map{ |k,v| [k, Rack::Utils.escape(v)] }] + def interpolation_required?(string, params) + !params.empty? && string && string.match(/%\{\w*\}/) end end @@ -81,17 +117,22 @@ module ActionDispatch url_options[:path] = (url_options[:path] % escape_path(params)) end + unless options[:host] || options[:domain] + if relative_path?(url_options[:path]) + url_options[:path] = "/#{url_options[:path]}" + url_options[:script_name] = request.script_name + elsif url_options[:path].empty? + url_options[:path] = request.script_name.empty? ? "/" : "" + url_options[:script_name] = request.script_name + end + end + ActionDispatch::Http::URL.url_for url_options end def inspect "redirect(#{status}, #{options.map{ |k,v| "#{k}: #{v}" }.join(', ')})" end - - private - def escape_path(params) - Hash[params.map{ |k,v| [k, URI.parser.escape(v)] }] - end end module Redirection @@ -104,6 +145,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 3ae9f92c0b..a03fb4cee7 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?) @@ -163,9 +163,10 @@ module ActionDispatch def initialize(route, options) super - @path_parts = @route.required_parts - @arg_size = @path_parts.size - @string_route = @route.optimized_path + @klass = Journey::Router::Utils + @required_parts = @route.required_parts + @arg_size = @required_parts.size + @optimized_path = @route.optimized_path end def call(t, args) @@ -182,26 +183,44 @@ module ActionDispatch private def optimized_helper(args) - path = @string_route.dup - klass = Journey::Router::Utils - - @path_parts.zip(args) do |part, arg| - # 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)) + params = Hash[parameterize_args(args)] + missing_keys = missing_keys(params) + + unless missing_keys.empty? + raise_generation_error(params, missing_keys) end - path + + @optimized_path.map{ |segment| replace_segment(params, segment) }.join + end + + def replace_segment(params, segment) + Symbol === segment ? @klass.escape_fragment(params[segment]) : segment end def optimize_routes_generation?(t) t.send(:optimize_routes_generation?) end + + def parameterize_args(args) + @required_parts.zip(args.map(&:to_param)) + end + + def missing_keys(args) + args.select{ |part, arg| arg.nil? || arg.empty? }.keys + end + + def raise_generation_error(args, missing_keys) + constraints = Hash[@route.requirements.merge(args).sort] + message = "No route matches #{constraints.inspect}" + message << " missing required keys: #{missing_keys.sort.inspect}" + + raise ActionController::UrlGenerationError, message + end end def initialize(route, options) @options = options - @segment_keys = route.segment_keys + @segment_keys = route.segment_keys.uniq @route = route end @@ -336,7 +355,7 @@ module ActionDispatch include UrlFor end - # Contains all the mounted helpers accross different + # Contains all the mounted helpers across different # engines and the `main_app` helper for the application. # You can include this in your classes if you want to # access routes for other engines. @@ -489,11 +508,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 @@ -512,6 +532,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 @@ -527,8 +552,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 @@ -538,8 +563,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) @@ -561,13 +584,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]. diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb index 8e19025722..4a0ef40873 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, @@ -155,6 +155,8 @@ module ActionDispatch _routes.url_for(options.symbolize_keys.reverse_merge!(url_options)) when String options + when Array + polymorphic_url(options, options.extract_options!) else polymorphic_url(options) end 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..68feb26936 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/response.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/response.rb @@ -27,6 +27,9 @@ module ActionDispatch assert @response.send("#{type}?"), message else code = Rack::Utils::SYMBOL_TO_STATUS_CODE[type] + if code.nil? + raise ArgumentError, "Invalid response type :#{type}" + end assert_equal code, @response.response_code, message end else @@ -67,21 +70,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 496682e8bd..f1f998d932 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb @@ -211,7 +211,7 @@ module ActionDispatch def fail_on(exception_class) yield rescue exception_class => e - raise MiniTest::Assertion, e.message + raise Minitest::Assertion, e.message end end end diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb index 70b42ccd1b..cc6b763093 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: @@ -137,7 +137,7 @@ module ActionDispatch class Session DEFAULT_HOST = "www.example.com" - include MiniTest::Assertions + include Minitest::Assertions include TestProcess, RequestHelpers, Assertions %w( status status_message headers body redirect? ).each do |method| @@ -242,7 +242,7 @@ module ActionDispatch @https = flag end - # Return +true+ if the session is mimicking a secure HTTPS request. + # Returns +true+ if the session is mimicking a secure HTTPS request. # # if session.https? # ... @@ -298,8 +298,6 @@ module ActionDispatch session = Rack::Test::Session.new(_mock_session) - env.merge!(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('/') @@ -489,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_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.rb b/actionpack/lib/action_pack.rb index ad5acd8080..77f656d6f1 100644 --- a/actionpack/lib/action_pack.rb +++ b/actionpack/lib/action_pack.rb @@ -1,5 +1,5 @@ #-- -# Copyright (c) 2004-2013 David Heinemeier Hansson +# Copyright (c) 2004-2014 David Heinemeier Hansson # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the diff --git a/actionpack/lib/action_pack/version.rb b/actionpack/lib/action_pack/version.rb index fd08f392aa..a51f6a434a 100644 --- a/actionpack/lib/action_pack/version.rb +++ b/actionpack/lib/action_pack/version.rb @@ -1,7 +1,7 @@ module ActionPack # Returns the version of the currently loaded ActionPack as a Gem::Version def self.version - Gem::Version.new "4.1.0.beta" + Gem::Version.new "4.1.0.beta1" end module VERSION #:nodoc: 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 361a0dccbe..0000000000 --- a/actionpack/lib/action_view/buffers.rb +++ /dev/null @@ -1,49 +0,0 @@ -require 'active_support/core_ext/string/output_safety' - -module ActionView - class OutputBuffer < ActiveSupport::SafeBuffer #:nodoc: - def initialize(*) - super - encode! - end - - def <<(value) - return self if value.nil? - super(value.to_s) - end - alias :append= :<< - - def safe_concat(value) - return self if value.nil? - super(value.to_s) - end - 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 45d17be605..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"'][@\w\/\."']+) # 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 3a6f449eb8..0000000000 --- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb +++ /dev/null @@ -1,320 +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 explicitly 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+ - # - # ==== Examples - # - # 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' - # - # ==== Examples - # - # favicon_link_tag '/myicon.ico' - # # => <link href="/assets/myicon.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. - # - # ==== Examples - # - # 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 b5f2df76ab..0000000000 --- a/actionpack/lib/action_view/helpers/asset_url_helper.rb +++ /dev/null @@ -1,355 +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 - - 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 - - return unless host - - 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 8fb5eb1548..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 are 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 36dedf0676..0000000000 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ /dev/null @@ -1,1880 +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 %> - # - # 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 - - # A +FormBuilder+ object is associated with a particular model object and - # allows you to generate fields associated with the model object. The - # +FormBuilder+ object is yielded when using +form_for+ or +fields_for+. - # For example: - # - # <%= form_for @person do |person_form| %> - # Name: <%= person_form.text_field :name %> - # Admin: <%= person_form.check_box :admin %> - # <% end %> - # - # In the above block, the a +FormBuilder+ object is yielded as the - # +person_form+ variable. This allows you to generate the +text_field+ - # and +check_box+ fields by specifying their eponymous methods, which - # modify the underlying template and associates the +@person+ model object - # with the form. - # - # The +FormBuilder+ object can be thought of as serving as a proxy for the - # methods in the +FormHelper+ module. This class, however, allows you to - # call methods with the model object you are building the form for. - # - # You can create your own custom FormBuilder templates by subclasses this - # class. For example: - # - # class MyFormBuilder < ActionView::Helpers::FormBuilder - # def div_radio_button(method, tag_value, options = {}) - # @template.content_tag(:div, - # @template.radio_button( - # @object_name, method, tag_value, objectify_options(options) - # ) - # ) - # end - # - # The above code creates a new method +div_radio_button+ which wraps a div - # around the a new radio button. Note that when options are passed in, you - # must called +objectify_options+ in order for the model object to get - # correctly passed to the method. If +objectify_options+ is not called, - # then the newly created helper will not be linked back to the model. - # - # The +div_radio_button+ code from above can now be used as follows: - # - # <%= form_for @person, :builder => MyFormBuilder do |f| %> - # I am a child: <%= f.div_radio_button(:admin, "child") %> - # I am an adult: <%= f.div_radio_button(:admin, "adult") %> - # <% end -%> - # - # The standard set of helper methods for form building are located in the - # +field_helpers+ class attribute. - class FormBuilder - include ModelNaming - - # The methods which wrap a form helper call. - class_attribute :field_helpers - self.field_helpers = [:fields_for, :label, :text_field, :password_field, - :hidden_field, :file_field, :text_area, :check_box, - :radio_button, :color_field, :search_field, - :telephone_field, :phone_field, :date_field, - :time_field, :datetime_field, :datetime_local_field, - :month_field, :week_field, :url_field, :email_field, - :number_field, :range_field] - - 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 %> - # - # <%= person_form.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 %> - # - # 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 ad26505086..0000000000 --- a/actionpack/lib/action_view/helpers/form_options_helper.rb +++ /dev/null @@ -1,832 +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), option_html_attributes(element)] - 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 - 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.select { |z| z =~ 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.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 c10566a87d..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 created. - # * <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 edff98ddaa..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 recommend using 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 recommend using 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 fda7038a5d..0000000000 --- a/actionpack/lib/action_view/helpers/number_helper.rb +++ /dev/null @@ -1,441 +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. - # - # ==== Options - # - # * <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. - # - # ==== Examples - # - # 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. - # - # ==== Options - # - # * <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. - # - # ==== Examples - # - # 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. - # - # ==== 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 +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. - # - # ==== Examples - # - # 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. - # - # ==== Options - # - # * <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. - # - # ==== Examples - # - # 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. - # - # ==== 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 +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. - # - # ==== Examples - # - # 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. - # - # ==== 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>: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. - # - # ==== Examples - # - # 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. - # - # ==== Examples - # - # 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 3939e4737b..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 3fe3f4e9df..0000000000 --- a/actionpack/lib/action_view/helpers/tags/base.rb +++ /dev/null @@ -1,147 +0,0 @@ -module ActionView - module Helpers - module Tags # :nodoc: - 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["multiple"]) } - options["id"] = options.fetch("id"){ tag_id_with_index(options["index"]) } - options.delete("index") - elsif defined?(@auto_index) - options["name"] ||= options.fetch("name"){ tag_name_with_index(@auto_index, options["multiple"]) } - options["id"] = options.fetch("id"){ tag_id_with_index(@auto_index) } - else - options["name"] ||= options.fetch("name"){ tag_name(options["multiple"]) } - options["id"] = options.fetch("id"){ tag_id } - end - - options["id"] = [options.delete('namespace'), options["id"]].compact.join("_").presence - end - - def tag_name(multiple = false) - "#{@object_name}[#{sanitized_method_name}]#{"[]" if multiple}" - end - - def tag_name_with_index(index, multiple = false) - "#{@object_name}[#{index}][#{sanitized_method_name}]#{"[]" if multiple}" - end - - def tag_id - "#{sanitized_object_name}_#{sanitized_method_name}" - 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 6d51f2629a..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 # :nodoc: - 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 052e9df662..0000000000 --- a/actionpack/lib/action_view/helpers/tags/checkable.rb +++ /dev/null @@ -1,16 +0,0 @@ -module ActionView - module Helpers - module Tags # :nodoc: - module Checkable # :nodoc: - 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 52006d856b..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 # :nodoc: - class CollectionCheckBoxes < Base # :nodoc: - include CollectionHelpers - - class CheckBoxBuilder < Builder # :nodoc: - 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 cd12ddaf65..0000000000 --- a/actionpack/lib/action_view/helpers/tags/collection_helpers.rb +++ /dev/null @@ -1,83 +0,0 @@ -module ActionView - module Helpers - module Tags # :nodoc: - module CollectionHelpers # :nodoc: - class Builder # :nodoc: - 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 20be34c1f2..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 # :nodoc: - class CollectionRadioButtons < Base # :nodoc: - include CollectionHelpers - - class RadioButtonBuilder < Builder # :nodoc: - 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 6cb2b2e0d3..0000000000 --- a/actionpack/lib/action_view/helpers/tags/collection_select.rb +++ /dev/null @@ -1,28 +0,0 @@ -module ActionView - module Helpers - module Tags # :nodoc: - 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 d8fc797035..0000000000 --- a/actionpack/lib/action_view/helpers/tags/color_field.rb +++ /dev/null @@ -1,25 +0,0 @@ -module ActionView - module Helpers - module Tags # :nodoc: - 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 c22be0db29..0000000000 --- a/actionpack/lib/action_view/helpers/tags/date_field.rb +++ /dev/null @@ -1,13 +0,0 @@ -module ActionView - module Helpers - module Tags # :nodoc: - 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 0c4ac40070..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 # :nodoc: - 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 9a2279c611..0000000000 --- a/actionpack/lib/action_view/helpers/tags/datetime_field.rb +++ /dev/null @@ -1,22 +0,0 @@ -module ActionView - module Helpers - module Tags # :nodoc: - 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 b4a74185d1..0000000000 --- a/actionpack/lib/action_view/helpers/tags/datetime_local_field.rb +++ /dev/null @@ -1,19 +0,0 @@ -module ActionView - module Helpers - module Tags # :nodoc: - 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 563de1840e..0000000000 --- a/actionpack/lib/action_view/helpers/tags/datetime_select.rb +++ /dev/null @@ -1,8 +0,0 @@ -module ActionView - module Helpers - module Tags # :nodoc: - 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 7ce3ccb9bf..0000000000 --- a/actionpack/lib/action_view/helpers/tags/email_field.rb +++ /dev/null @@ -1,8 +0,0 @@ -module ActionView - module Helpers - module Tags # :nodoc: - 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 476b820d84..0000000000 --- a/actionpack/lib/action_view/helpers/tags/file_field.rb +++ /dev/null @@ -1,8 +0,0 @@ -module ActionView - module Helpers - module Tags # :nodoc: - 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 2ed4712dac..0000000000 --- a/actionpack/lib/action_view/helpers/tags/grouped_collection_select.rb +++ /dev/null @@ -1,29 +0,0 @@ -module ActionView - module Helpers - module Tags # :nodoc: - 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 c3757c2461..0000000000 --- a/actionpack/lib/action_view/helpers/tags/hidden_field.rb +++ /dev/null @@ -1,8 +0,0 @@ -module ActionView - module Helpers - module Tags # :nodoc: - 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 35d3ba8434..0000000000 --- a/actionpack/lib/action_view/helpers/tags/label.rb +++ /dev/null @@ -1,65 +0,0 @@ -module ActionView - module Helpers - module Tags # :nodoc: - 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 4c0fb846ee..0000000000 --- a/actionpack/lib/action_view/helpers/tags/month_field.rb +++ /dev/null @@ -1,13 +0,0 @@ -module ActionView - module Helpers - module Tags # :nodoc: - 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 4f95b1b4de..0000000000 --- a/actionpack/lib/action_view/helpers/tags/number_field.rb +++ /dev/null @@ -1,18 +0,0 @@ -module ActionView - module Helpers - module Tags # :nodoc: - 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 6099fa6f19..0000000000 --- a/actionpack/lib/action_view/helpers/tags/password_field.rb +++ /dev/null @@ -1,12 +0,0 @@ -module ActionView - module Helpers - module Tags # :nodoc: - 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 4849c537a5..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 # :nodoc: - 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 f98ae88043..0000000000 --- a/actionpack/lib/action_view/helpers/tags/range_field.rb +++ /dev/null @@ -1,8 +0,0 @@ -module ActionView - module Helpers - module Tags # :nodoc: - 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 c09e2f1be7..0000000000 --- a/actionpack/lib/action_view/helpers/tags/search_field.rb +++ /dev/null @@ -1,24 +0,0 @@ -module ActionView - module Helpers - module Tags # :nodoc: - 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 d64e2f68ef..0000000000 --- a/actionpack/lib/action_view/helpers/tags/select.rb +++ /dev/null @@ -1,40 +0,0 @@ -module ActionView - module Helpers - module Tags # :nodoc: - 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 987bb9e67a..0000000000 --- a/actionpack/lib/action_view/helpers/tags/tel_field.rb +++ /dev/null @@ -1,8 +0,0 @@ -module ActionView - module Helpers - module Tags # :nodoc: - 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 c81156c0c8..0000000000 --- a/actionpack/lib/action_view/helpers/tags/text_area.rb +++ /dev/null @@ -1,18 +0,0 @@ -module ActionView - module Helpers - module Tags # :nodoc: - 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 baa5ff768e..0000000000 --- a/actionpack/lib/action_view/helpers/tags/text_field.rb +++ /dev/null @@ -1,29 +0,0 @@ -module ActionView - module Helpers - module Tags # :nodoc: - 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 0e90a3aed7..0000000000 --- a/actionpack/lib/action_view/helpers/tags/time_field.rb +++ /dev/null @@ -1,13 +0,0 @@ -module ActionView - module Helpers - module Tags # :nodoc: - 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 0b06311d25..0000000000 --- a/actionpack/lib/action_view/helpers/tags/time_select.rb +++ /dev/null @@ -1,8 +0,0 @@ -module ActionView - module Helpers - module Tags # :nodoc: - 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 80d165ec7e..0000000000 --- a/actionpack/lib/action_view/helpers/tags/time_zone_select.rb +++ /dev/null @@ -1,20 +0,0 @@ -module ActionView - module Helpers - module Tags # :nodoc: - 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 d76340178d..0000000000 --- a/actionpack/lib/action_view/helpers/tags/url_field.rb +++ /dev/null @@ -1,8 +0,0 @@ -module ActionView - module Helpers - module Tags # :nodoc: - 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 5b3d0494e9..0000000000 --- a/actionpack/lib/action_view/helpers/tags/week_field.rb +++ /dev/null @@ -1,13 +0,0 @@ -module ActionView - module Helpers - module Tags # :nodoc: - 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 147f9fd8ed..0000000000 --- a/actionpack/lib/action_view/helpers/text_helper.rb +++ /dev/null @@ -1,442 +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. Use the - # <tt>:separator</tt> option to choose the delimitation. 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("<blink>Unblinkable.</blink>") - # # => "<p>Unblinkable.</p>" - # - # simple_format("<blink>Blinkable!</blink> It's true.", {}, sanitize: false) - # # => "<p><blink>Blinkable!</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 22059a0170..0000000000 --- a/actionpack/lib/action_view/helpers/url_helper.rb +++ /dev/null @@ -1,634 +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> - # - # You can use a block as well if your link target is hard to fit into the name parameter. ERB example: - # - # <%= mail_to "me@domain.com" do %> - # <strong>Email me:</strong> <span>me@domain.com</span> - # <% end %> - # # => <a href="mailto:me@domain.com"> - # <strong>Email me:</strong> <span>me@domain.com</span> - # </a> - def mail_to(email_address, name = nil, html_options = {}, &block) - email_address = ERB::Util.html_escape(email_address) - - html_options, name = name, nil if block_given? - html_options = (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('&')) - - html_options["href"] = "mailto:#{email_address}#{extras}".html_safe - - content_tag(:a, name || email_address.html_safe, html_options, &block) - 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 f9d5b97fe3..0000000000 --- a/actionpack/lib/action_view/lookup_context.rb +++ /dev/null @@ -1,241 +0,0 @@ -require 'thread_safe' -require 'active_support/core_ext/module/remove_method' -require 'active_support/core_ext/module/attribute_accessors' - -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) do - locales = [I18n.locale] - locales.concat(I18n.fallbacks[I18n.locale]) if I18n.respond_to? :fallbacks - locales << I18n.default_locale - locales.uniq! - locales - end - 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 91ee2ea8f5..0000000000 --- a/actionpack/lib/action_view/path_set.rb +++ /dev/null @@ -1,77 +0,0 @@ -module ActionView #:nodoc: - # = Action View PathSet - # - # This class is used to store and access paths in Action View. A number of - # operations are defined so that you can search among the paths in this - # set and also perform operations on other +PathSet+ objects. - # - # A +LookupContext+ will use a +PathSet+ to store the paths in its context. - 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 73c19a0ae2..0000000000 --- a/actionpack/lib/action_view/renderer/abstract_renderer.rb +++ /dev/null @@ -1,47 +0,0 @@ -module ActionView - # This class defines the interface for a renderer. Each class that - # subclasses +AbstractRenderer+ is used by the base +Renderer+ class to - # render a specific type of object. - # - # The base +Renderer+ class uses its +render+ method to delegate to the - # renderers. These currently consist of - # - # PartialRenderer - Used for rendering partials - # TemplateRenderer - Used for rendering other types of templates - # StreamingTemplateRenderer - Used for streaming - # - # Whenever the +render+ method is called on the base +Renderer+ class, a new - # renderer object of the correct type is created, and the +render+ method on - # that new object is called in turn. This abstracts the setup and rendering - # into a separate classes for partials and templates. - 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 821026268a..0000000000 --- a/actionpack/lib/action_view/renderer/partial_renderer.rb +++ /dev/null @@ -1,492 +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 - - # Sets up instance variables needed for rendering a partial. This method - # finds the options and details and extracts them. The method also contains - # logic that handles the type of object passed in as the partial. - # - # If +options[:partial]+ is a string, then the +@path+ instance variable is - # set to that string. Otherwise, the +options[:partial]+ object must - # respond to +to_partial_path+ in order to setup the path. - 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 - - # Obtains the path to where the object's partial is located. If the object - # responds to +to_partial_path+, then +to_partial_path+ will be called and - # will provide the path. If the object does not respond to +to_partial_path+, - # then an +ArgumentError+ is raised. - # - # If +prefix_partial_path_with_controller_namespace+ is true, then this - # method will prefix the partial paths with a namespace. - 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 964b18337e..0000000000 --- a/actionpack/lib/action_view/renderer/renderer.rb +++ /dev/null @@ -1,50 +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. - # - # The Renderer will parse the options from the +render+ or +render_body+ - # method and render a partial or a template based on the options. The - # +TemplateRenderer+ and +PartialRenderer+ objects are wrappers which do all - # the setup and logic necessary to render a view and a new object is created - # each time +render+ is called. - 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 ebbc1c79d6..0000000000 --- a/actionpack/lib/action_view/template.rb +++ /dev/null @@ -1,346 +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) - instrument("!render_template") 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 - - instrument("!compile_template") do - compile(view, mod) - end - - # 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: - # Double assign to suppress the dreaded 'assigned but unused variable' warning - @locals.map { |key| "#{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 - - def instrument(action, &block) - payload = { virtual_path: @virtual_path, identifier: @identifier } - ActiveSupport::Notifications.instrument("#{action}.action_view", payload, &block) - 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 7d7a7af51d..0000000000 --- a/actionpack/lib/action_view/template/handlers/erb.rb +++ /dev/null @@ -1,146 +0,0 @@ -require 'action_dispatch/http/mime_type' -require 'erubis' - -module ActionView - class Template - module Handlers - class Erubis < ::Erubis::Eruby - def add_preamble(src) - @newline_pending = 0 - src << "@output_buffer = output_buffer || ActionView::OutputBuffer.new;" - end - - def add_text(src, text) - return if text.empty? - - if text == "\n" - @newline_pending += 1 - else - src << "@output_buffer.safe_append='" - src << "\n" * @newline_pending if @newline_pending > 0 - src << escape_text(text) - src << "';" - - @newline_pending = 0 - end - 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) - flush_newline_if_pending(src) - if code =~ BLOCK_EXPR - src << '@output_buffer.append= ' << code - else - src << '@output_buffer.append=(' << code << ');' - end - end - - def add_expr_escaped(src, code) - flush_newline_if_pending(src) - if code =~ BLOCK_EXPR - src << "@output_buffer.safe_append= " << code - else - src << "@output_buffer.safe_append=(" << code << ");" - end - end - - def add_stmt(src, code) - flush_newline_if_pending(src) - super - end - - def add_postamble(src) - flush_newline_if_pending(src) - src << '@output_buffer.to_s' - end - - def flush_newline_if_pending(src) - if @newline_pending > 0 - src << "@output_buffer.safe_append='#{"\n" * @newline_pending}';" - @newline_pending = 0 - end - 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 3304605c1a..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 save 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 separate 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 3145446114..0000000000 --- a/actionpack/lib/action_view/test_case.rb +++ /dev/null @@ -1,272 +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, - :@failures, - :@assertions, - :@__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 30b6b8b141..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)|(%|%)3A/i - - # 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 !~ /\A([:,;#%.\sa-zA-Z0-9!]|\w-\w|\'[\s\w]+\'|\"[\s\w]+\"|\([\d,\s]+\))*\z/ || - style !~ /\A(\s*[-\w]+\s*:\s*[^:;]*(;|$)\s*)*\z/ - 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 !~ /\A(#[0-9a-f]+|rgb\(\d+%?,\d*%?,?\d*%?\)?|\d{0,2}\.?\d{0,2}(cm|em|ex|in|mm|pc|pt|px|%|,|\))?)\z/ - 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)|(%|%)3A/i && !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 7f8609c408..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 - 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 - 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 - 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 - 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 - 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 - 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 - 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 - 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 |