diff options
Diffstat (limited to 'actionpack/lib')
63 files changed, 604 insertions, 257 deletions
diff --git a/actionpack/lib/abstract_controller.rb b/actionpack/lib/abstract_controller.rb index b95ea5f0b2..867a7954e0 100644 --- a/actionpack/lib/abstract_controller.rb +++ b/actionpack/lib/abstract_controller.rb @@ -1,9 +1,6 @@ require 'action_pack' -require 'active_support/concern' -require 'active_support/dependencies/autoload' -require 'active_support/core_ext/class/attribute' +require 'active_support/rails' require 'active_support/core_ext/module/attr_internal' -require 'active_support/core_ext/module/delegation' require 'active_support/core_ext/module/anonymous' require 'active_support/i18n' diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index 7c10fcbb8a..ceb90f8cee 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -1,5 +1,7 @@ +require 'active_support/rails' require 'abstract_controller' require 'action_dispatch' +require 'action_controller/metal/live' module ActionController extend ActiveSupport::Autoload @@ -53,11 +55,9 @@ require 'action_view' require 'action_controller/vendor/html-scanner' # Common Active Support usage in Action Controller -require 'active_support/concern' require 'active_support/core_ext/class/attribute_accessors' require 'active_support/core_ext/load_error' require 'active_support/core_ext/module/attr_internal' -require 'active_support/core_ext/module/delegation' require 'active_support/core_ext/name_error' require 'active_support/core_ext/uri' require 'active_support/inflector' diff --git a/actionpack/lib/action_controller/caching/fragments.rb b/actionpack/lib/action_controller/caching/fragments.rb index abeb49d16f..9c77b0ccf4 100644 --- a/actionpack/lib/action_controller/caching/fragments.rb +++ b/actionpack/lib/action_controller/caching/fragments.rb @@ -5,48 +5,18 @@ module ActionController #:nodoc: # useful when certain elements of an action change frequently or # depend on complicated state while other parts rarely change or # can be shared amongst multiple parties. The caching is done using - # the <tt>cache</tt> helper available in the Action View. A - # template with fragment caching might look like: + # the <tt>cache</tt> helper available in the Action View. See + # ActionView::Helpers::CacheHelper for more information. # - # <b>Hello <%= @name %></b> + # While it's strongly recommended that you use key-based cache + # expiration (see links in CacheHelper for more information), + # it is also possible to manually expire caches. For example: # - # <% cache do %> - # All the topics in the system: - # <%= render :partial => "topic", :collection => Topic.all %> - # <% end %> - # - # This cache will bind the name of the action that called it, so if - # this code was part of the view for the topics/list action, you - # would be able to invalidate it using: - # - # expire_fragment(:controller => "topics", :action => "list") - # - # This default behavior is limited if you need to cache multiple - # fragments per action or if the action itself is cached using - # <tt>caches_action</tt>. To remedy this, there is an option to - # qualify the name of the cached fragment by using the - # <tt>:action_suffix</tt> option: - # - # <% cache(:action => "list", :action_suffix => "all_topics") do %> - # - # That would result in a name such as - # <tt>/topics/list/all_topics</tt>, avoiding conflicts with the - # action cache and with any fragments that use a different suffix. - # Note that the URL doesn't have to really exist or be callable - # - the url_for system is just used to generate unique cache names - # that we can refer to when we need to expire the cache. - # - # The expiration call for this example is: - # - # expire_fragment(:controller => "topics", - # :action => "list", - # :action_suffix => "all_topics") + # expire_fragment("name_of_cache") module Fragments # Given a key (as described in <tt>expire_fragment</tt>), returns # a key suitable for use in reading, writing, or expiring a - # cached fragment. If the key is a hash, the generated key is the - # return value of url_for on that hash (without the protocol). - # All keys are prefixed with <tt>views/</tt> and uses + # cached fragment. All keys are prefixed with <tt>views/</tt> and uses # ActiveSupport::Cache.expand_cache_key for the expansion. def fragment_cache_key(key) ActiveSupport::Cache.expand_cache_key(key.is_a?(Hash) ? url_for(key).split("://").last : key, :views) diff --git a/actionpack/lib/action_controller/log_subscriber.rb b/actionpack/lib/action_controller/log_subscriber.rb index 0fb419f941..a7c0e971e7 100644 --- a/actionpack/lib/action_controller/log_subscriber.rb +++ b/actionpack/lib/action_controller/log_subscriber.rb @@ -1,4 +1,3 @@ -require 'active_support/core_ext/object/blank' module ActionController class LogSubscriber < ActiveSupport::LogSubscriber diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb index 92433ab462..b38f990efa 100644 --- a/actionpack/lib/action_controller/metal.rb +++ b/actionpack/lib/action_controller/metal.rb @@ -1,5 +1,3 @@ -require 'active_support/core_ext/class/attribute' -require 'active_support/core_ext/object/blank' require 'action_dispatch/middleware/stack' module ActionController @@ -187,7 +185,7 @@ module ActionController end def performed? - !!response_body + response_body || (response && response.committed?) end def dispatch(name, request) #:nodoc: diff --git a/actionpack/lib/action_controller/metal/force_ssl.rb b/actionpack/lib/action_controller/metal/force_ssl.rb index 77d799a38a..e905a3cf1d 100644 --- a/actionpack/lib/action_controller/metal/force_ssl.rb +++ b/actionpack/lib/action_controller/metal/force_ssl.rb @@ -32,7 +32,7 @@ module ActionController # ==== Options # * <tt>host</tt> - Redirect to a different host name # * <tt>only</tt> - The callback should be run only for this action - # * <tt>except<tt> - The callback should be run for all actions except this action + # * <tt>except</tt> - The callback should be run for all actions except this action # * <tt>if</tt> - A symbol naming an instance method or a proc; the callback # will be called only when it returns a true value. # * <tt>unless</tt> - A symbol naming an instance method or a proc; the callback diff --git a/actionpack/lib/action_controller/metal/head.rb b/actionpack/lib/action_controller/metal/head.rb index 2fcd933d32..747e1273be 100644 --- a/actionpack/lib/action_controller/metal/head.rb +++ b/actionpack/lib/action_controller/metal/head.rb @@ -29,19 +29,19 @@ module ActionController self.status = status self.location = url_for(location) if location - if include_content_headers?(self.status) + if include_content?(self.status) self.content_type = content_type || (Mime[formats.first] if formats) + self.response_body = " " else headers.delete('Content-Type') headers.delete('Content-Length') + self.response_body = "" end - - self.response_body = " " end private # :nodoc: - def include_content_headers?(status) + def include_content?(status) case status when 100..199 false diff --git a/actionpack/lib/action_controller/metal/helpers.rb b/actionpack/lib/action_controller/metal/helpers.rb index 66cdfd40ff..d2cbbd3330 100644 --- a/actionpack/lib/action_controller/metal/helpers.rb +++ b/actionpack/lib/action_controller/metal/helpers.rb @@ -1,4 +1,3 @@ -require 'active_support/core_ext/class/attribute' module ActionController # The \Rails framework provides a large number of helpers for working with assets, dates, forms, diff --git a/actionpack/lib/action_controller/metal/hide_actions.rb b/actionpack/lib/action_controller/metal/hide_actions.rb index b55c4643be..420b22cf56 100644 --- a/actionpack/lib/action_controller/metal/hide_actions.rb +++ b/actionpack/lib/action_controller/metal/hide_actions.rb @@ -1,4 +1,3 @@ -require 'active_support/core_ext/class/attribute' module ActionController # Adds the ability to prevent public methods on a controller to be called as actions. diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb index 0050ede806..03b8d8db1a 100644 --- a/actionpack/lib/action_controller/metal/http_authentication.rb +++ b/actionpack/lib/action_controller/metal/http_authentication.rb @@ -1,5 +1,4 @@ require 'base64' -require 'active_support/core_ext/object/blank' module ActionController # Makes it dead easy to do HTTP Basic, Digest and Token authentication. @@ -194,7 +193,7 @@ module ActionController return false unless password method = request.env['rack.methodoverride.original_method'] || request.env['REQUEST_METHOD'] - uri = credentials[:uri][0,1] == '/' ? request.original_fullpath : request.original_url + uri = credentials[:uri] [true, false].any? do |trailing_question_mark| [true, false].any? do |password_is_ha1| @@ -229,9 +228,9 @@ module ActionController end def decode_credentials(header) - Hash[header.to_s.gsub(/^Digest\s+/,'').split(',').map do |pair| + HashWithIndifferentAccess[header.to_s.gsub(/^Digest\s+/,'').split(',').map do |pair| key, value = pair.split('=', 2) - [key.strip.to_sym, value.to_s.gsub(/^"|"$/,'').delete('\'')] + [key.strip, value.to_s.gsub(/^"|"$/,'').delete('\'')] end] end @@ -372,7 +371,7 @@ module ActionController # def test_access_granted_from_xml # get( # "/notes/1.xml", nil, - # :authorization => ActionController::HttpAuthentication::Token.encode_credentials(users(:dhh).token) + # 'HTTP_AUTHORIZATION' => ActionController::HttpAuthentication::Token.encode_credentials(users(:dhh).token) # ) # # assert_equal 200, status diff --git a/actionpack/lib/action_controller/metal/live.rb b/actionpack/lib/action_controller/metal/live.rb new file mode 100644 index 0000000000..32e5afa335 --- /dev/null +++ b/actionpack/lib/action_controller/metal/live.rb @@ -0,0 +1,141 @@ +require 'action_dispatch/http/response' +require 'delegate' + +module ActionController + # Mix this module in to your controller, and all actions in that controller + # will be able to stream data to the client as it's written. + # + # class MyController < ActionController::Base + # include ActionController::Live + # + # def stream + # response.headers['Content-Type'] = 'text/event-stream' + # 100.times { + # response.stream.write "hello world\n" + # sleep 1 + # } + # response.stream.close + # end + # end + # + # There are a few caveats with this use. You *cannot* write headers after the + # response has been committed (Response#committed? will return truthy). + # Calling +write+ or +close+ on the response stream will cause the response + # object to be committed. Make sure all headers are set before calling write + # or close on your stream. + # + # You *must* call close on your stream when you're finished, otherwise the + # socket may be left open forever. + # + # The final caveat is that your actions are executed in a separate thread than + # 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 + class Buffer < ActionDispatch::Response::Buffer #:nodoc: + def initialize(response) + super(response, SizedQueue.new(10)) + end + + def write(string) + unless @response.committed? + @response.headers["Cache-Control"] = "no-cache" + @response.headers.delete "Content-Length" + end + + super + end + + def each + while str = @buf.pop + yield str + end + end + + def close + super + @buf.push nil + end + end + + class Response < ActionDispatch::Response #:nodoc: all + class Header < DelegateClass(Hash) + def initialize(response, header) + @response = response + super(header) + end + + def []=(k,v) + if @response.committed? + raise ActionDispatch::IllegalStateError, 'header already sent' + end + + super + end + + def merge(other) + self.class.new @response, __getobj__.merge(other) + end + + def to_hash + __getobj__.dup + end + end + + def commit! + headers.freeze + super + end + + private + + def build_buffer(response, body) + buf = Live::Buffer.new response + body.each { |part| buf.write part } + buf + end + + def merge_default_headers(original, default) + Header.new self, super + end + end + + def process(name) + t1 = Thread.current + locals = t1.keys.map { |key| [key, t1[key]] } + + # This processes the action in a child thread. It lets us return the + # response code and headers back up the rack stack, and still process + # the body in parallel with sending data to the client + Thread.new { + t2 = Thread.current + t2.abort_on_exception = true + + # Since we're processing the view in a different thread, copy the + # thread locals from the main thread to the child thread. :'( + locals.each { |k,v| t2[k] = v } + + begin + super(name) + ensure + @_response.commit! + end + } + + @_response.await_commit + end + + def response_body=(body) + super + response.stream.close if response + end + + def set_response!(request) + if request.env["HTTP_VERSION"] == "HTTP/1.0" + super + else + @_response = Live::Response.new + @_response.request = request + end + end + end +end diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb index 4665fea91a..18ae2c3bfc 100644 --- a/actionpack/lib/action_controller/metal/mime_responds.rb +++ b/actionpack/lib/action_controller/metal/mime_responds.rb @@ -1,6 +1,4 @@ require 'abstract_controller/collector' -require 'active_support/core_ext/class/attribute' -require 'active_support/core_ext/object/inclusion' module ActionController #:nodoc: module MimeResponds @@ -343,9 +341,9 @@ module ActionController #:nodoc: config = self.class.mimes_for_respond_to[mime] if config[:except] - !action.in?(config[:except]) + !config[:except].include?(action) elsif config[:only] - action.in?(config[:only]) + config[:only].include?(action) else true end diff --git a/actionpack/lib/action_controller/metal/params_wrapper.rb b/actionpack/lib/action_controller/metal/params_wrapper.rb index aa67fa7f23..2736948ce0 100644 --- a/actionpack/lib/action_controller/metal/params_wrapper.rb +++ b/actionpack/lib/action_controller/metal/params_wrapper.rb @@ -1,4 +1,3 @@ -require 'active_support/core_ext/class/attribute' require 'active_support/core_ext/hash/slice' require 'active_support/core_ext/hash/except' require 'active_support/core_ext/module/anonymous' diff --git a/actionpack/lib/action_controller/metal/renderers.rb b/actionpack/lib/action_controller/metal/renderers.rb index 1927c8bdc7..78aeeef2bf 100644 --- a/actionpack/lib/action_controller/metal/renderers.rb +++ b/actionpack/lib/action_controller/metal/renderers.rb @@ -1,5 +1,3 @@ -require 'active_support/core_ext/class/attribute' -require 'active_support/core_ext/object/blank' require 'set' module ActionController diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb index 53534c0307..d5f1cbc1a8 100644 --- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb +++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb @@ -1,4 +1,3 @@ -require 'active_support/core_ext/class/attribute' require 'action_controller/metal/exceptions' module ActionController #:nodoc: diff --git a/actionpack/lib/action_controller/metal/url_for.rb b/actionpack/lib/action_controller/metal/url_for.rb index e28c05cc2d..0cdd17bc2e 100644 --- a/actionpack/lib/action_controller/metal/url_for.rb +++ b/actionpack/lib/action_controller/metal/url_for.rb @@ -27,12 +27,18 @@ module ActionController :host => request.host, :port => request.optional_port, :protocol => request.protocol, - :_path_segments => request.symbolized_path_parameters + :_recall => request.symbolized_path_parameters ).freeze - if _routes.equal?(env["action_dispatch.routes"]) + if (same_origin = _routes.equal?(env["action_dispatch.routes"])) || + (script_name = env["ROUTES_#{_routes.object_id}_SCRIPT_NAME"]) || + (original_script_name = env['SCRIPT_NAME']) @_url_options.dup.tap do |options| - options[:script_name] = request.script_name.dup + if original_script_name + options[:original_script_name] = original_script_name + else + options[:script_name] = same_origin ? request.script_name.dup : script_name + end options.freeze end else diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index 5f50bf5de6..bb693c6494 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -1,7 +1,5 @@ require 'rack/session/abstract/id' -require 'active_support/core_ext/object/blank' require 'active_support/core_ext/object/to_query' -require 'active_support/core_ext/class/attribute' require 'active_support/core_ext/module/anonymous' module ActionController @@ -220,14 +218,7 @@ module ActionController class TestResponse < ActionDispatch::TestResponse def recycle! - @status = 200 - @header = {} - @writer = lambda { |x| @body << x } - @block = nil - @length = 0 - @body = [] - @charset = @content_type = nil - @request = @template = nil + initialize end end @@ -524,20 +515,36 @@ module ActionController end def setup_controller_request_and_response - @request = TestRequest.new - @response = TestResponse.new + @request = build_request + @response = build_response @response.request = @request + @controller = nil unless defined? @controller + if klass = self.class.controller_class - @controller ||= klass.new rescue nil + unless @controller + begin + @controller = klass.new + rescue + warn "could not construct controller #{klass}" if $VERBOSE + end + end end - if defined?(@controller) && @controller + if @controller @controller.request = @request @controller.params = {} end end + def build_request + TestRequest.new + end + + def build_response + TestResponse.new + end + included do include ActionController::TemplateAssertions include ActionDispatch::Assertions @@ -574,7 +581,7 @@ module ActionController :only_path => true, :action => action, :relative_url_root => nil, - :_path_segments => @request.symbolized_path_parameters) + :_recall => @request.symbolized_path_parameters) url, query_string = @routes.url_for(options).split("?", 2) diff --git a/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb b/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb index 6b269e7a31..6b4ececda2 100644 --- a/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb +++ b/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb @@ -1,6 +1,5 @@ require 'set' require 'cgi' -require 'active_support/core_ext/class/attribute' require 'active_support/core_ext/class/attribute_accessors' module HTML diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb index 1e4ac70f3d..cc81970ddc 100644 --- a/actionpack/lib/action_dispatch.rb +++ b/actionpack/lib/action_dispatch.rb @@ -22,7 +22,7 @@ #++ require 'active_support' -require 'active_support/dependencies/autoload' +require 'active_support/rails' require 'active_support/core_ext/module/attribute_accessors' require 'action_pack' @@ -36,6 +36,9 @@ end module ActionDispatch extend ActiveSupport::Autoload + class IllegalStateError < StandardError + end + autoload_under 'http' do autoload :Request autoload :Response diff --git a/actionpack/lib/action_dispatch/http/cache.rb b/actionpack/lib/action_dispatch/http/cache.rb index 0b895e7860..a7f93b780e 100644 --- a/actionpack/lib/action_dispatch/http/cache.rb +++ b/actionpack/lib/action_dispatch/http/cache.rb @@ -1,4 +1,3 @@ -require 'active_support/core_ext/object/blank' module ActionDispatch module Http @@ -86,21 +85,29 @@ module ActionDispatch CACHE_CONTROL = "Cache-Control".freeze SPESHUL_KEYS = %w[extras no-cache max-age public must-revalidate] + def cache_control_segments + if cache_control = self[CACHE_CONTROL] + cache_control.delete(' ').split(',') + else + [] + end + end + def cache_control_headers cache_control = {} - if cc = self[CACHE_CONTROL] - cc.delete(' ').split(',').each do |segment| - directive, argument = segment.split('=', 2) - case directive - when *SPESHUL_KEYS - key = directive.tr('-', '_') - cache_control[key.to_sym] = argument || true - else - cache_control[:extras] ||= [] - cache_control[:extras] << segment - end + + cache_control_segments.each do |segment| + directive, argument = segment.split('=', 2) + + if SPESHUL_KEYS.include? directive + key = directive.tr('-', '_') + cache_control[key.to_sym] = argument || true + else + cache_control[:extras] ||= [] + cache_control[:extras] << segment end end + cache_control end diff --git a/actionpack/lib/action_dispatch/http/filter_parameters.rb b/actionpack/lib/action_dispatch/http/filter_parameters.rb index 6413929be3..47cf41cfa3 100644 --- a/actionpack/lib/action_dispatch/http/filter_parameters.rb +++ b/actionpack/lib/action_dispatch/http/filter_parameters.rb @@ -1,4 +1,3 @@ -require 'active_support/core_ext/object/blank' require 'active_support/core_ext/hash/keys' require 'active_support/core_ext/object/duplicable' diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb index e31f3b823d..0f98e84788 100644 --- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb +++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb @@ -80,6 +80,27 @@ module ActionDispatch @env["action_dispatch.request.formats"] = [Mime::Type.lookup_by_extension(parameters[:format])] end + # Sets the \formats by string extensions. This differs from #format= by allowing you + # to set multiple, ordered formats, which is useful when you want to have a fallback. + # + # In this example, the :iphone format will be used if it's available, otherwise it'll fallback + # to the :html format. + # + # class ApplicationController < ActionController::Base + # before_filter :adjust_format_for_iphone_with_html_fallback + # + # private + # def adjust_format_for_iphone_with_html_fallback + # request.formats = [ :iphone, :html ] if request.env["HTTP_USER_AGENT"][/iPhone/] + # end + # end + def formats=(extensions) + parameters[:format] = extensions.first.to_s + @env["action_dispatch.request.formats"] = extensions.collect do |extension| + Mime::Type.lookup_by_extension(extension) + end + end + # Receives an array of mimes and return the first user sent mime that # matches the order array. # diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb index fe39c220a5..fd86966c50 100644 --- a/actionpack/lib/action_dispatch/http/mime_type.rb +++ b/actionpack/lib/action_dispatch/http/mime_type.rb @@ -1,6 +1,5 @@ require 'set' require 'active_support/core_ext/class/attribute_accessors' -require 'active_support/core_ext/object/blank' module Mime class Mimes < Array diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb index bcfd0b0d00..9a7b5bc8c7 100644 --- a/actionpack/lib/action_dispatch/http/parameters.rb +++ b/actionpack/lib/action_dispatch/http/parameters.rb @@ -4,6 +4,11 @@ require 'active_support/core_ext/hash/indifferent_access' module ActionDispatch module Http module Parameters + def initialize(env) + super + @symbolized_path_params = nil + end + # Returns both GET and POST \parameters in a single hash. def parameters @env["action_dispatch.request.parameters"] ||= begin diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index 8cea17c7a6..d24c7c7f3f 100644 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -38,6 +38,17 @@ module ActionDispatch METHOD end + def initialize(env) + super + @method = nil + @request_method = nil + @remote_ip = nil + @original_fullpath = nil + @fullpath = nil + @ip = nil + @uuid = nil + end + def key?(key) @env.key?(key) end @@ -119,9 +130,9 @@ module ActionDispatch end # Is this a HEAD request? - # Equivalent to <tt>request.method_symbol == :head</tt>. + # Equivalent to <tt>request.request_method_symbol == :head</tt>. def head? - HTTP_METHOD_LOOKUP[method] == :head + HTTP_METHOD_LOOKUP[request_method] == :head end # Provides access to the request's HTTP headers, for example: diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb index cc46f9983c..11b7534ea4 100644 --- a/actionpack/lib/action_dispatch/http/response.rb +++ b/actionpack/lib/action_dispatch/http/response.rb @@ -1,7 +1,6 @@ require 'digest/md5' -require 'active_support/core_ext/module/delegation' -require 'active_support/core_ext/object/blank' require 'active_support/core_ext/class/attribute_accessors' +require 'monitor' module ActionDispatch # :nodoc: # Represents an HTTP response generated by a controller action. Use it to @@ -41,7 +40,7 @@ module ActionDispatch # :nodoc: alias_method :headers, :header delegate :[], :[]=, :to => :@header - delegate :each, :to => :@body + delegate :each, :to => :@stream # Sets the HTTP response's content MIME type. For example, in the controller # you could write this: @@ -59,15 +58,55 @@ module ActionDispatch # :nodoc: LOCATION = "Location".freeze cattr_accessor(:default_charset) { "utf-8" } + cattr_accessor(:default_headers) include Rack::Response::Helpers include ActionDispatch::Http::Cache::Response + include MonitorMixin + + class Buffer # :nodoc: + def initialize(response, buf) + @response = response + @buf = buf + @closed = false + end + + def write(string) + raise IOError, "closed stream" if closed? + + @response.commit! + @buf.push string + end + + def each(&block) + @buf.each(&block) + end + + def close + @response.commit! + @closed = true + end + + def closed? + @closed + end + end + + attr_reader :stream def initialize(status = 200, header = {}, body = []) + super() + + header = merge_default_headers(header, self.class.default_headers) + self.body, self.header, self.status = body, header, status @sending_file = false - @blank = false + @blank = false + @cv = new_cond + @committed = false + @content_type = nil + @charset = nil if content_type = self[CONTENT_TYPE] type, charset = content_type.split(/;\s*charset=/) @@ -80,6 +119,23 @@ module ActionDispatch # :nodoc: yield self if block_given? end + def await_commit + synchronize do + @cv.wait_until { @committed } + end + end + + def commit! + synchronize do + @committed = true + @cv.broadcast + end + end + + def committed? + @committed + end + def status=(status) @status = Rack::Utils.status_code(status) end @@ -105,14 +161,14 @@ module ActionDispatch # :nodoc: def respond_to?(method) if method.to_sym == :to_path - @body.respond_to?(:to_path) + stream.respond_to?(:to_path) else super end end def to_path - @body.to_path + stream.to_path end def body @@ -126,11 +182,17 @@ module ActionDispatch # :nodoc: def body=(body) @blank = true if body == EMPTY - @body = body.respond_to?(:each) ? body : [body] + if body.respond_to?(:to_path) + @stream = body + else + @stream = build_buffer self, munge_body_object(body) + end end def body_parts - @body + parts = [] + @stream.each { |x| parts << x } + parts end def set_cookie(key, value) @@ -151,21 +213,11 @@ module ActionDispatch # :nodoc: end def close - @body.close if @body.respond_to?(:close) + stream.close if stream.respond_to?(:close) end def to_a - assign_default_content_type_and_charset! - handle_conditional_get! - - @header[SET_COOKIE] = @header[SET_COOKIE].join("\n") if @header[SET_COOKIE].respond_to?(:join) - - if [204, 304].include?(@status) - @header.delete CONTENT_TYPE - [@status, @header, []] - else - [@status, @header, self] - end + rack_response @status, @header.to_hash end alias prepare! to_a alias to_ary to_a # For implicit splat on 1.9.2 @@ -189,7 +241,21 @@ module ActionDispatch # :nodoc: private - def assign_default_content_type_and_charset! + def merge_default_headers(original, default) + return original unless default.respond_to?(:merge) + + default.merge(original) + end + + def build_buffer(response, body) + Buffer.new response, body + end + + def munge_body_object(body) + body.respond_to?(:each) ? body : [body] + end + + def assign_default_content_type_and_charset!(headers) return if headers[CONTENT_TYPE].present? @content_type ||= Mime::HTML @@ -200,5 +266,19 @@ module ActionDispatch # :nodoc: headers[CONTENT_TYPE] = type end + + def rack_response(status, header) + assign_default_content_type_and_charset!(header) + handle_conditional_get! + + header[SET_COOKIE] = header[SET_COOKIE].join("\n") if header[SET_COOKIE].respond_to?(:join) + + if [204, 304].include?(@status) + header.delete CONTENT_TYPE + [status, header, []] + else + [status, header, self] + end + end end end diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb index 4266ec042e..8aa02ec482 100644 --- a/actionpack/lib/action_dispatch/http/url.rb +++ b/actionpack/lib/action_dispatch/http/url.rb @@ -87,6 +87,12 @@ module ActionDispatch end end + def initialize(env) + super + @protocol = nil + @port = nil + end + # Returns the complete URL used for this request. def url protocol + host_with_port + fullpath diff --git a/actionpack/lib/action_dispatch/middleware/callbacks.rb b/actionpack/lib/action_dispatch/middleware/callbacks.rb index 338b116940..852f1cf6f5 100644 --- a/actionpack/lib/action_dispatch/middleware/callbacks.rb +++ b/actionpack/lib/action_dispatch/middleware/callbacks.rb @@ -1,4 +1,3 @@ -require 'active_support/core_ext/module/delegation' module ActionDispatch # Provide callbacks to be executed before and after the request dispatch. diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb index 771f075275..ba5d332d49 100644 --- a/actionpack/lib/action_dispatch/middleware/cookies.rb +++ b/actionpack/lib/action_dispatch/middleware/cookies.rb @@ -1,5 +1,5 @@ -require 'active_support/core_ext/object/blank' require 'active_support/core_ext/hash/keys' +require 'active_support/core_ext/module/attribute_accessors' module ActionDispatch class Request < Rack::Request diff --git a/actionpack/lib/action_dispatch/middleware/head.rb b/actionpack/lib/action_dispatch/middleware/head.rb deleted file mode 100644 index f1906a3ab3..0000000000 --- a/actionpack/lib/action_dispatch/middleware/head.rb +++ /dev/null @@ -1,18 +0,0 @@ -module ActionDispatch - class Head - def initialize(app) - @app = app - end - - def call(env) - if env["REQUEST_METHOD"] == "HEAD" - env["REQUEST_METHOD"] = "GET" - env["rack.methodoverride.original_method"] = "HEAD" - status, headers, _ = @app.call(env) - [status, headers, []] - else - @app.call(env) - end - end - end -end diff --git a/actionpack/lib/action_dispatch/middleware/request_id.rb b/actionpack/lib/action_dispatch/middleware/request_id.rb index 6fff94707c..44290445d4 100644 --- a/actionpack/lib/action_dispatch/middleware/request_id.rb +++ b/actionpack/lib/action_dispatch/middleware/request_id.rb @@ -1,6 +1,5 @@ require 'securerandom' require 'active_support/core_ext/string/access' -require 'active_support/core_ext/object/blank' module ActionDispatch # Makes a unique request id available to the action_dispatch.request_id env variable (which is then accessible through diff --git a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb index 64159fa8e7..7c12590c49 100644 --- a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb @@ -3,7 +3,6 @@ require 'rack/request' require 'rack/session/abstract/id' require 'action_dispatch/middleware/cookies' require 'action_dispatch/request/session' -require 'active_support/core_ext/object/blank' module ActionDispatch module Session diff --git a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb index 7efc094f98..9b159b2caf 100644 --- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb @@ -1,5 +1,4 @@ require 'active_support/core_ext/hash/keys' -require 'active_support/core_ext/object/blank' require 'action_dispatch/middleware/session/abstract_store' require 'rack/session/cookie' diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb index 62f906219c..0dcf1fc4fe 100644 --- a/actionpack/lib/action_dispatch/railtie.rb +++ b/actionpack/lib/action_dispatch/railtie.rb @@ -19,10 +19,16 @@ module ActionDispatch :verbose => false } + config.action_dispatch.default_headers = { + 'X-Frame-Options' => 'SAMEORIGIN', + 'X-XSS-Protection' => '1; mode=block' + } + 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::Response.default_charset = app.config.action_dispatch.default_charset || app.config.encoding + ActionDispatch::Response.default_headers = app.config.action_dispatch.default_headers ActionDispatch::ExceptionWrapper.rescue_responses.merge!(config.action_dispatch.rescue_responses) ActionDispatch::ExceptionWrapper.rescue_templates.merge!(config.action_dispatch.rescue_templates) diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 0a65b4dbcc..ea5028a7c0 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -1,7 +1,6 @@ require 'active_support/core_ext/hash/except' require 'active_support/core_ext/hash/reverse_merge' require 'active_support/core_ext/hash/slice' -require 'active_support/core_ext/object/blank' require 'active_support/core_ext/enumerable' require 'active_support/inflector' require 'action_dispatch/routing/redirection' @@ -404,6 +403,10 @@ module ActionDispatch # # # Matches any request starting with 'path' # match 'path' => 'c#a', :anchor => false + # + # [:format] + # Allows you to specify the default value for optional +format+ + # segment or disable it by supplying +false+. def match(path, options=nil) end @@ -906,7 +909,7 @@ module ActionDispatch # CANONICAL_ACTIONS holds all actions that does not need a prefix or # a path appended since they fit properly in their scope level. VALID_ON_OPTIONS = [:new, :collection, :member] - RESOURCE_OPTIONS = [:as, :controller, :path, :only, :except, :param] + RESOURCE_OPTIONS = [:as, :controller, :path, :only, :except, :param, :concerns] CANONICAL_ACTIONS = %w(index create new show update destroy) class Resource #:nodoc: @@ -1043,6 +1046,8 @@ module ActionDispatch resource_scope(:resource, SingletonResource.new(resources.pop, options)) do yield if block_given? + concerns(options[:concerns]) if options[:concerns] + collection do post :create end if parent_resource.actions.include?(:create) @@ -1186,6 +1191,10 @@ module ActionDispatch # sekret_comment PATCH/PUT /comments/:id(.:format) # sekret_comment DELETE /comments/:id(.:format) # + # [:format] + # Allows you to specify the default value for optional +format+ + # segment or disable it by supplying +false+. + # # === Examples # # # routes call <tt>Admin::PostsController</tt> @@ -1203,6 +1212,8 @@ module ActionDispatch resource_scope(:resources, Resource.new(resources.pop, options)) do yield if block_given? + concerns(options[:concerns]) if options[:concerns] + collection do get :index if parent_resource.actions.include?(:index) post :create if parent_resource.actions.include?(:create) @@ -1573,15 +1584,71 @@ module ActionDispatch end end + # Routing Concerns allows you to declare common routes that can be reused + # inside others resources and routes. + # + # concern :commentable do + # resources :comments + # end + # + # concern :image_attachable do + # resources :images, only: :index + # end + # + # These concerns are used in Resources routing: + # + # resources :messages, concerns: [:commentable, :image_attachable] + # + # or in a scope or namespace: + # + # namespace :posts do + # concerns :commentable + # end + module Concerns + # Define a routing concern using a name. + # + # concern :commentable do + # resources :comments + # end + # + # Any routing helpers can be used inside a concern. + def concern(name, &block) + @concerns[name] = block + end + + # Use the named concerns + # + # resources :posts do + # concerns :commentable + # end + # + # concerns also work in any routes helper that you want to use: + # + # namespace :posts do + # concerns :commentable + # end + def concerns(*names) + names.flatten.each do |name| + if concern = @concerns[name] + instance_eval(&concern) + else + raise ArgumentError, "No concern named #{name} was found!" + end + end + end + end + def initialize(set) #:nodoc: @set = set @scope = { :path_names => @set.resources_path_names } + @concerns = {} end include Base include HttpHelpers include Redirection include Scoping + include Concerns include Resources end end diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 0bbed6cbea..32d267d1d6 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -1,6 +1,5 @@ require 'journey' require 'forwardable' -require 'active_support/core_ext/object/blank' require 'active_support/core_ext/object/to_query' require 'active_support/core_ext/hash/slice' require 'active_support/core_ext/module/remove_method' @@ -162,19 +161,12 @@ module ActionDispatch end private - def url_helper_name(name, only_path) - if only_path - :"#{name}_path" - else - :"#{name}_url" - end - end def define_named_route_methods(name, route) - [true, false].each do |only_path| - hash = route.defaults.merge(:use_route => name, :only_path => only_path) - define_url_helper route, name, hash - end + define_url_helper route, :"#{name}_path", + route.defaults.merge(:use_route => name, :only_path => true) + define_url_helper route, :"#{name}_url", + route.defaults.merge(:use_route => name, :only_path => false) end # Create a url helper allowing ordered parameters to be associated @@ -191,11 +183,9 @@ module ActionDispatch # foo_url(bar, baz, bang, :sort_by => 'baz') # def define_url_helper(route, name, options) - selector = url_helper_name(name, options[:only_path]) - @module.module_eval <<-END_EVAL, __FILE__, __LINE__ + 1 - remove_possible_method :#{selector} - def #{selector}(*args) + remove_possible_method :#{name} + def #{name}(*args) if #{optimize_helper?(route)} && args.size == #{route.required_parts.size} && !args.last.is_a?(Hash) && optimize_routes_generation? options = #{options.inspect} options.merge!(url_options) if respond_to?(:url_options) @@ -207,7 +197,7 @@ module ActionDispatch end END_EVAL - helpers << selector + helpers << name end # Clause check about when we need to generate an optimized helper. @@ -236,7 +226,7 @@ module ActionDispatch attr_accessor :formatter, :set, :named_routes, :default_scope, :router attr_accessor :disable_clear_and_finalize, :resources_path_names - attr_accessor :default_url_options, :request_class, :valid_conditions + attr_accessor :default_url_options, :request_class alias :routes :set @@ -248,13 +238,7 @@ module ActionDispatch self.named_routes = NamedRouteCollection.new self.resources_path_names = self.class.default_resources_path_names.dup self.default_url_options = {} - self.request_class = request_class - @valid_conditions = { :controller => true, :action => true } - request_class.public_instance_methods.each { |m| - @valid_conditions[m] = true - } - @valid_conditions.delete(:id) @append = [] @prepend = [] @@ -385,7 +369,7 @@ module ActionDispatch raise ArgumentError, "Invalid route name: '#{name}'" unless name.blank? || name.to_s.match(/^[_a-z]\w*$/i) path = build_path(conditions.delete(:path_info), requirements, SEPARATORS, anchor) - conditions = build_conditions(conditions, valid_conditions, path.names.map { |x| x.to_sym }) + conditions = build_conditions(conditions, path.names.map { |x| x.to_sym }) route = @set.add_route(app, path, conditions, defaults, name) named_routes[name] = route if name && !named_routes[name] @@ -422,21 +406,22 @@ module ActionDispatch end private :build_path - def build_conditions(current_conditions, req_predicates, path_values) + def build_conditions(current_conditions, path_values) conditions = current_conditions.dup - verbs = conditions[:request_method] || [] - # Rack-Mount requires that :request_method be a regular expression. # :request_method represents the HTTP verb that matches this route. # # Here we munge values before they get sent on to rack-mount. + verbs = conditions[:request_method] || [] unless verbs.empty? conditions[:request_method] = %r[^#{verbs.join('|')}$] end - conditions.delete_if { |k,v| !(req_predicates.include?(k) || path_values.include?(k)) } - conditions + conditions.keep_if do |k, _| + k == :action || k == :controller || + @request_class.public_method_defined?(k) || path_values.include?(k) + end end private :build_conditions @@ -477,9 +462,7 @@ module ActionDispatch def use_recall_for(key) if @recall[key] && (!@options.key?(key) || @options[key] == @recall[key]) - if named_route_exists? - @options[key] = @recall.delete(key) if segment_keys.include?(key) - else + if !named_route_exists? || segment_keys.include?(key) @options[key] = @recall.delete(key) end end @@ -589,7 +572,8 @@ module ActionDispatch end RESERVED_OPTIONS = [:host, :protocol, :port, :subdomain, :domain, :tld_length, - :trailing_slash, :anchor, :params, :only_path, :script_name] + :trailing_slash, :anchor, :params, :only_path, :script_name, + :original_script_name] def mounted? false @@ -608,13 +592,19 @@ module ActionDispatch options = default_url_options.merge(options || {}) user, password = extract_authentication(options) - path_segments = options.delete(:_path_segments) - script_name = options.delete(:script_name).presence || _generate_prefix(options) + recall = options.delete(:_recall) + + original_script_name = options.delete(:original_script_name).presence + script_name = options.delete(:script_name).presence || _generate_prefix(options) + + if script_name && original_script_name + script_name = original_script_name + script_name + end path_options = options.except(*RESERVED_OPTIONS) path_options = yield(path_options) if block_given? - path, params = generate(path_options, path_segments || {}) + path, params = generate(path_options, recall || {}) params.merge!(options[:params] || {}) ActionDispatch::Http::URL.url_for(options.merge!({ diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb index fd3bed7e8f..f4c708ea33 100644 --- a/actionpack/lib/action_dispatch/routing/url_for.rb +++ b/actionpack/lib/action_dispatch/routing/url_for.rb @@ -102,7 +102,7 @@ module ActionDispatch super end - # Hook overriden in controller to add request information + # Hook overridden in controller to add request information # with `default_url_options`. Application logic should not # go into url_options. def url_options diff --git a/actionpack/lib/action_dispatch/testing/assertions/response.rb b/actionpack/lib/action_dispatch/testing/assertions/response.rb index b4c8f839ac..b15e0446de 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/response.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/response.rb @@ -1,4 +1,3 @@ -require 'active_support/core_ext/object/inclusion' module ActionDispatch module Assertions diff --git a/actionpack/lib/action_dispatch/testing/assertions/selector.rb b/actionpack/lib/action_dispatch/testing/assertions/selector.rb index 5f9c3bbf48..d19d116a1f 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/selector.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/selector.rb @@ -1,5 +1,4 @@ require 'action_controller/vendor/html-scanner' -require 'active_support/core_ext/object/inclusion' #-- # Copyright (c) 2006 Assaf Arkin (http://labnotes.org) diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb index 50ca28395b..ab584abf68 100644 --- a/actionpack/lib/action_dispatch/testing/integration.rb +++ b/actionpack/lib/action_dispatch/testing/integration.rb @@ -1,7 +1,6 @@ require 'stringio' require 'uri' require 'active_support/core_ext/kernel/singleton_class' -require 'active_support/core_ext/object/inclusion' require 'active_support/core_ext/object/try' require 'rack/test' @@ -346,7 +345,7 @@ module ActionDispatch define_method(method) do |*args| reset! unless integration_session # reset the html_document variable, but only for new get/post calls - @html_document = nil unless method.in?(["cookies", "assigns"]) + @html_document = nil unless method == 'cookies' || method == 'assigns' integration_session.__send__(method, *args).tap do copy_session_variables! end diff --git a/actionpack/lib/action_dispatch/testing/test_request.rb b/actionpack/lib/action_dispatch/testing/test_request.rb index 639ae6f398..c63778f870 100644 --- a/actionpack/lib/action_dispatch/testing/test_request.rb +++ b/actionpack/lib/action_dispatch/testing/test_request.rb @@ -1,4 +1,3 @@ -require 'active_support/core_ext/object/blank' require 'active_support/core_ext/hash/indifferent_access' require 'rack/utils' diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb index 03dfa110e3..4bd72c5520 100644 --- a/actionpack/lib/action_view.rb +++ b/actionpack/lib/action_view.rb @@ -22,6 +22,7 @@ #++ require 'active_support' +require 'active_support/rails' require 'action_pack' module ActionView diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb index 7bfbc1f0aa..749332eca7 100644 --- a/actionpack/lib/action_view/base.rb +++ b/actionpack/lib/action_view/base.rb @@ -1,6 +1,4 @@ require 'active_support/core_ext/module/attr_internal' -require 'active_support/core_ext/module/delegation' -require 'active_support/core_ext/class/attribute' require 'active_support/core_ext/class/attribute_accessors' require 'active_support/ordered_options' require 'action_view/log_subscriber' diff --git a/actionpack/lib/action_view/helpers/active_model_helper.rb b/actionpack/lib/action_view/helpers/active_model_helper.rb index e27111012d..901f433c70 100644 --- a/actionpack/lib/action_view/helpers/active_model_helper.rb +++ b/actionpack/lib/action_view/helpers/active_model_helper.rb @@ -1,6 +1,5 @@ require 'active_support/core_ext/class/attribute_accessors' require 'active_support/core_ext/enumerable' -require 'active_support/core_ext/object/blank' module ActionView # = Active Model Helpers diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb index 1a54ca2399..e42e49fb04 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb @@ -1,4 +1,3 @@ -require 'active_support/core_ext/class/attribute' require 'active_support/core_ext/string/inflections' require 'active_support/core_ext/file' require 'action_view/helpers/tag_helper' diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb index e0dbfe62c6..139f4d19ab 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb @@ -1,4 +1,3 @@ -require 'active_support/concern' require 'active_support/core_ext/file' require 'action_view/helpers/asset_tag_helpers/asset_include_tag' @@ -27,7 +26,8 @@ module ActionView def expand_sources(sources, recursive = false) if sources.include?(:all) - all_asset_files = (collect_asset_files(custom_dir, ('**' if recursive), "*.#{extension}") - ['application']) << 'application' + all_asset_files = (collect_asset_files(custom_dir, ('**' if recursive), "*.#{extension}") - ['application']) + add_application_js(all_asset_files, sources) ((determine_source(:defaults, expansions).dup & all_asset_files) + all_asset_files).uniq else expanded_sources = sources.inject([]) do |list, source| @@ -40,7 +40,7 @@ module ActionView end def add_application_js(expanded_sources, sources) - if sources.include?(:defaults) && File.exist?(File.join(custom_dir, "application.#{extension}")) + if (sources.include?(:defaults) || sources.include?(:all)) && File.exist?(File.join(custom_dir, "application.#{extension}")) expanded_sources.delete('application') expanded_sources << "application" end @@ -107,8 +107,8 @@ module ActionView # # config.action_view.javascript_expansions[:defaults] = %w(foo.js bar.js) # - # When using <tt>:defaults</tt>, if an <tt>application.js</tt> file exists in - # <tt>public/javascripts</tt> it will be included as well at the end. + # When using <tt>:defaults</tt> or <tt>:all</tt>, if an <tt>application.js</tt> file exists + # in <tt>public/javascripts</tt> it will be included as well at the end. # # You can modify the HTML attributes of the script tag by passing a hash as the # last argument. @@ -134,14 +134,16 @@ module ActionView # # <script src="/javascripts/rails.js?1284139606"></script> # # <script src="/javascripts/application.js?1284139606"></script> # + # Note: The application.js file is only referenced if it exists + # # You can also include all JavaScripts in the +javascripts+ directory using <tt>:all</tt> as the source: # # javascript_include_tag :all # # => <script src="/javascripts/jquery.js?1284139606"></script> # # <script src="/javascripts/rails.js?1284139606"></script> - # # <script src="/javascripts/application.js?1284139606"></script> # # <script src="/javascripts/shop.js?1284139606"></script> # # <script src="/javascripts/checkout.js?1284139606"></script> + # # <script src="/javascripts/application.js?1284139606"></script> # # Note that your defaults of choice will be included first, so they will be available to all subsequently # included files. @@ -162,9 +164,9 @@ module ActionView # javascript_include_tag :all, :cache => true # # => <script src="/javascripts/jquery.js?1284139606"></script> # # <script src="/javascripts/rails.js?1284139606"></script> - # # <script src="/javascripts/application.js?1284139606"></script> # # <script src="/javascripts/shop.js?1284139606"></script> # # <script src="/javascripts/checkout.js?1284139606"></script> + # # <script src="/javascripts/application.js?1284139606"></script> # # # assuming config.perform_caching is true # javascript_include_tag :all, :cache => true diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb index 91318b2812..e3a86a8889 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb @@ -1,4 +1,3 @@ -require 'active_support/concern' require 'active_support/core_ext/file' require 'action_view/helpers/asset_tag_helpers/asset_include_tag' diff --git a/actionpack/lib/action_view/helpers/cache_helper.rb b/actionpack/lib/action_view/helpers/cache_helper.rb index 33799d7d71..39518268df 100644 --- a/actionpack/lib/action_view/helpers/cache_helper.rb +++ b/actionpack/lib/action_view/helpers/cache_helper.rb @@ -8,28 +8,28 @@ module ActionView # fragments, and so on. This method takes a block that contains # the content you wish to cache. # - # See ActionController::Caching::Fragments for usage instructions. + # 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 # - # If you want to cache a navigation menu, you can do following: + # When using this method, you list the cache dependencies as part of + # the name of the cache, like so: # - # <% cache do %> - # <%= render :partial => "menu" %> + # <% cache [ "v1", project ] do %> + # <b>All the topics on this project</b> + # <%= render project.topics %> # <% end %> # - # You can also cache static content: + # 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: # - # <% cache do %> - # <p>Hello users! Welcome to our website!</p> - # <% end %> - # - # Static content with embedded ruby content can be cached as - # well: + # views/v1/projects/123-20120806214154 + # ^class ^id ^updated_at # - # <% cache do %> - # Topics: - # <%= render :partial => "topics", :collection => @topic_list %> - # <i>Topics listed alphabetically</i> - # <% end %> + # If you update the rendering of topics, you just bump the version to v2. + # Otherwise the cache is automatically bumped whenever the project updated_at + # is touched. def cache(name = {}, options = nil, &block) if controller.perform_caching safe_concat(fragment_for(name, options, &block)) diff --git a/actionpack/lib/action_view/helpers/capture_helper.rb b/actionpack/lib/action_view/helpers/capture_helper.rb index 397738dd98..c98101a195 100644 --- a/actionpack/lib/action_view/helpers/capture_helper.rb +++ b/actionpack/lib/action_view/helpers/capture_helper.rb @@ -1,4 +1,3 @@ -require 'active_support/core_ext/object/blank' require 'active_support/core_ext/string/output_safety' module ActionView @@ -134,7 +133,7 @@ module ActionView # # <%# Add some other content, or use a different template: %> # - # <% content_for :navigation, true do %> + # <% content_for :navigation, flush: true do %> # <li><%= link_to 'Login', :action => 'login' %></li> # <% end %> # @@ -148,14 +147,14 @@ module ActionView # # 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, flush = false, &block) + def content_for(name, content = nil, options = {}, &block) if content || block_given? if block_given? - flush = content if content + options = content if content content = capture(&block) end if content - flush ? @view_flow.set(name, content) : @view_flow.append(name, content) + options[:flush] ? @view_flow.set(name, content) : @view_flow.append(name, content) end nil else @@ -213,7 +212,7 @@ module ActionView # 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.body_parts << output_buffer + response.stream.write output_buffer self.output_buffer = output_buffer.respond_to?(:clone_empty) ? output_buffer.clone_empty : output_buffer[0, 0] nil end diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb index 659aacf6d7..dea2aa69dd 100644 --- a/actionpack/lib/action_view/helpers/date_helper.rb +++ b/actionpack/lib/action_view/helpers/date_helper.rb @@ -427,6 +427,9 @@ module ActionView # # 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 @@ -504,6 +507,9 @@ module ActionView # # # 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 @@ -734,7 +740,11 @@ module ActionView if @options[:use_hidden] || @options[:discard_hour] build_hidden(:hour, hour) else - build_options_and_select(:hour, hour, :end => 23, :ampm => @options[:ampm]) + 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 diff --git a/actionpack/lib/action_view/helpers/debug_helper.rb b/actionpack/lib/action_view/helpers/debug_helper.rb index 878a8734a4..d8b92c5cab 100644 --- a/actionpack/lib/action_view/helpers/debug_helper.rb +++ b/actionpack/lib/action_view/helpers/debug_helper.rb @@ -4,6 +4,9 @@ module ActionView # 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. @@ -26,10 +29,11 @@ module ActionView def debug(object) begin Marshal::dump(object) - "<pre class='debug_dump'>#{h(object.to_yaml).gsub(" ", " ")}</pre>".html_safe + 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 - "<code class='debug_dump'>#{h(object.inspect)}</code>".html_safe + content_tag(:code, object.to_yaml, :class => "debug_dump") end end end diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb index b34f6c8650..5dc5bb8a98 100644 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ b/actionpack/lib/action_view/helpers/form_helper.rb @@ -4,13 +4,10 @@ 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 'active_support/core_ext/class/attribute' require 'active_support/core_ext/class/attribute_accessors' require 'active_support/core_ext/hash/slice' -require 'active_support/core_ext/object/blank' require 'active_support/core_ext/string/output_safety' require 'active_support/core_ext/array/extract_options' -require 'active_support/deprecation' require 'active_support/core_ext/string/inflections' require 'action_controller/model_naming' @@ -326,6 +323,24 @@ module ActionView # ... # </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='put' /> + # </div> + # ... + # </form> + # # === Removing hidden model id's # # The form_for method automatically includes the model id as a hidden field in the form. @@ -408,10 +423,12 @@ module ActionView object = nil else object = record.is_a?(Array) ? record.last : record + raise ArgumentError, "First argument in form cannot contain nil or be empty" if object.blank? object_name = options[:as] || model_name_from_record_or_class(object).param_key apply_form_for_options!(record, object, options) end + options[:html][:data] = options.delete(:data) if options.has_key?(:data) options[:html][:remote] = options.delete(:remote) if options.has_key?(:remote) options[:html][:method] = options.delete(:method) if options.has_key?(:method) options[:html][:authenticity_token] = options.delete(:authenticity_token) diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb index c88af0355f..e4f4ebc7ff 100644 --- a/actionpack/lib/action_view/helpers/form_options_helper.rb +++ b/actionpack/lib/action_view/helpers/form_options_helper.rb @@ -1,7 +1,6 @@ require 'cgi' require 'erb' require 'action_view/helpers/form_helper' -require 'active_support/core_ext/object/blank' require 'active_support/core_ext/string/output_safety' module ActionView @@ -709,9 +708,11 @@ module ActionView private def option_html_attributes(element) - return {} unless Array === element - - Hash[element.select { |e| Hash === e }.reduce({}, :merge).map { |k, v| [k, v] }] + if Array === element + element.select { |e| Hash === e }.reduce({}, :merge!) + else + {} + end end def option_text_and_value(option) diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb index d7d9c45120..a9b91d1db3 100644 --- a/actionpack/lib/action_view/helpers/form_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb @@ -1,13 +1,12 @@ require 'cgi' require 'action_view/helpers/tag_helper' -require 'active_support/core_ext/object/blank' 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 doesn't rely on an Active Record object assigned to the template like + # 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 diff --git a/actionpack/lib/action_view/helpers/javascript_helper.rb b/actionpack/lib/action_view/helpers/javascript_helper.rb index cc20518b93..9f8cd8caaa 100644 --- a/actionpack/lib/action_view/helpers/javascript_helper.rb +++ b/actionpack/lib/action_view/helpers/javascript_helper.rb @@ -67,6 +67,46 @@ module ActionView 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. Use Unobtrusive JavaScript instead." + 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. Use Unobtrusive JavaScript instead." + 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/sanitize_helper.rb b/actionpack/lib/action_view/helpers/sanitize_helper.rb index a727b910e5..aaf0e0344a 100644 --- a/actionpack/lib/action_view/helpers/sanitize_helper.rb +++ b/actionpack/lib/action_view/helpers/sanitize_helper.rb @@ -78,7 +78,7 @@ module ActionView # 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).try(:html_safe) + self.class.full_sanitizer.sanitize(html) end # Strips all link tags from +text+ leaving just the link text. diff --git a/actionpack/lib/action_view/helpers/tag_helper.rb b/actionpack/lib/action_view/helpers/tag_helper.rb index 9572f1c192..3327c69d61 100644 --- a/actionpack/lib/action_view/helpers/tag_helper.rb +++ b/actionpack/lib/action_view/helpers/tag_helper.rb @@ -1,4 +1,3 @@ -require 'active_support/core_ext/object/blank' require 'active_support/core_ext/string/output_safety' require 'set' diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb index 0cc0d069ea..0f599d5f41 100644 --- a/actionpack/lib/action_view/helpers/text_helper.rb +++ b/actionpack/lib/action_view/helpers/text_helper.rb @@ -1,4 +1,3 @@ -require 'active_support/core_ext/object/blank' require 'active_support/core_ext/string/filters' require 'active_support/core_ext/array/extract_options' diff --git a/actionpack/lib/action_view/lookup_context.rb b/actionpack/lib/action_view/lookup_context.rb index 47dd932c71..f0ea92b018 100644 --- a/actionpack/lib/action_view/lookup_context.rb +++ b/actionpack/lib/action_view/lookup_context.rb @@ -1,4 +1,3 @@ -require 'active_support/core_ext/object/blank' require 'active_support/core_ext/module/remove_method' module ActionView diff --git a/actionpack/lib/action_view/renderer/partial_renderer.rb b/actionpack/lib/action_view/renderer/partial_renderer.rb index a08a566b35..edefeac184 100644 --- a/actionpack/lib/action_view/renderer/partial_renderer.rb +++ b/actionpack/lib/action_view/renderer/partial_renderer.rb @@ -1,4 +1,3 @@ -require 'active_support/core_ext/object/blank' module ActionView # = Action View Partials @@ -337,17 +336,16 @@ module ActionView 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) + @variable, @variable_counter = retrieve_variable(@path, as) @template_keys = retrieve_template_keys else - paths.map! { |path| retrieve_variable(path).unshift(path) } - end - - if String === partial && @variable.to_s !~ /^[a-z_][a-zA-Z_0-9]*$/ - raise ArgumentError.new("The partial name (#{partial}) 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.") + paths.map! { |path| retrieve_variable(path, as).unshift(path) } end self @@ -456,10 +454,22 @@ module ActionView keys end - def retrieve_variable(path) - variable = @options.fetch(:as) { path[%r'_?(\w+)(\.\w+)*$', 1] }.try(:to_sym) + 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/template.rb b/actionpack/lib/action_view/template.rb index cd79468502..a04eac1d3f 100644 --- a/actionpack/lib/action_view/template.rb +++ b/actionpack/lib/action_view/template.rb @@ -1,4 +1,3 @@ -require 'active_support/core_ext/object/blank' require 'active_support/core_ext/object/try' require 'active_support/core_ext/kernel/singleton_class' require 'thread' diff --git a/actionpack/lib/action_view/template/handlers/erb.rb b/actionpack/lib/action_view/template/handlers/erb.rb index 19b9112afd..aa8eac7846 100644 --- a/actionpack/lib/action_view/template/handlers/erb.rb +++ b/actionpack/lib/action_view/template/handlers/erb.rb @@ -1,5 +1,4 @@ require 'action_dispatch/http/mime_type' -require 'active_support/core_ext/class/attribute' require 'erubis' module ActionView diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb index 53bde48e4d..55f79bf761 100644 --- a/actionpack/lib/action_view/test_case.rb +++ b/actionpack/lib/action_view/test_case.rb @@ -1,5 +1,3 @@ -require 'active_support/core_ext/object/blank' -require 'active_support/core_ext/module/delegation' require 'active_support/core_ext/module/remove_method' require 'action_controller' require 'action_controller/test_case' |