aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--actionmailer/README.rdoc2
-rw-r--r--actionpack/CHANGELOG.md56
-rw-r--r--actionpack/lib/action_controller/metal/redirecting.rb1
-rw-r--r--actionpack/lib/action_controller/metal/strong_parameters.rb6
-rw-r--r--actionpack/lib/action_controller/record_identifier.rb22
-rw-r--r--actionpack/lib/action_controller/test_case.rb3
-rw-r--r--actionpack/lib/action_dispatch/journey/formatter.rb7
-rw-r--r--actionpack/lib/action_dispatch/journey/route.rb40
-rw-r--r--actionpack/lib/action_dispatch/journey/router.rb6
-rw-r--r--actionpack/lib/action_dispatch/middleware/best_standards_support.rb4
-rw-r--r--actionpack/lib/action_dispatch/middleware/exception_wrapper.rb3
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb257
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb8
-rw-r--r--actionpack/lib/action_dispatch/testing/integration.rb2
-rw-r--r--actionpack/lib/action_view/helpers.rb5
-rw-r--r--actionpack/lib/action_view/helpers/benchmark_helper.rb13
-rw-r--r--actionpack/lib/action_view/helpers/date_helper.rb5
-rw-r--r--actionpack/lib/action_view/helpers/form_options_helper.rb8
-rw-r--r--actionpack/lib/action_view/helpers/tags/date_select.rb2
-rw-r--r--actionpack/test/controller/integration_test.rb52
-rw-r--r--actionpack/test/controller/layout_test.rb2
-rw-r--r--actionpack/test/controller/record_identifier_test.rb34
-rw-r--r--actionpack/test/controller/required_params_test.rb15
-rw-r--r--actionpack/test/controller/test_case_test.rb31
-rw-r--r--actionpack/test/dispatch/best_standards_support_test.rb3
-rw-r--r--actionpack/test/dispatch/debug_exceptions_test.rb6
-rw-r--r--actionpack/test/dispatch/request_test.rb21
-rw-r--r--actionpack/test/dispatch/routing_test.rb76
-rw-r--r--actionpack/test/journey/route_test.rb13
-rw-r--r--actionpack/test/journey/router_test.rb8
-rw-r--r--actionpack/test/template/benchmark_helper_test.rb24
-rw-r--r--actionpack/test/template/date_helper_test.rb132
-rw-r--r--actionpack/test/template/form_helper_test.rb53
-rw-r--r--activemodel/README.rdoc4
-rw-r--r--activemodel/test/cases/conversion_test.rb4
-rw-r--r--activemodel/test/cases/dirty_test.rb13
-rw-r--r--activemodel/test/cases/model_test.rb6
-rw-r--r--activemodel/test/models/helicopter.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb4
-rw-r--r--activerecord/lib/active_record/persistence.rb26
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb8
-rw-r--r--activerecord/lib/active_record/tasks/mysql_database_tasks.rb22
-rw-r--r--activerecord/test/cases/adapters/postgresql/active_schema_test.rb1
-rw-r--r--activesupport/test/core_ext/range_ext_test.rb6
-rw-r--r--guides/source/action_mailer_basics.md20
-rw-r--r--guides/source/active_record_validations.md1
-rw-r--r--guides/source/command_line.md4
-rw-r--r--guides/source/documents.yaml1
-rw-r--r--railties/CHANGELOG.md11
-rw-r--r--railties/lib/rails/info.rb2
-rw-r--r--railties/lib/rails/source_annotation_extractor.rb47
-rw-r--r--railties/lib/rails/tasks/log.rake22
-rw-r--r--railties/test/application/middleware/cache_test.rb12
-rw-r--r--railties/test/application/middleware/static_test.rb31
-rw-r--r--railties/test/application/rake/notes_test.rb39
-rw-r--r--railties/test/application/runner_test.rb12
57 files changed, 945 insertions, 279 deletions
diff --git a/actionmailer/README.rdoc b/actionmailer/README.rdoc
index 59c33b7940..9feb2add5b 100644
--- a/actionmailer/README.rdoc
+++ b/actionmailer/README.rdoc
@@ -98,7 +98,7 @@ Example:
class Mailman < ActionMailer::Base
def receive(email)
- page = Page.find_by_address(email.to.first)
+ page = Page.find_by(address: email.to.first)
page.emails.create(
subject: email.subject, body: email.body
)
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index 2cb7af05e5..2b6425caa5 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,5 +1,50 @@
## Rails 4.0.0 (unreleased) ##
+* `BestStandardsSupport` no longer duplicates `X-UA-Compatible` values on
+ each request to prevent header size from blowing up.
+
+ *Edward Anderson*
+
+* Change the behavior of route defaults so that explicit defaults are no longer
+ required where the key is not part of the path. For example:
+
+ resources :posts, bucket_type: 'posts'
+
+ will be required whenever constructing the url from a hash such as a functional
+ test or using url_for directly. However using the explicit form alters the
+ behavior so it's not required:
+
+ resources :projects, defaults: { bucket_type: 'projects' }
+
+ This changes existing behavior slightly in that any routes which only differ
+ in their defaults will match the first route rather than the closest match.
+
+ *Andrew White*
+
+* Add support for routing constraints other than Regexp and String.
+ For example this now allows the use of arrays like this:
+
+ get '/foo/:action', to: 'foo', constraints: { subdomain: %w[www admin] }
+
+ or constraints where the request method returns an Fixnum like this:
+
+ get '/foo', to: 'foo#index', constraints: { port: 8080 }
+
+ Note that this only applies to constraints on the request - path constraints
+ still need to be specified as Regexps as the various constraints are compiled
+ into a single Regexp.
+
+ *Andrew White*
+
+* Fix a bug in integration tests where setting the port via a url passed to
+ the process method was ignored when constructing the request environment.
+
+ *Andrew White*
+
+* Allow `:selected` to be set on `date_select` tag helper.
+
+ *Colin Burn-Murdoch*
+
* Fixed json params parsing regression for non-object JSON content.
*Dylan Smith*
@@ -447,10 +492,13 @@
*Richard Schneeman*
-* Deprecate availbility of `ActionView::RecordIdentifier` in controllers by default.
- It's view specific and can be easily included in controller manually if someone
- really needs it. RecordIdentifier will be removed from `ActionController::Base`
- in Rails 4.1. *Piotr Sarnacki*
+* Deprecate availability of `ActionView::RecordIdentifier` in controllers by default.
+ It's view specific and can be easily included in controllers manually if someone
+ really needs it. Also deprecate calling `ActionController::RecordIdentifier.dom_id` and
+ `dom_class` directly, in favor of `ActionView::RecordIdentifier.dom_id` and `dom_class`.
+ `RecordIdentifier` will be removed from `ActionController::Base` in Rails 4.1.
+
+ *Piotr Sarnacki*
* Fix `ActionView::RecordIdentifier` to work as a singleton. *Piotr Sarnacki*
diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb
index 13ec55fe92..59b91a240e 100644
--- a/actionpack/lib/action_controller/metal/redirecting.rb
+++ b/actionpack/lib/action_controller/metal/redirecting.rb
@@ -65,7 +65,6 @@ module ActionController
def redirect_to(options = {}, response_status = {}) #:doc:
raise ActionControllerError.new("Cannot redirect to nil!") unless options
raise AbstractController::DoubleRenderError if response_body
- logger.debug { "Redirected by #{caller(1).first rescue "unknown"}" } if logger
self.status = _extract_redirect_to_status(options, response_status)
self.location = _compute_redirect_to_location(options)
diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb
index 941b6e210f..e6e58ce6cd 100644
--- a/actionpack/lib/action_controller/metal/strong_parameters.rb
+++ b/actionpack/lib/action_controller/metal/strong_parameters.rb
@@ -373,12 +373,6 @@ module ActionController
extend ActiveSupport::Concern
include ActiveSupport::Rescuable
- included do
- rescue_from(ActionController::ParameterMissing) do |parameter_missing_exception|
- render text: "Required parameter missing: #{parameter_missing_exception.param}", status: :bad_request
- end
- end
-
# Returns a new ActionController::Parameters object that
# has been instantiated with the <tt>request.parameters</tt>.
def params
diff --git a/actionpack/lib/action_controller/record_identifier.rb b/actionpack/lib/action_controller/record_identifier.rb
index 70e0a820c4..d598bac467 100644
--- a/actionpack/lib/action_controller/record_identifier.rb
+++ b/actionpack/lib/action_controller/record_identifier.rb
@@ -2,17 +2,29 @@ require 'action_view/record_identifier'
module ActionController
module RecordIdentifier
- MESSAGE = 'method will no longer be included by default in controllers since Rails 4.1. ' +
- 'If you would like to use it in controllers, please include ' +
- 'ActionView::RecordIdentifier module.'
+ MODULE_MESSAGE = 'Calling ActionController::RecordIdentifier.%s is deprecated and ' \
+ 'will be removed in Rails 4.1, please call using ActionView::RecordIdentifier instead.'
+ INSTANCE_MESSAGE = '%s method will no longer be included by default in controllers ' \
+ 'since Rails 4.1. If you would like to use it in controllers, please include ' \
+ 'ActionView::RecordIdentifier module.'
def dom_id(record, prefix = nil)
- ActiveSupport::Deprecation.warn('dom_id ' + MESSAGE)
+ ActiveSupport::Deprecation.warn(INSTANCE_MESSAGE % 'dom_id')
ActionView::RecordIdentifier.dom_id(record, prefix)
end
def dom_class(record, prefix = nil)
- ActiveSupport::Deprecation.warn('dom_class ' + MESSAGE)
+ ActiveSupport::Deprecation.warn(INSTANCE_MESSAGE % 'dom_class')
+ ActionView::RecordIdentifier.dom_class(record, prefix)
+ end
+
+ def self.dom_id(record, prefix = nil)
+ ActiveSupport::Deprecation.warn(MODULE_MESSAGE % 'dom_id')
+ ActionView::RecordIdentifier.dom_id(record, prefix)
+ end
+
+ def self.dom_class(record, prefix = nil)
+ ActiveSupport::Deprecation.warn(MODULE_MESSAGE % 'dom_class')
ActionView::RecordIdentifier.dom_class(record, prefix)
end
end
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index 331d15d403..d8206b573d 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -81,8 +81,7 @@ module ActionController
# # assert that the "_customer" partial was rendered with a specific object
# assert_template partial: '_customer', locals: { customer: @customer }
def assert_template(options = {}, message = nil)
- # Force body to be read in case the
- # template is being streamed
+ # Force body to be read in case the template is being streamed.
response.body
case options
diff --git a/actionpack/lib/action_dispatch/journey/formatter.rb b/actionpack/lib/action_dispatch/journey/formatter.rb
index cf755bfbeb..82c55660ea 100644
--- a/actionpack/lib/action_dispatch/journey/formatter.rb
+++ b/actionpack/lib/action_dispatch/journey/formatter.rb
@@ -1,3 +1,5 @@
+require 'action_controller/metal/exceptions'
+
module ActionDispatch
module Journey
# The Formatter class is used for formatting URLs. For example, parameters
@@ -27,7 +29,10 @@ module ActionDispatch
return [route.format(parameterized_parts), params]
end
- raise Router::RoutingError.new "missing required keys: #{missing_keys}"
+ message = "No route matches #{constraints.inspect}"
+ message << " missing required keys: #{missing_keys.inspect}" if name
+
+ raise ActionController::UrlGenerationError, message
end
def clear
diff --git a/actionpack/lib/action_dispatch/journey/route.rb b/actionpack/lib/action_dispatch/journey/route.rb
index d3988cf31e..063302e0f2 100644
--- a/actionpack/lib/action_dispatch/journey/route.rb
+++ b/actionpack/lib/action_dispatch/journey/route.rb
@@ -1,7 +1,7 @@
module ActionDispatch
module Journey # :nodoc:
class Route # :nodoc:
- attr_reader :app, :path, :verb, :defaults, :ip, :name
+ attr_reader :app, :path, :defaults, :name
attr_reader :constraints
alias :conditions :constraints
@@ -12,15 +12,11 @@ module ActionDispatch
# +path+ is a path constraint.
# +constraints+ is a hash of constraints to be applied to this route.
def initialize(name, app, path, constraints, defaults = {})
- constraints = constraints.dup
@name = name
@app = app
@path = path
- @verb = constraints[:request_method] || //
- @ip = constraints.delete(:ip) || //
@constraints = constraints
- @constraints.keep_if { |_,v| Regexp === v || String === v }
@defaults = defaults
@required_defaults = nil
@required_parts = nil
@@ -49,7 +45,7 @@ module ActionDispatch
end
def required_keys
- path.required_names.map { |x| x.to_sym } + required_defaults.keys
+ required_parts + required_defaults.keys
end
def score(constraints)
@@ -83,12 +79,38 @@ module ActionDispatch
@required_parts ||= path.required_names.map { |n| n.to_sym }
end
+ def required_default?(key)
+ (constraints[:required_defaults] || []).include?(key)
+ end
+
def required_defaults
- @required_defaults ||= begin
- matches = parts
- @defaults.dup.delete_if { |k,_| matches.include?(k) }
+ @required_defaults ||= @defaults.dup.delete_if do |k,_|
+ parts.include?(k) || !required_default?(k)
+ end
+ end
+
+ def matches?(request)
+ constraints.all? do |method, value|
+ next true unless request.respond_to?(method)
+
+ case value
+ when Regexp, String
+ value === request.send(method).to_s
+ when Array
+ value.include?(request.send(method))
+ else
+ value === request.send(method)
+ end
end
end
+
+ def ip
+ constraints[:ip] || //
+ end
+
+ def verb
+ constraints[:request_method] || //
+ end
end
end
end
diff --git a/actionpack/lib/action_dispatch/journey/router.rb b/actionpack/lib/action_dispatch/journey/router.rb
index 1fc45a2109..31868b1814 100644
--- a/actionpack/lib/action_dispatch/journey/router.rb
+++ b/actionpack/lib/action_dispatch/journey/router.rb
@@ -131,11 +131,7 @@ module ActionDispatch
}
routes.concat get_routes_as_head(routes)
- routes.sort_by!(&:precedence).select! { |r|
- r.constraints.all? { |k, v| v === req.send(k) } &&
- r.verb === req.request_method
- }
- routes.reject! { |r| req.ip && !(r.ip === req.ip) }
+ routes.sort_by!(&:precedence).select! { |r| r.matches?(req) }
routes.map! { |r|
match_data = r.path.match(req.path_info)
diff --git a/actionpack/lib/action_dispatch/middleware/best_standards_support.rb b/actionpack/lib/action_dispatch/middleware/best_standards_support.rb
index d338996240..94efeb79fa 100644
--- a/actionpack/lib/action_dispatch/middleware/best_standards_support.rb
+++ b/actionpack/lib/action_dispatch/middleware/best_standards_support.rb
@@ -17,7 +17,9 @@ module ActionDispatch
status, headers, body = @app.call(env)
if headers["X-UA-Compatible"] && @header
- headers["X-UA-Compatible"] << "," << @header.to_s
+ unless headers["X-UA-Compatible"][@header]
+ headers["X-UA-Compatible"] << "," << @header.to_s
+ end
else
headers["X-UA-Compatible"] = @header
end
diff --git a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
index 869d0aa7af..7489ce8028 100644
--- a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
+++ b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
@@ -12,7 +12,8 @@ module ActionDispatch
'ActionController::NotImplemented' => :not_implemented,
'ActionController::UnknownFormat' => :not_acceptable,
'ActionController::InvalidAuthenticityToken' => :unprocessable_entity,
- 'ActionController::BadRequest' => :bad_request
+ 'ActionController::BadRequest' => :bad_request,
+ 'ActionController::ParameterMissing' => :bad_request
)
cattr_accessor :rescue_templates
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index 1046a7d75a..6d93f609a6 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -8,6 +8,8 @@ require 'action_dispatch/routing/redirection'
module ActionDispatch
module Routing
class Mapper
+ URL_OPTIONS = [:protocol, :subdomain, :domain, :host, :port]
+
class Constraints #:nodoc:
def self.new(app, constraints, request = Rack::Request)
if constraints.any?
@@ -45,37 +47,68 @@ module ActionDispatch
end
class Mapping #:nodoc:
- IGNORE_OPTIONS = [:to, :as, :via, :on, :constraints, :defaults, :only, :except, :anchor, :shallow, :shallow_path, :shallow_prefix]
+ IGNORE_OPTIONS = [:to, :as, :via, :on, :constraints, :defaults, :only, :except, :anchor, :shallow, :shallow_path, :shallow_prefix, :format]
ANCHOR_CHARACTERS_REGEX = %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z}
SHORTHAND_REGEX = %r{/[\w/]+$}
WILDCARD_PATH = %r{\*([^/\)]+)\)?$}
- def initialize(set, scope, path, options)
- @set, @scope = set, scope
- @segment_keys = nil
- @options = (@scope[:options] || {}).merge(options)
- @path = normalize_path(path)
- normalize_options!
+ attr_reader :scope, :path, :options, :requirements, :conditions, :defaults
- via_all = @options.delete(:via) if @options[:via] == :all
+ def initialize(set, scope, path, options)
+ @set, @scope, @path, @options = set, scope, path, options
+ @requirements, @conditions, @defaults = {}, {}, {}
- if !via_all && request_method_condition.empty?
- msg = "You should not use the `match` method in your router without specifying an HTTP method.\n" \
- "If you want to expose your action to GET, use `get` in the router:\n\n" \
- " Instead of: match \"controller#action\"\n" \
- " Do: get \"controller#action\""
- raise msg
- end
+ normalize_path!
+ normalize_options!
+ normalize_requirements!
+ normalize_conditions!
+ normalize_defaults!
end
def to_route
- [ app, conditions, requirements, defaults, @options[:as], @options[:anchor] ]
+ [ app, conditions, requirements, defaults, options[:as], options[:anchor] ]
end
private
+ def normalize_path!
+ raise ArgumentError, "path is required" if @path.blank?
+ @path = Mapper.normalize_path(@path)
+
+ if required_format?
+ @path = "#{@path}.:format"
+ elsif optional_format?
+ @path = "#{@path}(.:format)"
+ end
+ end
+
+ def required_format?
+ options[:format] == true
+ end
+
+ def optional_format?
+ options[:format] != false && !path.include?(':format') && !path.end_with?('/')
+ end
+
def normalize_options!
- path_without_format = @path.sub(/\(\.:format\)$/, '')
+ @options.reverse_merge!(scope[:options]) if scope[:options]
+ path_without_format = path.sub(/\(\.:format\)$/, '')
+
+ # Add a constraint for wildcard route to make it non-greedy and match the
+ # optional format part of the route by default
+ if path_without_format.match(WILDCARD_PATH) && @options[:format] != false
+ @options[$1.to_sym] ||= /.+?/
+ end
+
+ if path_without_format.match(':controller')
+ raise ArgumentError, ":controller segment is not allowed within a namespace block" if scope[:module]
+
+ # Add a default constraint for :controller path segments that matches namespaced
+ # controllers with default routes like :controller/:action/:id(.:format), e.g:
+ # GET /admin/products/show/1
+ # => { controller: 'admin/products', action: 'show', id: '1' }
+ @options[:controller] ||= /.+?/
+ end
if using_match_shorthand?(path_without_format, @options)
to_shorthand = @options[:to].blank?
@@ -83,85 +116,101 @@ module ActionDispatch
end
@options.merge!(default_controller_and_action(to_shorthand))
+ end
- requirements.each do |name, requirement|
- # segment_keys.include?(k.to_s) || k == :controller
- next unless Regexp === requirement && !constraints[name]
+ # match "account/overview"
+ def using_match_shorthand?(path, options)
+ path && (options[:to] || options[:action]).nil? && path =~ SHORTHAND_REGEX
+ end
+
+ def normalize_format!
+ if options[:format] == true
+ options[:format] = /.+/
+ elsif options[:format] == false
+ options.delete(:format)
+ end
+ end
+
+ def normalize_requirements!
+ constraints.each do |key, requirement|
+ next unless segment_keys.include?(key) || key == :controller
if requirement.source =~ ANCHOR_CHARACTERS_REGEX
raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}"
end
+
if requirement.multiline?
- raise ArgumentError, "Regexp multiline option not allowed in routing requirements: #{requirement.inspect}"
+ raise ArgumentError, "Regexp multiline option is not allowed in routing requirements: #{requirement.inspect}"
end
+
+ @requirements[key] = requirement
end
- if @options[:constraints].is_a?(Hash)
- (@options[:defaults] ||= {}).reverse_merge!(defaults_from_constraints(@options[:constraints]))
+ if options[:format] == true
+ @requirements[:format] = /.+/
+ elsif Regexp === options[:format]
+ @requirements[:format] = options[:format]
+ elsif String === options[:format]
+ @requirements[:format] = Regexp.compile(options[:format])
end
end
- # match "account/overview"
- def using_match_shorthand?(path, options)
- path && (options[:to] || options[:action]).nil? && path =~ SHORTHAND_REGEX
- end
+ def normalize_defaults!
+ @defaults.merge!(scope[:defaults]) if scope[:defaults]
+ @defaults.merge!(options[:defaults]) if options[:defaults]
- def normalize_path(path)
- raise ArgumentError, "path is required" if path.blank?
- path = Mapper.normalize_path(path)
+ options.each do |key, default|
+ next if Regexp === default || IGNORE_OPTIONS.include?(key)
+ @defaults[key] = default
+ end
- if path.match(':controller')
- raise ArgumentError, ":controller segment is not allowed within a namespace block" if @scope[:module]
+ if options[:constraints].is_a?(Hash)
+ options[:constraints].each do |key, default|
+ next unless URL_OPTIONS.include?(key) && (String === default || Fixnum === default)
+ @defaults[key] ||= default
+ end
+ end
- # Add a default constraint for :controller path segments that matches namespaced
- # controllers with default routes like :controller/:action/:id(.:format), e.g:
- # GET /admin/products/show/1
- # => { controller: 'admin/products', action: 'show', id: '1' }
- @options[:controller] ||= /.+?/
+ if Regexp === options[:format]
+ @defaults[:format] = nil
+ elsif String === options[:format]
+ @defaults[:format] = options[:format]
end
+ end
- # Add a constraint for wildcard route to make it non-greedy and match the
- # optional format part of the route by default
- if path.match(WILDCARD_PATH) && @options[:format] != false
- @options[$1.to_sym] ||= /.+?/
+ def normalize_conditions!
+ @conditions.merge!(:path_info => path)
+
+ constraints.each do |key, condition|
+ next if segment_keys.include?(key) || key == :controller
+ @conditions[key] = condition
end
- if @options[:format] == false
- @options.delete(:format)
- path
- elsif path.include?(":format") || path.end_with?('/')
- path
- elsif @options[:format] == true
- "#{path}.:format"
- else
- "#{path}(.:format)"
+ @conditions[:required_defaults] = []
+ options.each do |key, required_default|
+ next if segment_keys.include?(key) || IGNORE_OPTIONS.include?(key)
+ next if Regexp === required_default
+ @conditions[:required_defaults] << key
end
- end
- def app
- Constraints.new(
- to.respond_to?(:call) ? to : Routing::RouteSet::Dispatcher.new(:defaults => defaults),
- blocks,
- @set.request_class
- )
- end
+ via_all = options.delete(:via) if options[:via] == :all
- def conditions
- { :path_info => @path }.merge!(constraints).merge!(request_method_condition)
- end
+ if !via_all && options[:via].blank?
+ msg = "You should not use the `match` method in your router without specifying an HTTP method.\n" \
+ "If you want to expose your action to GET, use `get` in the router:\n\n" \
+ " Instead of: match \"controller#action\"\n" \
+ " Do: get \"controller#action\""
+ raise msg
+ end
- def requirements
- @requirements ||= (@options[:constraints].is_a?(Hash) ? @options[:constraints] : {}).tap do |requirements|
- requirements.reverse_merge!(@scope[:constraints]) if @scope[:constraints]
- @options.each { |k, v| requirements[k] ||= v if v.is_a?(Regexp) }
+ if via = options[:via]
+ list = Array(via).map { |m| m.to_s.dasherize.upcase }
+ @conditions.merge!(:request_method => list)
end
end
- def defaults
- @defaults ||= (@options[:defaults] || {}).tap do |defaults|
- defaults.reverse_merge!(@scope[:defaults]) if @scope[:defaults]
- @options.each { |k, v| defaults[k] = v unless v.is_a?(Regexp) || IGNORE_OPTIONS.include?(k.to_sym) }
- end
+ def app
+ Constraints.new(endpoint, blocks, @set.request_class)
end
def default_controller_and_action(to_shorthand=nil)
@@ -188,11 +237,11 @@ module ActionDispatch
controller = controller.to_s unless controller.is_a?(Regexp)
action = action.to_s unless action.is_a?(Regexp)
- if controller.blank? && segment_keys.exclude?("controller")
+ if controller.blank? && segment_keys.exclude?(:controller)
raise ArgumentError, "missing :controller"
end
- if action.blank? && segment_keys.exclude?("action")
+ if action.blank? && segment_keys.exclude?(:action)
raise ArgumentError, "missing :action"
end
@@ -204,50 +253,55 @@ module ActionDispatch
end
def blocks
- constraints = @options[:constraints]
- if constraints.present? && !constraints.is_a?(Hash)
- [constraints]
+ if options[:constraints].present? && !options[:constraints].is_a?(Hash)
+ [options[:constraints]]
else
- @scope[:blocks] || []
+ scope[:blocks] || []
end
end
def constraints
- @constraints ||= requirements.reject { |k, v| segment_keys.include?(k.to_s) || k == :controller }
- end
+ @constraints ||= {}.tap do |constraints|
+ constraints.merge!(scope[:constraints]) if scope[:constraints]
- def request_method_condition
- if via = @options[:via]
- list = Array(via).map { |m| m.to_s.dasherize.upcase }
- { :request_method => list }
- else
- { }
+ options.except(*IGNORE_OPTIONS).each do |key, option|
+ constraints[key] = option if Regexp === option
+ end
+
+ constraints.merge!(options[:constraints]) if options[:constraints].is_a?(Hash)
end
end
def segment_keys
- return @segment_keys if @segment_keys
+ @segment_keys ||= path_pattern.names.map{ |s| s.to_sym }
+ end
+
+ def path_pattern
+ Journey::Path::Pattern.new(strexp)
+ end
- @segment_keys = Journey::Path::Pattern.new(
- Journey::Router::Strexp.compile(@path, requirements, SEPARATORS)
- ).names
+ def strexp
+ Journey::Router::Strexp.compile(path, requirements, SEPARATORS)
+ end
+
+ def endpoint
+ to.respond_to?(:call) ? to : dispatcher
+ end
+
+ def dispatcher
+ Routing::RouteSet::Dispatcher.new(:defaults => defaults)
end
def to
- @options[:to]
+ options[:to]
end
def default_controller
- @options[:controller] || @scope[:controller]
+ options[:controller] || scope[:controller]
end
def default_action
- @options[:action] || @scope[:action]
- end
-
- def defaults_from_constraints(constraints)
- url_keys = [:protocol, :subdomain, :domain, :host, :port]
- constraints.select { |k, v| url_keys.include?(k) && (v.is_a?(String) || v.is_a?(Fixnum)) }
+ options[:action] || scope[:action]
end
end
@@ -641,7 +695,11 @@ module ActionDispatch
options[:constraints] ||= {}
if options[:constraints].is_a?(Hash)
- (options[:defaults] ||= {}).reverse_merge!(defaults_from_constraints(options[:constraints]))
+ defaults = options[:constraints].select do
+ |k, v| URL_OPTIONS.include?(k) && (v.is_a?(String) || v.is_a?(Fixnum))
+ end
+
+ (options[:defaults] ||= {}).reverse_merge!(defaults)
else
block, options[:constraints] = options[:constraints], {}
end
@@ -846,11 +904,6 @@ module ActionDispatch
def override_keys(child) #:nodoc:
child.key?(:only) || child.key?(:except) ? [:only, :except] : []
end
-
- def defaults_from_constraints(constraints)
- url_keys = [:protocol, :subdomain, :domain, :host, :port]
- constraints.select { |k, v| url_keys.include?(k) && (v.is_a?(String) || v.is_a?(Fixnum)) }
- end
end
# Resource routing allows you to quickly declare all of the common routes
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index b1959e388c..c72310cca3 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -421,7 +421,7 @@ module ActionDispatch
end
conditions.keep_if do |k, _|
- k == :action || k == :controller ||
+ k == :action || k == :controller || k == :required_defaults ||
@request_class.public_method_defined?(k) || path_values.include?(k)
end
end
@@ -527,12 +527,10 @@ module ActionDispatch
recall[:action] = options.delete(:action) if options[:action] == 'index'
end
- # Generates a path from routes, returns [path, params]
- # if no path is returned the formatter will raise Journey::Router::RoutingError
+ # Generates a path from routes, returns [path, params].
+ # If no route is generated the formatter will raise ActionController::UrlGenerationError
def generate
@set.formatter.generate(:path_info, named_route, options, recall, PARAMETERIZE)
- rescue Journey::Router::RoutingError => e
- raise ActionController::UrlGenerationError, "No route matches #{options.inspect} #{e.message}"
end
def different_controller?
diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb
index 1fc5933e98..ed4e88aab6 100644
--- a/actionpack/lib/action_dispatch/testing/integration.rb
+++ b/actionpack/lib/action_dispatch/testing/integration.rb
@@ -273,7 +273,7 @@ module ActionDispatch
if path =~ %r{://}
location = URI.parse(path)
https! URI::HTTPS === location if location.scheme
- host! location.host if location.host
+ host! "#{location.host}:#{location.port}" if location.host
path = location.query ? "#{location.path}?#{location.query}" : location.path
end
diff --git a/actionpack/lib/action_view/helpers.rb b/actionpack/lib/action_view/helpers.rb
index 269e78a021..8a78685ae1 100644
--- a/actionpack/lib/action_view/helpers.rb
+++ b/actionpack/lib/action_view/helpers.rb
@@ -1,3 +1,5 @@
+require 'active_support/benchmarkable'
+
module ActionView #:nodoc:
module Helpers #:nodoc:
extend ActiveSupport::Autoload
@@ -6,7 +8,6 @@ module ActionView #:nodoc:
autoload :AssetTagHelper
autoload :AssetUrlHelper
autoload :AtomFeedHelper
- autoload :BenchmarkHelper
autoload :CacheHelper
autoload :CaptureHelper
autoload :ControllerHelper
@@ -29,11 +30,11 @@ module ActionView #:nodoc:
extend ActiveSupport::Concern
+ include ActiveSupport::Benchmarkable
include ActiveModelHelper
include AssetTagHelper
include AssetUrlHelper
include AtomFeedHelper
- include BenchmarkHelper
include CacheHelper
include CaptureHelper
include ControllerHelper
diff --git a/actionpack/lib/action_view/helpers/benchmark_helper.rb b/actionpack/lib/action_view/helpers/benchmark_helper.rb
deleted file mode 100644
index 87fbf8f1a8..0000000000
--- a/actionpack/lib/action_view/helpers/benchmark_helper.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-require 'active_support/benchmarkable'
-
-module ActionView
- module Helpers
- module BenchmarkHelper #:nodoc:
- include ActiveSupport::Benchmarkable
-
- def benchmark(*)
- capture { super }
- end
- end
- end
-end
diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb
index 1fbf61a5a9..cf978d8e83 100644
--- a/actionpack/lib/action_view/helpers/date_helper.rb
+++ b/actionpack/lib/action_view/helpers/date_helper.rb
@@ -193,6 +193,7 @@ module ActionView
# * <tt>:include_blank</tt> - Include a blank option in every select field so it's possible to set empty
# dates.
# * <tt>:default</tt> - Set a default date if the affected date isn't set or is nil.
+ # * <tt>:selected</tt> - Set a date that overrides the actual value.
# * <tt>:disabled</tt> - Set to true if you want show the select fields as disabled.
# * <tt>:prompt</tt> - Set to true (for a generic prompt), a prompt string or a hash of prompt strings
# for <tt>:year</tt>, <tt>:month</tt>, <tt>:day</tt>, <tt>:hour</tt>, <tt>:minute</tt> and <tt>:second</tt>.
@@ -234,6 +235,10 @@ module ActionView
# # which is initially set to the date 3 days from the current date
# date_select("article", "written_on", default: 3.days.from_now)
#
+ # # Generates a date select that when POSTed is stored in the article variable, in the written_on attribute
+ # # which is set in the form with todays date, regardless of the value in the Active Record object.
+ # date_select("article", "written_on", selected: Date.today)
+ #
# # Generates a date select that when POSTed is stored in the credit_card variable, in the bill_due attribute
# # that will have a default day of 20.
# date_select("credit_card", "bill_due", default: { day: 20 })
diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb
index bcad05e033..ae7bfd1ec6 100644
--- a/actionpack/lib/action_view/helpers/form_options_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_options_helper.rb
@@ -772,12 +772,12 @@ module ActionView
@template.time_zone_select(@object_name, method, priority_zones, objectify_options(options), @default_options.merge(html_options))
end
- def collection_check_boxes(method, collection, value_method, text_method, options = {}, html_options = {})
- @template.collection_check_boxes(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options))
+ def collection_check_boxes(method, collection, value_method, text_method, options = {}, html_options = {}, &block)
+ @template.collection_check_boxes(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options), &block)
end
- def collection_radio_buttons(method, collection, value_method, text_method, options = {}, html_options = {})
- @template.collection_radio_buttons(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options))
+ def collection_radio_buttons(method, collection, value_method, text_method, options = {}, html_options = {}, &block)
+ @template.collection_radio_buttons(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options), &block)
end
end
end
diff --git a/actionpack/lib/action_view/helpers/tags/date_select.rb b/actionpack/lib/action_view/helpers/tags/date_select.rb
index 6c400f85cb..734591394b 100644
--- a/actionpack/lib/action_view/helpers/tags/date_select.rb
+++ b/actionpack/lib/action_view/helpers/tags/date_select.rb
@@ -27,7 +27,7 @@ module ActionView
end
def datetime_selector(options, html_options)
- datetime = value(object) || default_datetime(options)
+ datetime = options.fetch(:selected) { value(object) || default_datetime(options) }
@auto_index ||= nil
options = options.dup
diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb
index cf561d913a..e2239c05c7 100644
--- a/actionpack/test/controller/integration_test.rb
+++ b/actionpack/test/controller/integration_test.rb
@@ -466,6 +466,58 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest
assert_equal 'http://www.example.com/foo', url_for(:controller => "foo")
end
+ def test_port_via_host!
+ with_test_route_set do
+ host! 'www.example.com:8080'
+ get '/get'
+ assert_equal 8080, request.port
+ end
+ end
+
+ def test_port_via_process
+ with_test_route_set do
+ get 'http://www.example.com:8080/get'
+ assert_equal 8080, request.port
+ end
+ end
+
+ def test_https_and_port_via_host_and_https!
+ with_test_route_set do
+ host! 'www.example.com'
+ https! true
+
+ get '/get'
+ assert_equal 443, request.port
+ assert_equal true, request.ssl?
+
+ host! 'www.example.com:443'
+ https! true
+
+ get '/get'
+ assert_equal 443, request.port
+ assert_equal true, request.ssl?
+
+ host! 'www.example.com:8443'
+ https! true
+
+ get '/get'
+ assert_equal 8443, request.port
+ assert_equal true, request.ssl?
+ end
+ end
+
+ def test_https_and_port_via_process
+ with_test_route_set do
+ get 'https://www.example.com/get'
+ assert_equal 443, request.port
+ assert_equal true, request.ssl?
+
+ get 'https://www.example.com:8443/get'
+ assert_equal 8443, request.port
+ assert_equal true, request.ssl?
+ end
+ end
+
private
def with_test_route_set
with_routing do |set|
diff --git a/actionpack/test/controller/layout_test.rb b/actionpack/test/controller/layout_test.rb
index 94a8d2f180..365fa04570 100644
--- a/actionpack/test/controller/layout_test.rb
+++ b/actionpack/test/controller/layout_test.rb
@@ -80,7 +80,7 @@ end
class StreamingLayoutController < LayoutTest
def render(*args)
- options = args.extract_options! || {}
+ options = args.extract_options!
super(*args, options.merge(:stream => true))
end
end
diff --git a/actionpack/test/controller/record_identifier_test.rb b/actionpack/test/controller/record_identifier_test.rb
new file mode 100644
index 0000000000..ff5d7fd3bd
--- /dev/null
+++ b/actionpack/test/controller/record_identifier_test.rb
@@ -0,0 +1,34 @@
+require 'abstract_unit'
+require 'controller/fake_models'
+
+class ControllerRecordIdentifierTest < ActiveSupport::TestCase
+ include ActionController::RecordIdentifier
+
+ def setup
+ @record = Comment.new
+ end
+
+ def test_dom_id_deprecation
+ assert_deprecated(/dom_id method will no longer be included by default in controllers/) do
+ dom_id(@record)
+ end
+ end
+
+ def test_dom_class_deprecation
+ assert_deprecated(/dom_class method will no longer be included by default in controllers/) do
+ dom_class(@record)
+ end
+ end
+
+ def test_dom_id_from_module_deprecation
+ assert_deprecated(/Calling ActionController::RecordIdentifier.dom_id is deprecated/) do
+ ActionController::RecordIdentifier.dom_id(@record)
+ end
+ end
+
+ def test_dom_class_from_module_deprecation
+ assert_deprecated(/Calling ActionController::RecordIdentifier.dom_class is deprecated/) do
+ ActionController::RecordIdentifier.dom_class(@record)
+ end
+ end
+end
diff --git a/actionpack/test/controller/required_params_test.rb b/actionpack/test/controller/required_params_test.rb
index 661bcb3945..343d57c300 100644
--- a/actionpack/test/controller/required_params_test.rb
+++ b/actionpack/test/controller/required_params_test.rb
@@ -11,20 +11,17 @@ class ActionControllerRequiredParamsTest < ActionController::TestCase
tests BooksController
test "missing required parameters will raise exception" do
- post :create, { magazine: { name: "Mjallo!" } }
- assert_response :bad_request
+ assert_raise ActionController::ParameterMissing do
+ post :create, { magazine: { name: "Mjallo!" } }
+ end
- post :create, { book: { title: "Mjallo!" } }
- assert_response :bad_request
+ assert_raise ActionController::ParameterMissing do
+ post :create, { book: { title: "Mjallo!" } }
+ end
end
test "required parameters that are present will not raise" do
post :create, { book: { name: "Mjallo!" } }
assert_response :ok
end
-
- test "missing parameters will be mentioned in the return" do
- post :create, { magazine: { name: "Mjallo!" } }
- assert_equal "Required parameter missing: book", response.body
- end
end
diff --git a/actionpack/test/controller/test_case_test.rb b/actionpack/test/controller/test_case_test.rb
index e4d78d58b9..df31338f09 100644
--- a/actionpack/test/controller/test_case_test.rb
+++ b/actionpack/test/controller/test_case_test.rb
@@ -931,3 +931,34 @@ class AnonymousControllerTest < ActionController::TestCase
assert_equal 'anonymous', @response.body
end
end
+
+class RoutingDefaultsTest < ActionController::TestCase
+ def setup
+ @controller = Class.new(ActionController::Base) do
+ def post
+ render :text => request.fullpath
+ end
+
+ def project
+ render :text => request.fullpath
+ end
+ end.new
+
+ @routes = ActionDispatch::Routing::RouteSet.new.tap do |r|
+ r.draw do
+ get '/posts/:id', :to => 'anonymous#post', :bucket_type => 'post'
+ get '/projects/:id', :to => 'anonymous#project', :defaults => { :bucket_type => 'project' }
+ end
+ end
+ end
+
+ def test_route_option_can_be_passed_via_process
+ get :post, :id => 1, :bucket_type => 'post'
+ assert_equal '/posts/1', @response.body
+ end
+
+ def test_route_default_is_not_required_for_building_request_uri
+ get :project, :id => 2
+ assert_equal '/projects/2', @response.body
+ end
+end
diff --git a/actionpack/test/dispatch/best_standards_support_test.rb b/actionpack/test/dispatch/best_standards_support_test.rb
index 0737c40a39..551bb9621a 100644
--- a/actionpack/test/dispatch/best_standards_support_test.rb
+++ b/actionpack/test/dispatch/best_standards_support_test.rb
@@ -16,9 +16,10 @@ class BestStandardsSupportTest < ActiveSupport::TestCase
assert_equal nil, headers["X-UA-Compatible"]
end
- def test_appends_to_app_headers
+ def test_appends_to_app_headers_without_duplication_after_multiple_requests
app_headers = { "X-UA-Compatible" => "requiresActiveX=true" }
_, headers, _ = app(true, app_headers).call({})
+ _, headers, _ = app(true, app_headers).call({})
expects = "requiresActiveX=true,IE=Edge,chrome=1"
assert_equal expects, headers["X-UA-Compatible"]
diff --git a/actionpack/test/dispatch/debug_exceptions_test.rb b/actionpack/test/dispatch/debug_exceptions_test.rb
index 1319eba9ac..6035f0361e 100644
--- a/actionpack/test/dispatch/debug_exceptions_test.rb
+++ b/actionpack/test/dispatch/debug_exceptions_test.rb
@@ -39,6 +39,8 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
raise ActionController::BadRequest
when "/missing_keys"
raise ActionController::UrlGenerationError, "No route matches"
+ when "/parameter_missing"
+ raise ActionController::ParameterMissing, :missing_param_key
else
raise "puke!"
end
@@ -114,6 +116,10 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
get "/bad_request", {}, {'action_dispatch.show_exceptions' => true}
assert_response 400
assert_match(/ActionController::BadRequest/, body)
+
+ get "/parameter_missing", {}, {'action_dispatch.show_exceptions' => true}
+ assert_response 400
+ assert_match(/ActionController::ParameterMissing/, body)
end
test "does not show filtered parameters" do
diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb
index 02675c7f8c..39923d0d2b 100644
--- a/actionpack/test/dispatch/request_test.rb
+++ b/actionpack/test/dispatch/request_test.rb
@@ -462,6 +462,27 @@ class RequestTest < ActiveSupport::TestCase
assert request.put?
end
+ test "post uneffected by local inflections" do
+ existing_acrnoyms = ActiveSupport::Inflector.inflections.acronyms.dup
+ existing_acrnoym_regex = ActiveSupport::Inflector.inflections.acronym_regex.dup
+ begin
+ ActiveSupport::Inflector.inflections do |inflect|
+ inflect.acronym "POS"
+ end
+ assert_equal "pos_t", "POST".underscore
+ request = stub_request "REQUEST_METHOD" => "POST"
+ assert_equal :post, ActionDispatch::Request::HTTP_METHOD_LOOKUP["POST"]
+ assert_equal :post, request.method_symbol
+ assert request.post?
+ ensure
+ # Reset original acronym set
+ ActiveSupport::Inflector.inflections do |inflect|
+ inflect.send(:instance_variable_set,"@acronyms",existing_acrnoyms)
+ inflect.send(:instance_variable_set,"@acronym_regex",existing_acrnoym_regex)
+ end
+ end
+ end
+
test "xml format" do
request = stub_request
request.expects(:parameters).at_least_once.returns({ :format => 'xml' })
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index cb5299e8d3..da7474e73c 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -3252,3 +3252,79 @@ class TestOptionalRootSegments < ActionDispatch::IntegrationTest
assert_equal '/page/1', root_path(:page => '1')
end
end
+
+class TestPortConstraints < ActionDispatch::IntegrationTest
+ Routes = ActionDispatch::Routing::RouteSet.new.tap do |app|
+ app.draw do
+ ok = lambda { |env| [200, { 'Content-Type' => 'text/plain' }, []] }
+
+ get '/integer', to: ok, constraints: { :port => 8080 }
+ get '/string', to: ok, constraints: { :port => '8080' }
+ get '/array', to: ok, constraints: { :port => [8080] }
+ get '/regexp', to: ok, constraints: { :port => /8080/ }
+ end
+ end
+
+ include Routes.url_helpers
+ def app; Routes end
+
+ def test_integer_port_constraints
+ get 'http://www.example.com/integer'
+ assert_response :not_found
+
+ get 'http://www.example.com:8080/integer'
+ assert_response :success
+ end
+
+ def test_string_port_constraints
+ get 'http://www.example.com/string'
+ assert_response :not_found
+
+ get 'http://www.example.com:8080/string'
+ assert_response :success
+ end
+
+ def test_array_port_constraints
+ get 'http://www.example.com/array'
+ assert_response :not_found
+
+ get 'http://www.example.com:8080/array'
+ assert_response :success
+ end
+
+ def test_regexp_port_constraints
+ get 'http://www.example.com/regexp'
+ assert_response :not_found
+
+ get 'http://www.example.com:8080/regexp'
+ assert_response :success
+ end
+end
+
+class TestRouteDefaults < ActionDispatch::IntegrationTest
+ stub_controllers do |routes|
+ Routes = routes
+ Routes.draw do
+ resources :posts, bucket_type: 'post'
+ resources :projects, defaults: { bucket_type: 'project' }
+ end
+ end
+
+ def app
+ Routes
+ end
+
+ include Routes.url_helpers
+
+ def test_route_options_are_required_for_url_for
+ assert_raises(ActionController::UrlGenerationError) do
+ assert_equal '/posts/1', url_for(controller: 'posts', action: 'show', id: 1, only_path: true)
+ end
+
+ assert_equal '/posts/1', url_for(controller: 'posts', action: 'show', id: 1, bucket_type: 'post', only_path: true)
+ end
+
+ def test_route_defaults_are_not_required_for_url_for
+ assert_equal '/projects/1', url_for(controller: 'projects', action: 'show', id: 1, only_path: true)
+ end
+end
diff --git a/actionpack/test/journey/route_test.rb b/actionpack/test/journey/route_test.rb
index 78608a5c6b..cbe6284714 100644
--- a/actionpack/test/journey/route_test.rb
+++ b/actionpack/test/journey/route_test.rb
@@ -6,18 +6,18 @@ module ActionDispatch
def test_initialize
app = Object.new
path = Path::Pattern.new '/:controller(/:action(/:id(.:format)))'
- defaults = Object.new
+ defaults = {}
route = Route.new("name", app, path, {}, defaults)
assert_equal app, route.app
assert_equal path, route.path
- assert_equal defaults, route.defaults
+ assert_same defaults, route.defaults
end
def test_route_adds_itself_as_memo
app = Object.new
path = Path::Pattern.new '/:controller(/:action(/:id(.:format)))'
- defaults = Object.new
+ defaults = {}
route = Route.new("name", app, path, {}, defaults)
route.ast.grep(Nodes::Terminal).each do |node|
@@ -82,11 +82,14 @@ module ActionDispatch
end
def test_score
+ constraints = {:required_defaults => [:controller, :action]}
+ defaults = {:controller=>"pages", :action=>"show"}
+
path = Path::Pattern.new "/page/:id(/:action)(.:format)"
- specific = Route.new "name", nil, path, {}, {:controller=>"pages", :action=>"show"}
+ specific = Route.new "name", nil, path, constraints, defaults
path = Path::Pattern.new "/:controller(/:action(/:id))(.:format)"
- generic = Route.new "name", nil, path, {}
+ generic = Route.new "name", nil, path, constraints
knowledge = {:id=>20, :controller=>"pages", :action=>"show"}
diff --git a/actionpack/test/journey/router_test.rb b/actionpack/test/journey/router_test.rb
index 27bdb0108a..3d52b2e9ee 100644
--- a/actionpack/test/journey/router_test.rb
+++ b/actionpack/test/journey/router_test.rb
@@ -155,7 +155,7 @@ module ActionDispatch
Router::Strexp.new("/foo/:id", { :id => /\d/ }, ['/', '.', '?'], false)
]
- assert_raises(Router::RoutingError) do
+ assert_raises(ActionController::UrlGenerationError) do
@formatter.generate(:path_info, nil, { :id => '10' }, { })
end
end
@@ -168,7 +168,7 @@ module ActionDispatch
path, _ = @formatter.generate(:path_info, nil, { :id => '10' }, { })
assert_equal '/foo/10', path
- assert_raises(Router::RoutingError) do
+ assert_raises(ActionController::UrlGenerationError) do
@formatter.generate(:path_info, nil, { :id => 'aa' }, { })
end
end
@@ -194,11 +194,11 @@ module ActionDispatch
path = Path::Pattern.new pattern
@router.routes.add_route nil, path, {}, {}, route_name
- error = assert_raises(Router::RoutingError) do
+ error = assert_raises(ActionController::UrlGenerationError) do
@formatter.generate(:path_info, route_name, { }, { })
end
- assert_match(/required keys: \[:id\]/, error.message)
+ assert_match(/missing required keys: \[:id\]/, error.message)
end
def test_X_Cascade
diff --git a/actionpack/test/template/benchmark_helper_test.rb b/actionpack/test/template/benchmark_helper_test.rb
deleted file mode 100644
index 8c198d2562..0000000000
--- a/actionpack/test/template/benchmark_helper_test.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-require 'abstract_unit'
-require 'stringio'
-
-class BenchmarkHelperTest < ActionView::TestCase
- include RenderERBUtils
- tests ActionView::Helpers::BenchmarkHelper
-
- def test_output_in_erb
- output = render_erb("Hello <%= benchmark do %>world<% end %>")
- expected = 'Hello world'
- assert_equal expected, output
- end
-
- def test_returns_value_from_block
- assert_equal 'test', benchmark { 'test' }
- end
-
- def test_default_message
- log = StringIO.new
- self.stubs(:logger).returns(Logger.new(log))
- benchmark {}
- assert_match(/Benchmarking \(\d+.\d+ms\)/, log.rewind && log.read)
- end
-end
diff --git a/actionpack/test/template/date_helper_test.rb b/actionpack/test/template/date_helper_test.rb
index f9ce63fcb0..f11adefad8 100644
--- a/actionpack/test/template/date_helper_test.rb
+++ b/actionpack/test/template/date_helper_test.rb
@@ -1510,6 +1510,44 @@ class DateHelperTest < ActionView::TestCase
assert_dom_equal expected, date_select("post", "written_on")
end
+
+ def test_date_select_with_selected
+ @post = Post.new
+ @post.written_on = Date.new(2004, 6, 15)
+
+ expected = %{<select id="post_written_on_1i" name="post[written_on(1i)]">\n}
+ expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option selected="selected" value="2004">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n}
+ expected << "</select>\n"
+
+ expected << %{<select id="post_written_on_2i" name="post[written_on(2i)]">\n}
+ expected << %{<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7" selected="selected">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n}
+ expected << "</select>\n"
+
+ expected << %{<select id="post_written_on_3i" name="post[written_on(3i)]">\n}
+ expected << %{<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10" selected="selected">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n}
+
+ expected << "</select>\n"
+
+ assert_dom_equal expected, date_select("post", "written_on", :selected => Date.new(2004, 07, 10))
+ end
+
+ def test_date_select_with_selected_nil
+ @post = Post.new
+ @post.written_on = Date.new(2004, 6, 15)
+
+ expected = '<input id="post_written_on_1i" name="post[written_on(1i)]" type="hidden" value="1"/>' + "\n"
+
+ expected << %{<select id="post_written_on_2i" name="post[written_on(2i)]">\n}
+ expected << %{<option value=""></option>\n<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n}
+ expected << "</select>\n"
+
+ expected << %{<select id="post_written_on_3i" name="post[written_on(3i)]">\n}
+ expected << %{<option value=""></option>\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n}
+
+ expected << "</select>\n"
+
+ assert_dom_equal expected, date_select("post", "written_on", include_blank: true, discard_year: true, selected: nil)
+ end
def test_date_select_without_day
@post = Post.new
@@ -1968,6 +2006,44 @@ class DateHelperTest < ActionView::TestCase
assert_dom_equal expected, time_select("post", "written_on")
end
+ def test_time_select_with_selected
+ @post = Post.new
+ @post.written_on = Time.local(2004, 6, 15, 15, 16, 35)
+
+ expected = %{<input type="hidden" id="post_written_on_1i" name="post[written_on(1i)]" value="2004" />\n}
+ expected << %{<input type="hidden" id="post_written_on_2i" name="post[written_on(2i)]" value="6" />\n}
+ expected << %{<input type="hidden" id="post_written_on_3i" name="post[written_on(3i)]" value="15" />\n}
+
+ expected << %(<select id="post_written_on_4i" name="post[written_on(4i)]">\n)
+ 0.upto(23) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 12}>#{sprintf("%02d", i)}</option>\n) }
+ expected << "</select>\n"
+ expected << " : "
+ expected << %(<select id="post_written_on_5i" name="post[written_on(5i)]">\n)
+ 0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 20}>#{sprintf("%02d", i)}</option>\n) }
+ expected << "</select>\n"
+
+ assert_dom_equal expected, time_select("post", "written_on", selected: Time.local(2004, 6, 15, 12, 20, 30))
+ end
+
+ def test_time_select_with_selected_nil
+ @post = Post.new
+ @post.written_on = Time.local(2004, 6, 15, 15, 16, 35)
+
+ expected = %{<input type="hidden" id="post_written_on_1i" name="post[written_on(1i)]" value="1" />\n}
+ expected << %{<input type="hidden" id="post_written_on_2i" name="post[written_on(2i)]" value="1" />\n}
+ expected << %{<input type="hidden" id="post_written_on_3i" name="post[written_on(3i)]" value="1" />\n}
+
+ expected << %(<select id="post_written_on_4i" name="post[written_on(4i)]">\n)
+ 0.upto(23) { |i| expected << %(<option value="#{sprintf("%02d", i)}">#{sprintf("%02d", i)}</option>\n) }
+ expected << "</select>\n"
+ expected << " : "
+ expected << %(<select id="post_written_on_5i" name="post[written_on(5i)]">\n)
+ 0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}">#{sprintf("%02d", i)}</option>\n) }
+ expected << "</select>\n"
+
+ assert_dom_equal expected, time_select("post", "written_on", discard_year: true, discard_month: true, discard_day: true, selected: nil)
+ end
+
def test_time_select_without_date_hidden_fields
@post = Post.new
@post.written_on = Time.local(2004, 6, 15, 15, 16, 35)
@@ -2165,6 +2241,62 @@ class DateHelperTest < ActionView::TestCase
assert_dom_equal expected, datetime_select("post", "updated_at")
end
+ def test_datetime_select_with_selected
+ @post = Post.new
+ @post.updated_at = Time.local(2004, 6, 15, 16, 35)
+
+ expected = %{<select id="post_updated_at_1i" name="post[updated_at(1i)]">\n}
+ expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n}
+ expected << "</select>\n"
+
+ expected << %{<select id="post_updated_at_2i" name="post[updated_at(2i)]">\n}
+ expected << %{<option value="1">January</option>\n<option value="2">February</option>\n<option value="3" selected="selected">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n}
+ expected << "</select>\n"
+
+ expected << %{<select id="post_updated_at_3i" name="post[updated_at(3i)]">\n}
+ expected << %{<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10" selected="selected">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n}
+ expected << "</select>\n"
+
+ expected << " &mdash; "
+
+ expected << %{<select id="post_updated_at_4i" name="post[updated_at(4i)]">\n}
+ expected << %{<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12" selected="selected">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n}
+ expected << "</select>\n"
+ expected << " : "
+ expected << %{<select id="post_updated_at_5i" name="post[updated_at(5i)]">\n}
+ expected << %{<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30" selected="selected">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n}
+ expected << "</select>\n"
+
+ assert_dom_equal expected, datetime_select("post", "updated_at", :selected => Time.local(2004, 3, 10, 12, 30))
+ end
+
+ def test_datetime_select_with_selected_nil
+ @post = Post.new
+ @post.updated_at = Time.local(2004, 6, 15, 16, 35)
+
+ expected = '<input id="post_updated_at_1i" name="post[updated_at(1i)]" type="hidden" value="1"/>' + "\n"
+
+ expected << %{<select id="post_updated_at_2i" name="post[updated_at(2i)]">\n}
+ expected << %{<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n}
+ expected << "</select>\n"
+
+ expected << %{<select id="post_updated_at_3i" name="post[updated_at(3i)]">\n}
+ expected << %{<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n}
+ expected << "</select>\n"
+
+ expected << " &mdash; "
+
+ expected << %{<select id="post_updated_at_4i" name="post[updated_at(4i)]">\n}
+ expected << %{<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n}
+ expected << "</select>\n"
+ expected << " : "
+ expected << %{<select id="post_updated_at_5i" name="post[updated_at(5i)]">\n}
+ expected << %{<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n}
+ expected << "</select>\n"
+
+ assert_dom_equal expected, datetime_select("post", "updated_at", discard_year: true, selected: nil)
+ end
+
def test_datetime_select_defaults_to_time_zone_now_when_config_time_zone_is_set
# The love zone is UTC+0
mytz = Class.new(ActiveSupport::TimeZone) {
diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb
index 247068de4e..f9890a2eef 100644
--- a/actionpack/test/template/form_helper_test.rb
+++ b/actionpack/test/template/form_helper_test.rb
@@ -1120,6 +1120,28 @@ class FormHelperTest < ActionView::TestCase
assert_dom_equal expected, output_buffer
end
+ def test_form_for_with_collection_radio_buttons_with_custom_builder_block
+ post = Post.new
+ def post.active; false; end
+ form_for(post) do |f|
+ rendered_radio_buttons = f.collection_radio_buttons(:active, [true, false], :to_s, :to_s) do |b|
+ b.label { b.radio_button + b.text }
+ end
+ concat rendered_radio_buttons
+ end
+
+ expected = whole_form("/posts", "new_post" , "new_post") do
+ "<label for='post_active_true'>"+
+ "<input id='post_active_true' name='post[active]' type='radio' value='true' />" +
+ "true</label>" +
+ "<label for='post_active_false'>"+
+ "<input checked='checked' id='post_active_false' name='post[active]' type='radio' value='false' />" +
+ "false</label>"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
def test_form_for_with_collection_check_boxes
post = Post.new
def post.tag_ids; [1, 3]; end
@@ -1141,6 +1163,33 @@ class FormHelperTest < ActionView::TestCase
assert_dom_equal expected, output_buffer
end
+ def test_form_for_with_collection_check_boxes_with_custom_builder_block
+ post = Post.new
+ def post.tag_ids; [1, 3]; end
+ collection = (1..3).map{|i| [i, "Tag #{i}"] }
+ form_for(post) do |f|
+ rendered_check_boxes = f.collection_check_boxes(:tag_ids, collection, :first, :last) do |b|
+ b.label { b.check_box + b.text }
+ end
+ concat rendered_check_boxes
+ end
+
+ expected = whole_form("/posts", "new_post" , "new_post") do
+ "<label for='post_tag_ids_1'>" +
+ "<input checked='checked' id='post_tag_ids_1' name='post[tag_ids][]' type='checkbox' value='1' />" +
+ "Tag 1</label>" +
+ "<label for='post_tag_ids_2'>" +
+ "<input id='post_tag_ids_2' name='post[tag_ids][]' type='checkbox' value='2' />" +
+ "Tag 2</label>" +
+ "<label for='post_tag_ids_3'>" +
+ "<input checked='checked' id='post_tag_ids_3' name='post[tag_ids][]' type='checkbox' value='3' />" +
+ "Tag 3</label>" +
+ "<input name='post[tag_ids][]' type='hidden' value='' />"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
def test_form_for_with_file_field_generate_multipart
Post.send :attr_accessor, :file
@@ -2163,14 +2212,14 @@ class FormHelperTest < ActionView::TestCase
assert_dom_equal expected, output_buffer
end
- class FakeAssociatonProxy
+ class FakeAssociationProxy
def to_ary
[1, 2, 3]
end
end
def test_nested_fields_for_with_child_index_option_override_on_a_nested_attributes_collection_association_with_proxy
- @post.comments = FakeAssociatonProxy.new
+ @post.comments = FakeAssociationProxy.new
form_for(@post) do |f|
concat f.fields_for(:comments, Comment.new(321), :child_index => 'abc') { |cf|
diff --git a/activemodel/README.rdoc b/activemodel/README.rdoc
index 3cc2d9ad8e..1b1fe2fa2b 100644
--- a/activemodel/README.rdoc
+++ b/activemodel/README.rdoc
@@ -1,7 +1,7 @@
= Active Model -- model interfaces for Rails
Active Model provides a known set of interfaces for usage in model classes.
-They allow for Action Pack helpers to interact with non-ActiveRecord models,
+They allow for Action Pack helpers to interact with non-Active Record models,
for example. Active Model also helps building custom ORMs for use outside of
the Rails framework.
@@ -11,7 +11,7 @@ code from Rails, or monkey patch entire helpers to make them handle objects
that did not exactly conform to the Active Record interface. This would result
in code duplication and fragile applications that broke on upgrades. Active
Model solves this by defining an explicit API. You can read more about the
-API in ActiveModel::Lint::Tests.
+API in <tt>ActiveModel::Lint::Tests</tt>.
Active Model provides a default module that implements the basic API required
to integrate with Action Pack out of the box: <tt>ActiveModel::Model</tt>.
diff --git a/activemodel/test/cases/conversion_test.rb b/activemodel/test/cases/conversion_test.rb
index d679ad41aa..a037666cbc 100644
--- a/activemodel/test/cases/conversion_test.rb
+++ b/activemodel/test/cases/conversion_test.rb
@@ -29,4 +29,8 @@ class ConversionTest < ActiveModel::TestCase
assert_equal "helicopters/helicopter", Helicopter.new.to_partial_path,
"ActiveModel::Conversion#to_partial_path caching should be class-specific"
end
+
+ test "to_partial_path handles namespaced models" do
+ assert_equal "helicopter/comanches/comanche", Helicopter::Comanche.new.to_partial_path
+ end
end
diff --git a/activemodel/test/cases/dirty_test.rb b/activemodel/test/cases/dirty_test.rb
index 0b9f9537e2..ba45089cca 100644
--- a/activemodel/test/cases/dirty_test.rb
+++ b/activemodel/test/cases/dirty_test.rb
@@ -46,7 +46,7 @@ class DirtyTest < ActiveModel::TestCase
assert @model.name_changed?
end
- test "list of changed attributes" do
+ test "list of changed attribute keys" do
assert_equal [], @model.changed
@model.name = "Paul"
assert_equal ['name'], @model.changed
@@ -106,6 +106,17 @@ class DirtyTest < ActiveModel::TestCase
assert_equal [nil, "Jericho Cane"], @model.previous_changes['name']
end
+ test "previous value is preserved when changed after save" do
+ assert_equal({}, @model.changed_attributes)
+ @model.name = "Paul"
+ assert_equal({ "name" => nil }, @model.changed_attributes)
+
+ @model.save
+
+ @model.name = "John"
+ assert_equal({ "name" => "Paul" }, @model.changed_attributes)
+ end
+
test "changing the same attribute multiple times retains the correct original value" do
@model.name = "Otto"
@model.save
diff --git a/activemodel/test/cases/model_test.rb b/activemodel/test/cases/model_test.rb
index d93fd96b88..588d8e661e 100644
--- a/activemodel/test/cases/model_test.rb
+++ b/activemodel/test/cases/model_test.rb
@@ -20,7 +20,13 @@ class ModelTest < ActiveModel::TestCase
def test_initialize_with_nil_or_empty_hash_params_does_not_explode
assert_nothing_raised do
BasicModel.new()
+ BasicModel.new nil
BasicModel.new({})
end
end
+
+ def test_persisted_is_always_false
+ object = BasicModel.new(:attr => "value")
+ assert object.persisted? == false
+ end
end
diff --git a/activemodel/test/models/helicopter.rb b/activemodel/test/models/helicopter.rb
index a52b6fb4dd..933f3c463a 100644
--- a/activemodel/test/models/helicopter.rb
+++ b/activemodel/test/models/helicopter.rb
@@ -1,3 +1,7 @@
class Helicopter
include ActiveModel::Conversion
end
+
+class Helicopter::Comanche
+ include ActiveModel::Conversion
+end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index a6013f754a..20a5ca2baa 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -7,13 +7,15 @@ module ActiveRecord
module ConnectionHandling
# Establishes a connection to the database that's used by all Active Record objects.
def mysql2_connection(config)
+ config = config.symbolize_keys
+
config[:username] = 'root' if config[:username].nil?
if Mysql2::Client.const_defined? :FOUND_ROWS
config[:flags] = Mysql2::Client::FOUND_ROWS
end
- client = Mysql2::Client.new(config.symbolize_keys)
+ client = Mysql2::Client.new(config)
options = [config[:host], config[:username], config[:password], config[:database], config[:port], config[:socket], 0]
ConnectionAdapters::Mysql2Adapter.new(client, logger, options, config)
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
index e10b562fa4..8c68576bdc 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -18,9 +18,9 @@ module ActiveRecord
# create_database config[:database], config
# create_database 'foo_development', encoding: 'unicode'
def create_database(name, options = {})
- options = options.reverse_merge(:encoding => "utf8")
+ options = { encoding: 'utf8' }.merge!(options.symbolize_keys)
- option_string = options.symbolize_keys.sum do |key, value|
+ option_string = options.sum do |key, value|
case key
when :owner
" OWNER = \"#{value}\""
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index 3011f959a5..1b2aa9349e 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -236,26 +236,26 @@ module ActiveRecord
alias update_attributes! update!
- # Updates a single attribute of an object, without having to explicitly call save on that object.
- #
- # * Validation is skipped.
- # * Callbacks are skipped.
- # * updated_at/updated_on column is not updated if that column is available.
- #
- # Raises an +ActiveRecordError+ when called on new objects, or when the +name+
- # attribute is marked as readonly.
+ # Equivalent to <code>update_columns(name => value)</code>.
def update_column(name, value)
update_columns(name => value)
end
- # Updates the attributes from the passed-in hash, without having to explicitly call save on that object.
+ # Updates the attributes directly in the database issuing an UPDATE SQL
+ # statement and sets them in the receiver:
#
- # * Validation is skipped.
+ # user.update_columns(last_request_at: Time.current)
+ #
+ # This is the fastest way to update attributes because it goes straight to
+ # the database, but take into account that in consequence the regular update
+ # procedures are totally bypassed. In particular:
+ #
+ # * Validations are skipped.
# * Callbacks are skipped.
- # * updated_at/updated_on column is not updated if that column is available.
+ # * +updated_at+/+updated_on+ are not updated.
#
- # Raises an +ActiveRecordError+ when called on new objects, or when at least
- # one of the attributes is marked as readonly.
+ # This method raises an +ActiveRecord::ActiveRecordError+ when called on new
+ # objects, or when at least one of the attributes is marked as readonly.
def update_columns(attributes)
raise ActiveRecordError, "can not update on a new record object" unless persisted?
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index 83074e72c1..883d25d80b 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -7,12 +7,12 @@ module ActiveRecord
table = default_table
if value.is_a?(Hash)
- table = Arel::Table.new(column, default_table.engine)
- association = klass.reflect_on_association(column.to_sym)
-
if value.empty?
- queries.concat ['1 = 2']
+ queries << '1 = 2'
else
+ table = Arel::Table.new(column, default_table.engine)
+ association = klass.reflect_on_association(column.to_sym)
+
value.each do |k, v|
queries.concat expand(association && association.klass, table, k, v)
end
diff --git a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
index 1b4ed1cac4..17378969a5 100644
--- a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
@@ -15,18 +15,20 @@ module ActiveRecord
establish_connection configuration_without_database
connection.create_database configuration['database'], creation_options
establish_connection configuration
- rescue error_class => error
- raise error unless error.errno == ACCESS_DENIED_ERROR
-
- $stdout.print error.error
- establish_connection root_configuration_without_database
- connection.create_database configuration['database'], creation_options
- connection.execute grant_statement.gsub(/\s+/, ' ').strip
- establish_connection configuration
- rescue ActiveRecord::StatementInvalid, error_class => error
+ rescue ActiveRecord::StatementInvalid => error
if /database exists/ === error.message
raise DatabaseAlreadyExists
else
+ raise
+ end
+ rescue error_class => error
+ if error.respond_to?(:errno) && error.errno == ACCESS_DENIED_ERROR
+ $stdout.print error.error
+ establish_connection root_configuration_without_database
+ connection.create_database configuration['database'], creation_options
+ connection.execute grant_statement.gsub(/\s+/, ' ').strip
+ establish_connection configuration
+ else
$stderr.puts "Couldn't create database for #{configuration.inspect}, #{creation_options.inspect}"
$stderr.puts "(If you set the charset manually, make sure you have a matching collation)" if configuration['encoding']
end
@@ -96,6 +98,8 @@ module ActiveRecord
Mysql2::Error
elsif defined?(Mysql)
Mysql::Error
+ else
+ StandardError
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
index 1b4f4a5fc9..01c3e6b49b 100644
--- a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
@@ -16,6 +16,7 @@ class PostgresqlActiveSchemaTest < ActiveRecord::TestCase
def test_create_database_with_encoding
assert_equal %(CREATE DATABASE "matt" ENCODING = 'utf8'), create_database(:matt)
assert_equal %(CREATE DATABASE "aimonetti" ENCODING = 'latin1'), create_database(:aimonetti, :encoding => :latin1)
+ assert_equal %(CREATE DATABASE "aimonetti" ENCODING = 'latin1'), create_database(:aimonetti, 'encoding' => :latin1)
end
def test_create_database_with_collation_and_ctype
diff --git a/activesupport/test/core_ext/range_ext_test.rb b/activesupport/test/core_ext/range_ext_test.rb
index 0051c48984..6e94d5e10d 100644
--- a/activesupport/test/core_ext/range_ext_test.rb
+++ b/activesupport/test/core_ext/range_ext_test.rb
@@ -13,6 +13,12 @@ class RangeTest < ActiveSupport::TestCase
date_range = Time.utc(2005, 12, 10, 15, 30)..Time.utc(2005, 12, 10, 17, 30)
assert_equal "BETWEEN '2005-12-10 15:30:00' AND '2005-12-10 17:30:00'", date_range.to_s(:db)
end
+
+ def test_date_range
+ assert_instance_of Range, DateTime.new..DateTime.new
+ assert_instance_of Range, DateTime::Infinity.new..DateTime::Infinity.new
+ assert_instance_of Range, DateTime.new..DateTime::Infinity.new
+ end
def test_overlaps_last_inclusive
assert((1..5).overlaps?(5..10))
diff --git a/guides/source/action_mailer_basics.md b/guides/source/action_mailer_basics.md
index 7c6ef52f4a..590ad5738e 100644
--- a/guides/source/action_mailer_basics.md
+++ b/guides/source/action_mailer_basics.md
@@ -575,3 +575,23 @@ end
```
In the test we send the email and store the returned object in the `email` variable. We then ensure that it was sent (the first assert), then, in the second batch of assertions, we ensure that the email does indeed contain what we expect.
+
+Intercepting Emails
+-------------------
+There are situations where you need to edit an email before it's delivered. Fortunately Action Mailer provides hooks to intercept every email. You can register an interceptor to make modifications to mail messages right before they are handed to the delivery agents.
+
+```ruby
+class SandboxEmailInterceptor
+ def self.delivering_email(message)
+ message.to = ['sandbox@example.com']
+ end
+end
+```
+
+Before the interceptor can do its job you need to register it with the Action Mailer framework. You can do this in an initializer file `config/initializers/sandbox_email_interceptor.rb`
+
+```ruby
+ActionMailer::Base.register_interceptor(SandboxEmailInterceptor) if Rails.env.staging?
+```
+
+NOTE: The example above uses a custom environment called "staging" for a production like server but for testing purposes.
diff --git a/guides/source/active_record_validations.md b/guides/source/active_record_validations.md
index a911d6b941..eaa47d4ebf 100644
--- a/guides/source/active_record_validations.md
+++ b/guides/source/active_record_validations.md
@@ -117,7 +117,6 @@ database only if the object is valid:
* `save`
* `save!`
* `update`
-* `update`
* `update!`
The bang versions (e.g. `save!`) raise an exception if the record is invalid.
diff --git a/guides/source/command_line.md b/guides/source/command_line.md
index 12d39ea1cc..2790a4740a 100644
--- a/guides/source/command_line.md
+++ b/guides/source/command_line.md
@@ -355,7 +355,7 @@ rake assets:clean # Remove compiled assets
rake assets:precompile # Compile all the assets named in config.assets.precompile
rake db:create # Create the database from config/database.yml for the current Rails.env
...
-rake log:clear # Truncates all *.log files in log/ to zero bytes
+rake log:clear # Truncates all *.log files in log/ to zero bytes (specify which logs with LOGS=test,development)
rake middleware # Prints out your Rack middleware stack
...
rake tmp:clear # Clear session, cache, and socket files from tmp/ (narrow w/ tmp:sessions:clear, tmp:cache:clear, tmp:sockets:clear)
@@ -442,7 +442,7 @@ app/model/post.rb:
NOTE. When using specific annotations and custom annotations, the annotation name (FIXME, BUG etc) is not displayed in the output lines.
-By default, `rake notes` will look in the `app`, `config`, `lib`, `script` and `test` directories. If you would like to search other directories, you can provide them as a comma separated list in an environment variable `SOURCE_ANNOTATION_DIRECTORIES`.
+By default, `rake notes` will look in the `app`, `config`, `lib`, `bin` and `test` directories. If you would like to search other directories, you can provide them as a comma separated list in an environment variable `SOURCE_ANNOTATION_DIRECTORIES`.
```bash
$ export SOURCE_ANNOTATION_DIRECTORIES='rspec,vendor'
diff --git a/guides/source/documents.yaml b/guides/source/documents.yaml
index 13f982d7e2..c73bbeb90d 100644
--- a/guides/source/documents.yaml
+++ b/guides/source/documents.yaml
@@ -102,7 +102,6 @@
description: This guide documents the asset pipeline.
-
name: Working with JavaScript in Rails
- work_in_progress: true
url: working_with_javascript_in_rails.html
description: This guide covers the built-in Ajax/JavaScript functionality of Rails.
-
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index 4fd1a2369f..5dbca2e9b4 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1,5 +1,16 @@
## Rails 4.0.0 (unreleased) ##
+* Specify which logs to clear when using the `rake log:clear` task.
+ (e.g. rake log:clear LOGS=test,staging)
+
+ *Matt Bridges*
+
+* Allow a `:dirs` key in the `SourceAnnotationExtractor.enumerate` options
+ to explicitly set the directories to be traversed so it's easier to define
+ custom rake tasks.
+
+ *Brian D. Burns*
+
* Deprecate `Rails::Generators::ActiveModel#update_attributes` in favor of `#update`.
ORMs that implement `Generators::ActiveModel#update_attributes` should change
diff --git a/railties/lib/rails/info.rb b/railties/lib/rails/info.rb
index aacc1be2fc..592e74726e 100644
--- a/railties/lib/rails/info.rb
+++ b/railties/lib/rails/info.rb
@@ -46,7 +46,7 @@ module Rails
alias inspect to_s
def to_html
- (table = '<table>').tap do
+ '<table>'.tap do |table|
properties.each do |(name, value)|
table << %(<tr><td class="name">#{CGI.escapeHTML(name.to_s)}</td>)
formatted_value = if value.kind_of?(Array)
diff --git a/railties/lib/rails/source_annotation_extractor.rb b/railties/lib/rails/source_annotation_extractor.rb
index 971fbf627b..2cbb0a435c 100644
--- a/railties/lib/rails/source_annotation_extractor.rb
+++ b/railties/lib/rails/source_annotation_extractor.rb
@@ -32,15 +32,24 @@ class SourceAnnotationExtractor
end
# Prints all annotations with tag +tag+ under the root directories +app+,
- # +config+, +lib+, and +test+ (recursively). Filenames with extension
- # +.builder+, +.rb+, +.erb+, +.haml+, +.slim+, +.css+, +.scss+, +.js+,
- # +.coffee+, and +.rake+ are taken into account. The +options+ hash is
- # passed to each annotation's +to_s+.
+ # +config+, +db+, +lib+, and +test+ (recursively).
+ #
+ # Additional directories may be added using a comma-delimited list set using
+ # <tt>ENV['SOURCE_ANNOTATION_DIRECTORIES']</tt>.
+ #
+ # Directories may also be explicitly set using the <tt>:dirs</tt> key in +options+.
+ #
+ # SourceAnnotationExtractor.enumerate 'TODO|FIXME', dirs: %w(app lib), tag: true
+ #
+ # If +options+ has a <tt>:tag</tt> flag, it will be passed to each annotation's +to_s+.
+ #
+ # See <tt>#find_in</tt> for a list of file extensions that will be taken into account.
#
# This class method is the single entry point for the rake tasks.
def self.enumerate(tag, options={})
extractor = new(tag)
- extractor.display(extractor.find, options)
+ dirs = options.delete(:dirs) || Annotation.directories
+ extractor.display(extractor.find(dirs), options)
end
attr_reader :tag
@@ -51,7 +60,7 @@ class SourceAnnotationExtractor
# Returns a hash that maps filenames under +dirs+ (recursively) to arrays
# with their annotations.
- def find(dirs = Annotation.directories)
+ def find(dirs)
dirs.inject({}) { |h, dir| h.update(find_in(dir)) }
end
@@ -68,16 +77,22 @@ class SourceAnnotationExtractor
if File.directory?(item)
results.update(find_in(item))
- elsif item =~ /\.(builder|rb|coffee|rake)$/
- results.update(extract_annotations_from(item, /#\s*(#{tag}):?\s*(.*)$/))
- elsif item =~ /\.(css|scss|js)$/
- results.update(extract_annotations_from(item, /\/\/\s*(#{tag}):?\s*(.*)$/))
- elsif item =~ /\.erb$/
- results.update(extract_annotations_from(item, /<%\s*#\s*(#{tag}):?\s*(.*?)\s*%>/))
- elsif item =~ /\.haml$/
- results.update(extract_annotations_from(item, /-\s*#\s*(#{tag}):?\s*(.*)$/))
- elsif item =~ /\.slim$/
- results.update(extract_annotations_from(item, /\/\s*\s*(#{tag}):?\s*(.*)$/))
+ else
+ pattern =
+ case item
+ when /\.(builder|rb|coffee|rake)$/
+ /#\s*(#{tag}):?\s*(.*)$/
+ when /\.(css|scss|js)$/
+ /\/\/\s*(#{tag}):?\s*(.*)$/
+ when /\.erb$/
+ /<%\s*#\s*(#{tag}):?\s*(.*?)\s*%>/
+ when /\.haml$/
+ /-\s*#\s*(#{tag}):?\s*(.*)$/
+ when /\.slim$/
+ /\/\s*\s*(#{tag}):?\s*(.*)$/
+ else nil
+ end
+ results.update(extract_annotations_from(item, pattern)) if pattern
end
end
diff --git a/railties/lib/rails/tasks/log.rake b/railties/lib/rails/tasks/log.rake
index 6e1334692e..6c3f02eb0c 100644
--- a/railties/lib/rails/tasks/log.rake
+++ b/railties/lib/rails/tasks/log.rake
@@ -1,9 +1,23 @@
namespace :log do
- desc "Truncates all *.log files in log/ to zero bytes"
+ desc "Truncates all *.log files in log/ to zero bytes (specify which logs with LOGS=test,development)"
task :clear do
- FileList["log/*.log"].each do |log_file|
- f = File.open(log_file, "w")
- f.close
+ log_files.each do |file|
+ clear_log_file(file)
end
end
+
+ def log_files
+ if ENV['LOGS']
+ ENV['LOGS'].split(',')
+ .map { |file| "log/#{file.strip}.log" }
+ .select { |file| File.exists?(file) }
+ else
+ FileList["log/*.log"]
+ end
+ end
+
+ def clear_log_file(file)
+ f = File.open(file, "w")
+ f.close
+ end
end
diff --git a/railties/test/application/middleware/cache_test.rb b/railties/test/application/middleware/cache_test.rb
index c99666d7e4..b8e0c9be60 100644
--- a/railties/test/application/middleware/cache_test.rb
+++ b/railties/test/application/middleware/cache_test.rb
@@ -81,8 +81,8 @@ module ApplicationTests
add_to_config "config.action_dispatch.rack_cache = true"
get "/expires/expires_header"
- assert_equal "miss, store", last_response.headers["X-Rack-Cache"]
- assert_equal "max-age=10, public", last_response.headers["Cache-Control"]
+ assert_equal "miss, ignore, store", last_response.headers["X-Rack-Cache"]
+ assert_equal "max-age=10, public", last_response.headers["Cache-Control"]
body = last_response.body
@@ -115,8 +115,8 @@ module ApplicationTests
add_to_config "config.action_dispatch.rack_cache = true"
get "/expires/expires_etag"
- assert_equal "miss, store", last_response.headers["X-Rack-Cache"]
- assert_equal "public", last_response.headers["Cache-Control"]
+ assert_equal "miss, ignore, store", last_response.headers["X-Rack-Cache"]
+ assert_equal "public", last_response.headers["Cache-Control"]
body = last_response.body
etag = last_response.headers["ETag"]
@@ -149,8 +149,8 @@ module ApplicationTests
add_to_config "config.action_dispatch.rack_cache = true"
get "/expires/expires_last_modified"
- assert_equal "miss, store", last_response.headers["X-Rack-Cache"]
- assert_equal "public", last_response.headers["Cache-Control"]
+ assert_equal "miss, ignore, store", last_response.headers["X-Rack-Cache"]
+ assert_equal "public", last_response.headers["Cache-Control"]
body = last_response.body
last = last_response.headers["Last-Modified"]
diff --git a/railties/test/application/middleware/static_test.rb b/railties/test/application/middleware/static_test.rb
new file mode 100644
index 0000000000..0a793f8f60
--- /dev/null
+++ b/railties/test/application/middleware/static_test.rb
@@ -0,0 +1,31 @@
+# encoding: utf-8
+require 'isolation/abstract_unit'
+require 'rack/test'
+
+module ApplicationTests
+ class MiddlewareStaticTest < ActiveSupport::TestCase
+ include ActiveSupport::Testing::Isolation
+ include Rack::Test::Methods
+
+ def setup
+ build_app
+ FileUtils.rm_rf "#{app_path}/config/environments"
+ end
+
+ def teardown
+ teardown_app
+ end
+
+ # Regression test to #8907
+ # See https://github.com/rails/rails/commit/9cc82b77196d21a5c7021f6dca59ab9b2b158a45#commitcomment-2416514
+ test "doesn't set Cache-Control header when it is nil" do
+ app_file "public/foo.html", 'static'
+
+ require "#{app_path}/config/environment"
+
+ get 'foo'
+
+ assert_not last_response.headers.has_key?('Cache-Control'), "Cache-Control should not be set"
+ end
+ end
+end
diff --git a/railties/test/application/rake/notes_test.rb b/railties/test/application/rake/notes_test.rb
index 5893d58925..3508f4225a 100644
--- a/railties/test/application/rake/notes_test.rb
+++ b/railties/test/application/rake/notes_test.rb
@@ -130,6 +130,45 @@ module ApplicationTests
end
end
+ test 'custom rake task finds specific notes in specific directories' do
+ app_file "app/controllers/some_controller.rb", "# TODO: note in app directory"
+ app_file "lib/some_file.rb", "# OPTIMIZE: note in lib directory\n" << "# FIXME: note in lib directory"
+ app_file "test/some_test.rb", 1000.times.map { "" }.join("\n") << "# TODO: note in test directory"
+
+ app_file "lib/tasks/notes_custom.rake", <<-EOS
+ require 'rails/source_annotation_extractor'
+ task :notes_custom do
+ tags = 'TODO|FIXME'
+ opts = { dirs: %w(lib test), tag: true }
+ SourceAnnotationExtractor.enumerate(tags, opts)
+ end
+ EOS
+
+ boot_rails
+
+ require 'rake'
+ require 'rdoc/task'
+ require 'rake/testtask'
+
+ Rails.application.load_tasks
+
+ Dir.chdir(app_path) do
+ output = `bundle exec rake notes_custom`
+ lines = output.scan(/\[([0-9\s]+)\]/).flatten
+
+ assert_match(/\[FIXME\] note in lib directory/, output)
+ assert_match(/\[TODO\] note in test directory/, output)
+ assert_no_match(/OPTIMIZE/, output)
+ assert_no_match(/note in app directory/, output)
+
+ assert_equal 2, lines.size
+
+ lines.each do |line_number|
+ assert_equal 4, line_number.size
+ end
+ end
+ end
+
private
def boot_rails
super
diff --git a/railties/test/application/runner_test.rb b/railties/test/application/runner_test.rb
index f65b5e2f2d..6595c40f8b 100644
--- a/railties/test/application/runner_test.rb
+++ b/railties/test/application/runner_test.rb
@@ -37,27 +37,27 @@ module ApplicationTests
end
def test_should_run_file
- app_file "script/count_users.rb", <<-SCRIPT
+ app_file "bin/count_users.rb", <<-SCRIPT
puts User.count
SCRIPT
- assert_match "42", Dir.chdir(app_path) { `bundle exec rails runner "script/count_users.rb"` }
+ assert_match "42", Dir.chdir(app_path) { `bundle exec rails runner "bin/count_users.rb"` }
end
def test_should_set_dollar_0_to_file
- app_file "script/dollar0.rb", <<-SCRIPT
+ app_file "bin/dollar0.rb", <<-SCRIPT
puts $0
SCRIPT
- assert_match "script/dollar0.rb", Dir.chdir(app_path) { `bundle exec rails runner "script/dollar0.rb"` }
+ assert_match "bin/dollar0.rb", Dir.chdir(app_path) { `bundle exec rails runner "bin/dollar0.rb"` }
end
def test_should_set_dollar_program_name_to_file
- app_file "script/program_name.rb", <<-SCRIPT
+ app_file "bin/program_name.rb", <<-SCRIPT
puts $PROGRAM_NAME
SCRIPT
- assert_match "script/program_name.rb", Dir.chdir(app_path) { `bundle exec rails runner "script/program_name.rb"` }
+ assert_match "bin/program_name.rb", Dir.chdir(app_path) { `bundle exec rails runner "bin/program_name.rb"` }
end
def test_with_hook