diff options
author | Jon Leighton <j@jonathanleighton.com> | 2010-11-27 11:31:17 +0000 |
---|---|---|
committer | Jon Leighton <j@jonathanleighton.com> | 2010-11-27 11:31:17 +0000 |
commit | 3a7f43ca6ecf1735e1a82d4a68ac8f62b5cf2fcf (patch) | |
tree | 4960911c58f2c47ecaf4f4579167270ba9c257a7 /actionpack | |
parent | 1bc90044b655572a4b8aa3b323905e26d37e0f2b (diff) | |
parent | fd83f9d51583c080072dc9fd00f02ad742e265d4 (diff) | |
download | rails-3a7f43ca6ecf1735e1a82d4a68ac8f62b5cf2fcf.tar.gz rails-3a7f43ca6ecf1735e1a82d4a68ac8f62b5cf2fcf.tar.bz2 rails-3a7f43ca6ecf1735e1a82d4a68ac8f62b5cf2fcf.zip |
Merge branch 'master' into nested_has_many_through
Conflicts:
activerecord/CHANGELOG
activerecord/lib/active_record/associations.rb
Diffstat (limited to 'actionpack')
43 files changed, 623 insertions, 197 deletions
diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index db02beee35..74d017cc3d 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -1,5 +1,7 @@ *Rails 3.1.0 (unreleased)* +* Added config.action_controller.include_all_helpers. By default 'helper :all' is done in ActionController::Base, which includes all the helpers by default. Setting include_all_helpers to false will result in including only application_helper and helper corresponding to controller (like foo_helper for foo_controller). [Piotr Sarnacki] + * Added a convenience idiom to generate HTML5 data-* attributes in tag helpers from a :data hash of options: tag("div", :data => {:name => 'Stephen', :city_state => %w(Chicago IL)}) diff --git a/actionpack/Rakefile b/actionpack/Rakefile index a6ce08113f..9030db9f7a 100644..100755 --- a/actionpack/Rakefile +++ b/actionpack/Rakefile @@ -1,4 +1,4 @@ -require 'rake' +#!/usr/bin/env rake require 'rake/testtask' require 'rake/packagetask' require 'rake/gempackagetask' diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec index c2af3dfaf5..44967631ff 100644 --- a/actionpack/actionpack.gemspec +++ b/actionpack/actionpack.gemspec @@ -22,7 +22,7 @@ Gem::Specification.new do |s| s.add_dependency('activesupport', version) s.add_dependency('activemodel', version) s.add_dependency('rack-cache', '~> 0.5.3') - s.add_dependency('builder', '~> 2.1.2') + s.add_dependency('builder', '~> 3.0.0') s.add_dependency('i18n', '~> 0.4.2') s.add_dependency('rack', '~> 1.2.1') s.add_dependency('rack-test', '~> 0.5.6') diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb index 475785a4b4..91b75273fa 100644 --- a/actionpack/lib/abstract_controller/rendering.rb +++ b/actionpack/lib/abstract_controller/rendering.rb @@ -119,7 +119,7 @@ module AbstractController controller_path end - private + private # This method should return a hash with assigns. # You can overwrite this configuration per controller. @@ -128,7 +128,7 @@ module AbstractController hash = {} variables = instance_variable_names variables -= protected_instance_variables if respond_to?(:protected_instance_variables) - variables.each { |name| hash[name.to_s[1..-1]] = instance_variable_get(name) } + variables.each { |name| hash[name.to_s[1, name.length]] = instance_variable_get(name) } hash end @@ -138,13 +138,13 @@ module AbstractController case action when NilClass when Hash - options, action = action, nil + options = action when String, Symbol action = action.to_s key = action.include?(?/) ? :file : :action options[key] = action else - options.merge!(:partial => action) + options[:partial] = action end options diff --git a/actionpack/lib/action_controller/metal/helpers.rb b/actionpack/lib/action_controller/metal/helpers.rb index d14831b763..91a88ab68a 100644 --- a/actionpack/lib/action_controller/metal/helpers.rb +++ b/actionpack/lib/action_controller/metal/helpers.rb @@ -53,8 +53,9 @@ module ActionController include AbstractController::Helpers included do - config_accessor :helpers_path + config_accessor :helpers_path, :include_all_helpers self.helpers_path ||= [] + self.include_all_helpers = true end module ClassMethods diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb index f7dd0dcb69..9ba37134b8 100644 --- a/actionpack/lib/action_controller/metal/mime_responds.rb +++ b/actionpack/lib/action_controller/metal/mime_responds.rb @@ -258,9 +258,8 @@ module ActionController #:nodoc: # nil if :not_acceptable was sent to the client. # def retrieve_response_from_mimes(mimes=nil, &block) - collector = Collector.new { default_render } mimes ||= collect_mimes_from_class_level - mimes.each { |mime| collector.send(mime) } + collector = Collector.new(mimes) { default_render } block.call(collector) if block_given? if format = request.negotiate_mime(collector.order) @@ -277,8 +276,9 @@ module ActionController #:nodoc: include AbstractController::Collector attr_accessor :order - def initialize(&block) + def initialize(mimes, &block) @order, @responses, @default_response = [], {}, block + mimes.each { |mime| send(mime) } end def any(*args, &block) @@ -291,7 +291,7 @@ module ActionController #:nodoc: alias :all :any def custom(mime_type, &block) - mime_type = mime_type.is_a?(Mime::Type) ? mime_type : Mime::Type.lookup(mime_type.to_s) + mime_type = Mime::Type.lookup(mime_type.to_s) unless mime_type.is_a?(Mime::Type) @order << mime_type @responses[mime_type] ||= block end diff --git a/actionpack/lib/action_controller/metal/renderers.rb b/actionpack/lib/action_controller/metal/renderers.rb index f9b226b7c9..d6f6ab1855 100644 --- a/actionpack/lib/action_controller/metal/renderers.rb +++ b/actionpack/lib/action_controller/metal/renderers.rb @@ -15,30 +15,12 @@ module ActionController end module ClassMethods - def _write_render_options - renderers = _renderers.map do |name, value| - <<-RUBY_EVAL - if options.key?(:#{name}) - _process_options(options) - return _render_option_#{name}(options.delete(:#{name}), options) - end - RUBY_EVAL - end - - class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 - def _handle_render_options(options) - #{renderers.join} - end - RUBY_EVAL - end - def use_renderers(*args) new = _renderers.dup args.each do |key| new[key] = RENDERERS[key] end self._renderers = new.freeze - _write_render_options end alias use_renderer use_renderers end @@ -47,31 +29,33 @@ module ActionController _handle_render_options(options) || super end + def _handle_render_options(options) + _renderers.each do |name, value| + if options.key?(name.to_sym) + _process_options(options) + return send("_render_option_#{name}", options.delete(name.to_sym), options) + end + end + nil + end + RENDERERS = {} def self.add(key, &block) define_method("_render_option_#{key}", &block) RENDERERS[key] = block - All._write_render_options end module All extend ActiveSupport::Concern include Renderers - INCLUDED = [] included do self._renderers = RENDERERS - _write_render_options - INCLUDED << self - end - - def self._write_render_options - INCLUDED.each(&:_write_render_options) end end add :json do |json, options| - json = json.to_json(options) unless json.respond_to?(:to_str) + json = json.to_json(options) unless json.kind_of?(String) json = "#{options[:callback]}(#{json})" unless options[:callback].blank? self.content_type ||= Mime::JSON self.response_body = json diff --git a/actionpack/lib/action_controller/metal/rendering.rb b/actionpack/lib/action_controller/metal/rendering.rb index e524e546ad..14cc547dd0 100644 --- a/actionpack/lib/action_controller/metal/rendering.rb +++ b/actionpack/lib/action_controller/metal/rendering.rb @@ -20,36 +20,35 @@ module ActionController private - # Normalize arguments by catching blocks and setting them on :update. - def _normalize_args(action=nil, options={}, &blk) #:nodoc: - options = super - options[:update] = blk if block_given? - options - end - - # Normalize both text and status options. - def _normalize_options(options) #:nodoc: - if options.key?(:text) && options[:text].respond_to?(:to_text) - options[:text] = options[:text].to_text - end + # Normalize arguments by catching blocks and setting them on :update. + def _normalize_args(action=nil, options={}, &blk) #:nodoc: + options = super + options[:update] = blk if block_given? + options + end - if options[:status] - options[:status] = Rack::Utils.status_code(options[:status]) - end + # Normalize both text and status options. + def _normalize_options(options) #:nodoc: + if options.key?(:text) && options[:text].respond_to?(:to_text) + options[:text] = options[:text].to_text + end - super + if options[:status] + options[:status] = Rack::Utils.status_code(options[:status]) end - # Process controller specific options, as status, content-type and location. - def _process_options(options) #:nodoc: - status, content_type, location = options.values_at(:status, :content_type, :location) + super + end - self.status = status if status - self.content_type = content_type if content_type - self.headers["Location"] = url_for(location) if location + # Process controller specific options, as status, content-type and location. + def _process_options(options) #:nodoc: + status, content_type, location = options.values_at(:status, :content_type, :location) - super - end + self.status = status if status + self.content_type = content_type if content_type + self.headers["Location"] = url_for(location) if location + super + end end end diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb index 02f577647e..148efbb081 100644 --- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb +++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb @@ -53,9 +53,13 @@ module ActionController #:nodoc: # class FooController < ApplicationController # protect_from_forgery :except => :index # - # # you can disable csrf protection on controller-by-controller basis: - # skip_before_filter :verify_authenticity_token - # end + # You can disable csrf protection on controller-by-controller basis: + # + # skip_before_filter :verify_authenticity_token + # + # It can also be disabled for specific controller actions: + # + # skip_before_filter :verify_authenticity_token, :except => [:create] # # Valid Options: # diff --git a/actionpack/lib/action_controller/metal/url_for.rb b/actionpack/lib/action_controller/metal/url_for.rb index 333eeaeffb..6fc0cf1fb8 100644 --- a/actionpack/lib/action_controller/metal/url_for.rb +++ b/actionpack/lib/action_controller/metal/url_for.rb @@ -6,7 +6,8 @@ module ActionController def url_options @_url_options ||= super.reverse_merge( - :host => request.host_with_port, + :host => request.host, + :port => request.optional_port, :protocol => request.protocol, :_path_segments => request.symbolized_path_parameters ).freeze @@ -20,5 +21,6 @@ module ActionController @_url_options end end + end end diff --git a/actionpack/lib/action_controller/railties/paths.rb b/actionpack/lib/action_controller/railties/paths.rb index 7a59d4f2f3..699c44c62c 100644 --- a/actionpack/lib/action_controller/railties/paths.rb +++ b/actionpack/lib/action_controller/railties/paths.rb @@ -13,7 +13,9 @@ module ActionController end klass.helpers_path = paths - klass.helper :all if klass.superclass == ActionController::Base + if klass.superclass == ActionController::Base && ActionController::Base.include_all_helpers + klass.helper :all + end end end end diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index 2b2f647d32..0f43527a56 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -1,6 +1,7 @@ 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' module ActionController module TemplateAssertions @@ -325,11 +326,11 @@ module ActionController def controller_class=(new_class) prepare_controller_class(new_class) if new_class - write_inheritable_attribute(:controller_class, new_class) + self._controller_class = new_class end def controller_class - if current_controller_class = read_inheritable_attribute(:controller_class) + if current_controller_class = self._controller_class current_controller_class else self.controller_class = determine_default_controller_class(name) @@ -442,6 +443,7 @@ module ActionController included do include ActionController::TemplateAssertions include ActionDispatch::Assertions + class_attribute :_controller_class setup :setup_controller_request_and_response end 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 dceddb9b80..3e5d23b5c1 100644 --- a/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb +++ b/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb @@ -1,5 +1,5 @@ require 'set' -require 'active_support/core_ext/class/inheritable_attributes' +require 'active_support/core_ext/class/attribute' module HTML class Sanitizer @@ -60,7 +60,7 @@ module HTML 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_inheritable_accessor attr, :instance_writer => false + class_attribute attr, :instance_writer => false end # A regular expression of the valid characters used to separate protocols like diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb index b959aa258e..68ba1a81b5 100644 --- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb +++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb @@ -36,19 +36,21 @@ module ActionDispatch # # GET /posts/5.xml | request.format => Mime::XML # GET /posts/5.xhtml | request.format => Mime::HTML - # GET /posts/5 | request.format => Mime::HTML or MIME::JS, or request.accepts.first depending on the value of <tt>ActionController::Base.use_accept_header</tt> + # GET /posts/5 | request.format => Mime::HTML or MIME::JS, or request.accepts.first # def format(view_path = []) formats.first end + BROWSER_LIKE_ACCEPTS = /,\s*\*\/\*|\*\/\*\s*,/ + def formats accept = @env['HTTP_ACCEPT'] @env["action_dispatch.request.formats"] ||= if parameters[:format] Array(Mime[parameters[:format]]) - elsif xhr? || (accept && accept !~ /,\s*\*\/\*/) + elsif xhr? || (accept && accept !~ BROWSER_LIKE_ACCEPTS) accepts else [Mime::HTML] diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb index 8f1c9b6691..08eab5634a 100644 --- a/actionpack/lib/action_dispatch/http/mime_type.rb +++ b/actionpack/lib/action_dispatch/http/mime_type.rb @@ -80,6 +80,9 @@ module Mime end class << self + + TRAILING_STAR_REGEXP = /(text|application)\/\*/ + def lookup(string) LOOKUP[string] end @@ -105,7 +108,11 @@ module Mime def parse(accept_header) if accept_header !~ /,/ - [Mime::Type.lookup(accept_header)] + if accept_header =~ TRAILING_STAR_REGEXP + parse_data_with_trailing_star($1) + else + [Mime::Type.lookup(accept_header)] + end else # keep track of creation order to keep the subsequent sort stable list = [] @@ -113,7 +120,11 @@ module Mime params, q = header.split(/;\s*q=/) if params params.strip! - list << AcceptItem.new(index, params, q) unless params.empty? + if params =~ TRAILING_STAR_REGEXP + parse_data_with_trailing_star($1).each { |m| list << AcceptItem.new(index, m.to_s, q) } + else + list << AcceptItem.new(index, params, q) unless params.empty? + end end end list.sort! @@ -160,6 +171,28 @@ module Mime list end end + + # input: 'text' + # returend value: [Mime::JSON, Mime::XML, Mime::ICS, Mime::HTML, Mime::CSS, Mime::CSV, Mime::JS, Mime::YAML, Mime::TEXT] + # + # input: 'application' + # returend value: [Mime::HTML, Mime::JS, Mime::XML, Mime::YAML, Mime::ATOM, Mime::JSON, Mime::RSS, Mime::URL_ENCODED_FORM + def parse_data_with_trailing_star(input) + keys = Mime::LOOKUP.keys.select{|k| k.include?(input)} + Mime::LOOKUP.values_at(*keys).uniq + end + + # This method is opposite of register method. + # + # Usage: + # + # Mime::Type.unregister("text/x-mobile", :mobile) + def unregister(string, symbol) + EXTENSION_LOOKUP.delete(symbol.to_s) + LOOKUP.delete(string) + symbol = symbol.to_s.upcase.intern + Mime.module_eval { remove_const(symbol) if const_defined?(symbol) } + end end def initialize(string, symbol = nil, synonyms = []) diff --git a/actionpack/lib/action_dispatch/http/upload.rb b/actionpack/lib/action_dispatch/http/upload.rb index 84e58d7d6a..37effade4f 100644 --- a/actionpack/lib/action_dispatch/http/upload.rb +++ b/actionpack/lib/action_dispatch/http/upload.rb @@ -1,5 +1,3 @@ -require 'active_support/core_ext/object/blank' - module ActionDispatch module Http class UploadedFile @@ -13,6 +11,14 @@ module ActionDispatch raise(ArgumentError, ':tempfile is required') unless @tempfile end + def open + @tempfile.open + end + + def path + @tempfile.path + end + def read(*args) @tempfile.read(*args) end diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb index 9c9eed2c6d..1e7054f381 100644 --- a/actionpack/lib/action_dispatch/http/url.rb +++ b/actionpack/lib/action_dispatch/http/url.rb @@ -4,6 +4,27 @@ module ActionDispatch mattr_accessor :tld_length self.tld_length = 1 + def self.extract_domain(host, tld_length = @@tld_length) + return nil unless named_host?(host) + + host.split('.').last(1 + tld_length).join('.') + end + + def self.extract_subdomains(host, tld_length = @@tld_length) + return [] unless named_host?(host) + parts = host.split('.') + parts[0..-(tld_length+2)] + end + + def self.extract_subdomain(host, tld_length = @@tld_length) + extract_subdomains(host, tld_length).join('.') + end + + def self.named_host?(host) + !(host.nil? || /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.match(host)) + end + + # Returns the complete URL used for this request. def url protocol + host_with_port + fullpath @@ -36,10 +57,12 @@ module ActionDispatch # Returns the port number of this request as an integer. def port - if raw_host_with_port =~ /:(\d+)$/ - $1.to_i - else - standard_port + @port ||= begin + if raw_host_with_port =~ /:(\d+)$/ + $1.to_i + else + standard_port + end end end @@ -56,10 +79,16 @@ module ActionDispatch port == standard_port end - # Returns a \port suffix like ":8080" if the \port number of this request + # Returns a number \port suffix like 8080 if the \port number of this request # is not the default HTTP \port 80 or HTTPS \port 443. + def optional_port + standard_port? ? nil : port + end + + # Returns a string \port suffix, including colon, like ":8080" if the \port + # number of this request is not the default HTTP \port 80 or HTTPS \port 443. def port_string - port == standard_port ? '' : ":#{port}" + standard_port? ? '' : ":#{port}" end def server_port @@ -69,9 +98,7 @@ module ActionDispatch # Returns the \domain part of a \host, such as "rubyonrails.org" in "www.rubyonrails.org". You can specify # a different <tt>tld_length</tt>, such as 2 to catch rubyonrails.co.uk in "www.rubyonrails.co.uk". def domain(tld_length = @@tld_length) - return nil unless named_host?(host) - - host.split('.').last(1 + tld_length).join('.') + ActionDispatch::Http::URL.extract_domain(host, tld_length) end # Returns all the \subdomains as an array, so <tt>["dev", "www"]</tt> would be @@ -79,20 +106,17 @@ module ActionDispatch # such as 2 to catch <tt>["www"]</tt> instead of <tt>["www", "rubyonrails"]</tt> # in "www.rubyonrails.co.uk". def subdomains(tld_length = @@tld_length) - return [] unless named_host?(host) - parts = host.split('.') - parts[0..-(tld_length+2)] + ActionDispatch::Http::URL.extract_subdomains(host, tld_length) end + # Returns all the \subdomains as a string, so <tt>"dev.www"</tt> would be + # returned for "dev.www.rubyonrails.org". You can specify a different <tt>tld_length</tt>, + # such as 2 to catch <tt>["www"]</tt> instead of <tt>"www.rubyonrails"</tt> + # in "www.rubyonrails.co.uk". def subdomain(tld_length = @@tld_length) - subdomains(tld_length).join('.') + subdomains(tld_length) end - private - - def named_host?(host) - !(host.nil? || /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.match(host)) - end end end end diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb index c4c2e1acd7..b0a4e3d949 100644 --- a/actionpack/lib/action_dispatch/middleware/cookies.rb +++ b/actionpack/lib/action_dispatch/middleware/cookies.rb @@ -16,17 +16,23 @@ module ActionDispatch # Examples for writing: # # # Sets a simple session cookie. + # # This cookie will be deleted when the user's browser is closed. # cookies[:user_name] = "david" # + # # Assign an array of values to a cookie. + # cookies[:lat_lon] = [47.68, -122.37] + # # # Sets a cookie that expires in 1 hour. # cookies[:login] = { :value => "XJ-122", :expires => 1.hour.from_now } # # # Sets a signed cookie, which prevents a user from tampering with its value. - # # You must specify a value in ActionController::Base.cookie_verifier_secret. - # cookies.signed[:remember_me] = [current_user.id, current_user.salt] + # # The cookie is signed by your app's <tt>config.secret_token</tt> value. + # # Rails generates this value by default when you create a new Rails app. + # cookies.signed[:user_id] = current_user.id # # # Sets a "permanent" cookie (which expires in 20 years from now). # cookies.permanent[:login] = "XJ-122" + # # # You can also chain these methods: # cookies.permanent.signed[:login] = "XJ-122" # @@ -34,6 +40,7 @@ module ActionDispatch # # cookies[:user_name] # => "david" # cookies.size # => 2 + # cookies[:lat_lon] # => [47.68, -122.37] # # Example for deleting: # @@ -275,7 +282,7 @@ module ActionDispatch "integrity hash for cookie session data. Use " + "config.secret_token = \"some secret phrase of at " + "least #{SECRET_MIN_LENGTH} characters\"" + - "in config/application.rb" + "in config/initializers/secret_token.rb" end if secret.length < SECRET_MIN_LENGTH diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 879ccc5791..01826fcede 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -439,13 +439,13 @@ module ActionDispatch # This will create a number of routes for each of the posts and comments # controller. For Admin::PostsController, Rails will create: # - # GET /admin/photos - # GET /admin/photos/new - # POST /admin/photos - # GET /admin/photos/1 - # GET /admin/photos/1/edit - # PUT /admin/photos/1 - # DELETE /admin/photos/1 + # GET /admin/posts + # GET /admin/posts/new + # POST /admin/posts + # GET /admin/posts/1 + # GET /admin/posts/1/edit + # PUT /admin/posts/1 + # DELETE /admin/posts/1 # # If you want to route /posts (without the prefix /admin) to # Admin::PostsController, you could use @@ -473,21 +473,19 @@ module ActionDispatch # not use scope. In the last case, the following paths map to # PostsController: # - # GET /admin/photos - # GET /admin/photos/new - # POST /admin/photos - # GET /admin/photos/1 - # GET /admin/photos/1/edit - # PUT /admin/photos/1 - # DELETE /admin/photos/1 + # GET /admin/posts + # GET /admin/posts/new + # POST /admin/posts + # GET /admin/posts/1 + # GET /admin/posts/1/edit + # PUT /admin/posts/1 + # DELETE /admin/posts/1 module Scoping def initialize(*args) #:nodoc: @scope = {} super end - # Used to route <tt>/photos</tt> (without the prefix <tt>/admin</tt>) - # to Admin::PostsController: # === Supported options # [:module] # If you want to route /posts (without the prefix /admin) to @@ -588,13 +586,13 @@ module ActionDispatch # # This generates the following routes: # - # admin_posts GET /admin/posts(.:format) {:action=>"index", :controller=>"admin/posts"} - # admin_posts POST /admin/posts(.:format) {:action=>"create", :controller=>"admin/posts"} - # new_admin_post GET /admin/posts/new(.:format) {:action=>"new", :controller=>"admin/posts"} - # edit_admin_post GET /admin/posts/:id/edit(.:format) {:action=>"edit", :controller=>"admin/posts"} - # admin_post GET /admin/posts/:id(.:format) {:action=>"show", :controller=>"admin/posts"} - # admin_post PUT /admin/posts/:id(.:format) {:action=>"update", :controller=>"admin/posts"} - # admin_post DELETE /admin/posts/:id(.:format) {:action=>"destroy", :controller=>"admin/posts"} + # admin_posts GET /admin/posts(.:format) {:action=>"index", :controller=>"admin/posts"} + # admin_posts POST /admin/posts(.:format) {:action=>"create", :controller=>"admin/posts"} + # new_admin_post GET /admin/posts/new(.:format) {:action=>"new", :controller=>"admin/posts"} + # edit_admin_post GET /admin/posts/:id/edit(.:format) {:action=>"edit", :controller=>"admin/posts"} + # admin_post GET /admin/posts/:id(.:format) {:action=>"show", :controller=>"admin/posts"} + # admin_post PUT /admin/posts/:id(.:format) {:action=>"update", :controller=>"admin/posts"} + # admin_post DELETE /admin/posts/:id(.:format) {:action=>"destroy", :controller=>"admin/posts"} # === Supported options # # The +:path+, +:as+, +:module+, +:shallow_path+ and +:shallow_prefix+ all default to the name of the namespace. @@ -707,61 +705,61 @@ module ActionDispatch end private - def scope_options + def scope_options #:nodoc: @scope_options ||= private_methods.grep(/^merge_(.+)_scope$/) { $1.to_sym } end - def merge_path_scope(parent, child) + def merge_path_scope(parent, child) #:nodoc: Mapper.normalize_path("#{parent}/#{child}") end - def merge_shallow_path_scope(parent, child) + def merge_shallow_path_scope(parent, child) #:nodoc: Mapper.normalize_path("#{parent}/#{child}") end - def merge_as_scope(parent, child) + def merge_as_scope(parent, child) #:nodoc: parent ? "#{parent}_#{child}" : child end - def merge_shallow_prefix_scope(parent, child) + def merge_shallow_prefix_scope(parent, child) #:nodoc: parent ? "#{parent}_#{child}" : child end - def merge_module_scope(parent, child) + def merge_module_scope(parent, child) #:nodoc: parent ? "#{parent}/#{child}" : child end - def merge_controller_scope(parent, child) + def merge_controller_scope(parent, child) #:nodoc: child end - def merge_path_names_scope(parent, child) + def merge_path_names_scope(parent, child) #:nodoc: merge_options_scope(parent, child) end - def merge_constraints_scope(parent, child) + def merge_constraints_scope(parent, child) #:nodoc: merge_options_scope(parent, child) end - def merge_defaults_scope(parent, child) + def merge_defaults_scope(parent, child) #:nodoc: merge_options_scope(parent, child) end - def merge_blocks_scope(parent, child) + def merge_blocks_scope(parent, child) #:nodoc: merged = parent ? parent.dup : [] merged << child if child merged end - def merge_options_scope(parent, child) + def merge_options_scope(parent, child) #:nodoc: (parent || {}).except(*override_keys(child)).merge(child) end - def merge_shallow_scope(parent, child) + def merge_shallow_scope(parent, child) #:nodoc: child ? true : false end - def override_keys(child) + def override_keys(child) #:nodoc: child.key?(:only) || child.key?(:except) ? [:only, :except] : [] end end @@ -969,6 +967,22 @@ module ActionDispatch # GET /photos/:id/edit # PUT /photos/:id # DELETE /photos/:id + # + # Resources can also be nested infinitely by using this block syntax: + # + # resources :photos do + # resources :comments + # end + # + # This generates the following comments routes: + # + # GET /photos/:id/comments/new + # POST /photos/:id/comments + # GET /photos/:id/comments/:id + # GET /photos/:id/comments/:id/edit + # PUT /photos/:id/comments/:id + # DELETE /photos/:id/comments/:id + # # === Supported options # [:path_names] # Allows you to change the paths of the seven default actions. @@ -977,6 +991,21 @@ module ActionDispatch # resources :posts, :path_names => { :new => "brand_new" } # # The above example will now change /posts/new to /posts/brand_new + # + # [:module] + # Set the module where the controller can be found. Defaults to nothing. + # + # resources :posts, :module => "admin" + # + # All requests to the posts resources will now go to +Admin::PostsController+. + # + # [:path] + # + # Set a path prefix for this resource. + # + # resources :posts, :path => "admin" + # + # All actions for this resource will now be at +/admin/posts+. def resources(*resources, &block) options = resources.extract_options! @@ -1167,7 +1196,7 @@ module ActionDispatch @scope[:scope_level_resource] end - def apply_common_behavior_for(method, resources, options, &block) + def apply_common_behavior_for(method, resources, options, &block) #:nodoc: if resources.length > 1 resources.each { |r| send(method, r, options, &block) } return true @@ -1197,23 +1226,23 @@ module ActionDispatch false end - def action_options?(options) + def action_options?(options) #:nodoc: options[:only] || options[:except] end - def scope_action_options? + def scope_action_options? #:nodoc: @scope[:options].is_a?(Hash) && (@scope[:options][:only] || @scope[:options][:except]) end - def scope_action_options + def scope_action_options #:nodoc: @scope[:options].slice(:only, :except) end - def resource_scope? + def resource_scope? #:nodoc: [:resource, :resources].include?(@scope[:scope_level]) end - def resource_method_scope? + def resource_method_scope? #:nodoc: [:collection, :member, :new].include?(@scope[:scope_level]) end @@ -1239,7 +1268,7 @@ module ActionDispatch @scope[:scope_level_resource] = old_resource end - def resource_scope(resource) + def resource_scope(resource) #:nodoc: with_scope_level(resource.is_a?(SingletonResource) ? :resource : :resources, resource) do scope(parent_resource.resource_scope) do yield @@ -1247,30 +1276,30 @@ module ActionDispatch end end - def nested_options + def nested_options #:nodoc: {}.tap do |options| options[:as] = parent_resource.member_name options[:constraints] = { "#{parent_resource.singular}_id".to_sym => id_constraint } if id_constraint? end end - def id_constraint? + def id_constraint? #:nodoc: @scope[:constraints] && @scope[:constraints][:id].is_a?(Regexp) end - def id_constraint + def id_constraint #:nodoc: @scope[:constraints][:id] end - def canonical_action?(action, flag) + def canonical_action?(action, flag) #:nodoc: flag && resource_method_scope? && CANONICAL_ACTIONS.include?(action.to_s) end - def shallow_scoping? + def shallow_scoping? #:nodoc: shallow? && @scope[:scope_level] == :member end - def path_for_action(action, path) + def path_for_action(action, path) #:nodoc: prefix = shallow_scoping? ? "#{@scope[:shallow_path]}/#{parent_resource.path}/:id" : @scope[:path] @@ -1281,11 +1310,11 @@ module ActionDispatch end end - def action_path(name, path = nil) + def action_path(name, path = nil) #:nodoc: path || @scope[:path_names][name.to_sym] || name.to_s end - def prefix_name_for_action(as, action) + def prefix_name_for_action(as, action) #:nodoc: if as as.to_s elsif !canonical_action?(action, @scope[:scope_level]) @@ -1293,12 +1322,14 @@ module ActionDispatch end end - def name_for_action(as, action) + def name_for_action(as, action) #:nodoc: prefix = prefix_name_for_action(as, action) prefix = Mapper.normalize_name(prefix) if prefix name_prefix = @scope[:as] if parent_resource + return nil if as.nil? && action.nil? + collection_name = parent_resource.collection_name member_name = parent_resource.member_name end @@ -1323,7 +1354,7 @@ module ActionDispatch end end - module Shorthand + module Shorthand #:nodoc: def match(*args) if args.size == 1 && args.last.is_a?(Hash) options = args.pop diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 32f41934f1..ebced9cabe 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -485,7 +485,8 @@ module ActionDispatch Generator.new(options, recall, self, extras).generate end - RESERVED_OPTIONS = [:anchor, :params, :only_path, :host, :protocol, :port, :trailing_slash, :script_name] + RESERVED_OPTIONS = [:host, :protocol, :port, :subdomain, :domain, :tld_length, + :trailing_slash, :script_name, :anchor, :params, :only_path ] def _generate_prefix(options = {}) nil @@ -504,11 +505,8 @@ module ActionDispatch rewritten_url << (options[:protocol] || "http") rewritten_url << "://" unless rewritten_url.match("://") rewritten_url << rewrite_authentication(options) - - raise "Missing host to link to! Please provide :host parameter or set default_url_options[:host]" unless options[:host] - - rewritten_url << options[:host] - rewritten_url << ":#{options.delete(:port)}" if options.key?(:port) + rewritten_url << host_from_options(options) + rewritten_url << ":#{options.delete(:port)}" if options[:port] end script_name = options.delete(:script_name) @@ -562,6 +560,26 @@ module ActionDispatch end private + + def host_from_options(options) + computed_host = subdomain_and_domain(options) || options[:host] + unless computed_host + raise ArgumentError, "Missing host to link to! Please provide :host parameter or set default_url_options[:host]" + end + computed_host + end + + def subdomain_and_domain(options) + return nil unless options[:subdomain] || options[:domain] + tld_length = options[:tld_length] || ActionDispatch::Http::URL.tld_length + + host = "" + host << (options[:subdomain] || ActionDispatch::Http::URL.extract_subdomain(options[:host], tld_length)) + host << "." + host << (options[:domain] || ActionDispatch::Http::URL.extract_domain(options[:host], tld_length)) + host + end + def handle_positional_args(options) return unless args = options.delete(:_positional_args) diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb index bfdea41f60..d4db78a25a 100644 --- a/actionpack/lib/action_dispatch/routing/url_for.rb +++ b/actionpack/lib/action_dispatch/routing/url_for.rb @@ -115,6 +115,13 @@ module ActionDispatch # * <tt>:host</tt> - Specifies the host the link should be targeted at. # If <tt>:only_path</tt> is false, this option must be # provided either explicitly, or via +default_url_options+. + # * <tt>:subdomain</tt> - Specifies the subdomain of the link, using the +tld_length+ + # to split the domain from the host. + # * <tt>:domain</tt> - Specifies the domain of the link, using the +tld_length+ + # to split the subdomain from the host. + # * <tt>:tld_length</tt> - Number of labels the TLD id composed of, only used if + # <tt>:subdomain</tt> or <tt>:domain</tt> are supplied. Defaults to + # <tt>ActionDispatch::Http::URL.tld_length</tt>, which in turn defaults to 1. # * <tt>:port</tt> - Optionally specify the port to connect to. # * <tt>:anchor</tt> - An anchor name to be appended to the path. # * <tt>:trailing_slash</tt> - If true, adds a trailing slash, as in "/archive/2009/" diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb index fee8cad9f5..8fe74c3c80 100644 --- a/actionpack/lib/action_dispatch/testing/integration.rb +++ b/actionpack/lib/action_dispatch/testing/integration.rb @@ -363,6 +363,10 @@ module ActionDispatch integration_session.url_options end + def respond_to?(method, include_private = false) + integration_session.respond_to?(method, include_private) || super + end + # Delegate unhandled messages to the current session instance. def method_missing(sym, *args, &block) reset! unless integration_session 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 3bc81ae068..e52797042f 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 @@ -57,8 +57,8 @@ module ActionView private - def path_to_asset(source) - asset_paths.compute_public_path(source, asset_name.to_s.pluralize, extension) + def path_to_asset(source, include_host = true) + asset_paths.compute_public_path(source, asset_name.to_s.pluralize, extension, include_host) end def compute_paths(*args) @@ -77,9 +77,8 @@ module ActionView def ensure_sources!(sources) sources.each do |source| - asset_file_path!(path_to_asset(source)) + asset_file_path!(path_to_asset(source, false)) end - return sources end def collect_asset_files(*path) diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb index 8c300ec745..ef5bbd8ae3 100644 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ b/actionpack/lib/action_view/helpers/form_helper.rb @@ -2,7 +2,7 @@ require 'cgi' require 'action_view/helpers/date_helper' require 'action_view/helpers/tag_helper' require 'action_view/helpers/form_tag_helper' -require 'active_support/core_ext/class/inheritable_attributes' +require 'active_support/core_ext/class/attribute' require 'active_support/core_ext/hash/slice' require 'active_support/core_ext/object/blank' require 'active_support/core_ext/string/output_safety' @@ -1117,7 +1117,7 @@ module ActionView class FormBuilder #:nodoc: # The methods which wrap a form helper call. - class_inheritable_accessor :field_helpers + class_attribute :field_helpers self.field_helpers = (FormHelper.instance_method_names - ['form_for']) attr_accessor :object_name, :object, :options diff --git a/actionpack/lib/action_view/partials.rb b/actionpack/lib/action_view/partials.rb index 844c3e9572..cd3f130dc4 100644 --- a/actionpack/lib/action_view/partials.rb +++ b/actionpack/lib/action_view/partials.rb @@ -75,6 +75,11 @@ module ActionView # # <%= 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. # diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb index dbb1532cd4..831a19654e 100644 --- a/actionpack/lib/action_view/template.rb +++ b/actionpack/lib/action_view/template.rb @@ -184,6 +184,11 @@ module ActionView end end + # Used to store template data by template handlers. + def data + @data ||= {} + end + def inspect @inspect ||= if defined?(Rails.root) @@ -269,7 +274,8 @@ module ActionView end end - code = @handler.call(self) + arity = @handler.respond_to?(:arity) ? @handler.arity : @handler.method(:call).arity + code = arity == 1 ? @handler.call(self) : @handler.call(self, view) # Make sure that the resulting String to be evalled is in the # encoding of the code diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb index 92597e40ff..60534a9746 100644 --- a/actionpack/test/abstract_unit.rb +++ b/actionpack/test/abstract_unit.rb @@ -36,14 +36,6 @@ require 'active_record' require 'action_controller/caching' require 'action_controller/caching/sweeping' -begin - require 'ruby-debug' - Debugger.settings[:autoeval] = true - Debugger.start -rescue LoadError - # Debugging disabled. `gem install ruby-debug` to enable. -end - require 'pp' # require 'pp' early to prevent hidden_methods from not picking up the pretty-print methods until too late module Rails diff --git a/actionpack/test/activerecord/active_record_store_test.rb b/actionpack/test/activerecord/active_record_store_test.rb index 7c595d1b89..f0fb113860 100644 --- a/actionpack/test/activerecord/active_record_store_test.rb +++ b/actionpack/test/activerecord/active_record_store_test.rb @@ -23,6 +23,7 @@ class ActiveRecordStoreTest < ActionDispatch::IntegrationTest def call_reset_session session[:foo] reset_session + reset_session if params[:twice] session[:foo] = "baz" head :ok end @@ -94,6 +95,17 @@ class ActiveRecordStoreTest < ActionDispatch::IntegrationTest end end + def test_calling_reset_session_twice_does_not_raise_errors + with_test_route_set do + get '/call_reset_session', :twice => "true" + assert_response :success + + get '/get_session_value' + assert_response :success + assert_equal 'foo: "baz"', response.body + end + end + def test_setting_session_value_after_session_reset with_test_route_set do get '/set_session_value' diff --git a/actionpack/test/controller/action_pack_assertions_test.rb b/actionpack/test/controller/action_pack_assertions_test.rb index 22ccd2e6b3..7f3d943bba 100644 --- a/actionpack/test/controller/action_pack_assertions_test.rb +++ b/actionpack/test/controller/action_pack_assertions_test.rb @@ -173,7 +173,7 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase def test_string_constraint with_routing do |set| - set.draw do |map| + set.draw do match "photos", :to => 'action_pack_assertions#nothing', :constraints => {:subdomain => "admin"} end end diff --git a/actionpack/test/controller/mime_responds_test.rb b/actionpack/test/controller/mime_responds_test.rb index b6ce9f7d34..82969b2979 100644 --- a/actionpack/test/controller/mime_responds_test.rb +++ b/actionpack/test/controller/mime_responds_test.rb @@ -2,6 +2,14 @@ require 'abstract_unit' require 'controller/fake_models' require 'active_support/core_ext/hash/conversions' +class StarStarMimeController < ActionController::Base + layout nil + + def index + render + end +end + class RespondToController < ActionController::Base layout :set_layout @@ -89,7 +97,6 @@ class RespondToController < ActionController::Base end end - Mime::Type.register("text/x-mobile", :mobile) def custom_constant_handling respond_to do |type| @@ -126,7 +133,6 @@ class RespondToController < ActionController::Base end end - Mime::Type.register_alias("text/html", :iphone) def iphone_with_html_response_type request.format = :iphone if request.env["HTTP_ACCEPT"] == "text/iphone" @@ -160,16 +166,43 @@ class RespondToController < ActionController::Base end end +class StarStarMimeControllerTest < ActionController::TestCase + tests StarStarMimeController + + def test_javascript_with_format + @request.accept = "text/javascript" + get :index, :format => 'js' + assert_match "function addition(a,b){ return a+b; }", @response.body + end + + def test_javascript_with_no_format + @request.accept = "text/javascript" + get :index + assert_match "function addition(a,b){ return a+b; }", @response.body + end + + def test_javascript_with_no_format_only_star_star + @request.accept = "*/*" + get :index + assert_match "function addition(a,b){ return a+b; }", @response.body + end + +end + class RespondToControllerTest < ActionController::TestCase tests RespondToController def setup super @request.host = "www.example.com" + Mime::Type.register_alias("text/html", :iphone) + Mime::Type.register("text/x-mobile", :mobile) end def teardown super + Mime::Type.unregister('text/x-mobile', :iphone) + Mime::Type.unregister('text/iphone', :mobile) end def test_html @@ -216,6 +249,16 @@ class RespondToControllerTest < ActionController::TestCase assert_response 406 end + def test_json_or_yaml_with_leading_star_star + @request.accept = "*/*, application/json" + get :json_xml_or_html + assert_equal 'HTML', @response.body + + @request.accept = "*/* , application/json" + get :json_xml_or_html + assert_equal 'HTML', @response.body + end + def test_json_or_yaml xhr :get, :json_or_yaml assert_equal 'JSON', @response.body @@ -556,6 +599,17 @@ class InheritedRespondWithController < RespondWithController end end +class RenderJsonRespondWithController < RespondWithController + clear_respond_to + respond_to :json + + def index + respond_with(resource) do |format| + format.json { render :json => RenderJsonTestException.new('boom') } + end + end +end + class EmptyRespondWithController < ActionController::Base def index respond_with(Customer.new("david", 13)) @@ -572,6 +626,8 @@ class RespondWithControllerTest < ActionController::TestCase def teardown super + Mime::Type.unregister('text/x-mobile', :iphone) + Mime::Type.unregister('text/iphone', :mobile) end def test_using_resource @@ -869,6 +925,13 @@ class RespondWithControllerTest < ActionController::TestCase assert_equal "JSON", @response.body end + def test_render_json_object_responds_to_str_still_produce_json + @controller = RenderJsonRespondWithController.new + @request.accept = "application/json" + get :index, :format => :json + assert_equal %Q{{"message":"boom","error":"RenderJsonTestException"}}, @response.body + end + def test_no_double_render_is_raised @request.accept = "text/html" assert_raise ActionView::MissingTemplate do @@ -952,6 +1015,13 @@ class MimeControllerLayoutsTest < ActionController::TestCase def setup super @request.host = "www.example.com" + Mime::Type.register_alias("text/html", :iphone) + end + + def teardown + super + Mime::Type.unregister('text/x-mobile', :iphone) + Mime::Type.unregister('text/iphone', :mobile) end def test_missing_layout_renders_properly diff --git a/actionpack/test/controller/new_base/render_template_test.rb b/actionpack/test/controller/new_base/render_template_test.rb index 9899036fe8..584f2d772c 100644 --- a/actionpack/test/controller/new_base/render_template_test.rb +++ b/actionpack/test/controller/new_base/render_template_test.rb @@ -4,16 +4,16 @@ module RenderTemplate class WithoutLayoutController < ActionController::Base self.view_paths = [ActionView::FixtureResolver.new( - "test/basic.html.erb" => "Hello from basic.html.erb", - "shared.html.erb" => "Elastica", - "locals.html.erb" => "The secret is <%= secret %>", - "xml_template.xml.builder" => "xml.html do\n xml.p 'Hello'\nend", - "with_raw.html.erb" => "Hello <%=raw '<strong>this is raw</strong>' %>", - "with_implicit_raw.html.erb"=> "Hello <%== '<strong>this is also raw</strong>' %>", - "test/with_json.html.erb" => "<%= render :template => 'test/with_json.json' %>", - "test/with_json.json.erb" => "<%= render :template => 'test/final' %>", - "test/final.json.erb" => "{ final: json }", - "test/with_error.html.erb" => "<%= idontexist %>" + "test/basic.html.erb" => "Hello from basic.html.erb", + "shared.html.erb" => "Elastica", + "locals.html.erb" => "The secret is <%= secret %>", + "xml_template.xml.builder" => "xml.html do\n xml.p 'Hello'\nend", + "with_raw.html.erb" => "Hello <%=raw '<strong>this is raw</strong>' %>", + "with_implicit_raw.html.erb" => "Hello <%== '<strong>this is also raw</strong>' %>", + "test/with_json.html.erb" => "<%= render :template => 'test/with_json.json' %>", + "test/with_json.json.erb" => "<%= render :template => 'test/final' %>", + "test/final.json.erb" => "{ final: json }", + "test/with_error.html.erb" => "<%= idontexist %>" )] def index diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb index ce5c899a19..cd067b7d18 100644 --- a/actionpack/test/controller/routing_test.rb +++ b/actionpack/test/controller/routing_test.rb @@ -1444,7 +1444,7 @@ class RouteSetTest < ActiveSupport::TestCase end def test_expand_array_build_query_string - assert_uri_equal '/foo?x[]=1&x[]=2', default_route_set.generate({:controller => 'foo', :x => [1, 2]}) + assert_uri_equal '/foo?x%5B%5D=1&x%5B%5D=2', default_route_set.generate({:controller => 'foo', :x => [1, 2]}) end def test_escape_spaces_build_query_string_selected_keys @@ -1744,9 +1744,9 @@ class RackMountIntegrationTests < ActiveSupport::TestCase assert_equal '/posts', @routes.generate({:controller => 'posts'}, {:controller => 'posts', :action => 'index'}) assert_equal '/posts/create', @routes.generate({:action => 'create'}, {:controller => 'posts'}) assert_equal '/posts?foo=bar', @routes.generate(:controller => 'posts', :foo => 'bar') - assert_equal '/posts?foo[]=bar&foo[]=baz', @routes.generate(:controller => 'posts', :foo => ['bar', 'baz']) + assert_equal '/posts?foo%5B%5D=bar&foo%5B%5D=baz', @routes.generate(:controller => 'posts', :foo => ['bar', 'baz']) assert_equal '/posts?page=2', @routes.generate(:controller => 'posts', :page => 2) - assert_equal '/posts?q[foo][a]=b', @routes.generate(:controller => 'posts', :q => { :foo => { :a => 'b'}}) + assert_equal '/posts?q%5Bfoo%5D%5Ba%5D=b', @routes.generate(:controller => 'posts', :q => { :foo => { :a => 'b'}}) assert_equal '/news.rss', @routes.generate(:controller => 'news', :action => 'index', :format => 'rss') diff --git a/actionpack/test/controller/runner_test.rb b/actionpack/test/controller/runner_test.rb new file mode 100644 index 0000000000..24c220dcd5 --- /dev/null +++ b/actionpack/test/controller/runner_test.rb @@ -0,0 +1,22 @@ +require 'abstract_unit' +require 'action_dispatch/testing/integration' + +module ActionDispatch + class RunnerTest < Test::Unit::TestCase + class MyRunner + include Integration::Runner + + def initialize(session) + @integration_session = session + end + + def hi; end + end + + def test_respond_to? + runner = MyRunner.new(Class.new { def x; end }.new) + assert runner.respond_to?(:hi) + assert runner.respond_to?(:x) + end + end +end diff --git a/actionpack/test/controller/url_for_test.rb b/actionpack/test/controller/url_for_test.rb index 4c07ca4cc3..1f62d29e80 100644 --- a/actionpack/test/controller/url_for_test.rb +++ b/actionpack/test/controller/url_for_test.rb @@ -17,7 +17,7 @@ module AbstractController end def test_exception_is_thrown_without_host - assert_raise RuntimeError do + assert_raise ArgumentError do W.new.url_for :controller => 'c', :action => 'a', :id => 'i' end end @@ -60,6 +60,27 @@ module AbstractController ) end + def test_subdomain_may_be_changed + add_host! + assert_equal('http://api.basecamphq.com/c/a/i', + W.new.url_for(:subdomain => 'api', :controller => 'c', :action => 'a', :id => 'i') + ) + end + + def test_domain_may_be_changed + add_host! + assert_equal('http://www.37signals.com/c/a/i', + W.new.url_for(:domain => '37signals.com', :controller => 'c', :action => 'a', :id => 'i') + ) + end + + def test_tld_length_may_be_changed + add_host! + assert_equal('http://mobile.www.basecamphq.com/c/a/i', + W.new.url_for(:subdomain => 'mobile', :tld_length => 2, :controller => 'c', :action => 'a', :id => 'i') + ) + end + def test_port add_host! assert_equal('http://www.basecamphq.com:3000/c/a/i', diff --git a/actionpack/test/dispatch/mime_type_test.rb b/actionpack/test/dispatch/mime_type_test.rb index 4c2b95550c..9424d88498 100644 --- a/actionpack/test/dispatch/mime_type_test.rb +++ b/actionpack/test/dispatch/mime_type_test.rb @@ -6,10 +6,65 @@ class MimeTypeTest < ActiveSupport::TestCase test "parse single" do Mime::LOOKUP.keys.each do |mime_type| - assert_equal [Mime::Type.lookup(mime_type)], Mime::Type.parse(mime_type) + unless mime_type == 'image/*' + assert_equal [Mime::Type.lookup(mime_type)], Mime::Type.parse(mime_type) + end end end + test "unregister" do + begin + Mime::Type.register("text/x-mobile", :mobile) + assert defined?(Mime::MOBILE) + assert_equal Mime::MOBILE, Mime::LOOKUP['text/x-mobile'] + assert_equal Mime::MOBILE, Mime::EXTENSION_LOOKUP['mobile'] + + Mime::Type.unregister("text/x-mobile", :mobile) + assert !defined?(Mime::MOBILE), "Mime::MOBILE should not be defined" + assert !Mime::LOOKUP.has_key?('text/x-mobile'), "Mime::LOOKUP should not have key ['text/x-mobile]" + assert !Mime::EXTENSION_LOOKUP.has_key?('mobile'), "Mime::EXTENSION_LOOKUP should not have key ['mobile]" + ensure + Mime.module_eval { remove_const :MOBILE if const_defined?(:MOBILE) } + Mime::LOOKUP.reject!{|key,_| key == 'text/x-mobile'} + end + end + + test "parse text with trailing star at the beginning" do + accept = "text/*, text/html, application/json, multipart/form-data" + expect = [Mime::JSON, Mime::XML, Mime::ICS, Mime::HTML, Mime::CSS, Mime::CSV, Mime::TEXT, Mime::YAML, Mime::JS, Mime::MULTIPART_FORM] + parsed = Mime::Type.parse(accept) + assert_equal expect.size, parsed.size + Range.new(0,expect.size-1).to_a.each do |index| + assert_equal expect[index], parsed[index], "Failed for index number #{index}" + end + end + + test "parse text with trailing star in the end" do + accept = "text/html, application/json, multipart/form-data, text/*" + expect = [Mime::HTML, Mime::JSON, Mime::MULTIPART_FORM, Mime::XML, Mime::ICS, Mime::CSS, Mime::CSV, Mime::JS, Mime::YAML, Mime::TEXT] + parsed = Mime::Type.parse(accept) + assert_equal 10, parsed.size + Range.new(0,expect.size-1).to_a.each do |index| + assert_equal expect[index], parsed[index], "Failed for index number #{index}" + end + end + + test "parse text with trailing star" do + accept = "text/*" + expect = [Mime::JSON, Mime::XML, Mime::ICS, Mime::HTML, Mime::CSS, Mime::CSV, Mime::JS, Mime::YAML, Mime::TEXT].sort_by(&:to_s) + parsed = Mime::Type.parse(accept) + assert_equal 9, parsed.size + assert_equal expect, parsed.sort_by(&:to_s) + end + + test "parse application with trailing star" do + accept = "application/*" + expect = [Mime::HTML, Mime::JS, Mime::XML, Mime::YAML, Mime::ATOM, Mime::JSON, Mime::RSS, Mime::PDF, Mime::URL_ENCODED_FORM].sort_by(&:to_s) + parsed = Mime::Type.parse(accept) + assert_equal 9, parsed.size + assert_equal expect, parsed.sort_by(&:to_s) + end + test "parse without q" do accept = "text/xml,application/xhtml+xml,text/yaml,application/xml,text/html,image/png,text/plain,application/pdf,*/*" expect = [Mime::HTML, Mime::XML, Mime::YAML, Mime::PNG, Mime::TEXT, Mime::PDF, Mime::ALL] @@ -44,7 +99,7 @@ class MimeTypeTest < ActiveSupport::TestCase assert_equal Mime::GIF, Mime::SET.last end ensure - Mime.module_eval { remove_const :GIF if const_defined?(:GIF) } + Mime::Type.unregister('image/gif', :gif) end end diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb index 4764a8c2a8..8f672c1149 100644 --- a/actionpack/test/dispatch/request_test.rb +++ b/actionpack/test/dispatch/request_test.rb @@ -164,12 +164,20 @@ class RequestTest < ActiveSupport::TestCase assert !request.standard_port? end + test "optional port" do + request = stub_request 'HTTP_HOST' => 'www.example.org:80' + assert_equal nil, request.optional_port + + request = stub_request 'HTTP_HOST' => 'www.example.org:8080' + assert_equal 8080, request.optional_port + end + test "port string" do request = stub_request 'HTTP_HOST' => 'www.example.org:80' - assert_equal "", request.port_string + assert_equal '', request.port_string request = stub_request 'HTTP_HOST' => 'www.example.org:8080' - assert_equal ":8080", request.port_string + assert_equal ':8080', request.port_string end test "full path" do @@ -392,7 +400,7 @@ class RequestTest < ActiveSupport::TestCase mock_rack_env = { "QUERY_STRING" => "x[y]=1&x[y][][w]=2", "rack.input" => "foo" } request = nil begin - request = stub_request(mock_rack_env) + request = stub_request(mock_rack_env) request.parameters rescue TypeError => e # rack will raise a TypeError when parsing this query string diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index 0ac8c249cb..bbd010ea6d 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -155,6 +155,11 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end resources :replies do + collection do + get 'page/:page' => 'replies#index', :page => %r{\d+} + get ':page' => 'replies#index', :page => %r{\d+} + end + new do post :preview end @@ -1241,6 +1246,12 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end + def test_dynamically_generated_helpers_on_collection_do_not_clobber_resources_url_helper + with_test_routes do + assert_equal '/replies', replies_path + end + end + def test_scoped_controller_with_namespace_and_action with_test_routes do assert_equal '/account/twitter/callback', account_callback_path("twitter") diff --git a/actionpack/test/dispatch/uploaded_file_test.rb b/actionpack/test/dispatch/uploaded_file_test.rb index b51697b930..e2a7f1bad7 100644 --- a/actionpack/test/dispatch/uploaded_file_test.rb +++ b/actionpack/test/dispatch/uploaded_file_test.rb @@ -28,6 +28,18 @@ module ActionDispatch assert_equal 'foo', uf.tempfile end + def test_delegates_path_to_tempfile + tf = Class.new { def path; 'thunderhorse' end } + uf = Http::UploadedFile.new(:tempfile => tf.new) + assert_equal 'thunderhorse', uf.path + end + + def test_delegates_open_to_tempfile + tf = Class.new { def open; 'thunderhorse' end } + uf = Http::UploadedFile.new(:tempfile => tf.new) + assert_equal 'thunderhorse', uf.open + end + def test_delegates_to_tempfile tf = Class.new { def read; 'thunderhorse' end } uf = Http::UploadedFile.new(:tempfile => tf.new) diff --git a/actionpack/test/fixtures/star_star_mime/index.js.erb b/actionpack/test/fixtures/star_star_mime/index.js.erb new file mode 100644 index 0000000000..4da4181f56 --- /dev/null +++ b/actionpack/test/fixtures/star_star_mime/index.js.erb @@ -0,0 +1 @@ +function addition(a,b){ return a+b; } diff --git a/actionpack/test/lib/controller/fake_models.rb b/actionpack/test/lib/controller/fake_models.rb index ae0c38184d..bd18cdc1b8 100644 --- a/actionpack/test/lib/controller/fake_models.rb +++ b/actionpack/test/lib/controller/fake_models.rb @@ -194,3 +194,9 @@ class ArelLike a.each { |i| yield i } end end + +class RenderJsonTestException < Exception + def to_json(options = nil) + return { :error => self.class.name, :message => self.to_str }.to_json + end +end diff --git a/actionpack/test/template/asset_tag_helper_test.rb b/actionpack/test/template/asset_tag_helper_test.rb index 139d832c5f..fbcc99a17a 100644 --- a/actionpack/test/template/asset_tag_helper_test.rb +++ b/actionpack/test/template/asset_tag_helper_test.rb @@ -680,6 +680,26 @@ class AssetTagHelperTest < ActionView::TestCase FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'money.js')) end + def test_caching_javascript_include_tag_with_named_paths_and_relative_url_root_when_caching_off + ENV["RAILS_ASSET_ID"] = "" + @controller.config.relative_url_root = "/collaboration/hieraki" + config.perform_caching = false + + assert_dom_equal( + %(<script src="/collaboration/hieraki/javascripts/robber.js" type="text/javascript"></script>), + javascript_include_tag('robber', :cache => true) + ) + + assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'all.js')) + + assert_dom_equal( + %(<script src="/collaboration/hieraki/javascripts/robber.js" type="text/javascript"></script>), + javascript_include_tag('robber', :cache => "money", :recursive => true) + ) + + assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'money.js')) + end + def test_caching_javascript_include_tag_when_caching_off ENV["RAILS_ASSET_ID"] = "" config.perform_caching = false @@ -907,6 +927,30 @@ class AssetTagHelperTest < ActionView::TestCase FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'money.css')) end + + def test_caching_stylesheet_link_tag_with_named_paths_and_relative_url_root_when_caching_off + ENV["RAILS_ASSET_ID"] = "" + @controller.config.relative_url_root = "/collaboration/hieraki" + config.perform_caching = false + + assert_dom_equal( + %(<link href="/collaboration/hieraki/stylesheets/robber.css" media="screen" rel="stylesheet" type="text/css" />), + stylesheet_link_tag('robber', :cache => true) + ) + + assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'all.css')) + + assert_dom_equal( + %(<link href="/collaboration/hieraki/stylesheets/robber.css" media="screen" rel="stylesheet" type="text/css" />), + stylesheet_link_tag('robber', :cache => "money") + ) + + assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'money.css')) + end + + + + def test_caching_stylesheet_include_tag_when_caching_off ENV["RAILS_ASSET_ID"] = "" config.perform_caching = false diff --git a/actionpack/test/template/lookup_context_test.rb b/actionpack/test/template/lookup_context_test.rb index 6d3b26e131..c9dd27cf2a 100644 --- a/actionpack/test/template/lookup_context_test.rb +++ b/actionpack/test/template/lookup_context_test.rb @@ -180,6 +180,16 @@ class LookupContextTest < ActiveSupport::TestCase assert_not_equal template, old_template end + + test "data can be stored in cached templates" do + template = @lookup_context.find("hello_world", "test") + template.data["cached"] = "data" + assert_equal "Hello world!", template.source + + template = @lookup_context.find("hello_world", "test") + assert_equal "data", template.data["cached"] + assert_equal "Hello world!", template.source + end end class LookupContextWithFalseCaching < ActiveSupport::TestCase @@ -205,7 +215,7 @@ class LookupContextWithFalseCaching < ActiveSupport::TestCase assert_equal "Bar", template.source end - test "if no template was found in the second lookup, give it higher preference" do + test "if no template was found in the second lookup, with no cache, raise error" do template = @lookup_context.find("foo", "test", true) assert_equal "Foo", template.source @@ -215,7 +225,7 @@ class LookupContextWithFalseCaching < ActiveSupport::TestCase end end - test "if no template was cached in the first lookup, do not use the cache in the second" do + test "if no template was cached in the first lookup, retrieval should work in the second call" do @resolver.hash.clear assert_raise ActionView::MissingTemplate do @lookup_context.find("foo", "test", true) @@ -225,4 +235,19 @@ class LookupContextWithFalseCaching < ActiveSupport::TestCase template = @lookup_context.find("foo", "test", true) assert_equal "Foo", template.source end + + test "data can be stored as long as template was not updated" do + template = @lookup_context.find("foo", "test", true) + template.data["cached"] = "data" + assert_equal "Foo", template.source + + template = @lookup_context.find("foo", "test", true) + assert_equal "data", template.data["cached"] + assert_equal "Foo", template.source + + @resolver.hash["test/_foo.erb"][1] = Time.now.utc + template = @lookup_context.find("foo", "test", true) + assert_nil template.data["cached"] + assert_equal "Foo", template.source + end end
\ No newline at end of file diff --git a/actionpack/test/template/render_test.rb b/actionpack/test/template/render_test.rb index d2bf45a63a..8087429d62 100644 --- a/actionpack/test/template/render_test.rb +++ b/actionpack/test/template/render_test.rb @@ -221,6 +221,15 @@ module RenderTestCases "@output_buffer << 'source: #{template.source.inspect}'\n" end + WithViewHandler = lambda do |template, view| + %'"#{template.class} #{view.class}"' + end + + def test_render_inline_with_template_handler_with_view + ActionView::Template.register_template_handler :with_view, WithViewHandler + assert_equal 'ActionView::Template ActionView::Base', @view.render(:inline => "Hello, World!", :type => :with_view) + end + def test_render_inline_with_compilable_custom_type ActionView::Template.register_template_handler :foo, CustomHandler assert_equal 'source: "Hello, World!"', @view.render(:inline => "Hello, World!", :type => :foo) |