diff options
194 files changed, 1934 insertions, 1278 deletions
diff --git a/.travis.yml b/.travis.yml index 605d1ff247..c648bd2ca7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,7 +21,7 @@ env: - "GEM=aj:integration" - "GEM=guides" rvm: - - 2.2.2 + - 2.2.3 - ruby-head matrix: allow_failures: diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 65c05c5748..078d5f1219 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,22 +1,12 @@ # Contributor Code of Conduct -As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. +The Rails team is committed to fostering a welcoming community. -We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality. +**Our Code of Conduct can be found here**: -Examples of unacceptable behavior by participants include: +http://rubyonrails.org/conduct/ -* The use of sexualized language or imagery -* Personal attacks -* Trolling or insulting/derogatory comments -* Public or private harassment -* Publishing other's private information, such as physical or electronic addresses, without explicit permission -* Other unethical or unprofessional conduct. +For a history of updates, see the page history here: -Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team. +https://github.com/rails/rails.github.com/commits/master/conduct/index.html -This code of conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. - -Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers. - -This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0, available at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/)
\ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock index daaa208a74..ae2f00df2e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -27,7 +27,7 @@ GIT GIT remote: git://github.com/rack/rack.git - revision: 2cb9430fce32da2f2dbccf9de97ab691351c1408 + revision: 2fe4a79667603a0c3d636daec06281459b10f7f8 specs: rack (2.0.0.alpha) json @@ -79,7 +79,7 @@ GIT GIT remote: git://github.com/rails/sprockets.git - revision: 653de5268ba862ea23457291824180c67ad5dc99 + revision: a61550db7184fc83964fb983547ff09da9efa9be branch: master specs: sprockets (4.0.0) @@ -118,7 +118,7 @@ PATH activesupport (= 5.0.0.alpha) arel (= 7.0.0.alpha) activesupport (5.0.0.alpha) - concurrent-ruby (~> 0.9.0) + concurrent-ruby (~> 0.9.1) i18n (~> 0.7) json (~> 1.7, >= 1.7.7) method_source @@ -203,7 +203,7 @@ GEM nokogiri (1.6.6.2) mini_portile (~> 0.6.0) pg (0.18.2) - psych (2.0.13) + psych (2.0.15) que (0.10.0) racc (1.4.12) rack-cache (1.2) @@ -236,7 +236,7 @@ GEM resque (~> 1.25) rufus-scheduler (~> 3.0) rufus-scheduler (3.1.3) - sass (3.4.16) + sass (3.4.17) sdoc (0.4.1) json (~> 1.7, >= 1.7.7) rdoc (~> 4.0) @@ -252,7 +252,7 @@ GEM sigdump (0.2.3) sinatra (1.0) rack (>= 1.0) - sneakers (1.1.0) + sneakers (1.1.1) bunny (>= 1.7.0, <= 2.0.0) serverengine (~> 1.5.5) thor @@ -76,7 +76,7 @@ and may also be used independently outside Rails. We encourage you to contribute to Ruby on Rails! Please check out the [Contributing to Ruby on Rails guide](http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html) for guidelines about how to proceed. [Join us!](http://contributors.rubyonrails.org) -Everyone interacting in Rails and its sub-project’s codebases, issue trackers, chat rooms, and mailing lists is expected to follow the Rails [code of conduct](CODE_OF_CONDUCT.md). +Everyone interacting in Rails and its sub-project’s codebases, issue trackers, chat rooms, and mailing lists is expected to follow the Rails [code of conduct](http://rubyonrails.org/conduct/). ## Code Status diff --git a/actionmailer/test/message_delivery_test.rb b/actionmailer/test/message_delivery_test.rb index 862ce26187..b834cdd08c 100644 --- a/actionmailer/test/message_delivery_test.rb +++ b/actionmailer/test/message_delivery_test.rb @@ -1,6 +1,5 @@ require 'abstract_unit' require 'active_job' -require 'minitest/mock' require 'mailers/delayed_mailer' class MessageDeliveryTest < ActiveSupport::TestCase diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index 8eea4ccd41..1cfd633606 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,3 +1,8 @@ +* Update `ActionController::TestSession#fetch` to behave more like + `ActionDispatch::Request::Session#fetch` when using non-string keys. + + *Jeremy Friesen* + * Using strings or symbols for middleware class names is deprecated. Convert things like this: diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb index 887196b3d2..6db0941b52 100644 --- a/actionpack/lib/abstract_controller/rendering.rb +++ b/actionpack/lib/abstract_controller/rendering.rb @@ -23,7 +23,11 @@ module AbstractController def render(*args, &block) options = _normalize_render(*args, &block) self.response_body = render_to_body(options) - _process_format(rendered_format, options) if rendered_format + if options[:html] + _set_content_type Mime::HTML.to_s + else + _set_content_type _get_content_type(rendered_format) + end self.response_body end @@ -99,7 +103,14 @@ module AbstractController # Process the rendered format. # :api: private - def _process_format(format, options = {}) + def _process_format(format) + end + + def _get_content_type(rendered_format) # :nodoc: + rendered_format.to_s + end + + def _set_content_type(type) # :nodoc: end # Normalize args and options. @@ -107,7 +118,7 @@ module AbstractController def _normalize_render(*args, &block) options = _normalize_args(*args, &block) #TODO: remove defined? when we restore AP <=> AV dependency - if defined?(request) && request && request.variant.present? + if defined?(request) && request.variant.present? options[:variant] = request.variant end _normalize_options(options) diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index 89fc4520d3..3d3af555c9 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -30,7 +30,6 @@ module ActionController autoload :Instrumentation autoload :MimeResponds autoload :ParamsWrapper - autoload :RackDelegation autoload :Redirecting autoload :Renderers autoload :Rendering diff --git a/actionpack/lib/action_controller/api.rb b/actionpack/lib/action_controller/api.rb index b4594bf302..1a46d49a49 100644 --- a/actionpack/lib/action_controller/api.rb +++ b/actionpack/lib/action_controller/api.rb @@ -115,7 +115,6 @@ module ActionController Rendering, Renderers::All, ConditionalGet, - RackDelegation, BasicImplicitRender, StrongParameters, diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 55734b9774..6c644862d5 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -213,7 +213,6 @@ module ActionController Renderers::All, ConditionalGet, EtagWithTemplateDigest, - RackDelegation, Caching, MimeResponds, ImplicitRender, @@ -252,7 +251,7 @@ module ActionController # Define some internal variables that should not be propagated to the view. PROTECTED_IVARS = AbstractController::Rendering::DEFAULT_PROTECTED_INSTANCE_VARIABLES + [ - :@_status, :@_headers, :@_params, :@_response, :@_request, + :@_params, :@_response, :@_request, :@_view_runtime, :@_stream, :@_url_options, :@_action_has_layout ] def _protected_ivars # :nodoc: diff --git a/actionpack/lib/action_controller/caching.rb b/actionpack/lib/action_controller/caching.rb index a4e4992cfe..0b8fa2ea09 100644 --- a/actionpack/lib/action_controller/caching.rb +++ b/actionpack/lib/action_controller/caching.rb @@ -1,6 +1,5 @@ require 'fileutils' require 'uri' -require 'set' module ActionController # \Caching is a cheap way of speeding up slow applications by keeping the result of @@ -46,7 +45,6 @@ module ActionController end end - include RackDelegation include AbstractController::Callbacks include ConfigMethods diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb index 914b0d4b30..030a1f3478 100644 --- a/actionpack/lib/action_controller/metal.rb +++ b/actionpack/lib/action_controller/metal.rb @@ -1,6 +1,8 @@ require 'active_support/core_ext/array/extract_options' require 'action_dispatch/middleware/stack' require 'active_support/deprecation' +require 'action_dispatch/http/request' +require 'action_dispatch/http/response' module ActionController # Extend ActionDispatch middleware stack to make it aware of options @@ -132,23 +134,30 @@ module ActionController @controller_name ||= name.demodulize.sub(/Controller$/, '').underscore end + def self.make_response!(request) + ActionDispatch::Response.new.tap do |res| + res.request = request + end + end + + def self.build_with_env(env = {}) #:nodoc: + new.tap { |c| + c.set_request! ActionDispatch::Request.new(env) + c.set_response! make_response!(c.request) + } + end + # Delegates to the class' <tt>controller_name</tt> def controller_name self.class.controller_name end - # The details below can be overridden to support a specific - # Request and Response object. The default ActionController::Base - # implementation includes RackDelegation, which makes a request - # and response object available. You might wish to control the - # environment and response manually for performance reasons. - - attr_internal :headers, :response, :request + attr_internal :response, :request delegate :session, :to => "@_request" + delegate :headers, :status=, :location=, :content_type=, + :status, :location, :content_type, :to => "@_response" def initialize - @_headers = {"Content-Type" => "text/html"} - @_status = 200 @_request = nil @_response = nil @_routes = nil @@ -163,63 +172,46 @@ module ActionController @_params = val end - # Basic implementations for content_type=, location=, and headers are - # provided to reduce the dependency on the RackDelegation module - # in Renderer and Redirector. - - def content_type=(type) - headers["Content-Type"] = type.to_s - end - - def content_type - headers["Content-Type"] - end - - def location - headers["Location"] - end - - def location=(url) - headers["Location"] = url - end + alias :response_code :status # :nodoc: # Basic url_for that can be overridden for more robust functionality def url_for(string) string end - def status - @_status - end - alias :response_code :status # :nodoc: - - def status=(status) - @_status = Rack::Utils.status_code(status) - end - def response_body=(body) body = [body] unless body.nil? || body.respond_to?(:each) + response.body = body super end # Tests if render or redirect has already happened. def performed? - response_body || (response && response.committed?) + response_body || response.committed? end - def dispatch(name, request) #:nodoc: + def dispatch(name, request, response) #:nodoc: set_request!(request) + set_response!(response) process(name) to_a end + def set_response!(response) # :nodoc: + @_response = response + end + def set_request!(request) #:nodoc: @_request = request @_request.controller_instance = self end def to_a #:nodoc: - response ? response.to_a : [status, headers, response_body] + response.to_a + end + + def reset_session + @_request.reset_session end class_attribute :middleware_stack @@ -247,15 +239,32 @@ module ActionController req = ActionDispatch::Request.new env action(req.path_parameters[:action]).call(env) end + class << self; deprecate :call; end # Returns a Rack endpoint for the given action name. def self.action(name) if middleware_stack.any? middleware_stack.build(name) do |env| - new.dispatch(name, ActionDispatch::Request.new(env)) + req = ActionDispatch::Request.new(env) + res = make_response! req + new.dispatch(name, req, res) end else - lambda { |env| new.dispatch(name, ActionDispatch::Request.new(env)) } + lambda { |env| + req = ActionDispatch::Request.new(env) + res = make_response! req + new.dispatch(name, req, res) + } + end + end + + # Direct dispatch to the controller. Instantiates the controller, then + # executes the action named +name+. + def self.dispatch(name, req, res) + if middleware_stack.any? + middleware_stack.build(name) { |env| new.dispatch(name, req, res) }.call req.env + else + new.dispatch(name, req, res) end end end diff --git a/actionpack/lib/action_controller/metal/conditional_get.rb b/actionpack/lib/action_controller/metal/conditional_get.rb index bb3ad9b850..89d589c486 100644 --- a/actionpack/lib/action_controller/metal/conditional_get.rb +++ b/actionpack/lib/action_controller/metal/conditional_get.rb @@ -4,7 +4,6 @@ module ActionController module ConditionalGet extend ActiveSupport::Concern - include RackDelegation include Head included do diff --git a/actionpack/lib/action_controller/metal/cookies.rb b/actionpack/lib/action_controller/metal/cookies.rb index d787f014cd..f8efb2b076 100644 --- a/actionpack/lib/action_controller/metal/cookies.rb +++ b/actionpack/lib/action_controller/metal/cookies.rb @@ -2,8 +2,6 @@ module ActionController #:nodoc: module Cookies extend ActiveSupport::Concern - include RackDelegation - included do helper_method :cookies end diff --git a/actionpack/lib/action_controller/metal/etag_with_template_digest.rb b/actionpack/lib/action_controller/metal/etag_with_template_digest.rb index f9303efe6c..669cf55bca 100644 --- a/actionpack/lib/action_controller/metal/etag_with_template_digest.rb +++ b/actionpack/lib/action_controller/metal/etag_with_template_digest.rb @@ -25,7 +25,7 @@ module ActionController class_attribute :etag_with_template_digest self.etag_with_template_digest = true - ActiveSupport.on_load :action_view, yield: true do |action_view_base| + ActiveSupport.on_load :action_view, yield: true do etag do |options| determine_template_etag(options) if etag_with_template_digest end diff --git a/actionpack/lib/action_controller/metal/head.rb b/actionpack/lib/action_controller/metal/head.rb index f445094bdc..b2110bf946 100644 --- a/actionpack/lib/action_controller/metal/head.rb +++ b/actionpack/lib/action_controller/metal/head.rb @@ -28,7 +28,7 @@ module ActionController end status ||= :ok - + location = options.delete(:location) content_type = options.delete(:content_type) @@ -43,12 +43,9 @@ module ActionController if include_content?(self.response_code) self.content_type = content_type || (Mime[formats.first] if formats) - self.response.charset = false if self.response - else - headers.delete('Content-Type') - headers.delete('Content-Length') + self.response.charset = false end - + true end diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb index bbb38cf8fc..15d4562abb 100644 --- a/actionpack/lib/action_controller/metal/http_authentication.rb +++ b/actionpack/lib/action_controller/metal/http_authentication.rb @@ -203,7 +203,7 @@ module ActionController password = password_procedure.call(credentials[:username]) return false unless password - method = request.env['rack.methodoverride.original_method'] || request.env['REQUEST_METHOD'] + method = request.get_header('rack.methodoverride.original_method') || request.get_header('REQUEST_METHOD') uri = credentials[:uri] [true, false].any? do |trailing_question_mark| @@ -260,8 +260,8 @@ module ActionController end def secret_token(request) - key_generator = request.env["action_dispatch.key_generator"] - http_auth_salt = request.env["action_dispatch.http_auth_salt"] + key_generator = request.key_generator + http_auth_salt = request.http_auth_salt key_generator.generate_key(http_auth_salt) end diff --git a/actionpack/lib/action_controller/metal/instrumentation.rb b/actionpack/lib/action_controller/metal/instrumentation.rb index a3e1a71b0a..3dbf34eb2a 100644 --- a/actionpack/lib/action_controller/metal/instrumentation.rb +++ b/actionpack/lib/action_controller/metal/instrumentation.rb @@ -11,7 +11,6 @@ module ActionController extend ActiveSupport::Concern include AbstractController::Logger - include ActionController::RackDelegation attr_internal :view_runtime diff --git a/actionpack/lib/action_controller/metal/live.rb b/actionpack/lib/action_controller/metal/live.rb index 58150cd9a9..69583f8ab4 100644 --- a/actionpack/lib/action_controller/metal/live.rb +++ b/actionpack/lib/action_controller/metal/live.rb @@ -33,6 +33,20 @@ module ActionController # the main thread. Make sure your actions are thread safe, and this shouldn't # be a problem (don't share state across threads, etc). module Live + extend ActiveSupport::Concern + + module ClassMethods + def make_response!(request) + if request.env["HTTP_VERSION"] == "HTTP/1.0" + super + else + Live::Response.new.tap do |res| + res.request = request + end + end + end + end + # This class provides the ability to write an SSE (Server Sent Event) # to an IO stream. The class is initialized with a stream and can be used # to either write a JSON string or an object which can be converted to JSON. @@ -311,12 +325,7 @@ module ActionController end def set_response!(request) - if request.env["HTTP_VERSION"] == "HTTP/1.0" - super - else - @_response = Live::Response.new - @_response.request = request - end + @_response = self.class.make_response! request end end end diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb index 1db68db20f..e62da0fa70 100644 --- a/actionpack/lib/action_controller/metal/mime_responds.rb +++ b/actionpack/lib/action_controller/metal/mime_responds.rb @@ -191,6 +191,7 @@ module ActionController #:nodoc: if format = collector.negotiate_format(request) _process_format(format) + _set_content_type _get_content_type format response = collector.response response ? response.call : render({}) else diff --git a/actionpack/lib/action_controller/metal/rack_delegation.rb b/actionpack/lib/action_controller/metal/rack_delegation.rb deleted file mode 100644 index ae9d89cc8c..0000000000 --- a/actionpack/lib/action_controller/metal/rack_delegation.rb +++ /dev/null @@ -1,38 +0,0 @@ -require 'action_dispatch/http/request' -require 'action_dispatch/http/response' - -module ActionController - module RackDelegation - extend ActiveSupport::Concern - - delegate :headers, :status=, :location=, :content_type=, - :status, :location, :content_type, :response_code, :to => "@_response" - - module ClassMethods - def build_with_env(env = {}) #:nodoc: - new.tap { |c| c.set_request! ActionDispatch::Request.new(env) } - end - end - - def set_request!(request) #:nodoc: - super - set_response!(request) - end - - def response_body=(body) - response.body = body if response - super - end - - def reset_session - @_request.reset_session - end - - private - - def set_response!(request) - @_response = ActionDispatch::Response.new - @_response.request = request - end - end -end diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb index acaa8227c9..0febc905f1 100644 --- a/actionpack/lib/action_controller/metal/redirecting.rb +++ b/actionpack/lib/action_controller/metal/redirecting.rb @@ -11,7 +11,6 @@ module ActionController extend ActiveSupport::Concern include AbstractController::Logger - include ActionController::RackDelegation include ActionController::UrlFor # Redirects the browser to the target specified in +options+. This parameter can be any one of: diff --git a/actionpack/lib/action_controller/metal/rendering.rb b/actionpack/lib/action_controller/metal/rendering.rb index a3b0645dc0..c8934b367f 100644 --- a/actionpack/lib/action_controller/metal/rendering.rb +++ b/actionpack/lib/action_controller/metal/rendering.rb @@ -56,14 +56,12 @@ module ActionController nil end - def _process_format(format, options = {}) - super + def _get_content_type(rendered_format) + self.content_type || super + end - if options[:plain] - self.content_type = Mime::TEXT - else - self.content_type ||= format.to_s - end + def _set_content_type(format) + self.content_type = format end # Normalize arguments by catching blocks and setting them on :update. diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb index d21a778d8d..e5f3cb8e8d 100644 --- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb +++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb @@ -136,7 +136,7 @@ module ActionController #:nodoc: # This is the method that defines the application behavior when a request is found to be unverified. def handle_unverified_request request = @controller.request - request.session = NullSessionHash.new(request.env) + request.session = NullSessionHash.new(request) request.env['action_dispatch.request.flash_hash'] = nil request.env['rack.session.options'] = { skip: true } request.cookie_jar = NullCookieJar.build(request, {}) @@ -145,8 +145,8 @@ module ActionController #:nodoc: protected class NullSessionHash < Rack::Session::Abstract::SessionHash #:nodoc: - def initialize(env) - super(nil, env) + def initialize(req) + super(nil, req) @data = {} @loaded = true end diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb index fc8e345d43..bf5c7003ff 100644 --- a/actionpack/lib/action_controller/metal/strong_parameters.rb +++ b/actionpack/lib/action_controller/metal/strong_parameters.rb @@ -240,19 +240,58 @@ module ActionController self end - # Ensures that a parameter is present. If it's present, returns - # the parameter at the given +key+, otherwise raises an - # <tt>ActionController::ParameterMissing</tt> error. + # This method accepts both a single key and an array of keys. + # + # When passed a single key, if it exists and its associated value is + # either present or the singleton +false+, returns said value: # # ActionController::Parameters.new(person: { name: 'Francesco' }).require(:person) # # => {"name"=>"Francesco"} # + # Otherwise raises <tt>ActionController::ParameterMissing</tt>: + # + # ActionController::Parameters.new.require(:person) + # # ActionController::ParameterMissing: param is missing or the value is empty: person + # # ActionController::Parameters.new(person: nil).require(:person) - # # => ActionController::ParameterMissing: param is missing or the value is empty: person + # # ActionController::ParameterMissing: param is missing or the value is empty: person + # + # ActionController::Parameters.new(person: "\t").require(:person) + # # ActionController::ParameterMissing: param is missing or the value is empty: person # # ActionController::Parameters.new(person: {}).require(:person) - # # => ActionController::ParameterMissing: param is missing or the value is empty: person + # # ActionController::ParameterMissing: param is missing or the value is empty: person + # + # When given an array of keys, the method tries to require each one of them + # in order. If it succeeds, an array with the respective return values is + # returned: + # + # params = ActionController::Parameters.new(user: { ... }, profile: { ... }) + # user_params, profile_params = params.require(:user, :profile) + # + # Otherwise, the method reraises the first exception found: + # + # params = ActionController::Parameters.new(user: {}, profile: {}) + # user_params, profile_params = params.require(:user, :profile) + # # ActionController::ParameterMissing: param is missing or the value is empty: user + # + # Technically this method can be used to fetch terminal values: + # + # # CAREFUL + # params = ActionController::Parameters.new(person: { name: 'Finn' }) + # name = params.require(:person).require(:name) # CAREFUL + # + # but take into account that at some point those ones have to be permitted: + # + # def person_params + # params.require(:person).permit(:name).tap do |person_params| + # person_params.require(:name) # SAFER + # end + # end + # + # for example. def require(key) + return key.map { |k| require(k) } if key.is_a?(Array) value = self[key] if value.present? || value == false value diff --git a/actionpack/lib/action_controller/metal/testing.rb b/actionpack/lib/action_controller/metal/testing.rb index d01927b7cb..47d940f692 100644 --- a/actionpack/lib/action_controller/metal/testing.rb +++ b/actionpack/lib/action_controller/metal/testing.rb @@ -2,8 +2,6 @@ module ActionController module Testing extend ActiveSupport::Concern - include RackDelegation - # TODO : Rewrite tests using controller.headers= to use Rack env def headers=(new_headers) @_response ||= ActionDispatch::Response.new diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index e012fa617e..39cbc0cd70 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -36,11 +36,11 @@ module ActionController end def query_string=(string) - @env[Rack::QUERY_STRING] = string + set_header Rack::QUERY_STRING, string end - def request_parameters=(params) - @env["action_dispatch.request.request_parameters"] = params + def content_type=(type) + set_header 'CONTENT_TYPE', type end def assign_parameters(routes, controller_path, action, parameters, generated_path, query_string_keys) @@ -67,10 +67,12 @@ module ActionController end else if ENCODER.should_multipart?(non_path_parameters) - @env['CONTENT_TYPE'] = ENCODER.content_type + self.content_type = ENCODER.content_type data = ENCODER.build_multipart non_path_parameters else - @env['CONTENT_TYPE'] ||= 'application/x-www-form-urlencoded' + get_header('CONTENT_TYPE') do |k| + set_header k, 'application/x-www-form-urlencoded' + end # FIXME: setting `request_parametes` is normally handled by the # params parser middleware, and we should remove this roundtripping @@ -92,11 +94,13 @@ module ActionController end end - @env['CONTENT_LENGTH'] = data.length.to_s - @env['rack.input'] = StringIO.new(data) + set_header 'CONTENT_LENGTH', data.length.to_s + set_header 'rack.input', StringIO.new(data) end - @env["PATH_INFO"] ||= generated_path + get_header("PATH_INFO") do |k| + set_header k, generated_path + end path_parameters[:controller] = controller_path path_parameters[:action] = action @@ -170,8 +174,8 @@ module ActionController clear end - def fetch(*args, &block) - @data.fetch(*args, &block) + def fetch(key, *args, &block) + @data.fetch(key.to_s, *args, &block) end private @@ -450,7 +454,7 @@ module ActionController end if body.present? - @request.env['RAW_POST_DATA'] = body + @request.set_header 'RAW_POST_DATA', body end if http_method.present? @@ -472,15 +476,15 @@ module ActionController end self.cookies.update @request.cookies - @request.env['HTTP_COOKIE'] = cookies.to_header - @request.env['action_dispatch.cookies'] = nil + @request.set_header 'HTTP_COOKIE', cookies.to_header + @request.delete_header 'action_dispatch.cookies' @request = TestRequest.new scrub_env!(@request.env), @request.session @response = build_response @response_klass @response.request = @request @controller.recycle! - @request.env['REQUEST_METHOD'] = http_method + @request.set_header 'REQUEST_METHOD', http_method parameters = parameters.symbolize_keys @@ -494,24 +498,28 @@ module ActionController @request.flash.update(flash || {}) if xhr - @request.env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest' - @request.env['HTTP_ACCEPT'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ') + @request.set_header 'HTTP_X_REQUESTED_WITH', 'XMLHttpRequest' + @request.get_header('HTTP_ACCEPT') do |k| + @request.set_header k, [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ') + end end @controller.request = @request @controller.response = @response - @request.env["SCRIPT_NAME"] ||= @controller.config.relative_url_root + @request.get_header("SCRIPT_NAME") do |k| + @request.set_header k, @controller.config.relative_url_root + end @controller.recycle! @controller.process(action) - @request.env.delete 'HTTP_COOKIE' + @request.delete_header 'HTTP_COOKIE' - if cookies = @request.env['action_dispatch.cookies'] + if @request.have_cookie_jar? unless @response.committed? - cookies.write(@response) - self.cookies.update(cookies.instance_variable_get(:@cookies)) + @request.cookie_jar.write(@response) + self.cookies.update(@request.cookie_jar.instance_variable_get(:@cookies)) end end @response.prepare! @@ -523,8 +531,8 @@ module ActionController end if xhr - @request.env.delete 'HTTP_X_REQUESTED_WITH' - @request.env.delete 'HTTP_ACCEPT' + @request.delete_header 'HTTP_X_REQUESTED_WITH' + @request.delete_header 'HTTP_ACCEPT' end @request.query_string = '' diff --git a/actionpack/lib/action_dispatch/http/cache.rb b/actionpack/lib/action_dispatch/http/cache.rb index cc1cb3f0f0..08ebd2e8b2 100644 --- a/actionpack/lib/action_dispatch/http/cache.rb +++ b/actionpack/lib/action_dispatch/http/cache.rb @@ -8,13 +8,13 @@ module ActionDispatch HTTP_IF_NONE_MATCH = 'HTTP_IF_NONE_MATCH'.freeze def if_modified_since - if since = env[HTTP_IF_MODIFIED_SINCE] + if since = get_header(HTTP_IF_MODIFIED_SINCE) Time.rfc2822(since) rescue nil end end def if_none_match - env[HTTP_IF_NONE_MATCH] + get_header HTTP_IF_NONE_MATCH end def if_none_match_etags @@ -51,41 +51,45 @@ module ActionDispatch end module Response - attr_reader :cache_control, :etag - alias :etag? :etag + attr_reader :cache_control def last_modified - if last = headers[LAST_MODIFIED] + if last = get_header(LAST_MODIFIED) Time.httpdate(last) end end def last_modified? - headers.include?(LAST_MODIFIED) + have_header? LAST_MODIFIED end def last_modified=(utc_time) - headers[LAST_MODIFIED] = utc_time.httpdate + set_header LAST_MODIFIED, utc_time.httpdate end def date - if date_header = headers[DATE] + if date_header = get_header(DATE) Time.httpdate(date_header) end end def date? - headers.include?(DATE) + have_header? DATE end def date=(utc_time) - headers[DATE] = utc_time.httpdate + set_header DATE, utc_time.httpdate end def etag=(etag) key = ActiveSupport::Cache.expand_cache_key(etag) - @etag = self[ETAG] = %("#{Digest::MD5.hexdigest(key)}") + set_header ETAG, %("#{Digest::MD5.hexdigest(key)}") + end + + def etag + get_header ETAG end + alias :etag? :etag private @@ -96,7 +100,7 @@ module ActionDispatch SPECIAL_KEYS = Set.new(%w[extras no-cache max-age public must-revalidate]) def cache_control_segments - if cache_control = self[CACHE_CONTROL] + if cache_control = get_header(CACHE_CONTROL) cache_control.delete(' ').split(',') else [] @@ -123,12 +127,11 @@ module ActionDispatch def prepare_cache_control! @cache_control = cache_control_headers - @etag = self[ETAG] end def handle_conditional_get! if etag? || last_modified? || !@cache_control.empty? - set_conditional_cache_control! + set_conditional_cache_control!(@cache_control) end end @@ -138,24 +141,24 @@ module ActionDispatch PRIVATE = "private".freeze MUST_REVALIDATE = "must-revalidate".freeze - def set_conditional_cache_control! + def set_conditional_cache_control!(cache_control) control = {} cc_headers = cache_control_headers if extras = cc_headers.delete(:extras) - @cache_control[:extras] ||= [] - @cache_control[:extras] += extras - @cache_control[:extras].uniq! + cache_control[:extras] ||= [] + cache_control[:extras] += extras + cache_control[:extras].uniq! end control.merge! cc_headers - control.merge! @cache_control + control.merge! cache_control if control.empty? - self[CACHE_CONTROL] = DEFAULT_CACHE_CONTROL + set_header CACHE_CONTROL, DEFAULT_CACHE_CONTROL elsif control[:no_cache] - self[CACHE_CONTROL] = NO_CACHE + set_header CACHE_CONTROL, NO_CACHE if control[:extras] - self[CACHE_CONTROL] += ", #{control[:extras].join(', ')}" + set_header(CACHE_CONTROL, get_header(CACHE_CONTROL) + ", #{control[:extras].join(', ')}") end else extras = control[:extras] @@ -167,7 +170,7 @@ module ActionDispatch options << MUST_REVALIDATE if control[:must_revalidate] options.concat(extras) if extras - self[CACHE_CONTROL] = options.join(", ") + set_header CACHE_CONTROL, options.join(", ") end end end diff --git a/actionpack/lib/action_dispatch/http/filter_parameters.rb b/actionpack/lib/action_dispatch/http/filter_parameters.rb index 3170389b36..e70e90018c 100644 --- a/actionpack/lib/action_dispatch/http/filter_parameters.rb +++ b/actionpack/lib/action_dispatch/http/filter_parameters.rb @@ -50,13 +50,13 @@ module ActionDispatch protected def parameter_filter - parameter_filter_for @env.fetch("action_dispatch.parameter_filter") { + parameter_filter_for get_header("action_dispatch.parameter_filter") { return NULL_PARAM_FILTER } end def env_filter - user_key = @env.fetch("action_dispatch.parameter_filter") { + user_key = get_header("action_dispatch.parameter_filter") { return NULL_ENV_FILTER } parameter_filter_for(Array(user_key) + ENV_MATCH) diff --git a/actionpack/lib/action_dispatch/http/filter_redirect.rb b/actionpack/lib/action_dispatch/http/filter_redirect.rb index 94c1f2b41f..f4b806b8b5 100644 --- a/actionpack/lib/action_dispatch/http/filter_redirect.rb +++ b/actionpack/lib/action_dispatch/http/filter_redirect.rb @@ -16,7 +16,7 @@ module ActionDispatch def location_filters if request - request.env['action_dispatch.redirect_filter'] || [] + request.get_header('action_dispatch.redirect_filter') || [] else [] end diff --git a/actionpack/lib/action_dispatch/http/headers.rb b/actionpack/lib/action_dispatch/http/headers.rb index bc5410dc38..fbdec6c132 100644 --- a/actionpack/lib/action_dispatch/http/headers.rb +++ b/actionpack/lib/action_dispatch/http/headers.rb @@ -30,27 +30,32 @@ module ActionDispatch HTTP_HEADER = /\A[A-Za-z0-9-]+\z/ include Enumerable - attr_reader :env - def initialize(env = {}) # :nodoc: - @env = env + def self.from_hash(hash) + new ActionDispatch::Request.new hash + end + + def initialize(request) # :nodoc: + @req = request end # Returns the value for the given key mapped to @env. def [](key) - @env[env_name(key)] + @req.get_header env_name(key) end # Sets the given value for the key mapped to @env. def []=(key, value) - @env[env_name(key)] = value + @req.set_header env_name(key), value end def key?(key) - @env.key? env_name(key) + @req.has_header? env_name(key) end alias :include? :key? + DEFAULT = Object.new # :nodoc: + # Returns the value for the given key mapped to @env. # # If the key is not found and an optional code block is not provided, @@ -58,18 +63,22 @@ module ActionDispatch # # If the code block is provided, then it will be run and # its result returned. - def fetch(key, *args, &block) - @env.fetch env_name(key), *args, &block + def fetch(key, default = DEFAULT) + @req.get_header(env_name(key)) do + return default unless default == DEFAULT + return yield if block_given? + raise NameError, key + end end def each(&block) - @env.each(&block) + @req.each_header(&block) end # Returns a new Http::Headers instance containing the contents of # <tt>headers_or_env</tt> and the original instance. def merge(headers_or_env) - headers = Http::Headers.new(env.dup) + headers = @req.dup.headers headers.merge!(headers_or_env) headers end @@ -79,11 +88,14 @@ module ActionDispatch # <tt>headers_or_env</tt>. def merge!(headers_or_env) headers_or_env.each do |key, value| - self[env_name(key)] = value + @req.set_header env_name(key), value end end + def env; @req.env.dup; end + private + # Converts a HTTP header name to an environment variable name if it is # not contained within the headers hash. def env_name(key) diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb index ff336b7354..e01d5ecc8f 100644 --- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb +++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb @@ -15,12 +15,13 @@ module ActionDispatch # For backward compatibility, the post \format is extracted from the # X-Post-Data-Format HTTP header if present. def content_mime_type - @env["action_dispatch.request.content_type"] ||= begin - if @env['CONTENT_TYPE'] =~ /^([^,\;]*)/ + get_header("action_dispatch.request.content_type") do |k| + v = if get_header('CONTENT_TYPE') =~ /^([^,\;]*)/ Mime::Type.lookup($1.strip.downcase) else nil end + set_header k, v end end @@ -30,14 +31,15 @@ module ActionDispatch # Returns the accepted MIME type for the request. def accepts - @env["action_dispatch.request.accepts"] ||= begin - header = @env['HTTP_ACCEPT'].to_s.strip + get_header("action_dispatch.request.accepts") do |k| + header = get_header('HTTP_ACCEPT').to_s.strip - if header.empty? + v = if header.empty? [content_mime_type] else Mime::Type.parse(header) end + set_header k, v end end @@ -52,14 +54,14 @@ module ActionDispatch end def formats - @env["action_dispatch.request.formats"] ||= begin + get_header("action_dispatch.request.formats") do |k| params_readable = begin parameters[:format] rescue ActionController::BadRequest false end - if params_readable + v = if params_readable Array(Mime[parameters[:format]]) elsif use_accept_header && valid_accept_header accepts @@ -68,6 +70,7 @@ module ActionDispatch else [Mime::HTML] end + set_header k, v end end @@ -102,7 +105,7 @@ module ActionDispatch # end def format=(extension) parameters[:format] = extension.to_s - @env["action_dispatch.request.formats"] = [Mime::Type.lookup_by_extension(parameters[:format])] + set_header "action_dispatch.request.formats", [Mime::Type.lookup_by_extension(parameters[:format])] end # Sets the \formats by string extensions. This differs from #format= by allowing you @@ -121,9 +124,9 @@ module ActionDispatch # end def formats=(extensions) parameters[:format] = extensions.first.to_s - @env["action_dispatch.request.formats"] = extensions.collect do |extension| + set_header "action_dispatch.request.formats", extensions.collect { |extension| Mime::Type.lookup_by_extension(extension) - end + } end # Receives an array of mimes and return the first user sent mime that diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb index 4defb7f858..3c9f8cd9e4 100644 --- a/actionpack/lib/action_dispatch/http/parameters.rb +++ b/actionpack/lib/action_dispatch/http/parameters.rb @@ -1,6 +1,3 @@ -require 'active_support/core_ext/hash/keys' -require 'active_support/core_ext/hash/indifferent_access' - module ActionDispatch module Http module Parameters @@ -8,20 +5,23 @@ module ActionDispatch # Returns both GET and POST \parameters in a single hash. def parameters - @env["action_dispatch.request.parameters"] ||= begin - params = begin - request_parameters.merge(query_parameters) - rescue EOFError - query_parameters.dup - end - params.merge!(path_parameters) - end + params = get_header("action_dispatch.request.parameters") + return params if params + + params = begin + request_parameters.merge(query_parameters) + rescue EOFError + query_parameters.dup + end + params.merge!(path_parameters) + set_header("action_dispatch.request.parameters", params) + params end alias :params :parameters def path_parameters=(parameters) #:nodoc: - @env.delete('action_dispatch.request.parameters') - @env[PARAMETERS_KEY] = parameters + delete_header('action_dispatch.request.parameters') + set_header PARAMETERS_KEY, parameters end # Returns a hash with the \parameters used to form the \path of the request. @@ -29,15 +29,7 @@ module ActionDispatch # # {'action' => 'my_action', 'controller' => 'my_controller'} def path_parameters - @env[PARAMETERS_KEY] ||= {} - end - - private - - # Convert nested Hash to HashWithIndifferentAccess. - # - def normalize_encode_params(params) - ActionDispatch::Request::Utils.normalize_encode_params params + get_header(PARAMETERS_KEY) || {} end end end diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index de28cd0998..45600d0a61 100644 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -20,8 +20,6 @@ module ActionDispatch include ActionDispatch::Http::FilterParameters include ActionDispatch::Http::URL - HTTP_X_REQUEST_ID = "HTTP_X_REQUEST_ID".freeze # :nodoc: - autoload :Session, 'action_dispatch/request/session' autoload :Utils, 'action_dispatch/request/utils' @@ -31,17 +29,20 @@ module ActionDispatch PATH_TRANSLATED REMOTE_HOST REMOTE_IDENT REMOTE_USER REMOTE_ADDR SERVER_NAME SERVER_PROTOCOL + ORIGINAL_SCRIPT_NAME HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING HTTP_ACCEPT_LANGUAGE HTTP_CACHE_CONTROL HTTP_FROM HTTP_NEGOTIATE HTTP_PRAGMA HTTP_CLIENT_IP HTTP_X_FORWARDED_FOR HTTP_VERSION + HTTP_X_REQUEST_ID HTTP_X_FORWARDED_HOST + SERVER_ADDR ].freeze ENV_METHODS.each do |env| class_eval <<-METHOD, __FILE__, __LINE__ + 1 def #{env.sub(/^HTTP_/n, '').downcase} # def accept_charset - @env["#{env}".freeze] # @env["HTTP_ACCEPT_CHARSET".freeze] + get_header "#{env}".freeze # get_header "HTTP_ACCEPT_CHARSET".freeze end # end METHOD end @@ -67,8 +68,27 @@ module ActionDispatch end end + PASS_NOT_FOUND = Class.new { # :nodoc: + def self.action(_); self; end + def self.call(_); [404, {'X-Cascade' => 'pass'}, []]; end + } + + def controller_class + check_path_parameters! + params = path_parameters + + if params.key?(:controller) + controller_param = params[:controller].underscore + params[:action] ||= 'index' + const_name = "#{controller_param.camelize}Controller" + ActiveSupport::Dependencies.constantize(const_name) + else + PASS_NOT_FOUND + end + end + def key?(key) - @env.key?(key) + has_header? key end # List of HTTP request methods from the following RFCs: @@ -109,44 +129,44 @@ module ActionDispatch end def routes # :nodoc: - env["action_dispatch.routes".freeze] + get_header("action_dispatch.routes".freeze) end def routes=(routes) # :nodoc: - env["action_dispatch.routes".freeze] = routes - end - - def original_script_name # :nodoc: - env['ORIGINAL_SCRIPT_NAME'.freeze] + set_header("action_dispatch.routes".freeze, routes) end def engine_script_name(_routes) # :nodoc: - env[_routes.env_key] + get_header(_routes.env_key) end def engine_script_name=(name) # :nodoc: - env[routes.env_key] = name.dup + set_header(routes.env_key, name.dup) end def request_method=(request_method) #:nodoc: if check_method(request_method) - @request_method = env["REQUEST_METHOD"] = request_method + @request_method = set_header("REQUEST_METHOD", request_method) end end def controller_instance # :nodoc: - env['action_controller.instance'.freeze] + get_header('action_controller.instance'.freeze) end def controller_instance=(controller) # :nodoc: - env['action_controller.instance'.freeze] = controller + set_header('action_controller.instance'.freeze, controller) + end + + def http_auth_salt + get_header "action_dispatch.http_auth_salt" end def show_exceptions? # :nodoc: # We're treating `nil` as "unset", and we want the default setting to be # `true`. This logic should be extracted to `env_config` and calculated # once. - !(env['action_dispatch.show_exceptions'.freeze] == false) + !(get_header('action_dispatch.show_exceptions'.freeze) == false) end # Returns a symbol form of the #request_method @@ -158,7 +178,7 @@ module ActionDispatch # even if it was overridden by middleware. See #request_method for # more information. def method - @method ||= check_method(env["rack.methodoverride.original_method"] || env['REQUEST_METHOD']) + @method ||= check_method(get_header("rack.methodoverride.original_method") || get_header('REQUEST_METHOD')) end # Returns a symbol form of the #method @@ -170,7 +190,7 @@ module ActionDispatch # # request.headers["Content-Type"] # => "text/plain" def headers - @headers ||= Http::Headers.new(@env) + @headers ||= Http::Headers.new(self) end # Returns a +String+ with the last requested path including their params. @@ -181,7 +201,7 @@ module ActionDispatch # # get '/foo?bar' # request.original_fullpath # => '/foo?bar' def original_fullpath - @original_fullpath ||= (env["ORIGINAL_FULLPATH"] || fullpath) + @original_fullpath ||= (get_header("ORIGINAL_FULLPATH") || fullpath) end # Returns the +String+ full path including params of the last URL requested. @@ -220,7 +240,7 @@ module ActionDispatch # (case-insensitive), which may need to be manually added depending on the # choice of JavaScript libraries and frameworks. def xml_http_request? - @env['HTTP_X_REQUESTED_WITH'] =~ /XMLHttpRequest/i + get_header('HTTP_X_REQUESTED_WITH') =~ /XMLHttpRequest/i end alias :xhr? :xml_http_request? @@ -232,11 +252,11 @@ module ActionDispatch # Returns the IP address of client as a +String+, # usually set by the RemoteIp middleware. def remote_ip - @remote_ip ||= (@env["action_dispatch.remote_ip"] || ip).to_s + @remote_ip ||= (get_header("action_dispatch.remote_ip") || ip).to_s end def remote_ip=(remote_ip) - @env["action_dispatch.remote_ip".freeze] = remote_ip + set_header "action_dispatch.remote_ip".freeze, remote_ip end ACTION_DISPATCH_REQUEST_ID = "action_dispatch.request_id".freeze # :nodoc: @@ -248,43 +268,39 @@ module ActionDispatch # This unique ID is useful for tracing a request from end-to-end as part of logging or debugging. # This relies on the rack variable set by the ActionDispatch::RequestId middleware. def request_id - env[ACTION_DISPATCH_REQUEST_ID] + get_header ACTION_DISPATCH_REQUEST_ID end def request_id=(id) # :nodoc: - env[ACTION_DISPATCH_REQUEST_ID] = id + set_header ACTION_DISPATCH_REQUEST_ID, id end alias_method :uuid, :request_id - def x_request_id # :nodoc: - @env[HTTP_X_REQUEST_ID] - end - # Returns the lowercase name of the HTTP server software. def server_software - (@env['SERVER_SOFTWARE'] && /^([a-zA-Z]+)/ =~ @env['SERVER_SOFTWARE']) ? $1.downcase : nil + (get_header('SERVER_SOFTWARE') && /^([a-zA-Z]+)/ =~ get_header('SERVER_SOFTWARE')) ? $1.downcase : nil end # Read the request \body. This is useful for web services that need to # work with raw requests directly. def raw_post - unless @env.include? 'RAW_POST_DATA' + unless has_header? 'RAW_POST_DATA' raw_post_body = body - @env['RAW_POST_DATA'] = raw_post_body.read(content_length) + set_header('RAW_POST_DATA', raw_post_body.read(content_length)) raw_post_body.rewind if raw_post_body.respond_to?(:rewind) end - @env['RAW_POST_DATA'] + get_header 'RAW_POST_DATA' end # The request body is an IO input stream. If the RAW_POST_DATA environment # variable is already set, wrap it in a StringIO. def body - if raw_post = @env['RAW_POST_DATA'] + if raw_post = get_header('RAW_POST_DATA') raw_post.force_encoding(Encoding::BINARY) StringIO.new(raw_post) else - @env['rack.input'] + body_stream end end @@ -295,7 +311,7 @@ module ActionDispatch end def body_stream #:nodoc: - @env['rack.input'] + get_header('rack.input') end # TODO This should be broken apart into AD::Request::Session and probably @@ -306,20 +322,22 @@ module ActionDispatch else self.session = {} end - @env['action_dispatch.request.flash_hash'] = nil + set_header('action_dispatch.request.flash_hash', nil) end def session=(session) #:nodoc: - Session.set @env, session + Session.set self, session end def session_options=(options) - Session::Options.set @env, options + Session::Options.set self, options end # Override Rack's GET method to support indifferent access def GET - @env["action_dispatch.request.query_parameters"] ||= normalize_encode_params(super || {}) + get_header("action_dispatch.request.query_parameters") do |k| + set_header k, Request::Utils.normalize_encode_params(super || {}) + end rescue Rack::Utils::ParameterTypeError, Rack::Utils::InvalidParameterError => e raise ActionController::BadRequest.new(:query, e) end @@ -327,7 +345,9 @@ module ActionDispatch # Override Rack's POST method to support indifferent access def POST - @env["action_dispatch.request.request_parameters"] ||= normalize_encode_params(super || {}) + get_header("action_dispatch.request.request_parameters") do + self.request_parameters = Request::Utils.normalize_encode_params(super || {}) + end rescue Rack::Utils::ParameterTypeError, Rack::Utils::InvalidParameterError => e raise ActionController::BadRequest.new(:request, e) end @@ -336,10 +356,10 @@ module ActionDispatch # Returns the authorization header regardless of whether it was specified directly or through one of the # proxy alternatives. def authorization - @env['HTTP_AUTHORIZATION'] || - @env['X-HTTP_AUTHORIZATION'] || - @env['X_HTTP_AUTHORIZATION'] || - @env['REDIRECT_X_HTTP_AUTHORIZATION'] + get_header('HTTP_AUTHORIZATION') || + get_header('X-HTTP_AUTHORIZATION') || + get_header('X_HTTP_AUTHORIZATION') || + get_header('REDIRECT_X_HTTP_AUTHORIZATION') end # True if the request came from localhost, 127.0.0.1. @@ -348,11 +368,12 @@ module ActionDispatch end def request_parameters=(params) - env["action_dispatch.request.request_parameters".freeze] = params + raise if params.nil? + set_header("action_dispatch.request.request_parameters".freeze, params) end def logger - env["action_dispatch.logger".freeze] + get_header("action_dispatch.logger".freeze) end private diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb index fd92e89231..4aee489912 100644 --- a/actionpack/lib/action_dispatch/http/response.rb +++ b/actionpack/lib/action_dispatch/http/response.rb @@ -65,7 +65,7 @@ module ActionDispatch # :nodoc: CONTENT_TYPE = "Content-Type".freeze SET_COOKIE = "Set-Cookie".freeze LOCATION = "Location".freeze - NO_CONTENT_CODES = [204, 304] + NO_CONTENT_CODES = [100, 101, 102, 204, 205, 304] cattr_accessor(:default_charset) { "utf-8" } cattr_accessor(:default_headers) @@ -150,6 +150,11 @@ module ActionDispatch # :nodoc: yield self if block_given? end + def have_header?(key); headers.key? key; end + def get_header(key); headers[key]; end + def set_header(key, v); headers[key] = v; end + def delete_header(key); headers.delete key; end + def await_commit synchronize do @cv.wait_until { @committed } @@ -256,25 +261,9 @@ module ActionDispatch # :nodoc: parts end - def set_cookie(key, value) - ::Rack::Utils.set_cookie_header!(header, key, value) - end - - def delete_cookie(key, value={}) - ::Rack::Utils.delete_cookie_header!(header, key, value) - end - # The location header we'll be responding with. - def location - headers[LOCATION] - end alias_method :redirect_url, :location - # Sets the location header we'll be responding with. - def location=(url) - headers[LOCATION] = url - end - def close stream.close if stream.respond_to?(:close) end @@ -305,7 +294,7 @@ module ActionDispatch # :nodoc: # assert_equal 'AuthorOfNewPage', r.cookies['author'] def cookies cookies = {} - if header = self[SET_COOKIE] + if header = get_header(SET_COOKIE) header = header.split("\n") if header.respond_to?(:to_str) header.each do |cookie| if pair = cookie.split(';').first @@ -341,14 +330,14 @@ module ActionDispatch # :nodoc: end def assign_default_content_type_and_charset! - return if self[CONTENT_TYPE].present? + return if get_header(CONTENT_TYPE).present? @content_type ||= Mime::HTML type = @content_type.to_s.dup type << "; charset=#{charset}" if append_charset? - self[CONTENT_TYPE] = type + set_header CONTENT_TYPE, type end def append_charset? @@ -392,10 +381,9 @@ module ActionDispatch # :nodoc: end def rack_response(status, header) - header[SET_COOKIE] = header[SET_COOKIE].join("\n") if header[SET_COOKIE].respond_to?(:join) - if NO_CONTENT_CODES.include?(@status) header.delete CONTENT_TYPE + header.delete 'Content-Length' [status, header, []] else [status, header, RackBody.new(self)] diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb index 6fcf49030b..e413954066 100644 --- a/actionpack/lib/action_dispatch/http/url.rb +++ b/actionpack/lib/action_dispatch/http/url.rb @@ -229,10 +229,10 @@ module ActionDispatch # req = Request.new 'HTTP_HOST' => 'example.com:8080' # req.raw_host_with_port # => "example.com:8080" def raw_host_with_port - if forwarded = env["HTTP_X_FORWARDED_HOST"].presence + if forwarded = x_forwarded_host.presence forwarded.split(/,\s?/).last else - env['HTTP_HOST'] || "#{env['SERVER_NAME'] || env['SERVER_ADDR']}:#{env['SERVER_PORT']}" + get_header('HTTP_HOST') || "#{server_name || server_addr}:#{get_header('SERVER_PORT')}" end end @@ -348,7 +348,7 @@ module ActionDispatch end def server_port - @env['SERVER_PORT'].to_i + get_header('SERVER_PORT').to_i end # Returns the \domain part of a \host, such as "rubyonrails.org" in "www.rubyonrails.org". You can specify diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb index cf4f654ed6..6d0387cf74 100644 --- a/actionpack/lib/action_dispatch/middleware/cookies.rb +++ b/actionpack/lib/action_dispatch/middleware/cookies.rb @@ -1,6 +1,4 @@ require 'active_support/core_ext/hash/keys' -require 'active_support/core_ext/module/attribute_accessors' -require 'active_support/core_ext/object/blank' require 'active_support/key_generator' require 'active_support/message_verifier' require 'active_support/json' @@ -8,48 +6,50 @@ require 'active_support/json' module ActionDispatch class Request < Rack::Request def cookie_jar - env['action_dispatch.cookies'.freeze] ||= Cookies::CookieJar.build(self, cookies) + get_header('action_dispatch.cookies'.freeze) do + self.cookie_jar = Cookies::CookieJar.build(self, cookies) + end end # :stopdoc: def have_cookie_jar? - env.key? 'action_dispatch.cookies'.freeze + has_header? 'action_dispatch.cookies'.freeze end def cookie_jar=(jar) - env['action_dispatch.cookies'.freeze] = jar + set_header 'action_dispatch.cookies'.freeze, jar end def key_generator - env[Cookies::GENERATOR_KEY] + get_header Cookies::GENERATOR_KEY end def signed_cookie_salt - env[Cookies::SIGNED_COOKIE_SALT] + get_header Cookies::SIGNED_COOKIE_SALT end def encrypted_cookie_salt - env[Cookies::ENCRYPTED_COOKIE_SALT] + get_header Cookies::ENCRYPTED_COOKIE_SALT end def encrypted_signed_cookie_salt - env[Cookies::ENCRYPTED_SIGNED_COOKIE_SALT] + get_header Cookies::ENCRYPTED_SIGNED_COOKIE_SALT end def secret_token - env[Cookies::SECRET_TOKEN] + get_header Cookies::SECRET_TOKEN end def secret_key_base - env[Cookies::SECRET_KEY_BASE] + get_header Cookies::SECRET_KEY_BASE end def cookies_serializer - env[Cookies::COOKIES_SERIALIZER] + get_header Cookies::COOKIES_SERIALIZER end def cookies_digest - env[Cookies::COOKIES_DIGEST] + get_header Cookies::COOKIES_DIGEST end # :startdoc: end diff --git a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb index 226a688fe2..66bb74b9c5 100644 --- a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb @@ -55,18 +55,17 @@ module ActionDispatch response rescue Exception => exception raise exception unless request.show_exceptions? - render_exception(env, exception) + render_exception(request, exception) end private - def render_exception(env, exception) - backtrace_cleaner = env['action_dispatch.backtrace_cleaner'] + def render_exception(request, exception) + backtrace_cleaner = request.get_header('action_dispatch.backtrace_cleaner') wrapper = ExceptionWrapper.new(backtrace_cleaner, exception) - log_error(env, wrapper) + log_error(request, wrapper) - if env['action_dispatch.show_detailed_exceptions'] - request = Request.new(env) + if request.get_header('action_dispatch.show_detailed_exceptions') traces = wrapper.traces trace_to_show = 'Application Trace' @@ -108,8 +107,8 @@ module ActionDispatch [status, {'Content-Type' => "#{format}; charset=#{Response.default_charset}", 'Content-Length' => body.bytesize.to_s}, [body]] end - def log_error(env, wrapper) - logger = logger(env) + def log_error(request, wrapper) + logger = logger(request) return unless logger exception = wrapper.exception @@ -125,8 +124,8 @@ module ActionDispatch end end - def logger(env) - env['action_dispatch.logger'] || stderr_logger + def logger(request) + request.logger || stderr_logger end def stderr_logger diff --git a/actionpack/lib/action_dispatch/middleware/flash.rb b/actionpack/lib/action_dispatch/middleware/flash.rb index 23da169b22..6041f84834 100644 --- a/actionpack/lib/action_dispatch/middleware/flash.rb +++ b/actionpack/lib/action_dispatch/middleware/flash.rb @@ -6,15 +6,17 @@ module ActionDispatch # read a notice you put there or <tt>flash["notice"] = "hello"</tt> # to put a new one. def flash - @env[Flash::KEY] ||= Flash::FlashHash.from_session_value(session["flash"]) + flash = flash_hash + return flash if flash + self.flash = Flash::FlashHash.from_session_value(session["flash"]) end def flash=(flash) - @env[Flash::KEY] = flash + set_header Flash::KEY, flash end def flash_hash # :nodoc: - @env[Flash::KEY] + get_header Flash::KEY end end @@ -274,7 +276,7 @@ module ActionDispatch req = ActionDispatch::Request.new env @app.call(env) ensure - session = Request::Session.find(env) || {} + session = Request::Session.find(req) || {} flash_hash = req.flash_hash if flash_hash && (flash_hash.present? || session.key?('flash')) diff --git a/actionpack/lib/action_dispatch/middleware/params_parser.rb b/actionpack/lib/action_dispatch/middleware/params_parser.rb index 402ad778fa..9cde9c9b98 100644 --- a/actionpack/lib/action_dispatch/middleware/params_parser.rb +++ b/actionpack/lib/action_dispatch/middleware/params_parser.rb @@ -37,7 +37,9 @@ module ActionDispatch def call(env) request = Request.new(env) - request.request_parameters = parse_formatted_parameters(request, @parsers) + parse_formatted_parameters(request, @parsers) do |params| + request.request_parameters = params + end @app.call(env) end @@ -48,7 +50,7 @@ module ActionDispatch strategy = parsers.fetch(request.content_mime_type) { return nil } - strategy.call(request.raw_post) + yield strategy.call(request.raw_post) rescue => e # JSON or Ruby code block errors logger(request).debug "Error occurred while parsing request parameters.\nContents:\n\n#{request.raw_post}" diff --git a/actionpack/lib/action_dispatch/middleware/public_exceptions.rb b/actionpack/lib/action_dispatch/middleware/public_exceptions.rb index 7cde76b30e..0f27984550 100644 --- a/actionpack/lib/action_dispatch/middleware/public_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/public_exceptions.rb @@ -17,8 +17,8 @@ module ActionDispatch end def call(env) - status = env["PATH_INFO"][1..-1].to_i request = ActionDispatch::Request.new(env) + status = request.path_info[1..-1].to_i content_type = request.formats.first body = { :status => status, :error => Rack::Utils::HTTP_STATUS_CODES.fetch(status, Rack::Utils::HTTP_STATUS_CODES[500]) } diff --git a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb index 84df55fd5a..b924df789f 100644 --- a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb @@ -36,6 +36,11 @@ module ActionDispatch @default_options.delete(:sidbits) @default_options.delete(:secure_random) end + + private + def make_request(env) + ActionDispatch::Request.new env + end end module StaleSessionCheck @@ -65,8 +70,8 @@ module ActionDispatch end module SessionObject # :nodoc: - def prepare_session(env) - Request::Session.create(self, env, @default_options) + def prepare_session(req) + Request::Session.create(self, req, @default_options) end def loaded_session?(session) @@ -81,8 +86,7 @@ module ActionDispatch private - def set_cookie(env, session_id, cookie) - request = ActionDispatch::Request.new(env) + def set_cookie(request, session_id, cookie) request.cookie_jar[key] = cookie end end diff --git a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb index d8f9614904..e225f356df 100644 --- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb @@ -71,16 +71,16 @@ module ActionDispatch super(app, options.merge!(:cookie_only => true)) end - def destroy_session(env, session_id, options) + def destroy_session(req, session_id, options) new_sid = generate_sid unless options[:drop] # Reset hash and Assign the new session id - env["action_dispatch.request.unsigned_session_cookie"] = new_sid ? { "session_id" => new_sid } : {} + req.set_header("action_dispatch.request.unsigned_session_cookie", new_sid ? { "session_id" => new_sid } : {}) new_sid end - def load_session(env) + def load_session(req) stale_session_check! do - data = unpacked_cookie_data(env) + data = unpacked_cookie_data(req) data = persistent_session_id!(data) [data["session_id"], data] end @@ -88,20 +88,21 @@ module ActionDispatch private - def extract_session_id(env) + def extract_session_id(req) stale_session_check! do - unpacked_cookie_data(env)["session_id"] + unpacked_cookie_data(req)["session_id"] end end - def unpacked_cookie_data(env) - env["action_dispatch.request.unsigned_session_cookie"] ||= begin - stale_session_check! do - if data = get_cookie(env) + def unpacked_cookie_data(req) + req.get_header("action_dispatch.request.unsigned_session_cookie") do |k| + v = stale_session_check! do + if data = get_cookie(req) data.stringify_keys! end data || {} end + req.set_header k, v end end @@ -111,21 +112,20 @@ module ActionDispatch data end - def set_session(env, sid, session_data, options) + def set_session(req, sid, session_data, options) session_data["session_id"] = sid session_data end - def set_cookie(env, session_id, cookie) - cookie_jar(env)[@key] = cookie + def set_cookie(request, session_id, cookie) + cookie_jar(request)[@key] = cookie end - def get_cookie(env) - cookie_jar(env)[@key] + def get_cookie(req) + cookie_jar(req)[@key] end - def cookie_jar(env) - request = ActionDispatch::Request.new(env) + def cookie_jar(request) request.cookie_jar.signed_or_encrypted end end diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb index 12d8dab8eb..64695f9738 100644 --- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb @@ -31,7 +31,7 @@ module ActionDispatch @app.call(env) rescue Exception => exception if request.show_exceptions? - render_exception(env, exception) + render_exception(request, exception) else raise exception end @@ -39,14 +39,14 @@ module ActionDispatch private - def render_exception(env, exception) - backtrace_cleaner = env['action_dispatch.backtrace_cleaner'] + def render_exception(request, exception) + backtrace_cleaner = request.get_header 'action_dispatch.backtrace_cleaner' wrapper = ExceptionWrapper.new(backtrace_cleaner, exception) status = wrapper.status_code - env["action_dispatch.exception"] = wrapper.exception - env["action_dispatch.original_path"] = env["PATH_INFO"] - env["PATH_INFO"] = "/#{status}" - response = @exceptions_app.call(env) + request.set_header "action_dispatch.exception", wrapper.exception + request.set_header "action_dispatch.original_path", request.path_info + request.path_info = "/#{status}" + response = @exceptions_app.call(request.env) response[1]['X-Cascade'] == 'pass' ? pass_response(status) : response rescue Exception => failsafe_error $stderr.puts "Error during failsafe response: #{failsafe_error}\n #{failsafe_error.backtrace * "\n "}" diff --git a/actionpack/lib/action_dispatch/middleware/stack.rb b/actionpack/lib/action_dispatch/middleware/stack.rb index 0430ce3b9a..90e2ae6802 100644 --- a/actionpack/lib/action_dispatch/middleware/stack.rb +++ b/actionpack/lib/action_dispatch/middleware/stack.rb @@ -87,7 +87,7 @@ module ActionDispatch middlewares.freeze.reverse.inject(app) { |a, e| e.build(a) } end - protected + private def assert_index(index, where) index = get_class index @@ -96,8 +96,6 @@ module ActionDispatch i end - private - def get_class(klass) if klass.is_a?(String) || klass.is_a?(Symbol) classcache = ActiveSupport::Dependencies::Reference diff --git a/actionpack/lib/action_dispatch/request/session.rb b/actionpack/lib/action_dispatch/request/session.rb index bae3d909fa..b946ccb49f 100644 --- a/actionpack/lib/action_dispatch/request/session.rb +++ b/actionpack/lib/action_dispatch/request/session.rb @@ -11,31 +11,31 @@ module ActionDispatch Unspecified = Object.new # Creates a session hash, merging the properties of the previous session if any - def self.create(store, env, default_options) - session_was = find env - session = Request::Session.new(store, env) + def self.create(store, req, default_options) + session_was = find req + session = Request::Session.new(store, req) session.merge! session_was if session_was - set(env, session) - Options.set(env, Request::Session::Options.new(store, default_options)) + set(req, session) + Options.set(req, Request::Session::Options.new(store, default_options)) session end - def self.find(env) - env[ENV_SESSION_KEY] + def self.find(req) + req.get_header ENV_SESSION_KEY end - def self.set(env, session) - env[ENV_SESSION_KEY] = session + def self.set(req, session) + req.set_header ENV_SESSION_KEY, session end class Options #:nodoc: - def self.set(env, options) - env[ENV_SESSION_OPTIONS_KEY] = options + def self.set(req, options) + req.set_header ENV_SESSION_OPTIONS_KEY, options end - def self.find(env) - env[ENV_SESSION_OPTIONS_KEY] + def self.find(req) + req.get_header ENV_SESSION_OPTIONS_KEY end def initialize(by, default_options) @@ -47,9 +47,9 @@ module ActionDispatch @delegate[key] end - def id(env) + def id(req) @delegate.fetch(:id) { - @by.send(:extract_session_id, env) + @by.send(:extract_session_id, req) } end @@ -58,26 +58,26 @@ module ActionDispatch def values_at(*args); @delegate.values_at(*args); end end - def initialize(by, env) + def initialize(by, req) @by = by - @env = env + @req = req @delegate = {} @loaded = false @exists = nil # we haven't checked yet end def id - options.id(@env) + options.id(@req) end def options - Options.find @env + Options.find @req end def destroy clear options = self.options || {} - @by.send(:destroy_session, @env, options.id(@env), options) + @by.send(:destroy_session, @req, options.id(@req), options) # Load the new sid to be written with the response @loaded = false @@ -181,7 +181,7 @@ module ActionDispatch def exists? return @exists unless @exists.nil? - @exists = @by.send(:session_exists?, @env) + @exists = @by.send(:session_exists?, @req) end def loaded? @@ -209,7 +209,7 @@ module ActionDispatch end def load! - id, session = @by.load_session @env + id, session = @by.load_session @req options[:id] = id @delegate.replace(stringify_keys(session)) @loaded = true diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 51bafb539f..1acfb2bfe8 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -283,12 +283,16 @@ module ActionDispatch end def app(blocks) - if to.respond_to?(:call) - Constraints.new(to, blocks, Constraints::CALL) - elsif blocks.any? - Constraints.new(dispatcher(defaults.key?(:controller)), blocks, Constraints::SERVE) + if to.is_a?(Class) && to < ActionController::Metal + Routing::RouteSet::StaticDispatcher.new to else - dispatcher(defaults.key?(:controller)) + if to.respond_to?(:call) + Constraints.new(to, blocks, Constraints::CALL) + elsif blocks.any? + Constraints.new(dispatcher(defaults.key?(:controller)), blocks, Constraints::SERVE) + else + dispatcher(defaults.key?(:controller)) + end end end @@ -368,7 +372,7 @@ module ActionDispatch end def dispatcher(raise_on_name_error) - @set.dispatcher raise_on_name_error + Routing::RouteSet::Dispatcher.new raise_on_name_error end end diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 20926012b4..e4b8d5993e 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -1,6 +1,5 @@ require 'action_dispatch/journey' require 'forwardable' -require 'thread_safe' require 'active_support/concern' require 'active_support/core_ext/object/to_query' require 'active_support/core_ext/hash/slice' @@ -23,64 +22,43 @@ module ActionDispatch class Dispatcher < Routing::Endpoint def initialize(raise_on_name_error) @raise_on_name_error = raise_on_name_error - @controller_class_names = ThreadSafe::Cache.new end def dispatcher?; true; end def serve(req) - req.check_path_parameters! - params = req.path_parameters - - prepare_params!(params) - - controller = controller(params, @raise_on_name_error) do + params = req.path_parameters + controller = controller req + res = controller.make_response! req + dispatch(controller, params[:action], req, res) + rescue NameError => e + if @raise_on_name_error + raise ActionController::RoutingError, e.message, e.backtrace + else return [404, {'X-Cascade' => 'pass'}, []] end - - dispatch(controller, params[:action], req) end - def prepare_params!(params) - normalize_controller!(params) - merge_default_action!(params) - end + private - # If this is a default_controller (i.e. a controller specified by the user) - # we should raise an error in case it's not found, because it usually means - # a user error. However, if the controller was retrieved through a dynamic - # segment, as in :controller(/:action), we should simply return nil and - # delegate the control back to Rack cascade. Besides, if this is not a default - # controller, it means we should respect the @scope[:module] parameter. - def controller(params, raise_on_name_error=true) - controller_reference params.fetch(:controller) { yield } - rescue NameError => e - raise ActionController::RoutingError, e.message, e.backtrace if raise_on_name_error - yield + def controller(req) + req.controller_class end - protected - - attr_reader :controller_class_names - - def controller_reference(controller_param) - const_name = controller_class_names[controller_param] ||= "#{controller_param.camelize}Controller" - ActiveSupport::Dependencies.constantize(const_name) + def dispatch(controller, action, req, res) + controller.dispatch(action, req, res) end + end - private - - def dispatch(controller, action, req) - controller.action(action).call(req.env) + class StaticDispatcher < Dispatcher + def initialize(controller_class) + super(false) + @controller_class = controller_class end - def normalize_controller!(params) - params[:controller] = params[:controller].underscore if params.key?(:controller) - end + private - def merge_default_action!(params) - params[:action] ||= 'index' - end + def controller(_); @controller_class; end end # A NamedRouteCollection instance is a collection of named routes, and also @@ -202,9 +180,9 @@ module ActionDispatch private def optimized_helper(args) - params = parameterize_args(args) { |k| + params = parameterize_args(args) do raise_generation_error(args) - } + end @route.format params end @@ -316,7 +294,7 @@ module ActionDispatch attr_accessor :formatter, :set, :named_routes, :default_scope, :router attr_accessor :disable_clear_and_finalize, :resources_path_names - attr_accessor :default_url_options, :dispatcher_class + attr_accessor :default_url_options attr_reader :env_key alias :routes :set @@ -359,7 +337,6 @@ module ActionDispatch @set = Journey::Routes.new @router = Journey::Router.new @set @formatter = Journey::Formatter.new self - @dispatcher_class = Routing::RouteSet::Dispatcher end def relative_url_root @@ -374,6 +351,11 @@ module ActionDispatch ActionDispatch::Request end + def make_request(env) + request_class.new env + end + private :make_request + def draw(&block) clear! unless @disable_clear_and_finalize eval_block(block) @@ -417,10 +399,6 @@ module ActionDispatch @prepend.each { |blk| eval_block(blk) } end - def dispatcher(raise_on_name_error) - dispatcher_class.new(raise_on_name_error) - end - module MountedHelpers extend ActiveSupport::Concern include UrlFor @@ -731,7 +709,7 @@ module ActionDispatch end def call(env) - req = request_class.new(env) + req = make_request(env) req.path_info = Journey::Router::Utils.normalize_path(req.path_info) @router.serve(req) end @@ -747,7 +725,7 @@ module ActionDispatch raise ActionController::RoutingError, e.message end - req = request_class.new(env) + req = make_request(env) @router.recognize(req) do |route, params| params.merge!(extras) params.each do |key, value| @@ -760,14 +738,13 @@ module ActionDispatch req.path_parameters = old_params.merge params app = route.app if app.matches?(req) && app.dispatcher? - dispatcher = app.app - - dispatcher.controller(params, false) do + begin + req.controller_class + rescue NameError raise ActionController::RoutingError, "A route matches #{path.inspect}, but references missing controller: #{params[:controller].camelize}Controller" end - dispatcher.prepare_params!(params) - return params + return req.path_parameters end end diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb index 0cdc6d4e77..4dfd4f3f71 100644 --- a/actionpack/lib/action_dispatch/testing/integration.rb +++ b/actionpack/lib/action_dispatch/testing/integration.rb @@ -359,10 +359,10 @@ module ActionDispatch # this modifies the passed request_env directly if headers.present? - Http::Headers.new(request_env).merge!(headers) + Http::Headers.from_hash(request_env).merge!(headers) end if env.present? - Http::Headers.new(request_env).merge!(env) + Http::Headers.from_hash(request_env).merge!(env) end session = Rack::Test::Session.new(_mock_session) diff --git a/actionpack/test/abstract/translation_test.rb b/actionpack/test/abstract/translation_test.rb index 8289252dfc..1435928578 100644 --- a/actionpack/test/abstract/translation_test.rb +++ b/actionpack/test/abstract/translation_test.rb @@ -24,7 +24,6 @@ module AbstractController }, }, }) - @controller.stubs(action_name: :index) end def test_action_controller_base_responds_to_translate @@ -44,25 +43,34 @@ module AbstractController end def test_lazy_lookup - assert_equal 'bar', @controller.t('.foo') + @controller.stub :action_name, :index do + assert_equal 'bar', @controller.t('.foo') + end end def test_lazy_lookup_with_symbol - assert_equal 'bar', @controller.t(:'.foo') + @controller.stub :action_name, :index do + assert_equal 'bar', @controller.t(:'.foo') + end end def test_lazy_lookup_fallback - assert_equal 'no_action_tr', @controller.t(:'.no_action') + @controller.stub :action_name, :index do + assert_equal 'no_action_tr', @controller.t(:'.no_action') + end end def test_default_translation - assert_equal 'bar', @controller.t('one.two') + @controller.stub :action_name, :index do + assert_equal 'bar', @controller.t('one.two') + end end def test_localize time, expected = Time.gm(2000), 'Sat, 01 Jan 2000 00:00:00 +0000' - I18n.stubs(:localize).with(time).returns(expected) - assert_equal expected, @controller.l(time) + I18n.stub :localize, expected do + assert_equal expected, @controller.l(time) + end end end end diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb index 60e2cea8a2..1954324222 100644 --- a/actionpack/test/abstract_unit.rb +++ b/actionpack/test/abstract_unit.rb @@ -19,7 +19,6 @@ begin rescue LoadError puts "'drb/unix' is not available" end -require 'tempfile' PROCESS_COUNT = (ENV['N'] || 4).to_i @@ -42,6 +41,8 @@ module Rails def env @_env ||= ActiveSupport::StringInquirer.new(ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "test") end + + def root; end; end end @@ -117,23 +118,36 @@ class ActionDispatch::IntegrationTest < ActiveSupport::TestCase get ':controller(/:action)' end - # Stub Rails dispatcher so it does not get controller references and - # simply return the controller#action as Rack::Body. - class StubDispatcher < ::ActionDispatch::Routing::RouteSet::Dispatcher - protected - def controller_reference(controller_param) - controller_param + class DeadEndRoutes < ActionDispatch::Routing::RouteSet + # Stub Rails dispatcher so it does not get controller references and + # simply return the controller#action as Rack::Body. + class NullController < ::ActionController::Metal + def initialize(controller_name) + @controller = controller_name + end + + def make_response!(request) + self.class.make_response! request + end + + def dispatch(action, req, res) + [200, {'Content-Type' => 'text/html'}, ["#{@controller}##{action}"]] + end + end + + class NullControllerRequest < DelegateClass(ActionDispatch::Request) + def controller_class + NullController.new params[:controller] + end end - def dispatch(controller, action, env) - [200, {'Content-Type' => 'text/html'}, ["#{controller}##{action}"]] + def make_request env + NullControllerRequest.new super end end - def self.stub_controllers(config = nil) - route_set = ActionDispatch::Routing::RouteSet.new(*[config].compact) - route_set.dispatcher_class = StubDispatcher - yield route_set + def self.stub_controllers(config = ActionDispatch::Routing::RouteSet::DEFAULT_CONFIG) + yield DeadEndRoutes.new(config) end def with_routing(&block) @@ -323,39 +337,37 @@ module RoutingTestHelpers end class TestSet < ActionDispatch::Routing::RouteSet - attr_reader :strict - - def initialize(block, strict = false) - @block = block - @strict = strict - super() - end - - class Dispatcher < ActionDispatch::Routing::RouteSet::Dispatcher - def initialize(defaults, set, block) - super(defaults) + class Request < DelegateClass(ActionDispatch::Request) + def initialize(target, helpers, block, strict) + super(target) + @helpers = helpers @block = block - @set = set - end - - def controller(params, default_controller=true) - super(params, @set.strict) + @strict = strict end - def controller_reference(controller_param) + def controller_class + helpers = @helpers block = @block - set = @set - super if @set.strict - Class.new(ActionController::Base) { - include set.url_helpers + Class.new(@strict ? super : ActionController::Base) { + include helpers define_method(:process) { |name| block.call(self) } def to_a; [200, {}, []]; end } end end - def dispatcher defaults - TestSet::Dispatcher.new defaults, self, @block + attr_reader :strict + + def initialize(block, strict = false) + @block = block + @strict = strict + super() + end + + private + + def make_request(env) + Request.new super, url_helpers, @block, strict end end end @@ -369,13 +381,11 @@ class ThreadsController < ResourcesController; end class MessagesController < ResourcesController; end class CommentsController < ResourcesController; end class ReviewsController < ResourcesController; end -class LogosController < ResourcesController; end class AccountsController < ResourcesController; end class AdminController < ResourcesController; end class ProductsController < ResourcesController; end class ImagesController < ResourcesController; end -class PreferencesController < ResourcesController; end module Backoffice class ProductsController < ResourcesController; end @@ -397,6 +407,7 @@ def jruby_skip(message = '') end require 'mocha/setup' # FIXME: stop using mocha +require 'active_support/testing/method_call_assertions' class ForkingExecutor class Server @@ -468,3 +479,7 @@ if RUBY_ENGINE == "ruby" && PROCESS_COUNT > 0 # Use N processes (N defaults to 4) Minitest.parallel_executor = ForkingExecutor.new(PROCESS_COUNT) end + +class ActiveSupport::TestCase + include ActiveSupport::Testing::MethodCallAssertions +end diff --git a/actionpack/test/controller/base_test.rb b/actionpack/test/controller/base_test.rb index d9374ce9c3..fb60dbd993 100644 --- a/actionpack/test/controller/base_test.rb +++ b/actionpack/test/controller/base_test.rb @@ -93,6 +93,8 @@ end class ControllerInstanceTests < ActiveSupport::TestCase def setup @empty = EmptyController.new + @empty.set_request!(ActionDispatch::Request.new({})) + @empty.set_response!(EmptyController.make_response!(@empty.request)) @contained = Submodule::ContainedEmptyController.new @empty_controllers = [@empty, @contained] end diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb index dc4c32b07e..de7e800ac1 100644 --- a/actionpack/test/controller/integration_test.rb +++ b/actionpack/test/controller/integration_test.rb @@ -26,289 +26,335 @@ class SessionTest < ActiveSupport::TestCase end def test_follow_redirect_raises_when_no_redirect - @session.stubs(:redirect?).returns(false) - assert_raise(RuntimeError) { @session.follow_redirect! } + @session.stub :redirect?, false do + assert_raise(RuntimeError) { @session.follow_redirect! } + end end def test_request_via_redirect_uses_given_method path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue"} - @session.expects(:process).with(:put, path, params: args, headers: headers) - @session.stubs(:redirect?).returns(false) - @session.request_via_redirect(:put, path, params: args, headers: headers) + assert_called_with @session, :process, [:put, path, params: args, headers: headers] do + @session.stub :redirect?, false do + @session.request_via_redirect(:put, path, params: args, headers: headers) + end + end end def test_deprecated_request_via_redirect_uses_given_method path = "/somepath"; args = { id: '1' }; headers = { "X-Test-Header" => "testvalue" } - @session.expects(:process).with(:put, path, params: args, headers: headers) - @session.stubs(:redirect?).returns(false) - assert_deprecated { @session.request_via_redirect(:put, path, args, headers) } + assert_called_with @session, :process, [:put, path, params: args, headers: headers] do + @session.stub :redirect?, false do + assert_deprecated { @session.request_via_redirect(:put, path, args, headers) } + end + end end def test_request_via_redirect_follows_redirects path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue"} - @session.stubs(:redirect?).returns(true, true, false) - @session.expects(:follow_redirect!).times(2) - @session.request_via_redirect(:get, path, params: args, headers: headers) + value_series = [true, true, false] + assert_called @session, :follow_redirect!, times: 2 do + @session.stub :redirect?, ->{ value_series.shift } do + @session.request_via_redirect(:get, path, params: args, headers: headers) + end + end end def test_request_via_redirect_returns_status path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue"} - @session.stubs(:redirect?).returns(false) - @session.stubs(:status).returns(200) - assert_equal 200, @session.request_via_redirect(:get, path, params: args, headers: headers) + @session.stub :redirect?, false do + @session.stub :status, 200 do + assert_equal 200, @session.request_via_redirect(:get, path, params: args, headers: headers) + end + end end def test_deprecated_get_via_redirect path = "/somepath"; args = { id: '1' }; headers = { "X-Test-Header" => "testvalue" } - @session.expects(:request_via_redirect).with(:get, path, args, headers) - assert_deprecated do - @session.get_via_redirect(path, args, headers) + assert_called_with @session, :request_via_redirect, [:get, path, args, headers] do + assert_deprecated do + @session.get_via_redirect(path, args, headers) + end end end def test_deprecated_post_via_redirect path = "/somepath"; args = { id: '1' }; headers = { "X-Test-Header" => "testvalue" } - @session.expects(:request_via_redirect).with(:post, path, args, headers) - assert_deprecated do - @session.post_via_redirect(path, args, headers) + assert_called_with @session, :request_via_redirect, [:post, path, args, headers] do + assert_deprecated do + @session.post_via_redirect(path, args, headers) + end end end def test_deprecated_patch_via_redirect path = "/somepath"; args = { id: '1' }; headers = { "X-Test-Header" => "testvalue" } - @session.expects(:request_via_redirect).with(:patch, path, args, headers) - assert_deprecated do - @session.patch_via_redirect(path, args, headers) + assert_called_with @session, :request_via_redirect, [:patch, path, args, headers] do + assert_deprecated do + @session.patch_via_redirect(path, args, headers) + end end end def test_deprecated_put_via_redirect path = "/somepath"; args = { id: '1' }; headers = { "X-Test-Header" => "testvalue" } - @session.expects(:request_via_redirect).with(:put, path, args, headers) - assert_deprecated do - @session.put_via_redirect(path, args, headers) + assert_called_with @session, :request_via_redirect, [:put, path, args, headers] do + assert_deprecated do + @session.put_via_redirect(path, args, headers) + end end end def test_deprecated_delete_via_redirect path = "/somepath"; args = { id: '1' }; headers = { "X-Test-Header" => "testvalue" } - @session.expects(:request_via_redirect).with(:delete, path, args, headers) - assert_deprecated do - @session.delete_via_redirect(path, args, headers) + assert_called_with @session, :request_via_redirect, [:delete, path, args, headers] do + assert_deprecated do + @session.delete_via_redirect(path, args, headers) + end end end def test_get path = "/index"; params = "blah"; headers = { location: 'blah' } - @session.expects(:process).with(:get, path, params: params, headers: headers) - @session.get(path, params: params, headers: headers) + + assert_called_with @session, :process, [:get, path, params: params, headers: headers] do + @session.get(path, params: params, headers: headers) + end end def test_get_with_env_and_headers path = "/index"; params = "blah"; headers = { location: 'blah' }; env = { 'HTTP_X_REQUESTED_WITH' => 'XMLHttpRequest' } - @session.expects(:process).with(:get, path, params: params, headers: headers, env: env) - @session.get(path, params: params, headers: headers, env: env) + assert_called_with @session, :process, [:get, path, params: params, headers: headers, env: env] do + @session.get(path, params: params, headers: headers, env: env) + end end def test_deprecated_get path = "/index"; params = "blah"; headers = { location: 'blah' } - @session.expects(:process).with(:get, path, params: params, headers: headers) - assert_deprecated { - @session.get(path, params, headers) - } + + assert_called_with @session, :process, [:get, path, params: params, headers: headers] do + assert_deprecated { + @session.get(path, params, headers) + } + end end def test_post path = "/index"; params = "blah"; headers = { location: 'blah' } - @session.expects(:process).with(:post, path, params: params, headers: headers) - assert_deprecated { - @session.post(path, params, headers) - } + assert_called_with @session, :process, [:post, path, params: params, headers: headers] do + assert_deprecated { + @session.post(path, params, headers) + } + end end def test_deprecated_post path = "/index"; params = "blah"; headers = { location: 'blah' } - @session.expects(:process).with(:post, path, params: params, headers: headers) - @session.post(path, params: params, headers: headers) + assert_called_with @session, :process, [:post, path, params: params, headers: headers] do + @session.post(path, params: params, headers: headers) + end end def test_patch path = "/index"; params = "blah"; headers = { location: 'blah' } - @session.expects(:process).with(:patch, path, params: params, headers: headers) - @session.patch(path, params: params, headers: headers) + assert_called_with @session, :process, [:patch, path, params: params, headers: headers] do + @session.patch(path, params: params, headers: headers) + end end def test_deprecated_patch path = "/index"; params = "blah"; headers = { location: 'blah' } - @session.expects(:process).with(:patch, path, params: params, headers: headers) - assert_deprecated { - @session.patch(path, params, headers) - } + assert_called_with @session, :process, [:patch, path, params: params, headers: headers] do + assert_deprecated { + @session.patch(path, params, headers) + } + end end def test_put path = "/index"; params = "blah"; headers = { location: 'blah' } - @session.expects(:process).with(:put, path, params: params, headers: headers) - @session.put(path, params: params, headers: headers) + assert_called_with @session, :process, [:put, path, params: params, headers: headers] do + @session.put(path, params: params, headers: headers) + end end def test_deprecated_put path = "/index"; params = "blah"; headers = { location: 'blah' } - @session.expects(:process).with(:put, path, params: params, headers: headers) - assert_deprecated { - @session.put(path, params, headers) - } + assert_called_with @session, :process, [:put, path, params: params, headers: headers] do + assert_deprecated { + @session.put(path, params, headers) + } + end end def test_delete path = "/index"; params = "blah"; headers = { location: 'blah' } - @session.expects(:process).with(:delete, path, params: params, headers: headers) - assert_deprecated { - @session.delete(path,params,headers) - } + assert_called_with @session, :process, [:delete, path, params: params, headers: headers] do + assert_deprecated { + @session.delete(path,params,headers) + } + end end def test_deprecated_delete path = "/index"; params = "blah"; headers = { location: 'blah' } - @session.expects(:process).with(:delete, path, params: params, headers: headers) - @session.delete(path, params: params, headers: headers) + assert_called_with @session, :process, [:delete, path, params: params, headers: headers] do + @session.delete(path, params: params, headers: headers) + end end def test_head path = "/index"; params = "blah"; headers = { location: 'blah' } - @session.expects(:process).with(:head, path, params: params, headers: headers) - @session.head(path, params: params, headers: headers) + assert_called_with @session, :process, [:head, path, params: params, headers: headers] do + @session.head(path, params: params, headers: headers) + end end def deprecated_test_head path = "/index"; params = "blah"; headers = { location: 'blah' } - @session.expects(:process).with(:head, path, params: params, headers: headers) - assert_deprecated { - @session.head(path, params, headers) - } + assert_called_with @session, :process, [:head, path, params: params, headers: headers] do + assert_deprecated { + @session.head(path, params, headers) + } + end end def test_xml_http_request_get path = "/index"; params = "blah"; headers = { location: 'blah' } - @session.expects(:process).with(:get, path, params: params, headers: headers, xhr: true) - @session.get(path, params: params, headers: headers, xhr: true) + assert_called_with @session, :process, [:get, path, params: params, headers: headers, xhr: true] do + @session.get(path, params: params, headers: headers, xhr: true) + end end def test_deprecated_xml_http_request_get path = "/index"; params = "blah"; headers = { location: 'blah' } - @session.expects(:process).with(:get, path, params: params, headers: headers, xhr: true) - @session.get(path, params: params, headers: headers, xhr: true) + assert_called_with @session, :process, [:get, path, params: params, headers: headers, xhr: true] do + @session.get(path, params: params, headers: headers, xhr: true) + end end def test_deprecated_args_xml_http_request_get path = "/index"; params = "blah"; headers = { location: 'blah' } - @session.expects(:process).with(:get, path, params: params, headers: headers, xhr: true) - assert_deprecated(/xml_http_request/) { - @session.xml_http_request(:get, path, params, headers) - } + assert_called_with @session, :process, [:get, path, params: params, headers: headers, xhr: true] do + assert_deprecated(/xml_http_request/) { + @session.xml_http_request(:get, path, params, headers) + } + end end def test_xml_http_request_post path = "/index"; params = "blah"; headers = { location: 'blah' } - @session.expects(:process).with(:post, path, params: params, headers: headers, xhr: true) - @session.post(path, params: params, headers: headers, xhr: true) + assert_called_with @session, :process, [:post, path, params: params, headers: headers, xhr: true] do + @session.post(path, params: params, headers: headers, xhr: true) + end end def test_deprecated_xml_http_request_post path = "/index"; params = "blah"; headers = { location: 'blah' } - @session.expects(:process).with(:post, path, params: params, headers: headers, xhr: true) - @session.post(path, params: params, headers: headers, xhr: true) + assert_called_with @session, :process, [:post, path, params: params, headers: headers, xhr: true] do + @session.post(path, params: params, headers: headers, xhr: true) + end end def test_deprecated_args_xml_http_request_post path = "/index"; params = "blah"; headers = { location: 'blah' } - @session.expects(:process).with(:post, path, params: params, headers: headers, xhr: true) - assert_deprecated(/xml_http_request/) { @session.xml_http_request(:post,path,params,headers) } + assert_called_with @session, :process, [:post, path, params: params, headers: headers, xhr: true] do + assert_deprecated(/xml_http_request/) { @session.xml_http_request(:post,path,params,headers) } + end end def test_xml_http_request_patch path = "/index"; params = "blah"; headers = { location: 'blah' } - @session.expects(:process).with(:patch, path, params: params, headers: headers, xhr: true) - @session.patch(path, params: params, headers: headers, xhr: true) + assert_called_with @session, :process, [:patch, path, params: params, headers: headers, xhr: true] do + @session.patch(path, params: params, headers: headers, xhr: true) + end end def test_deprecated_xml_http_request_patch path = "/index"; params = "blah"; headers = { location: 'blah' } - @session.expects(:process).with(:patch, path, params: params, headers: headers, xhr: true) - @session.patch(path, params: params, headers: headers, xhr: true) + assert_called_with @session, :process, [:patch, path, params: params, headers: headers, xhr: true] do + @session.patch(path, params: params, headers: headers, xhr: true) + end end def test_deprecated_args_xml_http_request_patch path = "/index"; params = "blah"; headers = { location: 'blah' } - @session.expects(:process).with(:patch, path, params: params, headers: headers, xhr: true) - assert_deprecated(/xml_http_request/) { @session.xml_http_request(:patch,path,params,headers) } + assert_called_with @session, :process, [:patch, path, params: params, headers: headers, xhr: true] do + assert_deprecated(/xml_http_request/) { @session.xml_http_request(:patch,path,params,headers) } + end end def test_xml_http_request_put path = "/index"; params = "blah"; headers = { location: 'blah' } - @session.expects(:process).with(:put, path, params: params, headers: headers, xhr: true) - @session.put(path, params: params, headers: headers, xhr: true) + assert_called_with @session, :process, [:put, path, params: params, headers: headers, xhr: true] do + @session.put(path, params: params, headers: headers, xhr: true) + end end def test_deprecated_xml_http_request_put path = "/index"; params = "blah"; headers = { location: 'blah' } - @session.expects(:process).with(:put, path, params: params, headers: headers, xhr: true) - @session.put(path, params: params, headers: headers, xhr: true) + assert_called_with @session, :process, [:put, path, params: params, headers: headers, xhr: true] do + @session.put(path, params: params, headers: headers, xhr: true) + end end def test_deprecated_args_xml_http_request_put path = "/index"; params = "blah"; headers = { location: 'blah' } - @session.expects(:process).with(:put, path, params: params, headers: headers, xhr: true) - assert_deprecated(/xml_http_request/) { @session.xml_http_request(:put, path, params, headers) } + assert_called_with @session, :process, [:put, path, params: params, headers: headers, xhr: true] do + assert_deprecated(/xml_http_request/) { @session.xml_http_request(:put, path, params, headers) } + end end def test_xml_http_request_delete path = "/index"; params = "blah"; headers = { location: 'blah' } - @session.expects(:process).with(:delete, path, params: params, headers: headers, xhr: true) - @session.delete(path, params: params, headers: headers, xhr: true) + assert_called_with @session, :process, [:delete, path, params: params, headers: headers, xhr: true] do + @session.delete(path, params: params, headers: headers, xhr: true) + end end def test_deprecated_xml_http_request_delete path = "/index"; params = "blah"; headers = { location: 'blah' } - @session.expects(:process).with(:delete, path, params: params, headers: headers, xhr: true) - assert_deprecated { @session.xml_http_request(:delete, path, params: params, headers: headers) } + assert_called_with @session, :process, [:delete, path, params: params, headers: headers, xhr: true] do + assert_deprecated { @session.xml_http_request(:delete, path, params: params, headers: headers) } + end end def test_deprecated_args_xml_http_request_delete path = "/index"; params = "blah"; headers = { location: 'blah' } - @session.expects(:process).with(:delete, path, params: params, headers: headers, xhr: true) - assert_deprecated(/xml_http_request/) { @session.xml_http_request(:delete, path, params, headers) } + assert_called_with @session, :process, [:delete, path, params: params, headers: headers, xhr: true] do + assert_deprecated(/xml_http_request/) { @session.xml_http_request(:delete, path, params, headers) } + end end def test_xml_http_request_head path = "/index"; params = "blah"; headers = { location: 'blah' } - @session.expects(:process).with(:head, path, params: params, headers: headers, xhr: true) - @session.head(path, params: params, headers: headers, xhr: true) + assert_called_with @session, :process, [:head, path, params: params, headers: headers, xhr: true] do + @session.head(path, params: params, headers: headers, xhr: true) + end end def test_deprecated_xml_http_request_head path = "/index"; params = "blah"; headers = { location: 'blah' } - @session.expects(:process).with(:head, path, params: params, headers: headers, xhr: true) - assert_deprecated(/xml_http_request/) { @session.xml_http_request(:head, path, params: params, headers: headers) } + assert_called_with @session, :process, [:head, path, params: params, headers: headers, xhr: true] do + assert_deprecated(/xml_http_request/) { @session.xml_http_request(:head, path, params: params, headers: headers) } + end end def test_deprecated_args_xml_http_request_head path = "/index"; params = "blah"; headers = { location: 'blah' } - @session.expects(:process).with(:head, path, params: params, headers: headers, xhr: true) - assert_deprecated { @session.xml_http_request(:head, path, params, headers) } + assert_called_with @session, :process, [:head, path, params: params, headers: headers, xhr: true] do + assert_deprecated { @session.xml_http_request(:head, path, params, headers) } + end end end class IntegrationTestTest < ActiveSupport::TestCase def setup @test = ::ActionDispatch::IntegrationTest.new(:app) - @test.class.stubs(:fixture_table_names).returns([]) - @session = @test.open_session end def test_opens_new_session @@ -340,14 +386,8 @@ end # Tests that integration tests don't call Controller test methods for processing. # Integration tests have their own setup and teardown. class IntegrationTestUsesCorrectClass < ActionDispatch::IntegrationTest - def self.fixture_table_names - [] - end - def test_integration_methods_called reset! - @integration_session.stubs(:generic_url_rewriter) - @integration_session.stubs(:process) %w( get post head patch put delete ).each do |verb| assert_nothing_raised("'#{verb}' should use integration test methods") { __send__(verb, '/') } diff --git a/actionpack/test/controller/live_stream_test.rb b/actionpack/test/controller/live_stream_test.rb index 6ba361f2f7..e9c19b7acf 100644 --- a/actionpack/test/controller/live_stream_test.rb +++ b/actionpack/test/controller/live_stream_test.rb @@ -315,7 +315,7 @@ module ActionController t = Thread.new(@response) { |resp| resp.await_commit _, _, body = resp.to_a - body.each do |part| + body.each do @controller.latch.wait body.close break @@ -339,7 +339,7 @@ module ActionController t = Thread.new(@response) { |resp| resp.await_commit _, _, body = resp.to_a - body.each do |part| + body.each do body.close break end diff --git a/actionpack/test/controller/new_base/bare_metal_test.rb b/actionpack/test/controller/new_base/bare_metal_test.rb index 710c428dcc..e61f4d241b 100644 --- a/actionpack/test/controller/new_base/bare_metal_test.rb +++ b/actionpack/test/controller/new_base/bare_metal_test.rb @@ -2,8 +2,6 @@ require "abstract_unit" module BareMetalTest class BareController < ActionController::Metal - include ActionController::RackDelegation - def index self.response_body = "Hello world" end @@ -28,6 +26,8 @@ module BareMetalTest test "response_body value is wrapped in an array when the value is a String" do controller = BareController.new + controller.set_request!(ActionDispatch::Request.new({})) + controller.set_response!(BareController.make_response!(controller.request)) controller.index assert_equal ["Hello world"], controller.response_body end @@ -37,8 +37,6 @@ module BareMetalTest controller = BareController.new controller.set_request! ActionDispatch::Request.new(env) assert controller.request - assert controller.response - assert env['action_controller.instance'] end end @@ -123,34 +121,40 @@ module BareMetalTest end test "head :no_content (204) does not return any content" do - content = HeadController.action(:no_content).call(Rack::MockRequest.env_for("/")).third.first + content = body(HeadController.action(:no_content).call(Rack::MockRequest.env_for("/"))) assert_empty content end test "head :reset_content (205) does not return any content" do - content = HeadController.action(:reset_content).call(Rack::MockRequest.env_for("/")).third.first + content = body(HeadController.action(:reset_content).call(Rack::MockRequest.env_for("/"))) assert_empty content end test "head :not_modified (304) does not return any content" do - content = HeadController.action(:not_modified).call(Rack::MockRequest.env_for("/")).third.first + content = body(HeadController.action(:not_modified).call(Rack::MockRequest.env_for("/"))) assert_empty content end test "head :continue (100) does not return any content" do - content = HeadController.action(:continue).call(Rack::MockRequest.env_for("/")).third.first + content = body(HeadController.action(:continue).call(Rack::MockRequest.env_for("/"))) assert_empty content end test "head :switching_protocols (101) does not return any content" do - content = HeadController.action(:switching_protocols).call(Rack::MockRequest.env_for("/")).third.first + content = body(HeadController.action(:switching_protocols).call(Rack::MockRequest.env_for("/"))) assert_empty content end test "head :processing (102) does not return any content" do - content = HeadController.action(:processing).call(Rack::MockRequest.env_for("/")).third.first + content = body(HeadController.action(:processing).call(Rack::MockRequest.env_for("/"))) assert_empty content end + + def body(rack_response) + buf = [] + rack_response[2].each { |x| buf << x } + buf.join + end end class BareControllerTest < ActionController::TestCase diff --git a/actionpack/test/controller/new_base/middleware_test.rb b/actionpack/test/controller/new_base/middleware_test.rb index a30e937bb3..85a1f351f0 100644 --- a/actionpack/test/controller/new_base/middleware_test.rb +++ b/actionpack/test/controller/new_base/middleware_test.rb @@ -75,7 +75,7 @@ module MiddlewareTest test "middleware that is 'use'd is called as part of the Rack application" do result = @app.call(env_for("/")) - assert_equal ["Hello World"], result[2] + assert_equal ["Hello World"], [].tap { |a| result[2].each { |x| a << x } } assert_equal "Success", result[1]["Middleware-Test"] end diff --git a/actionpack/test/controller/new_base/render_html_test.rb b/actionpack/test/controller/new_base/render_html_test.rb index fe11501eeb..e9ea57e329 100644 --- a/actionpack/test/controller/new_base/render_html_test.rb +++ b/actionpack/test/controller/new_base/render_html_test.rb @@ -179,7 +179,7 @@ module RenderHtml test "rendering from minimal controller returns response with text/html content type" do get "/render_html/minimal/index" - assert_content_type "text/html" + assert_content_type "text/html; charset=utf-8" end test "rendering from normal controller returns response with text/html content type" do diff --git a/actionpack/test/controller/new_base/render_plain_test.rb b/actionpack/test/controller/new_base/render_plain_test.rb index 0e36d36b50..0881442bd0 100644 --- a/actionpack/test/controller/new_base/render_plain_test.rb +++ b/actionpack/test/controller/new_base/render_plain_test.rb @@ -157,7 +157,7 @@ module RenderPlain test "rendering from minimal controller returns response with text/plain content type" do get "/render_plain/minimal/index" - assert_content_type "text/plain" + assert_content_type "text/plain; charset=utf-8" end test "rendering from normal controller returns response with text/plain content type" do diff --git a/actionpack/test/controller/parameters/always_permitted_parameters_test.rb b/actionpack/test/controller/parameters/always_permitted_parameters_test.rb index 59be08db54..efaf8a96c3 100644 --- a/actionpack/test/controller/parameters/always_permitted_parameters_test.rb +++ b/actionpack/test/controller/parameters/always_permitted_parameters_test.rb @@ -1,6 +1,5 @@ require 'abstract_unit' require 'action_controller/metal/strong_parameters' -require 'minitest/mock' class AlwaysPermittedParametersTest < ActiveSupport::TestCase def setup diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb index 9acdc29aeb..82c7ebf568 100644 --- a/actionpack/test/controller/render_test.rb +++ b/actionpack/test/controller/render_test.rb @@ -1,6 +1,5 @@ require 'abstract_unit' require 'controller/fake_models' -require 'pathname' class TestControllerWithExtraEtags < ActionController::Base etag { nil } @@ -235,8 +234,6 @@ class MetalTestController < ActionController::Metal include AbstractController::Rendering include ActionView::Rendering include ActionController::Rendering - include ActionController::RackDelegation - def accessing_logger_in_template render :inline => "<%= logger.class %>" @@ -295,9 +292,10 @@ class ExpiresInRenderTest < ActionController::TestCase def test_date_header_when_expires_in time = Time.mktime(2011,10,30) - Time.stubs(:now).returns(time) - get :conditional_hello_with_expires_in - assert_equal Time.now.httpdate, @response.headers["Date"] + Time.stub :now, time do + get :conditional_hello_with_expires_in + assert_equal Time.now.httpdate, @response.headers["Date"] + end end end diff --git a/actionpack/test/controller/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb index 868520a219..90fd8669c2 100644 --- a/actionpack/test/controller/request_forgery_protection_test.rb +++ b/actionpack/test/controller/request_forgery_protection_test.rb @@ -1,5 +1,4 @@ require 'abstract_unit' -require 'digest/sha1' require "active_support/log_subscriber/test_helper" # common controller actions @@ -132,10 +131,7 @@ end # common test methods module RequestForgeryProtectionTests def setup - @token = "cf50faa3fe97702ca1ae" - @controller.stubs(:form_authenticity_token).returns(@token) - @controller.stubs(:valid_authenticity_token?).with{ |_, t| t == @token }.returns(true) - @controller.stubs(:valid_authenticity_token?).with{ |_, t| t != @token }.returns(false) + @token = Base64.strict_encode64('railstestrailstestrailstestrails') @old_request_forgery_protection_token = ActionController::Base.request_forgery_protection_token ActionController::Base.request_forgery_protection_token = :custom_authenticity_token end @@ -145,17 +141,21 @@ module RequestForgeryProtectionTests end def test_should_render_form_with_token_tag - assert_not_blocked do - get :index + @controller.stub :form_authenticity_token, @token do + assert_not_blocked do + get :index + end + assert_select 'form>input[name=?][value=?]', 'custom_authenticity_token', @token end - assert_select 'form>input[name=?][value=?]', 'custom_authenticity_token', @token end def test_should_render_button_to_with_token_tag - assert_not_blocked do - get :show_button + @controller.stub :form_authenticity_token, @token do + assert_not_blocked do + get :show_button + end + assert_select 'form>input[name=?][value=?]', 'custom_authenticity_token', @token end - assert_select 'form>input[name=?][value=?]', 'custom_authenticity_token', @token end def test_should_render_form_without_token_tag_if_remote @@ -199,17 +199,21 @@ module RequestForgeryProtectionTests end def test_should_render_form_with_token_tag_if_remote_and_authenticity_token_requested - assert_not_blocked do - get :form_for_remote_with_token + @controller.stub :form_authenticity_token, @token do + assert_not_blocked do + get :form_for_remote_with_token + end + assert_select 'form>input[name=?][value=?]', 'custom_authenticity_token', @token end - assert_select 'form>input[name=?][value=?]', 'custom_authenticity_token', @token end def test_should_render_form_with_token_tag_with_authenticity_token_requested - assert_not_blocked do - get :form_for_with_token + @controller.stub :form_authenticity_token, @token do + assert_not_blocked do + get :form_for_with_token + end + assert_select 'form>input[name=?][value=?]', 'custom_authenticity_token', @token end - assert_select 'form>input[name=?][value=?]', 'custom_authenticity_token', @token end def test_should_allow_get @@ -249,37 +253,53 @@ module RequestForgeryProtectionTests end def test_should_allow_post_with_token - assert_not_blocked { post :index, params: { custom_authenticity_token: @token } } + session[:_csrf_token] = @token + @controller.stub :form_authenticity_token, @token do + assert_not_blocked { post :index, params: { custom_authenticity_token: @token } } + end end def test_should_allow_patch_with_token - assert_not_blocked { patch :index, params: { custom_authenticity_token: @token } } + session[:_csrf_token] = @token + @controller.stub :form_authenticity_token, @token do + assert_not_blocked { patch :index, params: { custom_authenticity_token: @token } } + end end def test_should_allow_put_with_token - assert_not_blocked { put :index, params: { custom_authenticity_token: @token } } + session[:_csrf_token] = @token + @controller.stub :form_authenticity_token, @token do + assert_not_blocked { put :index, params: { custom_authenticity_token: @token } } + end end def test_should_allow_delete_with_token - assert_not_blocked { delete :index, params: { custom_authenticity_token: @token } } + session[:_csrf_token] = @token + @controller.stub :form_authenticity_token, @token do + assert_not_blocked { delete :index, params: { custom_authenticity_token: @token } } + end end def test_should_allow_post_with_token_in_header + session[:_csrf_token] = @token @request.env['HTTP_X_CSRF_TOKEN'] = @token assert_not_blocked { post :index } end def test_should_allow_delete_with_token_in_header + session[:_csrf_token] = @token @request.env['HTTP_X_CSRF_TOKEN'] = @token assert_not_blocked { delete :index } end def test_should_allow_patch_with_token_in_header + session[:_csrf_token] = @token @request.env['HTTP_X_CSRF_TOKEN'] = @token assert_not_blocked { patch :index } end def test_should_allow_put_with_token_in_header + session[:_csrf_token] = @token @request.env['HTTP_X_CSRF_TOKEN'] = @token assert_not_blocked { put :index } end @@ -333,6 +353,7 @@ module RequestForgeryProtectionTests # Allow non-GET requests since GET is all a remote <script> tag can muster. def test_should_allow_non_get_js_without_xhr_header + session[:_csrf_token] = @token assert_cross_origin_not_blocked { post :same_origin_js, params: { custom_authenticity_token: @token } } assert_cross_origin_not_blocked { post :same_origin_js, params: { format: 'js', custom_authenticity_token: @token } } assert_cross_origin_not_blocked do @@ -402,11 +423,13 @@ class RequestForgeryProtectionControllerUsingResetSessionTest < ActionController end test 'should emit a csrf-param meta tag and a csrf-token meta tag' do - @controller.stubs(:form_authenticity_token).returns(@token + '<=?') - get :meta - assert_select 'meta[name=?][content=?]', 'csrf-param', 'custom_authenticity_token' - assert_select 'meta[name=?]', 'csrf-token' - assert_match(/cf50faa3fe97702ca1ae<=\?/, @response.body) + @controller.stub :form_authenticity_token, @token + '<=?' do + get :meta + assert_select 'meta[name=?][content=?]', 'csrf-param', 'custom_authenticity_token' + assert_select 'meta[name=?]', 'csrf-token' + regexp = "#{@token}<=\?" + assert_match(/#{regexp}/, @response.body) + end end end @@ -485,30 +508,36 @@ class FreeCookieControllerTest < ActionController::TestCase def setup @controller = FreeCookieController.new @token = "cf50faa3fe97702ca1ae" - - SecureRandom.stubs(:base64).returns(@token) super end def test_should_not_render_form_with_token_tag - get :index - assert_select 'form>div>input[name=?][value=?]', 'authenticity_token', @token, false + SecureRandom.stub :base64, @token do + get :index + assert_select 'form>div>input[name=?][value=?]', 'authenticity_token', @token, false + end end def test_should_not_render_button_to_with_token_tag - get :show_button - assert_select 'form>div>input[name=?][value=?]', 'authenticity_token', @token, false + SecureRandom.stub :base64, @token do + get :show_button + assert_select 'form>div>input[name=?][value=?]', 'authenticity_token', @token, false + end end def test_should_allow_all_methods_without_token - [:post, :patch, :put, :delete].each do |method| - assert_nothing_raised { send(method, :index)} + SecureRandom.stub :base64, @token do + [:post, :patch, :put, :delete].each do |method| + assert_nothing_raised { send(method, :index)} + end end end test 'should not emit a csrf-token meta tag' do - get :meta - assert @response.body.blank? + SecureRandom.stub :base64, @token do + get :meta + assert @response.body.blank? + end end end @@ -529,11 +558,11 @@ class CustomAuthenticityParamControllerTest < ActionController::TestCase def test_should_not_warn_if_form_authenticity_param_matches_form_authenticity_token ActionController::Base.logger = @logger - @controller.stubs(:valid_authenticity_token?).returns(:true) - begin - post :index, params: { custom_token_name: 'foobar' } - assert_equal 0, @logger.logged(:warn).size + @controller.stub :valid_authenticity_token?, :true do + post :index, params: { custom_token_name: 'foobar' } + assert_equal 0, @logger.logged(:warn).size + end ensure ActionController::Base.logger = @old_logger end diff --git a/actionpack/test/controller/required_params_test.rb b/actionpack/test/controller/required_params_test.rb index a901e56332..168f64ce41 100644 --- a/actionpack/test/controller/required_params_test.rb +++ b/actionpack/test/controller/required_params_test.rb @@ -48,4 +48,21 @@ class ParametersRequireTest < ActiveSupport::TestCase ActionController::Parameters.new(person: {}).require(:person) end end + + test "require array when all required params are present" do + safe_params = ActionController::Parameters.new(person: {first_name: 'Gaurish', title: 'Mjallo', city: 'Barcelona'}) + .require(:person) + .require([:first_name, :title]) + + assert_kind_of Array, safe_params + assert_equal ['Gaurish', 'Mjallo'], safe_params + end + + test "require array when a required param is missing" do + assert_raises(ActionController::ParameterMissing) do + ActionController::Parameters.new(person: {first_name: 'Gaurish', title: nil}) + .require(:person) + .require([:first_name, :title]) + end + end end diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb index dd7c128566..4490abf7b2 100644 --- a/actionpack/test/controller/resources_test.rb +++ b/actionpack/test/controller/resources_test.rb @@ -149,7 +149,7 @@ class ResourcesTest < ActionController::TestCase end end - assert_restful_named_routes_for :messages do |options| + assert_restful_named_routes_for :messages do actions.each_key do |action| assert_named_route "/messages/#{action}", "#{action}_messages_path", :action => action end @@ -179,7 +179,7 @@ class ResourcesTest < ActionController::TestCase end end - assert_restful_named_routes_for :messages, :path_prefix => 'threads/1/', :name_prefix => 'thread_', :options => { :thread_id => '1' } do |options| + assert_restful_named_routes_for :messages, :path_prefix => 'threads/1/', :name_prefix => 'thread_', :options => { :thread_id => '1' } do actions.each_key do |action| assert_named_route "/threads/1/messages/#{action}", "#{action}_thread_messages_path", :action => action end @@ -206,7 +206,7 @@ class ResourcesTest < ActionController::TestCase end end - assert_restful_named_routes_for :messages, :path_prefix => 'threads/1/', :name_prefix => 'thread_', :options => { :thread_id => '1' } do |options| + assert_restful_named_routes_for :messages, :path_prefix => 'threads/1/', :name_prefix => 'thread_', :options => { :thread_id => '1' } do actions.each_key do |action| assert_named_route "/threads/1/messages/#{action}", "#{action}_thread_messages_path", :action => action end @@ -236,7 +236,7 @@ class ResourcesTest < ActionController::TestCase end end - assert_restful_named_routes_for :messages, :path_prefix => 'threads/1/', :name_prefix => 'thread_', :options => { :thread_id => '1' } do |options| + assert_restful_named_routes_for :messages, :path_prefix => 'threads/1/', :name_prefix => 'thread_', :options => { :thread_id => '1' } do actions.each_key do |action| assert_named_route "/threads/1/messages/#{action}.xml", "#{action}_thread_messages_path", :action => action, :format => 'xml' end @@ -253,7 +253,7 @@ class ResourcesTest < ActionController::TestCase assert_recognizes(options.merge(mark_options), :path => mark_path, :method => method) end - assert_restful_named_routes_for :messages do |options| + assert_restful_named_routes_for :messages do assert_named_route mark_path, :mark_message_path, mark_options end end @@ -278,7 +278,7 @@ class ResourcesTest < ActionController::TestCase assert_recognizes(options.merge(mark_options), :path => mark_path, :method => method) end - assert_restful_named_routes_for :messages, :path_names => {:new => 'nuevo'} do |options| + assert_restful_named_routes_for :messages, :path_names => {:new => 'nuevo'} do assert_named_route mark_path, :mark_message_path, mark_options end end @@ -304,7 +304,7 @@ class ResourcesTest < ActionController::TestCase assert_recognizes(options.merge(action_options), :path => action_path, :method => method) end - assert_restful_named_routes_for :messages do |options| + assert_restful_named_routes_for :messages do assert_named_route action_path, "#{action}_message_path".to_sym, action_options end end @@ -351,7 +351,7 @@ class ResourcesTest < ActionController::TestCase assert_recognizes(options.merge(preview_options), :path => preview_path, :method => :post) end - assert_restful_named_routes_for :messages do |options| + assert_restful_named_routes_for :messages do assert_named_route preview_path, :preview_new_message_path, preview_options end end @@ -373,7 +373,7 @@ class ResourcesTest < ActionController::TestCase assert_recognizes(options.merge(preview_options), :path => preview_path, :method => :post) end - assert_restful_named_routes_for :messages, :path_prefix => 'threads/1/', :name_prefix => 'thread_', :options => { :thread_id => '1' } do |options| + assert_restful_named_routes_for :messages, :path_prefix => 'threads/1/', :name_prefix => 'thread_', :options => { :thread_id => '1' } do assert_named_route preview_path, :preview_new_thread_message_path, preview_options end end @@ -395,7 +395,7 @@ class ResourcesTest < ActionController::TestCase assert_recognizes(options.merge(preview_options), :path => preview_path, :method => :post) end - assert_restful_named_routes_for :messages, :path_prefix => 'threads/1/', :name_prefix => 'thread_', :options => { :thread_id => '1' } do |options| + assert_restful_named_routes_for :messages, :path_prefix => 'threads/1/', :name_prefix => 'thread_', :options => { :thread_id => '1' } do assert_named_route preview_path, :preview_new_thread_message_path, preview_options end end @@ -519,9 +519,9 @@ class ResourcesTest < ActionController::TestCase end def test_should_create_multiple_singleton_resource_routes - with_singleton_resources :account, :logo do + with_singleton_resources :account, :product do assert_singleton_restful_for :account - assert_singleton_restful_for :logo + assert_singleton_restful_for :product end end @@ -553,7 +553,7 @@ class ResourcesTest < ActionController::TestCase assert_recognizes(options.merge(reset_options), :path => reset_path, :method => method) end - assert_singleton_named_routes_for :account do |options| + assert_singleton_named_routes_for :account do assert_named_route reset_path, :reset_account_path, reset_options end end @@ -577,7 +577,7 @@ class ResourcesTest < ActionController::TestCase assert_recognizes(options.merge(action_options), :path => action_path, :method => method) end - assert_singleton_named_routes_for :account do |options| + assert_singleton_named_routes_for :account do assert_named_route action_path, "#{action}_account_path".to_sym, action_options end end @@ -1070,8 +1070,8 @@ class ResourcesTest < ActionController::TestCase end def test_singleton_resource_name_is_not_singularized - with_singleton_resources(:preferences) do - assert_singleton_restful_for :preferences + with_singleton_resources(:products) do + assert_singleton_restful_for :products end end diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb index feb3e7eab7..4a2b02a003 100644 --- a/actionpack/test/controller/routing_test.rb +++ b/actionpack/test/controller/routing_test.rb @@ -336,6 +336,16 @@ class LegacyRouteSetTests < ActiveSupport::TestCase assert_equal({controller: 'content', action: 'translate', url: 'example'}, rs.recognize_path('/example')) end + def test_route_with_regexp_for_action + rs.draw { get '/:controller/:action', action: /auth[-|_].+/ } + + assert_equal({ action: 'auth_google', controller: 'content' }, rs.recognize_path('/content/auth_google')) + assert_equal({ action: 'auth-facebook', controller: 'content' }, rs.recognize_path('/content/auth-facebook')) + + assert_equal '/content/auth_google', url_for(rs, { controller: "content", action: "auth_google" }) + assert_equal '/content/auth-facebook', url_for(rs, { controller: "content", action: "auth-facebook" }) + end + def test_route_with_regexp_for_controller rs.draw do get ':controller/:admintoken(/:action(/:id))', :controller => /admin\/.+/ @@ -1742,40 +1752,10 @@ class RouteSetTest < ActiveSupport::TestCase include ActionDispatch::RoutingVerbs - class TestSet < ActionDispatch::Routing::RouteSet - def initialize(block) - @block = block - super() - end - - class Dispatcher < ActionDispatch::Routing::RouteSet::Dispatcher - def initialize(defaults, set, block) - super(defaults) - @block = block - @set = set - end - - def controller_reference(controller_param) - block = @block - set = @set - Class.new(ActionController::Base) { - include set.url_helpers - define_method(:process) { |name| block.call(self) } - def to_a; [200, {}, []]; end - } - end - end - - def dispatcher defaults - TestSet::Dispatcher.new defaults, self, @block - end - end - alias :routes :set def test_generate_with_optional_params_recalls_last_request - controller = nil - @set = TestSet.new ->(c) { controller = c } + @set = make_set false set.draw do get "blog/", :controller => "blog", :action => "index" diff --git a/actionpack/test/controller/test_case_test.rb b/actionpack/test/controller/test_case_test.rb index 1c5de983d8..b3c3979c84 100644 --- a/actionpack/test/controller/test_case_test.rb +++ b/actionpack/test/controller/test_case_test.rb @@ -4,6 +4,8 @@ require 'active_support/json/decoding' require 'rails/engine' class TestCaseTest < ActionController::TestCase + def self.fixture_path; end; + class TestController < ActionController::Base def no_op render plain: 'dummy' @@ -158,7 +160,7 @@ XML def setup super @controller = TestController.new - @request.env['PATH_INFO'] = nil + @request.delete_header 'PATH_INFO' @routes = ActionDispatch::Routing::RouteSet.new.tap do |r| r.draw do get ':controller(/:action(/:id))' @@ -849,10 +851,10 @@ XML end def test_fixture_path_is_accessed_from_self_instead_of_active_support_test_case - TestCaseTest.stubs(:fixture_path).returns(FILES_DIR) - - uploaded_file = fixture_file_upload('/mona_lisa.jpg', 'image/png') - assert_equal File.open("#{FILES_DIR}/mona_lisa.jpg", READ_PLAIN).read, uploaded_file.read + TestCaseTest.stub :fixture_path, FILES_DIR do + uploaded_file = fixture_file_upload('/mona_lisa.jpg', 'image/png') + assert_equal File.open("#{FILES_DIR}/mona_lisa.jpg", READ_PLAIN).read, uploaded_file.read + end end def test_test_uploaded_file_with_binary @@ -893,13 +895,13 @@ XML end def test_fixture_file_upload_relative_to_fixture_path - TestCaseTest.stubs(:fixture_path).returns(FILES_DIR) - uploaded_file = fixture_file_upload("mona_lisa.jpg", "image/jpg") - assert_equal File.open("#{FILES_DIR}/mona_lisa.jpg", READ_PLAIN).read, uploaded_file.read + TestCaseTest.stub :fixture_path, FILES_DIR do + uploaded_file = fixture_file_upload("mona_lisa.jpg", "image/jpg") + assert_equal File.open("#{FILES_DIR}/mona_lisa.jpg", READ_PLAIN).read, uploaded_file.read + end end def test_fixture_file_upload_ignores_nil_fixture_path - TestCaseTest.stubs(:fixture_path).returns(nil) uploaded_file = fixture_file_upload("#{FILES_DIR}/mona_lisa.jpg", "image/jpg") assert_equal File.open("#{FILES_DIR}/mona_lisa.jpg", READ_PLAIN).read, uploaded_file.read end diff --git a/actionpack/test/dispatch/callbacks_test.rb b/actionpack/test/dispatch/callbacks_test.rb index f767b07e75..5ba76d9ab9 100644 --- a/actionpack/test/dispatch/callbacks_test.rb +++ b/actionpack/test/dispatch/callbacks_test.rb @@ -28,7 +28,7 @@ class DispatcherTest < ActiveSupport::TestCase assert_equal 4, Foo.a assert_equal 4, Foo.b - dispatch do |env| + dispatch do raise "error" end rescue nil assert_equal 6, Foo.a diff --git a/actionpack/test/dispatch/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb index aca28ae8d1..3454e60697 100644 --- a/actionpack/test/dispatch/cookies_test.rb +++ b/actionpack/test/dispatch/cookies_test.rb @@ -321,10 +321,12 @@ class CookiesTest < ActionController::TestCase end def test_setting_cookie_with_secure_when_always_write_cookie_is_true - ActionDispatch::Cookies::CookieJar.any_instance.stubs(:always_write_cookie).returns(true) + old_cookie, @request.cookie_jar.always_write_cookie = @request.cookie_jar.always_write_cookie, true get :authenticate_with_secure assert_cookie_header "user_name=david; path=/; secure" assert_equal({"user_name" => "david"}, @response.cookies) + ensure + @request.cookie_jar.always_write_cookie = old_cookie end def test_not_setting_cookie_with_secure diff --git a/actionpack/test/dispatch/debug_exceptions_test.rb b/actionpack/test/dispatch/debug_exceptions_test.rb index a867aee7ec..f9f379780c 100644 --- a/actionpack/test/dispatch/debug_exceptions_test.rb +++ b/actionpack/test/dispatch/debug_exceptions_test.rb @@ -71,14 +71,6 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest end end - def setup - app = ActiveSupport::OrderedOptions.new - app.config = ActiveSupport::OrderedOptions.new - app.config.assets = ActiveSupport::OrderedOptions.new - app.config.assets.prefix = '/sprockets' - Rails.stubs(:application).returns(app) - end - RoutesApp = Struct.new(:routes).new(SharedTestRoutes) ProductionApp = ActionDispatch::DebugExceptions.new(Boomer.new(false), RoutesApp) DevelopmentApp = ActionDispatch::DebugExceptions.new(Boomer.new(true), RoutesApp) @@ -338,36 +330,37 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest test 'debug exceptions app shows user code that caused the error in source view' do @app = DevelopmentApp - Rails.stubs(:root).returns(Pathname.new('.')) - cleaner = ActiveSupport::BacktraceCleaner.new.tap do |bc| - bc.add_silencer { |line| line =~ /method_that_raises/ } - bc.add_silencer { |line| line !~ %r{test/dispatch/debug_exceptions_test.rb} } - end + Rails.stub :root, Pathname.new('.') do + cleaner = ActiveSupport::BacktraceCleaner.new.tap do |bc| + bc.add_silencer { |line| line =~ /method_that_raises/ } + bc.add_silencer { |line| line !~ %r{test/dispatch/debug_exceptions_test.rb} } + end - get '/framework_raises', headers: { 'action_dispatch.backtrace_cleaner' => cleaner } + get '/framework_raises', headers: { 'action_dispatch.backtrace_cleaner' => cleaner } - # Assert correct error - assert_response 500 - assert_select 'h2', /error in framework/ + # Assert correct error + assert_response 500 + assert_select 'h2', /error in framework/ - # assert source view line is the call to method_that_raises - assert_select 'div.source:not(.hidden)' do - assert_select 'pre .line.active', /method_that_raises/ - end + # assert source view line is the call to method_that_raises + assert_select 'div.source:not(.hidden)' do + assert_select 'pre .line.active', /method_that_raises/ + end - # assert first source view (hidden) that throws the error - assert_select 'div.source:first' do - assert_select 'pre .line.active', /raise StandardError\.new/ - end + # assert first source view (hidden) that throws the error + assert_select 'div.source:first' do + assert_select 'pre .line.active', /raise StandardError\.new/ + end - # assert application trace refers to line that calls method_that_raises is first - assert_select '#Application-Trace' do - assert_select 'pre code a:first', %r{test/dispatch/debug_exceptions_test\.rb:\d+:in `call} - end + # assert application trace refers to line that calls method_that_raises is first + assert_select '#Application-Trace' do + assert_select 'pre code a:first', %r{test/dispatch/debug_exceptions_test\.rb:\d+:in `call} + end - # assert framework trace that that threw the error is first - assert_select '#Framework-Trace' do - assert_select 'pre code a:first', /method_that_raises/ + # assert framework trace that that threw the error is first + assert_select '#Framework-Trace' do + assert_select 'pre code a:first', /method_that_raises/ + end end end end diff --git a/actionpack/test/dispatch/exception_wrapper_test.rb b/actionpack/test/dispatch/exception_wrapper_test.rb index 3dac582407..f37cce4d45 100644 --- a/actionpack/test/dispatch/exception_wrapper_test.rb +++ b/actionpack/test/dispatch/exception_wrapper_test.rb @@ -17,8 +17,6 @@ module ActionDispatch end setup do - Rails.stubs(:root).returns(Pathname.new('.')) - @cleaner = ActiveSupport::BacktraceCleaner.new @cleaner.add_silencer { |line| line !~ /^lib/ } end diff --git a/actionpack/test/dispatch/header_test.rb b/actionpack/test/dispatch/header_test.rb index e2b38c23bc..79600b654b 100644 --- a/actionpack/test/dispatch/header_test.rb +++ b/actionpack/test/dispatch/header_test.rb @@ -1,15 +1,19 @@ require "abstract_unit" class HeaderTest < ActiveSupport::TestCase + def make_headers(hash) + ActionDispatch::Http::Headers.new ActionDispatch::Request.new hash + end + setup do - @headers = ActionDispatch::Http::Headers.new( + @headers = make_headers( "CONTENT_TYPE" => "text/plain", "HTTP_REFERER" => "/some/page" ) end test "#new does not normalize the data" do - headers = ActionDispatch::Http::Headers.new( + headers = make_headers( "Content-Type" => "application/json", "HTTP_REFERER" => "/some/page", "Host" => "http://test.com") @@ -108,7 +112,7 @@ class HeaderTest < ActiveSupport::TestCase end test "env variables with . are not modified" do - headers = ActionDispatch::Http::Headers.new + headers = make_headers({}) headers.merge! "rack.input" => "", "rack.request.cookie_hash" => "", "action_dispatch.logger" => "" @@ -119,7 +123,7 @@ class HeaderTest < ActiveSupport::TestCase end test "symbols are treated as strings" do - headers = ActionDispatch::Http::Headers.new + headers = make_headers({}) headers.merge!(:SERVER_NAME => "example.com", "HTTP_REFERER" => "/", :Host => "test.com") @@ -130,7 +134,7 @@ class HeaderTest < ActiveSupport::TestCase test "headers directly modifies the passed environment" do env = {"HTTP_REFERER" => "/"} - headers = ActionDispatch::Http::Headers.new(env) + headers = make_headers(env) headers['Referer'] = "http://example.com/" headers.merge! "CONTENT_TYPE" => "text/plain" assert_equal({"HTTP_REFERER"=>"http://example.com/", diff --git a/actionpack/test/dispatch/live_response_test.rb b/actionpack/test/dispatch/live_response_test.rb index 5cfa5f7b3b..1c128365c7 100644 --- a/actionpack/test/dispatch/live_response_test.rb +++ b/actionpack/test/dispatch/live_response_test.rb @@ -65,7 +65,7 @@ module ActionController latch = Concurrent::CountDownLatch.new t = Thread.new { - @response.stream.each do |chunk| + @response.stream.each do latch.count_down end } diff --git a/actionpack/test/dispatch/request/session_test.rb b/actionpack/test/dispatch/request/session_test.rb index 8bcc07dd04..410e3194e2 100644 --- a/actionpack/test/dispatch/request/session_test.rb +++ b/actionpack/test/dispatch/request/session_test.rb @@ -4,40 +4,42 @@ require 'action_dispatch/middleware/session/abstract_store' module ActionDispatch class Request class SessionTest < ActiveSupport::TestCase + attr_reader :req + + def setup + @req = ActionDispatch::Request.new({}) + end + def test_create_adds_itself_to_env - env = {} - s = Session.create(store, env, {}) - assert_equal s, env[Rack::RACK_SESSION] + s = Session.create(store, req, {}) + assert_equal s, req.env[Rack::RACK_SESSION] end def test_to_hash - env = {} - s = Session.create(store, env, {}) + s = Session.create(store, req, {}) s['foo'] = 'bar' assert_equal 'bar', s['foo'] assert_equal({'foo' => 'bar'}, s.to_hash) end def test_create_merges_old - env = {} - s = Session.create(store, env, {}) + s = Session.create(store, req, {}) s['foo'] = 'bar' - s1 = Session.create(store, env, {}) + s1 = Session.create(store, req, {}) assert_not_equal s, s1 assert_equal 'bar', s1['foo'] end def test_find - env = {} - assert_nil Session.find(env) + assert_nil Session.find(req) - s = Session.create(store, env, {}) - assert_equal s, Session.find(env) + s = Session.create(store, req, {}) + assert_equal s, Session.find(req) end def test_destroy - s = Session.create(store, {}, {}) + s = Session.create(store, req, {}) s['rails'] = 'ftw' s.destroy @@ -46,21 +48,21 @@ module ActionDispatch end def test_keys - s = Session.create(store, {}, {}) + s = Session.create(store, req, {}) s['rails'] = 'ftw' s['adequate'] = 'awesome' assert_equal %w[rails adequate], s.keys end def test_values - s = Session.create(store, {}, {}) + s = Session.create(store, req, {}) s['rails'] = 'ftw' s['adequate'] = 'awesome' assert_equal %w[ftw awesome], s.values end def test_clear - s = Session.create(store, {}, {}) + s = Session.create(store, req, {}) s['rails'] = 'ftw' s['adequate'] = 'awesome' @@ -69,7 +71,7 @@ module ActionDispatch end def test_update - s = Session.create(store, {}, {}) + s = Session.create(store, req, {}) s['rails'] = 'ftw' s.update(:rails => 'awesome') @@ -79,7 +81,7 @@ module ActionDispatch end def test_delete - s = Session.create(store, {}, {}) + s = Session.create(store, req, {}) s['rails'] = 'ftw' s.delete('rails') @@ -88,7 +90,7 @@ module ActionDispatch end def test_fetch - session = Session.create(store, {}, {}) + session = Session.create(store, req, {}) session['one'] = '1' assert_equal '1', session.fetch(:one) diff --git a/actionpack/test/dispatch/routing/inspector_test.rb b/actionpack/test/dispatch/routing/inspector_test.rb index 4047214843..24bd4b04ec 100644 --- a/actionpack/test/dispatch/routing/inspector_test.rb +++ b/actionpack/test/dispatch/routing/inspector_test.rb @@ -12,12 +12,6 @@ module ActionDispatch class RoutesInspectorTest < ActiveSupport::TestCase def setup @set = ActionDispatch::Routing::RouteSet.new - app = ActiveSupport::OrderedOptions.new - app.config = ActiveSupport::OrderedOptions.new - app.config.assets = ActiveSupport::OrderedOptions.new - app.config.assets.prefix = '/sprockets' - Rails.stubs(:application).returns(app) - Rails.stubs(:env).returns("development") end def draw(options = {}, &block) @@ -316,9 +310,6 @@ module ActionDispatch def test_inspect_routes_shows_resources_route_when_assets_disabled @set = ActionDispatch::Routing::RouteSet.new - app = ActiveSupport::OrderedOptions.new - - Rails.stubs(:application).returns(app) output = draw do get '/cart', to: 'cart#show' diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index 332a550de0..8972f3e74d 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -1,4 +1,3 @@ -# encoding: UTF-8 require 'erb' require 'abstract_unit' require 'controller/fake_controllers' @@ -4190,11 +4189,11 @@ class TestNamedRouteUrlHelpers < ActionDispatch::IntegrationTest include Routes.url_helpers test "url helpers do not ignore nil parameters when using non-optimized routes" do - Routes.stubs(:optimize_routes_generation?).returns(false) - - get "/categories/1" - assert_response :success - assert_raises(ActionController::UrlGenerationError) { product_path(nil) } + Routes.stub :optimize_routes_generation?, false do + get "/categories/1" + assert_response :success + assert_raises(ActionController::UrlGenerationError) { product_path(nil) } + end end end diff --git a/actionpack/test/dispatch/session/abstract_store_test.rb b/actionpack/test/dispatch/session/abstract_store_test.rb index fe1a7b4f86..1c35144e6f 100644 --- a/actionpack/test/dispatch/session/abstract_store_test.rb +++ b/actionpack/test/dispatch/session/abstract_store_test.rb @@ -27,7 +27,7 @@ module ActionDispatch as.call(env) assert @env - assert Request::Session.find @env + assert Request::Session.find ActionDispatch::Request.new @env end def test_new_session_object_is_merged_with_old @@ -36,11 +36,11 @@ module ActionDispatch as.call(env) assert @env - session = Request::Session.find @env + session = Request::Session.find ActionDispatch::Request.new @env session['foo'] = 'bar' as.call(@env) - session1 = Request::Session.find @env + session1 = Request::Session.find ActionDispatch::Request.new @env assert_not_equal session, session1 assert_equal session.to_hash, session1.to_hash diff --git a/actionpack/test/dispatch/session/cookie_store_test.rb b/actionpack/test/dispatch/session/cookie_store_test.rb index e432c65c62..f07e215e3a 100644 --- a/actionpack/test/dispatch/session/cookie_store_test.rb +++ b/actionpack/test/dispatch/session/cookie_store_test.rb @@ -274,28 +274,32 @@ class CookieStoreTest < ActionDispatch::IntegrationTest with_test_route_set(:expire_after => 5.hours) do # First request accesses the session time = Time.local(2008, 4, 24) - Time.stubs(:now).returns(time) - expected_expiry = (time + 5.hours).gmtime.strftime("%a, %d %b %Y %H:%M:%S -0000") + cookie_body = nil - cookies[SessionKey] = SignedBar + Time.stub :now, time do + expected_expiry = (time + 5.hours).gmtime.strftime("%a, %d %b %Y %H:%M:%S -0000") - get '/set_session_value' - assert_response :success + cookies[SessionKey] = SignedBar - cookie_body = response.body - assert_equal "_myapp_session=#{cookie_body}; path=/; expires=#{expected_expiry}; HttpOnly", - headers['Set-Cookie'] + get '/set_session_value' + assert_response :success + + cookie_body = response.body + assert_equal "_myapp_session=#{cookie_body}; path=/; expires=#{expected_expiry}; HttpOnly", + headers['Set-Cookie'] + end # Second request does not access the session time = Time.local(2008, 4, 25) - Time.stubs(:now).returns(time) - expected_expiry = (time + 5.hours).gmtime.strftime("%a, %d %b %Y %H:%M:%S -0000") + Time.stub :now, time do + expected_expiry = (time + 5.hours).gmtime.strftime("%a, %d %b %Y %H:%M:%S -0000") - get '/no_session_access' - assert_response :success + get '/no_session_access' + assert_response :success - assert_equal "_myapp_session=#{cookie_body}; path=/; expires=#{expected_expiry}; HttpOnly", - headers['Set-Cookie'] + assert_equal "_myapp_session=#{cookie_body}; path=/; expires=#{expected_expiry}; HttpOnly", + headers['Set-Cookie'] + end end end diff --git a/actionpack/test/dispatch/session/test_session_test.rb b/actionpack/test/dispatch/session/test_session_test.rb index 59c030176b..3e61d123e3 100644 --- a/actionpack/test/dispatch/session/test_session_test.rb +++ b/actionpack/test/dispatch/session/test_session_test.rb @@ -46,6 +46,16 @@ class ActionController::TestSessionTest < ActiveSupport::TestCase assert_equal('2', session.fetch(:two, '2')) end + def test_fetch_on_symbol_returns_value + session = ActionController::TestSession.new(one: '1') + assert_equal('1', session.fetch(:one)) + end + + def test_fetch_on_string_returns_value + session = ActionController::TestSession.new(one: '1') + assert_equal('1', session.fetch('one')) + end + def test_fetch_returns_block_value session = ActionController::TestSession.new(one: '1') assert_equal(2, session.fetch('2') { |key| key.to_i }) diff --git a/actionpack/test/dispatch/test_request_test.rb b/actionpack/test/dispatch/test_request_test.rb index ede1cec4e6..51c469a61a 100644 --- a/actionpack/test/dispatch/test_request_test.rb +++ b/actionpack/test/dispatch/test_request_test.rb @@ -53,10 +53,8 @@ class TestRequestTest < ActiveSupport::TestCase assert_cookies({"user_name" => "david"}, req.cookie_jar) end - test "does not complain when Rails.application is nil" do - Rails.stubs(:application).returns(nil) + test "does not complain when there is no application config" do req = ActionDispatch::TestRequest.create({}) - assert_equal false, req.env.empty? end diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md index 90c9c2171c..f97ca88b87 100644 --- a/actionview/CHANGELOG.md +++ b/actionview/CHANGELOG.md @@ -1,3 +1,13 @@ +* `number_to_currency` and `number_with_delimiter` now accept custom `delimiter_pattern` option + to handle placement of delimiter, to support currency formats like INR + + Example: + + number_to_currency(1230000, delimiter_pattern: /(\d+?)(?=(\d\d)+(\d)(?!\d))/, unit: '₹', format: "%u %n") + # => '₹ 12,30,000.00' + + *Vipul A M* + * Make `disable_with` the default behavior for submit tags. Disables the button on submit to prevent double submits. diff --git a/actionview/lib/action_view/layouts.rb b/actionview/lib/action_view/layouts.rb index 9d636c8c9e..cabba967c9 100644 --- a/actionview/lib/action_view/layouts.rb +++ b/actionview/lib/action_view/layouts.rb @@ -277,7 +277,7 @@ module ActionView remove_possible_method(:_layout) prefixes = _implied_layout_name =~ /\blayouts/ ? [] : ["layouts"] - default_behavior = "lookup_context.find_all('#{_implied_layout_name}', #{prefixes.inspect}).first || super" + default_behavior = "lookup_context.find_all('#{_implied_layout_name}', #{prefixes.inspect}, false, [], { formats: formats }).first || super" name_clause = if name default_behavior else @@ -316,7 +316,7 @@ module ActionView end self.class_eval <<-RUBY, __FILE__, __LINE__ + 1 - def _layout + def _layout(formats) if _conditional_layout? #{layout_definition} else @@ -372,7 +372,7 @@ module ActionView end # This will be overwritten by _write_layout_method - def _layout; end + def _layout(*); end # Determine the layout for a given name, taking into account the name type. # @@ -382,8 +382,8 @@ module ActionView case name when String then _normalize_layout(name) when Proc then name - when true then Proc.new { _default_layout(true) } - when :default then Proc.new { _default_layout(false) } + when true then Proc.new { |formats| _default_layout(formats, true) } + when :default then Proc.new { |formats| _default_layout(formats, false) } when false, nil then nil else raise ArgumentError, @@ -399,14 +399,15 @@ module ActionView # Optionally raises an exception if the layout could not be found. # # ==== Parameters + # * <tt>formats</tt> - The formats accepted to this layout # * <tt>require_layout</tt> - If set to true and layout is not found, # an ArgumentError exception is raised (defaults to false) # # ==== Returns # * <tt>template</tt> - The template object for the default layout (or nil) - def _default_layout(require_layout = false) + def _default_layout(formats, require_layout = false) begin - value = _layout if action_has_layout? + value = _layout(formats) if action_has_layout? rescue NameError => e raise e, "Could not render layout: #{e.message}" end diff --git a/actionview/lib/action_view/lookup_context.rb b/actionview/lib/action_view/lookup_context.rb index f4a10aa393..fba9a44cb1 100644 --- a/actionview/lib/action_view/lookup_context.rb +++ b/actionview/lib/action_view/lookup_context.rb @@ -229,21 +229,5 @@ module ActionView super(default_locale) end - - # Uses the first format in the formats array for layout lookup. - def with_layout_format - if formats.size == 1 - yield - else - old_formats = formats - _set_detail(:formats, formats[0,1]) - - begin - yield - ensure - _set_detail(:formats, old_formats) - end - end - end end end diff --git a/actionview/lib/action_view/renderer/streaming_template_renderer.rb b/actionview/lib/action_view/renderer/streaming_template_renderer.rb index 3ab2cd36fc..f38e2764d0 100644 --- a/actionview/lib/action_view/renderer/streaming_template_renderer.rb +++ b/actionview/lib/action_view/renderer/streaming_template_renderer.rb @@ -47,7 +47,7 @@ module ActionView return [super] unless layout_name && template.supports_streaming? locals ||= {} - layout = layout_name && find_layout(layout_name, locals.keys) + layout = layout_name && find_layout(layout_name, locals.keys, [formats.first]) Body.new do |buffer| delayed_render(buffer, template, layout, @view, locals) diff --git a/actionview/lib/action_view/renderer/template_renderer.rb b/actionview/lib/action_view/renderer/template_renderer.rb index dbb4855e39..75217e1630 100644 --- a/actionview/lib/action_view/renderer/template_renderer.rb +++ b/actionview/lib/action_view/renderer/template_renderer.rb @@ -57,7 +57,7 @@ module ActionView end def render_with_layout(path, locals) #:nodoc: - layout = path && find_layout(path, locals.keys) + layout = path && find_layout(path, locals.keys, [formats.first]) content = yield(layout) if layout @@ -72,27 +72,28 @@ module ActionView # This is the method which actually finds the layout using details in the lookup # context object. If no layout is found, it checks if at least a layout with # the given name exists across all details before raising the error. - def find_layout(layout, keys) - with_layout_format { resolve_layout(layout, keys) } + def find_layout(layout, keys, formats) + resolve_layout(layout, keys, formats) end - def resolve_layout(layout, keys) + def resolve_layout(layout, keys, formats) + details = @details.dup + details[:formats] = formats + case layout when String begin if layout =~ /^\// - with_fallbacks { find_template(layout, nil, false, keys, @details) } + with_fallbacks { find_template(layout, nil, false, keys, details) } else - find_template(layout, nil, false, keys, @details) + find_template(layout, nil, false, keys, details) end rescue ActionView::MissingTemplate all_details = @details.merge(:formats => @lookup_context.default_formats) raise unless template_exists?(layout, nil, false, keys, all_details) end when Proc - resolve_layout(layout.call, keys) - when FalseClass - nil + resolve_layout(layout.call(formats), keys, formats) else layout end diff --git a/actionview/lib/action_view/rendering.rb b/actionview/lib/action_view/rendering.rb index 86a80a5421..8604637da2 100644 --- a/actionview/lib/action_view/rendering.rb +++ b/actionview/lib/action_view/rendering.rb @@ -104,7 +104,7 @@ module ActionView end # Assign the rendered format to look up context. - def _process_format(format, options = {}) #:nodoc: + def _process_format(format) #:nodoc: super lookup_context.formats = [format.to_sym] lookup_context.rendered_format = lookup_context.formats.first diff --git a/actionview/test/abstract_unit.rb b/actionview/test/abstract_unit.rb index 35f520ed1c..12870acaa6 100644 --- a/actionview/test/abstract_unit.rb +++ b/actionview/test/abstract_unit.rb @@ -16,6 +16,7 @@ silence_warnings do end require 'active_support/testing/autorun' +require 'active_support/testing/method_call_assertions' require 'action_controller' require 'action_view' require 'action_view/testing/resolvers' @@ -281,3 +282,6 @@ def jruby_skip(message = '') end require 'mocha/setup' # FIXME: stop using mocha +class ActiveSupport::TestCase + include ActiveSupport::Testing::MethodCallAssertions +end diff --git a/actionview/test/template/asset_tag_helper_test.rb b/actionview/test/template/asset_tag_helper_test.rb index 01fc66bed6..496b33b35e 100644 --- a/actionview/test/template/asset_tag_helper_test.rb +++ b/actionview/test/template/asset_tag_helper_test.rb @@ -588,11 +588,13 @@ class AssetTagHelperTest < ActionView::TestCase end end - @controller.request.stubs(:ssl?).returns(false) - assert_equal "http://assets15.example.com/images/xml.png", image_path("xml.png") + @controller.request.stub(:ssl?, false) do + assert_equal "http://assets15.example.com/images/xml.png", image_path("xml.png") + end - @controller.request.stubs(:ssl?).returns(true) - assert_equal "http://localhost/images/xml.png", image_path("xml.png") + @controller.request.stub(:ssl?, true) do + assert_equal "http://localhost/images/xml.png", image_path("xml.png") + end end end diff --git a/actionview/test/template/form_helper_test.rb b/actionview/test/template/form_helper_test.rb index 37f2d7cad6..aef137935a 100644 --- a/actionview/test/template/form_helper_test.rb +++ b/actionview/test/template/form_helper_test.rb @@ -2003,21 +2003,22 @@ class FormHelperTest < ActionView::TestCase def test_form_for_with_remote_without_html @post.persisted = false - @post.stubs(:to_key).returns(nil) - form_for(@post, remote: true) do |f| - concat f.text_field(:title) - concat f.text_area(:body) - concat f.check_box(:secret) - end + @post.stub(:to_key, nil) do + form_for(@post, remote: true) do |f| + concat f.text_field(:title) + concat f.text_area(:body) + concat f.check_box(:secret) + end - expected = whole_form("/posts", "new_post", "new_post", remote: true) do - "<input name='post[title]' type='text' id='post_title' value='Hello World' />" + - "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" + - "<input name='post[secret]' type='hidden' value='0' />" + - "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" - end + expected = whole_form("/posts", "new_post", "new_post", remote: true) do + "<input name='post[title]' type='text' id='post_title' value='Hello World' />" + + "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" + + "<input name='post[secret]' type='hidden' value='0' />" + + "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" + end - assert_dom_equal expected, output_buffer + assert_dom_equal expected, output_buffer + end end def test_form_for_without_object @@ -2220,16 +2221,17 @@ class FormHelperTest < ActionView::TestCase def test_submit_with_object_as_new_record_and_locale_strings with_locale :submit do @post.persisted = false - @post.stubs(:to_key).returns(nil) - form_for(@post) do |f| - concat f.submit - end + @post.stub(:to_key, nil) do + form_for(@post) do |f| + concat f.submit + end - expected = whole_form('/posts', 'new_post', 'new_post') do - "<input name='commit' data-disable-with='Create Post' type='submit' value='Create Post' />" - end + expected = whole_form('/posts', 'new_post', 'new_post') do + "<input name='commit' data-disable-with='Create Post' type='submit' value='Create Post' />" + end - assert_dom_equal expected, output_buffer + assert_dom_equal expected, output_buffer + end end end @@ -2807,11 +2809,12 @@ class FormHelperTest < ActionView::TestCase def test_nested_fields_label_translation_with_more_than_10_records @post.comments = Array.new(11) { |id| Comment.new(id + 1) } - I18n.expects(:t).with('post.comments.body', default: [:"comment.body", ''], scope: "helpers.label").times(11).returns "Write body here" - - form_for(@post) do |f| - f.fields_for(:comments) do |cf| - concat cf.label(:body) + params = 11.times.map { ['post.comments.body', default: [:"comment.body", ''], scope: "helpers.label"] } + assert_called_with(I18n, :t, params, returns: "Write body here") do + form_for(@post) do |f| + f.fields_for(:comments) do |cf| + concat cf.label(:body) + end end end end diff --git a/actionview/test/template/form_options_helper_i18n_test.rb b/actionview/test/template/form_options_helper_i18n_test.rb index 4972ea6511..26ede09a5f 100644 --- a/actionview/test/template/form_options_helper_i18n_test.rb +++ b/actionview/test/template/form_options_helper_i18n_test.rb @@ -14,8 +14,9 @@ class FormOptionsHelperI18nTests < ActionView::TestCase end def test_select_with_prompt_true_translates_prompt_message - I18n.expects(:translate).with('helpers.select.prompt', { :default => 'Please select' }) - select('post', 'category', [], :prompt => true) + assert_called_with(I18n, :translate, ['helpers.select.prompt', { :default => 'Please select' }]) do + select('post', 'category', [], :prompt => true) + end end def test_select_with_translated_prompt diff --git a/actionview/test/template/lookup_context_test.rb b/actionview/test/template/lookup_context_test.rb index 4f7823045e..14dafd7d63 100644 --- a/actionview/test/template/lookup_context_test.rb +++ b/actionview/test/template/lookup_context_test.rb @@ -108,10 +108,11 @@ class LookupContextTest < ActiveSupport::TestCase end test "found templates respects given formats if one cannot be found from template or handler" do - ActionView::Template::Handlers::Builder.expects(:default_format).returns(nil) - @lookup_context.formats = [:text] - template = @lookup_context.find("hello", %w(test)) - assert_equal [:text], template.formats + assert_called(ActionView::Template::Handlers::Builder, :default_format, returns: nil) do + @lookup_context.formats = [:text] + template = @lookup_context.find("hello", %w(test)) + assert_equal [:text], template.formats + end end test "adds fallbacks to view paths when required" do @@ -210,45 +211,50 @@ end class LookupContextWithFalseCaching < ActiveSupport::TestCase def setup @resolver = ActionView::FixtureResolver.new("test/_foo.erb" => ["Foo", Time.utc(2000)]) - ActionView::Resolver.stubs(:caching?).returns(false) @lookup_context = ActionView::LookupContext.new(@resolver, {}) end test "templates are always found in the resolver but timestamp is checked before being compiled" do - template = @lookup_context.find("foo", %w(test), true) - assert_equal "Foo", template.source - - # Now we are going to change the template, but it won't change the returned template - # since the timestamp is the same. - @resolver.hash["test/_foo.erb"][0] = "Bar" - template = @lookup_context.find("foo", %w(test), true) - assert_equal "Foo", template.source - - # Now update the timestamp. - @resolver.hash["test/_foo.erb"][1] = Time.now.utc - template = @lookup_context.find("foo", %w(test), true) - assert_equal "Bar", template.source + ActionView::Resolver.stub(:caching?, false) do + template = @lookup_context.find("foo", %w(test), true) + assert_equal "Foo", template.source + + # Now we are going to change the template, but it won't change the returned template + # since the timestamp is the same. + @resolver.hash["test/_foo.erb"][0] = "Bar" + template = @lookup_context.find("foo", %w(test), true) + assert_equal "Foo", template.source + + # Now update the timestamp. + @resolver.hash["test/_foo.erb"][1] = Time.now.utc + template = @lookup_context.find("foo", %w(test), true) + assert_equal "Bar", template.source + end end test "if no template was found in the second lookup, with no cache, raise error" do - template = @lookup_context.find("foo", %w(test), true) - assert_equal "Foo", template.source + ActionView::Resolver.stub(:caching?, false) do + template = @lookup_context.find("foo", %w(test), true) + assert_equal "Foo", template.source - @resolver.hash.clear - assert_raise ActionView::MissingTemplate do - @lookup_context.find("foo", %w(test), true) + @resolver.hash.clear + assert_raise ActionView::MissingTemplate do + @lookup_context.find("foo", %w(test), true) + end end end test "if no template was cached in the first lookup, retrieval should work in the second call" do - @resolver.hash.clear - assert_raise ActionView::MissingTemplate do - @lookup_context.find("foo", %w(test), true) - end + ActionView::Resolver.stub(:caching?, false) do + @resolver.hash.clear + assert_raise ActionView::MissingTemplate do + @lookup_context.find("foo", %w(test), true) + end - @resolver.hash["test/_foo.erb"] = ["Foo", Time.utc(2000)] - template = @lookup_context.find("foo", %w(test), true) - assert_equal "Foo", template.source + @resolver.hash["test/_foo.erb"] = ["Foo", Time.utc(2000)] + template = @lookup_context.find("foo", %w(test), true) + assert_equal "Foo", template.source + end end end diff --git a/actionview/test/template/number_helper_test.rb b/actionview/test/template/number_helper_test.rb index b70b750869..631d45586b 100644 --- a/actionview/test/template/number_helper_test.rb +++ b/actionview/test/template/number_helper_test.rb @@ -1,3 +1,4 @@ +# encoding: utf-8 require "abstract_unit" class NumberHelperTest < ActionView::TestCase @@ -21,6 +22,7 @@ class NumberHelperTest < ActionView::TestCase assert_equal "<b>1,234,567,890.50</b> $", number_to_currency("1234567890.50", format: "<b>%n</b> %u") assert_equal "<b>1,234,567,890.50</b> $", number_to_currency("-1234567890.50", negative_format: "<b>%n</b> %u") assert_equal "<b>1,234,567,890.50</b> $", number_to_currency("-1234567890.50", 'negative_format' => "<b>%n</b> %u") + assert_equal '₹ 12,30,000.00', number_to_currency(1230000, delimiter_pattern: /(\d+?)(?=(\d\d)+(\d)(?!\d))/, unit: '₹', format: "%u %n") end def test_number_to_percentage diff --git a/actionview/test/template/template_test.rb b/actionview/test/template/template_test.rb index d3b51cd629..921011b073 100644 --- a/actionview/test/template/template_test.rb +++ b/actionview/test/template/template_test.rb @@ -118,15 +118,17 @@ class TestERBTemplate < ActiveSupport::TestCase def test_refresh_with_templates @template = new_template("Hello", :virtual_path => "test/foo/bar") @template.locals = [:key] - @context.lookup_context.expects(:find_template).with("bar", %w(test/foo), false, [:key]).returns("template") - assert_equal "template", @template.refresh(@context) + assert_called_with(@context.lookup_context, :find_template,["bar", %w(test/foo), false, [:key]], returns: "template") do + assert_equal "template", @template.refresh(@context) + end end def test_refresh_with_partials @template = new_template("Hello", :virtual_path => "test/_foo") @template.locals = [:key] - @context.lookup_context.expects(:find_template).with("foo", %w(test), true, [:key]).returns("partial") - assert_equal "partial", @template.refresh(@context) + assert_called_with(@context.lookup_context, :find_template,[ "foo", %w(test), true, [:key]], returns: "partial") do + assert_equal "partial", @template.refresh(@context) + end end def test_refresh_raises_an_error_without_virtual_path diff --git a/actionview/test/template/test_case_test.rb b/actionview/test/template/test_case_test.rb index 80fbaa8392..b057d43ee0 100644 --- a/actionview/test/template/test_case_test.rb +++ b/actionview/test/template/test_case_test.rb @@ -20,6 +20,7 @@ module ActionView class TestCase helper ASharedTestHelper + DeveloperStruct = Struct.new(:name) module SharedTests def self.included(test_case) @@ -50,7 +51,7 @@ module ActionView end test "works without testing a helper module" do - assert_equal 'Eloy', render('developers/developer', :developer => stub(:name => 'Eloy')) + assert_equal 'Eloy', render('developers/developer', :developer => DeveloperStruct.new('Eloy')) end test "can render a layout with block" do @@ -69,13 +70,15 @@ module ActionView end test "delegates notice to request.flash[:notice]" do - view.request.flash.expects(:[]).with(:notice) - view.notice + assert_called_with(view.request.flash, :[], [:notice]) do + view.notice + end end test "delegates alert to request.flash[:alert]" do - view.request.flash.expects(:[]).with(:alert) - view.alert + assert_called_with(view.request.flash, :[], [:alert]) do + view.alert + end end test "uses controller lookup context" do @@ -119,7 +122,7 @@ module ActionView test "helper class that is being tested is always included in view instance" do @controller.controller_path = 'test' - @customers = [stub(:name => 'Eloy'), stub(:name => 'Manfred')] + @customers = [DeveloperStruct.new('Eloy'), DeveloperStruct.new('Manfred')] assert_match(/Hello: EloyHello: Manfred/, render(:partial => 'test/from_helper')) end end @@ -255,15 +258,15 @@ module ActionView end test "is able to render partials with local variables" do - assert_equal 'Eloy', render('developers/developer', :developer => stub(:name => 'Eloy')) + assert_equal 'Eloy', render('developers/developer', :developer => DeveloperStruct.new('Eloy')) assert_equal 'Eloy', render(:partial => 'developers/developer', - :locals => { :developer => stub(:name => 'Eloy') }) + :locals => { :developer => DeveloperStruct.new('Eloy') }) end test "is able to render partials from templates and also use instance variables" do @controller.controller_path = "test" - @customers = [stub(:name => 'Eloy'), stub(:name => 'Manfred')] + @customers = [DeveloperStruct.new('Eloy'), DeveloperStruct.new('Manfred')] assert_match(/Hello: EloyHello: Manfred/, render(:file => 'test/list')) end @@ -272,7 +275,7 @@ module ActionView view - @customers = [stub(:name => 'Eloy'), stub(:name => 'Manfred')] + @customers = [DeveloperStruct.new('Eloy'), DeveloperStruct.new('Manfred')] assert_match(/Hello: EloyHello: Manfred/, render(:file => 'test/list')) end diff --git a/actionview/test/template/translation_helper_test.rb b/actionview/test/template/translation_helper_test.rb index 5dc281adb2..749d0dd7fd 100644 --- a/actionview/test/template/translation_helper_test.rb +++ b/actionview/test/template/translation_helper_test.rb @@ -42,14 +42,16 @@ class TranslationHelperTest < ActiveSupport::TestCase end def test_delegates_setting_to_i18n - I18n.expects(:translate).with(:foo, :locale => 'en', :raise => true).returns("") - translate :foo, :locale => 'en' + assert_called_with(I18n, :translate, [:foo, :locale => 'en', :raise => true], returns: "") do + translate :foo, :locale => 'en' + end end def test_delegates_localize_to_i18n @time = Time.utc(2008, 7, 8, 12, 18, 38) - I18n.expects(:localize).with(@time) - localize @time + assert_called_with(I18n, :localize, [@time]) do + localize @time + end end def test_returns_missing_translation_message_wrapped_into_span @@ -125,8 +127,9 @@ class TranslationHelperTest < ActiveSupport::TestCase end def test_translate_escapes_interpolations_in_translations_with_a_html_suffix + word_struct = Struct.new(:to_s) assert_equal '<a>Hello <World></a>', translate(:'translations.interpolated_html', :word => '<World>') - assert_equal '<a>Hello <World></a>', translate(:'translations.interpolated_html', :word => stub(:to_s => "<World>")) + assert_equal '<a>Hello <World></a>', translate(:'translations.interpolated_html', :word => word_struct.new("<World>")) end def test_translate_with_html_count diff --git a/actionview/test/template/url_helper_test.rb b/actionview/test/template/url_helper_test.rb index 416d30938a..027b72b354 100644 --- a/actionview/test/template/url_helper_test.rb +++ b/actionview/test/template/url_helper_test.rb @@ -1,5 +1,4 @@ require 'abstract_unit' -require 'minitest/mock' class UrlHelperTest < ActiveSupport::TestCase diff --git a/activejob/CHANGELOG.md b/activejob/CHANGELOG.md index 637096e935..965b556fab 100644 --- a/activejob/CHANGELOG.md +++ b/activejob/CHANGELOG.md @@ -1,3 +1,8 @@ +* Implement a simple `AsyncJob` processor and associated `AsyncAdapter` that + queue jobs to a `concurrent-ruby` thread pool. + + *Jerry D'Antonio* + * Implement `provider_job_id` for `queue_classic` adapter. This requires the latest, currently unreleased, version of queue_classic. diff --git a/activejob/Rakefile b/activejob/Rakefile index 8c86df3c91..d9648a7f16 100644 --- a/activejob/Rakefile +++ b/activejob/Rakefile @@ -1,6 +1,6 @@ require 'rake/testtask' -ACTIVEJOB_ADAPTERS = %w(inline delayed_job qu que queue_classic resque sidekiq sneakers sucker_punch backburner test) +ACTIVEJOB_ADAPTERS = %w(async inline delayed_job qu que queue_classic resque sidekiq sneakers sucker_punch backburner test) ACTIVEJOB_ADAPTERS -= %w(queue_classic) if defined?(JRUBY_VERSION) task default: :test diff --git a/activejob/lib/active_job.rb b/activejob/lib/active_job.rb index 3d4f63b261..eb8091a805 100644 --- a/activejob/lib/active_job.rb +++ b/activejob/lib/active_job.rb @@ -32,6 +32,7 @@ module ActiveJob autoload :Base autoload :QueueAdapters autoload :ConfiguredJob + autoload :AsyncJob autoload :TestCase autoload :TestHelper end diff --git a/activejob/lib/active_job/async_job.rb b/activejob/lib/active_job/async_job.rb new file mode 100644 index 0000000000..7fcffc4c24 --- /dev/null +++ b/activejob/lib/active_job/async_job.rb @@ -0,0 +1,75 @@ +require 'concurrent' +require 'thread_safe' + +module ActiveJob + # == Active Job Async Job + # + # When enqueueing jobs with Async Job each job will be executed asynchronously + # on a +concurrent-ruby+ thread pool. All job data is retained in memory. + # Because job data is not saved to a persistent datastore there is no + # additional infrastructure needed and jobs process quickly. The lack of + # persistence, however, means that all unprocessed jobs will be lost on + # application restart. Therefore in-memory queue adapters are unsuitable for + # most production environments but are excellent for development and testing. + # + # Read more about Concurrent Ruby {here}[https://github.com/ruby-concurrency/concurrent-ruby]. + # + # To use Async Job set the queue_adapter config to +:async+. + # + # Rails.application.config.active_job.queue_adapter = :async + # + # Async Job supports job queues specified with +queue_as+. Queues are created + # automatically as needed and each has its own thread pool. + class AsyncJob + + DEFAULT_EXECUTOR_OPTIONS = { + min_threads: [2, Concurrent.processor_count].max, + max_threads: Concurrent.processor_count * 10, + auto_terminate: true, + idletime: 60, # 1 minute + max_queue: 0, # unlimited + fallback_policy: :caller_runs # shouldn't matter -- 0 max queue + }.freeze + + QUEUES = ThreadSafe::Cache.new do |hash, queue_name| #:nodoc: + hash.compute_if_absent(queue_name) { ActiveJob::AsyncJob.create_thread_pool } + end + + class << self + # Forces jobs to process immediately when testing the Active Job gem. + # This should only be called from within unit tests. + def perform_immediately! #:nodoc: + @perform_immediately = true + end + + # Allows jobs to run asynchronously when testing the Active Job gem. + # This should only be called from within unit tests. + def perform_asynchronously! #:nodoc: + @perform_immediately = false + end + + def create_thread_pool #:nodoc: + if @perform_immediately + Concurrent::ImmediateExecutor.new + else + Concurrent::ThreadPoolExecutor.new(DEFAULT_EXECUTOR_OPTIONS) + end + end + + def enqueue(job_data, queue: 'default') #:nodoc: + QUEUES[queue].post(job_data) { |job| ActiveJob::Base.execute(job) } + end + + def enqueue_at(job_data, timestamp, queue: 'default') #:nodoc: + delay = timestamp - Time.current.to_f + if delay > 0 + Concurrent::ScheduledTask.execute(delay, args: [job_data], executor: QUEUES[queue]) do |job| + ActiveJob::Base.execute(job) + end + else + enqueue(job_data, queue: queue) + end + end + end + end +end diff --git a/activejob/lib/active_job/queue_adapters.rb b/activejob/lib/active_job/queue_adapters.rb index e8ceabaeba..aeb1fe1e73 100644 --- a/activejob/lib/active_job/queue_adapters.rb +++ b/activejob/lib/active_job/queue_adapters.rb @@ -12,6 +12,8 @@ module ActiveJob # * {Sidekiq}[http://sidekiq.org] # * {Sneakers}[https://github.com/jondot/sneakers] # * {Sucker Punch}[https://github.com/brandonhilkert/sucker_punch] + # * {Active Job Async Job}[http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters/AsyncAdapter.html] + # * {Active Job Inline}[http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters/InlineAdapter.html] # # === Backends Features # @@ -26,6 +28,7 @@ module ActiveJob # | Sidekiq | Yes | Yes | Yes | Queue | No | Job | # | Sneakers | Yes | Yes | No | Queue | Queue | No | # | Sucker Punch | Yes | Yes | No | No | No | No | + # | Active Job Async | Yes | Yes | Yes | No | No | No | # | Active Job Inline | No | Yes | N/A | N/A | N/A | N/A | # # ==== Async @@ -96,9 +99,15 @@ module ActiveJob # # N/A: The adapter does not run in a separate process, and therefore doesn't # support retries. + # + # === Async and Inline Queue Adapters + # + # Active Job has two built-in queue adapters intended for development and + # testing: +:async+ and +:inline+. module QueueAdapters extend ActiveSupport::Autoload + autoload :AsyncAdapter autoload :InlineAdapter autoload :BackburnerAdapter autoload :DelayedJobAdapter diff --git a/activejob/lib/active_job/queue_adapters/async_adapter.rb b/activejob/lib/active_job/queue_adapters/async_adapter.rb new file mode 100644 index 0000000000..3fc27f56e7 --- /dev/null +++ b/activejob/lib/active_job/queue_adapters/async_adapter.rb @@ -0,0 +1,23 @@ +require 'active_job/async_job' + +module ActiveJob + module QueueAdapters + # == Active Job Async adapter + # + # When enqueueing jobs with the Async adapter the job will be executed + # asynchronously using {AsyncJob}[http://api.rubyonrails.org/classes/ActiveJob/AsyncJob.html]. + # + # To use +AsyncJob+ set the queue_adapter config to +:async+. + # + # Rails.application.config.active_job.queue_adapter = :async + class AsyncAdapter + def enqueue(job) #:nodoc: + ActiveJob::AsyncJob.enqueue(job.serialize, queue: job.queue_name) + end + + def enqueue_at(job, timestamp) #:nodoc: + ActiveJob::AsyncJob.enqueue_at(job.serialize, timestamp, queue: job.queue_name) + end + end + end +end diff --git a/activejob/test/adapters/async.rb b/activejob/test/adapters/async.rb new file mode 100644 index 0000000000..df58027599 --- /dev/null +++ b/activejob/test/adapters/async.rb @@ -0,0 +1,5 @@ +require 'concurrent' +require 'active_job/async_job' + +ActiveJob::Base.queue_adapter = :async +ActiveJob::AsyncJob.perform_immediately! diff --git a/activejob/test/cases/async_job_test.rb b/activejob/test/cases/async_job_test.rb new file mode 100644 index 0000000000..2642cfc608 --- /dev/null +++ b/activejob/test/cases/async_job_test.rb @@ -0,0 +1,42 @@ +require 'helper' +require 'jobs/hello_job' +require 'jobs/queue_as_job' + +class AsyncJobTest < ActiveSupport::TestCase + def using_async_adapter? + ActiveJob::Base.queue_adapter.is_a? ActiveJob::QueueAdapters::AsyncAdapter + end + + setup do + ActiveJob::AsyncJob.perform_asynchronously! + end + + teardown do + ActiveJob::AsyncJob::QUEUES.clear + ActiveJob::AsyncJob.perform_immediately! + end + + test "#create_thread_pool returns a thread_pool" do + thread_pool = ActiveJob::AsyncJob.create_thread_pool + assert thread_pool.is_a? Concurrent::ExecutorService + assert_not thread_pool.is_a? Concurrent::ImmediateExecutor + end + + test "#create_thread_pool returns an ImmediateExecutor after #perform_immediately! is called" do + ActiveJob::AsyncJob.perform_immediately! + thread_pool = ActiveJob::AsyncJob.create_thread_pool + assert thread_pool.is_a? Concurrent::ImmediateExecutor + end + + test "enqueuing without specifying a queue uses the default queue" do + skip unless using_async_adapter? + HelloJob.perform_later + assert ActiveJob::AsyncJob::QUEUES.key? 'default' + end + + test "enqueuing to a queue that does not exist creates the queue" do + skip unless using_async_adapter? + QueueAsJob.perform_later + assert ActiveJob::AsyncJob::QUEUES.key? QueueAsJob::MY_QUEUE.to_s + end +end diff --git a/activejob/test/helper.rb b/activejob/test/helper.rb index 57907042d9..55f690bda8 100644 --- a/activejob/test/helper.rb +++ b/activejob/test/helper.rb @@ -3,6 +3,7 @@ require File.expand_path('../../../load_paths', __FILE__) require 'active_job' require 'support/job_buffer' +ActiveSupport.halt_callback_chains_on_return_false = false GlobalID.app = 'aj' @adapter = ENV['AJ_ADAPTER'] || 'inline' diff --git a/activejob/test/integration/queuing_test.rb b/activejob/test/integration/queuing_test.rb index ca8047ef0b..125ba54302 100644 --- a/activejob/test/integration/queuing_test.rb +++ b/activejob/test/integration/queuing_test.rb @@ -11,7 +11,7 @@ class QueuingTest < ActiveSupport::TestCase end test 'should not run jobs queued on a non-listening queue' do - skip if adapter_is?(:inline, :sucker_punch, :que) + skip if adapter_is?(:inline, :async, :sucker_punch, :que) old_queue = TestJob.queue_name begin diff --git a/activejob/test/jobs/queue_as_job.rb b/activejob/test/jobs/queue_as_job.rb new file mode 100644 index 0000000000..897aef52e5 --- /dev/null +++ b/activejob/test/jobs/queue_as_job.rb @@ -0,0 +1,10 @@ +require_relative '../support/job_buffer' + +class QueueAsJob < ActiveJob::Base + MY_QUEUE = :low_priority + queue_as MY_QUEUE + + def perform(greeter = "David") + JobBuffer.add("#{greeter} says hello") + end +end diff --git a/activejob/test/support/integration/adapters/async.rb b/activejob/test/support/integration/adapters/async.rb new file mode 100644 index 0000000000..42beb12b1f --- /dev/null +++ b/activejob/test/support/integration/adapters/async.rb @@ -0,0 +1,9 @@ +module AsyncJobsManager + def setup + ActiveJob::Base.queue_adapter = :async + end + + def clear_jobs + ActiveJob::AsyncJob::QUEUES.clear + end +end diff --git a/activemodel/lib/active_model/serialization.rb b/activemodel/lib/active_model/serialization.rb index 58d68d9620..341651d00d 100644 --- a/activemodel/lib/active_model/serialization.rb +++ b/activemodel/lib/active_model/serialization.rb @@ -31,12 +31,11 @@ module ActiveModel # of the attributes hash's keys. In order to override this behavior, take a look # at the private method +read_attribute_for_serialization+. # - # Most of the time though, either the JSON or XML serializations are needed. - # Both of these modules automatically include the - # <tt>ActiveModel::Serialization</tt> module, so there is no need to - # explicitly include it. + # The JSON serialization is provided by default when you include the + # <tt>ActiveModel::Serialization</tt> module, so there is no need to explicitly + # include it. # - # A minimal implementation including XML and JSON would be: + # A minimal implementation including JSON would be: # # class Person # include ActiveModel::Serializers::JSON diff --git a/activemodel/test/cases/helper.rb b/activemodel/test/cases/helper.rb index c100646837..0cd80be66f 100644 --- a/activemodel/test/cases/helper.rb +++ b/activemodel/test/cases/helper.rb @@ -13,8 +13,6 @@ I18n.enforce_available_locales = false require 'active_support/testing/autorun' require 'active_support/testing/method_call_assertions' -require 'minitest/mock' - # Skips the current run on Rubinius using Minitest::Assertions#skip def rubinius_skip(message = '') skip message if RUBY_ENGINE == 'rbx' diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 0841b2f679..ec3ec06c2b 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,27 @@ +* PostgreSQL, `create_schema`, `drop_schema` and `rename_table` now quote + schema names. + + Fixes #21418. + + Example: + + create_schema("my.schema") + # CREATE SCHEMA "my.schema"; + + *Yves Senn* + +* PostgreSQL, add `:if_exists` option to `#drop_schema`. This makes it + possible to drop a schema that might exist without raising an exception if + it doesn't. + + *Yves Senn* + +* Only try to nullify has_one target association if the record is persisted. + + Fixes #21223. + + *Agis Anastasopoulos* + * Uniqueness validator raises descriptive error when running on a persisted record without primary key. diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index a830b0e0e4..19ef37e228 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -619,10 +619,10 @@ module ActiveRecord # @tag = @post.tags.build name: "ruby" # @tag.save # - # The last line ought to save the through record (a <tt>Taggable</tt>). This will only work if the + # The last line ought to save the through record (a <tt>Tagging</tt>). This will only work if the # <tt>:inverse_of</tt> is set: # - # class Taggable < ActiveRecord::Base + # class Tagging < ActiveRecord::Base # belongs_to :post # belongs_to :tag, inverse_of: :taggings # end @@ -643,7 +643,7 @@ module ActiveRecord # You can turn off the automatic detection of inverse associations by setting # the <tt>:inverse_of</tt> option to <tt>false</tt> like so: # - # class Taggable < ActiveRecord::Base + # class Tagging < ActiveRecord::Base # belongs_to :tag, inverse_of: false # end # diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb index b5a8c81fe4..29c711082a 100644 --- a/activerecord/lib/active_record/associations/collection_proxy.rb +++ b/activerecord/lib/active_record/associations/collection_proxy.rb @@ -227,6 +227,31 @@ module ActiveRecord @association.last(*args) end + # Gives a record (or N records if a parameter is supplied) from the collection + # using the same rules as <tt>ActiveRecord::Base.take</tt>. + # + # class Person < ActiveRecord::Base + # has_many :pets + # end + # + # person.pets + # # => [ + # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>, + # # #<Pet id: 2, name: "Spook", person_id: 1>, + # # #<Pet id: 3, name: "Choo-Choo", person_id: 1> + # # ] + # + # person.pets.take # => #<Pet id: 1, name: "Fancy-Fancy", person_id: 1> + # + # person.pets.take(2) + # # => [ + # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>, + # # #<Pet id: 2, name: "Spook", person_id: 1> + # # ] + # + # another_person_without.pets # => [] + # another_person_without.pets.take # => nil + # another_person_without.pets.take(2) # => [] def take(n = nil) @association.take(n) end diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb index 5a92bc5e8a..1829453d73 100644 --- a/activerecord/lib/active_record/associations/has_one_association.rb +++ b/activerecord/lib/active_record/associations/has_one_association.rb @@ -65,7 +65,7 @@ module ActiveRecord when :destroy target.destroy when :nullify - target.update_columns(reflection.foreign_key => nil) + target.update_columns(reflection.foreign_key => nil) if target.persisted? end end end diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb index 2363cf7608..5197e21fa4 100644 --- a/activerecord/lib/active_record/attribute_methods/read.rb +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -56,14 +56,12 @@ module ActiveRecord end end - ID = 'id'.freeze - # Returns the value of the attribute identified by <tt>attr_name</tt> after # it has been typecast (for example, "2004-12-12" in a date column is cast # to a date object, like Date.new(2004, 12, 12)). def read_attribute(attr_name, &block) name = attr_name.to_s - name = self.class.primary_key if name == ID + name = self.class.primary_key if name == 'id'.freeze _read_attribute(name, &block) end diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 55a7e053bc..4b66d8cd36 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1,5 +1,4 @@ require 'yaml' -require 'set' require 'active_support/benchmarkable' require 'active_support/dependencies' require 'active_support/descendants_tracker' diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index dff6c69168..c086111d41 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -1054,7 +1054,7 @@ module ActiveRecord end end - class MysqlJson < Type::Json # :nodoc: + class MysqlJson < Type::Internal::AbstractJson # :nodoc: def changed_in_place?(raw_old_value, new_value) # Normalization is required because MySQL JSON data format includes # the space between the elements. diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb index 37226831dc..68752cdd80 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb @@ -8,6 +8,7 @@ require 'active_record/connection_adapters/postgresql/oid/decimal' require 'active_record/connection_adapters/postgresql/oid/enum' require 'active_record/connection_adapters/postgresql/oid/hstore' require 'active_record/connection_adapters/postgresql/oid/inet' +require 'active_record/connection_adapters/postgresql/oid/json' require 'active_record/connection_adapters/postgresql/oid/jsonb' require 'active_record/connection_adapters/postgresql/oid/money' require 'active_record/connection_adapters/postgresql/oid/point' diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb new file mode 100644 index 0000000000..dbc879ffd4 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb @@ -0,0 +1,10 @@ +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module OID # :nodoc: + class Json < Type::Internal::AbstractJson + end + end + end + end +end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb index 1f6d63582c..87391b5dc7 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb @@ -2,7 +2,7 @@ module ActiveRecord module ConnectionAdapters module PostgreSQL module OID # :nodoc: - class Jsonb < Type::Json # :nodoc: + class Jsonb < Json # :nodoc: def type :jsonb end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb index f175730551..d5879ea7df 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb @@ -31,6 +31,11 @@ module ActiveRecord Utils.extract_schema_qualified_name(name.to_s).quoted end + # Quotes schema names for use in SQL queries. + def quote_schema_name(name) + PGconn.quote_ident(name) + end + def quote_table_name_for_assignment(table, attr) quote_column_name(attr) 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 d114cad16b..a3fc8fbc51 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -68,7 +68,7 @@ module ActiveRecord execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}" end - # Returns the list of all tables in the schema search path or a specified schema. + # Returns the list of all tables in the schema search path. def tables(name = nil) select_values("SELECT tablename FROM pg_tables WHERE schemaname = ANY(current_schemas(false))", 'SCHEMA') end @@ -210,12 +210,12 @@ module ActiveRecord # Creates a schema for the given schema name. def create_schema schema_name - execute "CREATE SCHEMA #{schema_name}" + execute "CREATE SCHEMA #{quote_schema_name(schema_name)}" end # Drops the schema for the given schema name. - def drop_schema schema_name - execute "DROP SCHEMA #{schema_name} CASCADE" + def drop_schema(schema_name, options = {}) + execute "DROP SCHEMA#{' IF EXISTS' if options[:if_exists]} #{quote_schema_name(schema_name)} CASCADE" end # Sets the schema search path to a string of comma-separated schema names. @@ -376,7 +376,7 @@ module ActiveRecord new_seq = "#{new_name}_#{pk}_seq" idx = "#{table_name}_pkey" new_idx = "#{new_name}_pkey" - execute "ALTER TABLE #{quote_table_name(seq)} RENAME TO #{quote_table_name(new_seq)}" + execute "ALTER TABLE #{seq.quoted} RENAME TO #{quote_table_name(new_seq)}" execute "ALTER INDEX #{quote_table_name(idx)} RENAME TO #{quote_table_name(new_idx)}" end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 861edbf3a2..27291bd2ea 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -482,7 +482,7 @@ module ActiveRecord m.register_type 'bytea', OID::Bytea.new m.register_type 'point', OID::Point.new m.register_type 'hstore', OID::Hstore.new - m.register_type 'json', Type::Json.new + m.register_type 'json', OID::Json.new m.register_type 'jsonb', OID::Jsonb.new m.register_type 'cidr', OID::Cidr.new m.register_type 'inet', OID::Inet.new @@ -838,6 +838,7 @@ module ActiveRecord ActiveRecord::Type.register(:enum, OID::Enum, adapter: :postgresql) ActiveRecord::Type.register(:hstore, OID::Hstore, adapter: :postgresql) ActiveRecord::Type.register(:inet, OID::Inet, adapter: :postgresql) + ActiveRecord::Type.register(:json, OID::Json, adapter: :postgresql) ActiveRecord::Type.register(:jsonb, OID::Jsonb, adapter: :postgresql) ActiveRecord::Type.register(:money, OID::Money, adapter: :postgresql) ActiveRecord::Type.register(:point, OID::Point, adapter: :postgresql) diff --git a/activerecord/lib/active_record/enum.rb b/activerecord/lib/active_record/enum.rb index 91a13cb0cd..4902fcb1a2 100644 --- a/activerecord/lib/active_record/enum.rb +++ b/activerecord/lib/active_record/enum.rb @@ -18,10 +18,9 @@ module ActiveRecord # conversation.archived? # => true # conversation.status # => "archived" # - # # conversation.update! status: 1 + # # conversation.status = 1 # conversation.status = "archived" # - # # conversation.update! status: nil # conversation.status = nil # conversation.status.nil? # => true # conversation.status # => nil diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index c35efbdee8..b4dd8eff5a 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -168,7 +168,7 @@ module ActiveRecord # # rails generate migration add_fieldname_to_tablename fieldname:string # - # This will generate the file <tt>timestamp_add_fieldname_to_tablename</tt>, which will look like this: + # This will generate the file <tt>timestamp_add_fieldname_to_tablename.rb</tt>, which will look like this: # class AddFieldnameToTablename < ActiveRecord::Migration # def change # add_column :tablenames, :fieldname, :string @@ -459,7 +459,7 @@ module ActiveRecord # Or equivalently, if +TenderloveMigration+ is defined as in the # documentation for Migration: # - # require_relative '2012121212_tenderlove_migration' + # require_relative '20121212123456_tenderlove_migration' # # class FixupTLMigration < ActiveRecord::Migration # def change @@ -475,13 +475,13 @@ module ActiveRecord def revert(*migration_classes) run(*migration_classes.reverse, revert: true) unless migration_classes.empty? if block_given? - if @connection.respond_to? :revert - @connection.revert { yield } + if connection.respond_to? :revert + connection.revert { yield } else - recorder = CommandRecorder.new(@connection) + recorder = CommandRecorder.new(connection) @connection = recorder suppress_messages do - @connection.revert { yield } + connection.revert { yield } end @connection = recorder.delegate recorder.commands.each do |cmd, args, block| @@ -492,7 +492,7 @@ module ActiveRecord end def reverting? - @connection.respond_to?(:reverting) && @connection.reverting + connection.respond_to?(:reverting) && connection.reverting end class ReversibleBlockHelper < Struct.new(:reverting) # :nodoc: @@ -549,7 +549,7 @@ module ActiveRecord revert { run(*migration_classes, direction: dir, revert: true) } else migration_classes.each do |migration_class| - migration_class.new.exec_migration(@connection, dir) + migration_class.new.exec_migration(connection, dir) end end end @@ -641,7 +641,7 @@ module ActiveRecord arg_list = arguments.map(&:inspect) * ', ' say_with_time "#{method}(#{arg_list})" do - unless @connection.respond_to? :revert + unless connection.respond_to? :revert unless arguments.empty? || [:execute, :enable_extension, :disable_extension].include?(method) arguments[0] = proper_table_name(arguments.first, table_name_options) if [:rename_table, :add_foreign_key].include?(method) || @@ -814,7 +814,7 @@ module ActiveRecord new(:up, migrations, target_version).migrate end - def down(migrations_paths, target_version = nil, &block) + def down(migrations_paths, target_version = nil) migrations = migrations(migrations_paths) migrations.select! { |m| yield m } if block_given? diff --git a/activerecord/lib/active_record/migration/command_recorder.rb b/activerecord/lib/active_record/migration/command_recorder.rb index dcc2362397..3ab0f28c9b 100644 --- a/activerecord/lib/active_record/migration/command_recorder.rb +++ b/activerecord/lib/active_record/migration/command_recorder.rb @@ -5,15 +5,34 @@ module ActiveRecord # knows how to invert the following commands: # # * add_column + # * add_foreign_key # * add_index + # * add_reference # * add_timestamps - # * create_table + # * change_column_default (must supply a :from and :to option) + # * change_column_null # * create_join_table + # * create_table + # * disable_extension + # * drop_join_table + # * drop_table (must supply a block) + # * enable_extension + # * remove_column (must supply a type) + # * remove_foreign_key (must supply a second table) + # * remove_index + # * remove_reference # * remove_timestamps # * rename_column # * rename_index # * rename_table class CommandRecorder + ReversibleAndIrreversibleMethods = [:create_table, :create_join_table, :rename_table, :add_column, :remove_column, + :rename_index, :rename_column, :add_index, :remove_index, :add_timestamps, :remove_timestamps, + :change_column_default, :add_reference, :remove_reference, :transaction, + :drop_join_table, :drop_table, :execute_block, :enable_extension, :disable_extension, + :change_column, :execute, :remove_columns, :change_column_null, + :add_foreign_key, :remove_foreign_key + ] include JoinTable attr_accessor :commands, :delegate, :reverting @@ -41,7 +60,7 @@ module ActiveRecord @reverting = !@reverting end - # record +command+. +command+ should be a method name and arguments. + # Record +command+. +command+ should be a method name and arguments. # For example: # # recorder.record(:method_name, [:arg1, :arg2]) @@ -70,14 +89,7 @@ module ActiveRecord super || delegate.respond_to?(*args) end - [:create_table, :create_join_table, :rename_table, :add_column, :remove_column, - :rename_index, :rename_column, :add_index, :remove_index, :add_timestamps, :remove_timestamps, - :add_reference, :remove_reference, :transaction, - :drop_join_table, :drop_table, :execute_block, :enable_extension, - :change_column, :execute, :remove_columns, :change_column_null, - :add_foreign_key, :remove_foreign_key - # irreversible methods need to be here too - ].each do |method| + ReversibleAndIrreversibleMethods.each do |method| class_eval <<-EOV, __FILE__, __LINE__ + 1 def #{method}(*args, &block) # def create_table(*args, &block) record(:"#{method}", args, &block) # record(:create_table, args, &block) diff --git a/activerecord/lib/active_record/type.rb b/activerecord/lib/active_record/type.rb index 8f56a37f3c..53f3b53bec 100644 --- a/activerecord/lib/active_record/type.rb +++ b/activerecord/lib/active_record/type.rb @@ -10,7 +10,6 @@ require 'active_record/type/decimal' require 'active_record/type/decimal_without_scale' require 'active_record/type/float' require 'active_record/type/integer' -require 'active_record/type/json' require 'active_record/type/serialized' require 'active_record/type/string' require 'active_record/type/text' @@ -21,6 +20,8 @@ require 'active_record/type/adapter_specific_registry' require 'active_record/type/type_map' require 'active_record/type/hash_lookup_type_map' +require 'active_record/type/internal/abstract_json' + module ActiveRecord module Type @registry = AdapterSpecificRegistry.new @@ -60,7 +61,6 @@ module ActiveRecord register(:decimal, Type::Decimal, override: false) register(:float, Type::Float, override: false) register(:integer, Type::Integer, override: false) - register(:json, Type::Json, override: false) register(:string, Type::String, override: false) register(:text, Type::Text, override: false) register(:time, Type::Time, override: false) diff --git a/activerecord/lib/active_record/type/internal/abstract_json.rb b/activerecord/lib/active_record/type/internal/abstract_json.rb new file mode 100644 index 0000000000..963a8245d0 --- /dev/null +++ b/activerecord/lib/active_record/type/internal/abstract_json.rb @@ -0,0 +1,33 @@ +module ActiveRecord + module Type + module Internal # :nodoc: + class AbstractJson < Type::Value # :nodoc: + include Type::Helpers::Mutable + + def type + :json + end + + def deserialize(value) + if value.is_a?(::String) + ::ActiveSupport::JSON.decode(value) rescue nil + else + value + end + end + + def serialize(value) + if value.is_a?(::Array) || value.is_a?(::Hash) + ::ActiveSupport::JSON.encode(value) + else + value + end + end + + def accessor + ActiveRecord::Store::StringKeyedHashAccessor + end + end + end + end +end diff --git a/activerecord/lib/active_record/type/json.rb b/activerecord/lib/active_record/type/json.rb deleted file mode 100644 index 1728bd3a8e..0000000000 --- a/activerecord/lib/active_record/type/json.rb +++ /dev/null @@ -1,31 +0,0 @@ -module ActiveRecord - module Type - class Json < Type::Value # :nodoc: - include Type::Helpers::Mutable - - def type - :json - end - - def deserialize(value) - if value.is_a?(::String) - ::ActiveSupport::JSON.decode(value) rescue nil - else - value - end - end - - def serialize(value) - if value.is_a?(::Array) || value.is_a?(::Hash) - ::ActiveSupport::JSON.encode(value) - else - value - end - end - - def accessor - ActiveRecord::Store::StringKeyedHashAccessor - end - end - end -end diff --git a/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb b/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb index fa6584eae5..a0afd922b2 100644 --- a/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb +++ b/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb @@ -31,7 +31,7 @@ class SchemaAuthorizationTest < ActiveRecord::PostgreSQLTestCase set_session_auth @connection.execute "RESET search_path" USERS.each do |u| - @connection.execute "DROP SCHEMA #{u} CASCADE" + @connection.drop_schema u @connection.execute "DROP USER #{u}" end end diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb index 35d5581aa7..ea7e5ac587 100644 --- a/activerecord/test/cases/adapters/postgresql/schema_test.rb +++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb @@ -2,7 +2,17 @@ require "cases/helper" require 'models/default' require 'support/schema_dumping_helper' +module PGSchemaHelper + def with_schema_search_path(schema_search_path) + @connection.schema_search_path = schema_search_path + yield if block_given? + ensure + @connection.schema_search_path = "'$user', public" + end +end + class SchemaTest < ActiveRecord::PostgreSQLTestCase + include PGSchemaHelper self.use_transactional_tests = false SCHEMA_NAME = 'test_schema' @@ -84,8 +94,8 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase end teardown do - @connection.execute "DROP SCHEMA #{SCHEMA2_NAME} CASCADE" - @connection.execute "DROP SCHEMA #{SCHEMA_NAME} CASCADE" + @connection.drop_schema SCHEMA2_NAME, if_exists: true + @connection.drop_schema SCHEMA_NAME, if_exists: true end def test_schema_names @@ -121,10 +131,17 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase assert !@connection.schema_names.include?("test_schema3") end + def test_drop_schema_if_exists + @connection.create_schema "some_schema" + assert_includes @connection.schema_names, "some_schema" + @connection.drop_schema "some_schema", if_exists: true + assert_not_includes @connection.schema_names, "some_schema" + end + def test_habtm_table_name_with_schema + ActiveRecord::Base.connection.drop_schema "music", if_exists: true + ActiveRecord::Base.connection.create_schema "music" ActiveRecord::Base.connection.execute <<-SQL - DROP SCHEMA IF EXISTS music CASCADE; - CREATE SCHEMA music; CREATE TABLE music.albums (id serial primary key); CREATE TABLE music.songs (id serial primary key); CREATE TABLE music.albums_songs (album_id integer, song_id integer); @@ -134,12 +151,16 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase Album.create assert_equal song, Song.includes(:albums).references(:albums).first ensure - ActiveRecord::Base.connection.execute "DROP SCHEMA music CASCADE;" + ActiveRecord::Base.connection.drop_schema "music", if_exists: true end - def test_raise_drop_schema_with_nonexisting_schema + def test_drop_schema_with_nonexisting_schema assert_raises(ActiveRecord::StatementInvalid) do - @connection.drop_schema "test_schema3" + @connection.drop_schema "idontexist" + end + + assert_nothing_raised do + @connection.drop_schema "idontexist", if_exists: true end end @@ -404,13 +425,6 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase end end - def with_schema_search_path(schema_search_path) - @connection.schema_search_path = schema_search_path - yield if block_given? - ensure - @connection.schema_search_path = "'$user', public" - end - def do_dump_index_tests_for_schema(this_schema_name, first_index_column_name, second_index_column_name, third_index_column_name, fourth_index_column_name) with_schema_search_path(this_schema_name) do indexes = @connection.indexes(TABLE_NAME).sort_by(&:name) @@ -462,14 +476,14 @@ class SchemaForeignKeyTest < ActiveRecord::PostgreSQLTestCase ensure @connection.drop_table "wagons", if_exists: true @connection.drop_table "my_schema.trains", if_exists: true - @connection.execute "DROP SCHEMA IF EXISTS my_schema" + @connection.drop_schema "my_schema", if_exists: true end end class DefaultsUsingMultipleSchemasAndDomainTest < ActiveRecord::PostgreSQLTestCase setup do @connection = ActiveRecord::Base.connection - @connection.execute "DROP SCHEMA IF EXISTS schema_1 CASCADE" + @connection.drop_schema "schema_1", if_exists: true @connection.execute "CREATE SCHEMA schema_1" @connection.execute "CREATE DOMAIN schema_1.text AS text" @connection.execute "CREATE DOMAIN schema_1.varchar AS varchar" @@ -487,7 +501,7 @@ class DefaultsUsingMultipleSchemasAndDomainTest < ActiveRecord::PostgreSQLTestCa teardown do @connection.schema_search_path = @old_search_path - @connection.execute "DROP SCHEMA IF EXISTS schema_1 CASCADE" + @connection.drop_schema "schema_1", if_exists: true Default.reset_column_information end @@ -519,3 +533,40 @@ class DefaultsUsingMultipleSchemasAndDomainTest < ActiveRecord::PostgreSQLTestCa assert_equal "foo'::bar", Default.new.string_col end end + +class SchemaWithDotsTest < ActiveRecord::PostgreSQLTestCase + include PGSchemaHelper + self.use_transactional_tests = false + + setup do + @connection = ActiveRecord::Base.connection + @connection.create_schema "my.schema" + end + + teardown do + @connection.drop_schema "my.schema", if_exists: true + end + + test "rename_table" do + with_schema_search_path('"my.schema"') do + @connection.create_table :posts + @connection.rename_table :posts, :articles + assert_equal ["articles"], @connection.tables + end + end + + test "Active Record basics" do + with_schema_search_path('"my.schema"') do + @connection.create_table :articles do |t| + t.string :title + end + article_class = Class.new(ActiveRecord::Base) do + self.table_name = '"my.schema".articles' + end + + article_class.create!(title: "zOMG, welcome to my blorgh!") + welcome_article = article_class.last + assert_equal "zOMG, welcome to my blorgh!", welcome_article.title + end + end +end diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb index 7996e7ad50..77d99bc116 100644 --- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb @@ -425,13 +425,16 @@ module ActiveRecord configurations['arunit']['database']) statement = ::SQLite3::Statement.new(db, 'CREATE TABLE statement_test (number integer not null)') - statement.stubs(:step).raises(::SQLite3::BusyException, 'busy') - statement.stubs(:columns).once.returns([]) - statement.expects(:close).once - ::SQLite3::Statement.stubs(:new).returns(statement) - - assert_raises ActiveRecord::StatementInvalid do - @conn.exec_query 'select * from statement_test' + statement.stub(:step, ->{ raise ::SQLite3::BusyException.new('busy') }) do + assert_called(statement, :columns, returns: []) do + assert_called(statement, :close) do + ::SQLite3::Statement.stub(:new, statement) do + assert_raises ActiveRecord::StatementInvalid do + @conn.exec_query 'select * from statement_test' + end + end + end + end end end diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb index 0d2e2264e3..02b67f901f 100644 --- a/activerecord/test/cases/associations/belongs_to_associations_test.rb +++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb @@ -1,6 +1,5 @@ require 'cases/helper' require 'models/developer' -require 'models/computer' require 'models/project' require 'models/company' require 'models/topic' diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb index 8ed7b4e39f..ddfb856a05 100644 --- a/activerecord/test/cases/associations/eager_test.rb +++ b/activerecord/test/cases/associations/eager_test.rb @@ -108,53 +108,57 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_preloading_has_many_in_multiple_queries_with_more_ids_than_database_can_handle - Comment.connection.expects(:in_clause_length).at_least_once.returns(5) - posts = Post.all.merge!(:includes=>:comments).to_a - assert_equal 11, posts.size + assert_called(Comment.connection, :in_clause_length, returns: 5) do + posts = Post.all.merge!(:includes=>:comments).to_a + assert_equal 11, posts.size + end end def test_preloading_has_many_in_one_queries_when_database_has_no_limit_on_ids_it_can_handle - Comment.connection.expects(:in_clause_length).at_least_once.returns(nil) - posts = Post.all.merge!(:includes=>:comments).to_a - assert_equal 11, posts.size + assert_called(Comment.connection, :in_clause_length, returns: nil) do + posts = Post.all.merge!(:includes=>:comments).to_a + assert_equal 11, posts.size + end end def test_preloading_habtm_in_multiple_queries_with_more_ids_than_database_can_handle - Comment.connection.expects(:in_clause_length).at_least_once.returns(5) - posts = Post.all.merge!(:includes=>:categories).to_a - assert_equal 11, posts.size + assert_called(Comment.connection, :in_clause_length, times: 2, returns: 5) do + posts = Post.all.merge!(:includes=>:categories).to_a + assert_equal 11, posts.size + end end def test_preloading_habtm_in_one_queries_when_database_has_no_limit_on_ids_it_can_handle - Comment.connection.expects(:in_clause_length).at_least_once.returns(nil) - posts = Post.all.merge!(:includes=>:categories).to_a - assert_equal 11, posts.size + assert_called(Comment.connection, :in_clause_length, times: 2, returns: nil) do + posts = Post.all.merge!(:includes=>:categories).to_a + assert_equal 11, posts.size + end end def test_load_associated_records_in_one_query_when_adapter_has_no_limit - Comment.connection.expects(:in_clause_length).at_least_once.returns(nil) - - post = posts(:welcome) - assert_queries(2) do - Post.includes(:comments).where(:id => post.id).to_a + assert_called(Comment.connection, :in_clause_length, returns: nil) do + post = posts(:welcome) + assert_queries(2) do + Post.includes(:comments).where(:id => post.id).to_a + end end end def test_load_associated_records_in_several_queries_when_many_ids_passed - Comment.connection.expects(:in_clause_length).at_least_once.returns(1) - - post1, post2 = posts(:welcome), posts(:thinking) - assert_queries(3) do - Post.includes(:comments).where(:id => [post1.id, post2.id]).to_a + assert_called(Comment.connection, :in_clause_length, returns: 1) do + post1, post2 = posts(:welcome), posts(:thinking) + assert_queries(3) do + Post.includes(:comments).where(:id => [post1.id, post2.id]).to_a + end end end def test_load_associated_records_in_one_query_when_a_few_ids_passed - Comment.connection.expects(:in_clause_length).at_least_once.returns(3) - - post = posts(:welcome) - assert_queries(2) do - Post.includes(:comments).where(:id => post.id).to_a + assert_called(Comment.connection, :in_clause_length, returns: 3) do + post = posts(:welcome) + assert_queries(2) do + Post.includes(:comments).where(:id => post.id).to_a + end end end diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb index 7718b29125..d160c30375 100644 --- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb @@ -796,9 +796,10 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase end def test_association_proxy_transaction_method_starts_transaction_in_association_class - Post.expects(:transaction) - Category.first.posts.transaction do - # nothing + assert_called(Post, :transaction) do + Category.first.posts.transaction do + # nothing + end end end diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb index b22ce42696..cf730e4fe7 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -744,8 +744,9 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase def test_get_ids_for_has_many_through_with_conditions_should_not_preload Tagging.create!(:taggable_type => 'Post', :taggable_id => posts(:welcome).id, :tag => tags(:misc)) - ActiveRecord::Associations::Preloader.expects(:new).never - posts(:welcome).misc_tag_ids + assert_not_called(ActiveRecord::Associations::Preloader, :new) do + posts(:welcome).misc_tag_ids + end end def test_get_ids_for_loaded_associations @@ -765,9 +766,10 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase end def test_association_proxy_transaction_method_starts_transaction_in_association_class - Tag.expects(:transaction) - Post.first.tags.transaction do - # nothing + assert_called(Tag, :transaction) do + Post.first.tags.transaction do + # nothing + end end end diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb index 5a8afaf4d2..d46e7ad235 100644 --- a/activerecord/test/cases/associations/has_one_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_associations_test.rb @@ -107,6 +107,14 @@ class HasOneAssociationsTest < ActiveRecord::TestCase assert_nil Account.find(old_account_id).firm_id end + def test_nullification_on_destroyed_association + developer = Developer.create!(name: "Someone") + ship = Ship.create!(name: "Planet Caravan", developer: developer) + ship.destroy + assert !ship.persisted? + assert !developer.persisted? + end + def test_natural_assignment_to_nil_after_destroy firm = companies(:rails_core) old_account_id = firm.account.id diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb index 08f790e21b..61e1dd0717 100644 --- a/activerecord/test/cases/associations_test.rb +++ b/activerecord/test/cases/associations_test.rb @@ -1,7 +1,6 @@ require "cases/helper" require 'models/computer' require 'models/developer' -require 'models/computer' require 'models/project' require 'models/company' require 'models/categorization' diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index ea2b94cbf4..e2608e3670 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -1,7 +1,6 @@ require "cases/helper" require 'models/minimalistic' require 'models/developer' -require 'models/computer' require 'models/auto_id' require 'models/boolean' require 'models/computer' @@ -67,8 +66,9 @@ class AttributeMethodsTest < ActiveRecord::TestCase def test_caching_nil_primary_key klass = Class.new(Minimalistic) - klass.expects(:reset_primary_key).returns(nil).once - 2.times { klass.primary_key } + assert_called(klass, :reset_primary_key, returns: nil) do + 2.times { klass.primary_key } + end end def test_attribute_keys_on_new_instance diff --git a/activerecord/test/cases/attribute_test.rb b/activerecord/test/cases/attribute_test.rb index aa419c7a67..0ec368f51d 100644 --- a/activerecord/test/cases/attribute_test.rb +++ b/activerecord/test/cases/attribute_test.rb @@ -1,5 +1,4 @@ require 'cases/helper' -require 'minitest/mock' module ActiveRecord class AttributeTest < ActiveRecord::TestCase diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb index 48dc8135ab..9cb70ee239 100644 --- a/activerecord/test/cases/batches_test.rb +++ b/activerecord/test/cases/batches_test.rb @@ -69,13 +69,15 @@ class EachTest < ActiveRecord::TestCase end def test_warn_if_limit_scope_is_set - ActiveRecord::Base.logger.expects(:warn) - Post.limit(1).find_each { |post| post } + assert_called(ActiveRecord::Base.logger, :warn) do + Post.limit(1).find_each { |post| post } + end end def test_warn_if_order_scope_is_set - ActiveRecord::Base.logger.expects(:warn) - Post.order("title").find_each { |post| post } + assert_called(ActiveRecord::Base.logger, :warn) do + Post.order("title").find_each { |post| post } + end end def test_logger_not_required @@ -137,14 +139,15 @@ class EachTest < ActiveRecord::TestCase def test_find_in_batches_should_not_use_records_after_yielding_them_in_case_original_array_is_modified not_a_post = "not a post" - not_a_post.stubs(:id).raises(StandardError, "not_a_post had #id called on it") - - assert_nothing_raised do - Post.find_in_batches(:batch_size => 1) do |batch| - assert_kind_of Array, batch - assert_kind_of Post, batch.first + def not_a_post.id; end + not_a_post.stub(:id, ->{ raise StandardError.new("not_a_post had #id called on it") }) do + assert_nothing_raised do + Post.find_in_batches(:batch_size => 1) do |batch| + assert_kind_of Array, batch + assert_kind_of Post, batch.first - batch.map! { not_a_post } + batch.map! { not_a_post } + end end end end diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb index 76ea950fb1..64759160dc 100644 --- a/activerecord/test/cases/fixtures_test.rb +++ b/activerecord/test/cases/fixtures_test.rb @@ -11,7 +11,6 @@ require 'models/company' require 'models/computer' require 'models/course' require 'models/developer' -require 'models/computer' require 'models/joke' require 'models/matey' require 'models/parrot' @@ -259,18 +258,19 @@ class FixturesTest < ActiveRecord::TestCase def test_fixtures_are_set_up_with_database_env_variable db_url_tmp = ENV['DATABASE_URL'] ENV['DATABASE_URL'] = "sqlite3::memory:" - ActiveRecord::Base.stubs(:configurations).returns({}) - test_case = Class.new(ActiveRecord::TestCase) do - fixtures :accounts + ActiveRecord::Base.stub(:configurations, {}) do + test_case = Class.new(ActiveRecord::TestCase) do + fixtures :accounts - def test_fixtures - assert accounts(:signals37) + def test_fixtures + assert accounts(:signals37) + end end - end - result = test_case.new(:test_fixtures).run + result = test_case.new(:test_fixtures).run - assert result.passed?, "Expected #{result.name} to pass:\n#{result}" + assert result.passed?, "Expected #{result.name} to pass:\n#{result}" + end ensure ENV['DATABASE_URL'] = db_url_tmp end diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb index d9d0f929db..b61a5126e0 100644 --- a/activerecord/test/cases/helper.rb +++ b/activerecord/test/cases/helper.rb @@ -3,6 +3,7 @@ require File.expand_path('../../../../load_paths', __FILE__) require 'config' require 'active_support/testing/autorun' +require 'active_support/testing/method_call_assertions' require 'stringio' require 'active_record' @@ -141,6 +142,7 @@ require "cases/validations_repair_helper" class ActiveSupport::TestCase include ActiveRecord::TestFixtures include ActiveRecord::ValidationsRepairHelper + include ActiveSupport::Testing::MethodCallAssertions self.fixture_path = FIXTURES_ROOT self.use_instantiated_fixtures = false diff --git a/activerecord/test/cases/invertible_migration_test.rb b/activerecord/test/cases/invertible_migration_test.rb index 99230aa3d5..84b0ff8fcb 100644 --- a/activerecord/test/cases/invertible_migration_test.rb +++ b/activerecord/test/cases/invertible_migration_test.rb @@ -1,5 +1,8 @@ require "cases/helper" +class Horse < ActiveRecord::Base +end + module ActiveRecord class InvertibleMigrationTest < ActiveRecord::TestCase class SilentMigration < ActiveRecord::Migration @@ -76,6 +79,32 @@ module ActiveRecord end end + class ChangeColumnDefault1 < SilentMigration + def change + create_table("horses") do |t| + t.column :name, :string, default: "Sekitoba" + end + end + end + + class ChangeColumnDefault2 < SilentMigration + def change + change_column_default :horses, :name, from: "Sekitoba", to: "Diomed" + end + end + + class DisableExtension1 < SilentMigration + def change + enable_extension "hstore" + end + end + + class DisableExtension2 < SilentMigration + def change + disable_extension "hstore" + end + end + class LegacyMigration < ActiveRecord::Migration def self.up create_table("horses") do |t| @@ -223,6 +252,42 @@ module ActiveRecord assert !revert.connection.table_exists?("horses") end + def test_migrate_revert_change_column_default + migration1 = ChangeColumnDefault1.new + migration1.migrate(:up) + assert_equal "Sekitoba", Horse.new.name + + migration2 = ChangeColumnDefault2.new + migration2.migrate(:up) + Horse.reset_column_information + assert_equal "Diomed", Horse.new.name + + migration2.migrate(:down) + Horse.reset_column_information + assert_equal "Sekitoba", Horse.new.name + end + + if current_adapter?(:PostgreSQLAdapter) + def test_migrate_enable_and_disable_extension + migration1 = InvertibleMigration.new + migration2 = DisableExtension1.new + migration3 = DisableExtension2.new + + migration1.migrate(:up) + migration2.migrate(:up) + assert_equal true, Horse.connection.extension_enabled?('hstore') + + migration3.migrate(:up) + assert_equal false, Horse.connection.extension_enabled?('hstore') + + migration3.migrate(:down) + assert_equal true, Horse.connection.extension_enabled?('hstore') + + migration2.migrate(:down) + assert_equal false, Horse.connection.extension_enabled?('hstore') + end + end + def test_revert_order block = Proc.new{|t| t.string :name } recorder = ActiveRecord::Migration::CommandRecorder.new(ActiveRecord::Base.connection) diff --git a/activerecord/test/cases/migration/change_table_test.rb b/activerecord/test/cases/migration/change_table_test.rb index 2ffe7a1b0d..2f9c50141f 100644 --- a/activerecord/test/cases/migration/change_table_test.rb +++ b/activerecord/test/cases/migration/change_table_test.rb @@ -1,5 +1,4 @@ require "cases/migration/helper" -require "minitest/mock" module ActiveRecord class Migration diff --git a/activerecord/test/cases/migration/pending_migrations_test.rb b/activerecord/test/cases/migration/pending_migrations_test.rb index 7afac83bd2..4f5589f32a 100644 --- a/activerecord/test/cases/migration/pending_migrations_test.rb +++ b/activerecord/test/cases/migration/pending_migrations_test.rb @@ -1,5 +1,4 @@ require 'cases/helper' -require "minitest/mock" module ActiveRecord class Migration diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index b2f209fe97..128a242495 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -115,7 +115,7 @@ class MigrationTest < ActiveRecord::TestCase end def test_migration_version - ActiveRecord::Migrator.run(:up, MIGRATIONS_ROOT + "/version_check", 20131219224947) + assert_nothing_raised { ActiveRecord::Migrator.run(:up, MIGRATIONS_ROOT + "/version_check", 20131219224947) } end def test_create_table_with_force_true_does_not_drop_nonexisting_table @@ -132,13 +132,9 @@ class MigrationTest < ActiveRecord::TestCase Person.connection.drop_table :testings2, if_exists: true end - def connection - ActiveRecord::Base.connection - end - def test_migration_instance_has_connection migration = Class.new(ActiveRecord::Migration).new - assert_equal connection, migration.connection + assert_equal ActiveRecord::Base.connection, migration.connection end def test_method_missing_delegates_to_connection diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb index 42e7507631..cdf63957f4 100644 --- a/activerecord/test/cases/persistence_test.rb +++ b/activerecord/test/cases/persistence_test.rb @@ -17,6 +17,7 @@ require 'models/minivan' require 'models/owner' require 'models/person' require 'models/pet' +require 'models/ship' require 'models/toy' require 'rexml/document' diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb index 2f0b5df286..d84653e4c9 100644 --- a/activerecord/test/cases/query_cache_test.rb +++ b/activerecord/test/cases/query_cache_test.rb @@ -262,61 +262,66 @@ class QueryCacheExpiryTest < ActiveRecord::TestCase end def test_find - Task.connection.expects(:clear_query_cache).times(1) + assert_called(Task.connection, :clear_query_cache) do + assert !Task.connection.query_cache_enabled + Task.cache do + assert Task.connection.query_cache_enabled + Task.find(1) - assert !Task.connection.query_cache_enabled - Task.cache do - assert Task.connection.query_cache_enabled - Task.find(1) + Task.uncached do + assert !Task.connection.query_cache_enabled + Task.find(1) + end - Task.uncached do - assert !Task.connection.query_cache_enabled - Task.find(1) + assert Task.connection.query_cache_enabled end - - assert Task.connection.query_cache_enabled + assert !Task.connection.query_cache_enabled end - assert !Task.connection.query_cache_enabled end def test_update - Task.connection.expects(:clear_query_cache).times(2) - Task.cache do - task = Task.find(1) - task.starting = Time.now.utc - task.save! + assert_called(Task.connection, :clear_query_cache, times: 2) do + Task.cache do + task = Task.find(1) + task.starting = Time.now.utc + task.save! + end end end def test_destroy - Task.connection.expects(:clear_query_cache).times(2) - Task.cache do - Task.find(1).destroy + assert_called(Task.connection, :clear_query_cache, times: 2) do + Task.cache do + Task.find(1).destroy + end end end def test_insert - ActiveRecord::Base.connection.expects(:clear_query_cache).times(2) - Task.cache do - Task.create! + assert_called(ActiveRecord::Base.connection, :clear_query_cache, times: 2) do + Task.cache do + Task.create! + end end end def test_cache_is_expired_by_habtm_update - ActiveRecord::Base.connection.expects(:clear_query_cache).times(2) - ActiveRecord::Base.cache do - c = Category.first - p = Post.first - p.categories << c + assert_called(ActiveRecord::Base.connection, :clear_query_cache, times: 2) do + ActiveRecord::Base.cache do + c = Category.first + p = Post.first + p.categories << c + end end end def test_cache_is_expired_by_habtm_delete - ActiveRecord::Base.connection.expects(:clear_query_cache).times(2) - ActiveRecord::Base.cache do - p = Post.find(1) - assert p.categories.any? - p.categories.delete_all + assert_called(ActiveRecord::Base.connection, :clear_query_cache, times: 2) do + ActiveRecord::Base.cache do + p = Post.find(1) + assert p.categories.any? + p.categories.delete_all + end end end end diff --git a/activerecord/test/cases/readonly_test.rb b/activerecord/test/cases/readonly_test.rb index 1c919f0b57..5f6eb41240 100644 --- a/activerecord/test/cases/readonly_test.rb +++ b/activerecord/test/cases/readonly_test.rb @@ -7,6 +7,7 @@ require 'models/computer' require 'models/project' require 'models/reader' require 'models/person' +require 'models/ship' class ReadOnlyTest < ActiveRecord::TestCase fixtures :authors, :posts, :comments, :developers, :projects, :developers_projects, :people, :readers diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb index 7b47c80331..9c04a41e69 100644 --- a/activerecord/test/cases/reflection_test.rb +++ b/activerecord/test/cases/reflection_test.rb @@ -393,12 +393,14 @@ class ReflectionTest < ActiveRecord::TestCase product = Struct.new(:table_name, :pluralize_table_names).new('products', true) reflection = ActiveRecord::Reflection.create(:has_many, :categories, nil, {}, product) - reflection.stubs(:klass).returns(category) - assert_equal 'categories_products', reflection.join_table + reflection.stub(:klass, category) do + assert_equal 'categories_products', reflection.join_table + end reflection = ActiveRecord::Reflection.create(:has_many, :products, nil, {}, category) - reflection.stubs(:klass).returns(product) - assert_equal 'categories_products', reflection.join_table + reflection.stub(:klass, product) do + assert_equal 'categories_products', reflection.join_table + end end def test_join_table_with_common_prefix @@ -406,12 +408,14 @@ class ReflectionTest < ActiveRecord::TestCase product = Struct.new(:table_name, :pluralize_table_names).new('catalog_products', true) reflection = ActiveRecord::Reflection.create(:has_many, :categories, nil, {}, product) - reflection.stubs(:klass).returns(category) - assert_equal 'catalog_categories_products', reflection.join_table + reflection.stub(:klass, category) do + assert_equal 'catalog_categories_products', reflection.join_table + end reflection = ActiveRecord::Reflection.create(:has_many, :products, nil, {}, category) - reflection.stubs(:klass).returns(product) - assert_equal 'catalog_categories_products', reflection.join_table + reflection.stub(:klass, product) do + assert_equal 'catalog_categories_products', reflection.join_table + end end def test_join_table_with_different_prefix @@ -419,12 +423,14 @@ class ReflectionTest < ActiveRecord::TestCase page = Struct.new(:table_name, :pluralize_table_names).new('content_pages', true) reflection = ActiveRecord::Reflection.create(:has_many, :categories, nil, {}, page) - reflection.stubs(:klass).returns(category) - assert_equal 'catalog_categories_content_pages', reflection.join_table + reflection.stub(:klass, category) do + assert_equal 'catalog_categories_content_pages', reflection.join_table + end reflection = ActiveRecord::Reflection.create(:has_many, :pages, nil, {}, category) - reflection.stubs(:klass).returns(page) - assert_equal 'catalog_categories_content_pages', reflection.join_table + reflection.stub(:klass, page) do + assert_equal 'catalog_categories_content_pages', reflection.join_table + end end def test_join_table_can_be_overridden @@ -432,12 +438,14 @@ class ReflectionTest < ActiveRecord::TestCase product = Struct.new(:table_name, :pluralize_table_names).new('products', true) reflection = ActiveRecord::Reflection.create(:has_many, :categories, nil, { :join_table => 'product_categories' }, product) - reflection.stubs(:klass).returns(category) - assert_equal 'product_categories', reflection.join_table + reflection.stub(:klass, category) do + assert_equal 'product_categories', reflection.join_table + end reflection = ActiveRecord::Reflection.create(:has_many, :products, nil, { :join_table => 'product_categories' }, category) - reflection.stubs(:klass).returns(product) - assert_equal 'product_categories', reflection.join_table + reflection.stub(:klass, product) do + assert_equal 'product_categories', reflection.join_table + end end def test_includes_accepts_symbols diff --git a/activerecord/test/cases/scoping/named_scoping_test.rb b/activerecord/test/cases/scoping/named_scoping_test.rb index e4cc533517..7a8eaeccb7 100644 --- a/activerecord/test/cases/scoping/named_scoping_test.rb +++ b/activerecord/test/cases/scoping/named_scoping_test.rb @@ -188,8 +188,9 @@ class NamedScopingTest < ActiveRecord::TestCase def test_any_should_call_proxy_found_if_using_a_block topics = Topic.base assert_queries(1) do - topics.expects(:empty?).never - topics.any? { true } + assert_not_called(topics, :empty?) do + topics.any? { true } + end end end @@ -217,8 +218,9 @@ class NamedScopingTest < ActiveRecord::TestCase def test_many_should_call_proxy_found_if_using_a_block topics = Topic.base assert_queries(1) do - topics.expects(:size).never - topics.many? { true } + assert_not_called(topics, :size) do + topics.many? { true } + end end end diff --git a/activerecord/test/cases/validations/i18n_validation_test.rb b/activerecord/test/cases/validations/i18n_validation_test.rb index 268d7914b5..981239c4d6 100644 --- a/activerecord/test/cases/validations/i18n_validation_test.rb +++ b/activerecord/test/cases/validations/i18n_validation_test.rb @@ -53,8 +53,9 @@ class I18nValidationTest < ActiveRecord::TestCase test "validates_uniqueness_of on generated message #{name}" do Topic.validates_uniqueness_of :title, validation_options @topic.title = unique_topic.title - @topic.errors.expects(:generate_message).with(:title, :taken, generate_message_options.merge(:value => 'unique!')) - @topic.valid? + assert_called_with(@topic.errors, :generate_message, [:title, :taken, generate_message_options.merge(:value => 'unique!')]) do + @topic.valid? + end end end @@ -63,8 +64,9 @@ class I18nValidationTest < ActiveRecord::TestCase COMMON_CASES.each do |name, validation_options, generate_message_options| test "validates_associated on generated message #{name}" do Topic.validates_associated :replies, validation_options - replied_topic.errors.expects(:generate_message).with(:replies, :invalid, generate_message_options.merge(:value => replied_topic.replies)) - replied_topic.save + assert_called_with(replied_topic.errors, :generate_message, [:replies, :invalid, generate_message_options.merge(:value => replied_topic.replies)]) do + replied_topic.save + end end end diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb index d2a5a7fc49..8ac7a9df6a 100644 --- a/activerecord/test/models/developer.rb +++ b/activerecord/test/models/developer.rb @@ -50,6 +50,7 @@ class Developer < ActiveRecord::Base has_many :firms, :through => :contracts, :source => :firm has_many :comments, ->(developer) { where(body: "I'm #{developer.name}") } has_many :ratings, through: :comments + has_one :ship, dependent: :nullify scope :jamises, -> { where(:name => 'Jamis') } diff --git a/activerecord/test/models/ship.rb b/activerecord/test/models/ship.rb index 95172e4d3e..e333b964ab 100644 --- a/activerecord/test/models/ship.rb +++ b/activerecord/test/models/ship.rb @@ -3,6 +3,7 @@ class Ship < ActiveRecord::Base belongs_to :pirate belongs_to :update_only_pirate, :class_name => 'Pirate' + belongs_to :developer, dependent: :destroy has_many :parts, :class_name => 'ShipPart' has_many :treasures diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index 6f34115534..994ece9244 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -673,6 +673,7 @@ ActiveRecord::Schema.define do create_table :ships, force: true do |t| t.string :name t.integer :pirate_id + t.belongs_to :developer t.integer :update_only_pirate_id # Conventionally named column for counter_cache t.integer :treasures_count, default: 0 diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index eaaa3df061..aec8b1efab 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,3 +1,13 @@ +* `number_to_currency` and `number_with_delimiter` now accept custom `delimiter_pattern` option + to handle placement of delimiter, to support currency formats like INR + + Example: + + number_to_currency(1230000, delimiter_pattern: /(\d+?)(?=(\d\d)+(\d)(?!\d))/, unit: '₹', format: "%u %n") + # => '₹ 12,30,000.00' + + *Vipul A M* + * Deprecate `:prefix` option of `number_to_human_size` with no replacement. *Jean Boussier* diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec index 6d2ae9be76..67b6663915 100644 --- a/activesupport/activesupport.gemspec +++ b/activesupport/activesupport.gemspec @@ -25,6 +25,6 @@ Gem::Specification.new do |s| s.add_dependency 'tzinfo', '~> 1.1' s.add_dependency 'minitest', '~> 5.1' s.add_dependency 'thread_safe','~> 0.3', '>= 0.3.4' - s.add_dependency 'concurrent-ruby', '~> 0.9.0' + s.add_dependency 'concurrent-ruby', '~> 0.9.1' s.add_dependency 'method_source' end diff --git a/activesupport/lib/active_support/array_inquirer.rb b/activesupport/lib/active_support/array_inquirer.rb index e7188d7adb..f59ddf5403 100644 --- a/activesupport/lib/active_support/array_inquirer.rb +++ b/activesupport/lib/active_support/array_inquirer.rb @@ -23,7 +23,7 @@ module ActiveSupport super else candidates.any? do |candidate| - include?(candidate) || include?(candidate.to_sym) + include?(candidate.to_sym) || include?(candidate.to_s) end end end diff --git a/activesupport/lib/active_support/core_ext/kernel/reporting.rb b/activesupport/lib/active_support/core_ext/kernel/reporting.rb index 9189e6d977..8afc258df8 100644 --- a/activesupport/lib/active_support/core_ext/kernel/reporting.rb +++ b/activesupport/lib/active_support/core_ext/kernel/reporting.rb @@ -1,5 +1,3 @@ -require 'tempfile' - module Kernel # Sets $VERBOSE to nil for the duration of the block and back to its original # value afterwards. diff --git a/activesupport/lib/active_support/core_ext/string/strip.rb b/activesupport/lib/active_support/core_ext/string/strip.rb index 086c610976..9fdd9d8d2e 100644 --- a/activesupport/lib/active_support/core_ext/string/strip.rb +++ b/activesupport/lib/active_support/core_ext/string/strip.rb @@ -17,8 +17,8 @@ class String # # the user would see the usage message aligned against the left margin. # - # Technically, it looks for the least indented line in the whole string, and removes - # that amount of leading whitespace. + # Technically, it looks for the least indented non-empty line + # in the whole string, and removes that amount of leading whitespace. def strip_heredoc indent = scan(/^[ \t]*(?=\S)/).min.try(:size) || 0 gsub(/^[ \t]{#{indent}}/, '') diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb index f76ef04f49..8215a3085e 100644 --- a/activesupport/lib/active_support/dependencies.rb +++ b/activesupport/lib/active_support/dependencies.rb @@ -421,13 +421,13 @@ module ActiveSupport #:nodoc: bases.each do |root| expanded_root = File.expand_path(root) - next unless %r{\A#{Regexp.escape(expanded_root)}(/|\\)} =~ expanded_path + next unless expanded_path.start_with?(expanded_root) - nesting = expanded_path[(expanded_root.size)..-1] - nesting = nesting[1..-1] if nesting && nesting[0] == ?/ - next if nesting.blank? + root_size = expanded_root.size + next if expanded_path[root_size] != ?/.freeze - paths << nesting.camelize + nesting = expanded_path[(root_size + 1)..-1] + paths << nesting.camelize unless nesting.blank? end paths.uniq! diff --git a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb index 9b264cbb79..6572ff78a6 100644 --- a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb +++ b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb @@ -20,7 +20,7 @@ module ActiveSupport private def method_missing(called, *args, &block) - warn caller, called, args + warn caller_locations, called, args target.__send__(called, *args, &block) end end diff --git a/activesupport/lib/active_support/deprecation/reporting.rb b/activesupport/lib/active_support/deprecation/reporting.rb index a7d265d732..bbe25c9260 100644 --- a/activesupport/lib/active_support/deprecation/reporting.rb +++ b/activesupport/lib/active_support/deprecation/reporting.rb @@ -14,7 +14,7 @@ module ActiveSupport def warn(message = nil, callstack = nil) return if silenced - callstack ||= caller(2) + callstack ||= caller_locations(2) deprecation_message(callstack, message).tap do |m| behavior.each { |b| b.call(m, callstack) } end @@ -37,7 +37,7 @@ module ActiveSupport end def deprecation_warning(deprecated_method_name, message = nil, caller_backtrace = nil) - caller_backtrace ||= caller(2) + caller_backtrace ||= caller_locations(2) deprecated_method_warning(deprecated_method_name, message).tap do |msg| warn(msg, caller_backtrace) end @@ -79,6 +79,17 @@ module ActiveSupport end def extract_callstack(callstack) + return _extract_callstack(callstack) if callstack.first.is_a? String + + rails_gem_root = File.expand_path("../../../../..", __FILE__) + "/" + offending_line = callstack.find { |frame| + !frame.absolute_path.start_with?(rails_gem_root) + } || callstack.first + [offending_line.path, offending_line.lineno, offending_line.label] + end + + def _extract_callstack(callstack) + warn "Please pass `caller_locations` to the deprecation API" if $VERBOSE rails_gem_root = File.expand_path("../../../../..", __FILE__) + "/" offending_line = callstack.find { |line| !line.start_with?(rails_gem_root) } || callstack.first if offending_line diff --git a/activesupport/lib/active_support/log_subscriber/test_helper.rb b/activesupport/lib/active_support/log_subscriber/test_helper.rb index 70ac4a4d5c..cbc20c103d 100644 --- a/activesupport/lib/active_support/log_subscriber/test_helper.rb +++ b/activesupport/lib/active_support/log_subscriber/test_helper.rb @@ -11,6 +11,7 @@ module ActiveSupport # include ActiveSupport::LogSubscriber::TestHelper # # def setup + # super # ActiveRecord::LogSubscriber.attach_to(:active_record) # end # diff --git a/activesupport/lib/active_support/number_helper.rb b/activesupport/lib/active_support/number_helper.rb index 38a9ce361d..9762d95145 100644 --- a/activesupport/lib/active_support/number_helper.rb +++ b/activesupport/lib/active_support/number_helper.rb @@ -135,6 +135,9 @@ module ActiveSupport # to ","). # * <tt>:separator</tt> - Sets the separator between the # fractional and integer digits (defaults to "."). + # * <tt>:delimiter_pattern</tt> - Sets a custom regular expression used for + # deriving the placement of delimiter. Helpful when using currency formats + # like INR. # # ==== Examples # @@ -147,7 +150,10 @@ module ActiveSupport # number_to_delimited(12345678.05, locale: :fr) # => 12 345 678,05 # number_to_delimited('112a') # => 112a # number_to_delimited(98765432.98, delimiter: ' ', separator: ',') - # # => 98 765 432,98 + # # => 98 765 432,98 + # number_to_delimited("123456.78", + # delimiter_pattern: /(\d+?)(?=(\d\d)+(\d)(?!\d))/) + # # => 1,23,456.78 def number_to_delimited(number, options = {}) NumberToDelimitedConverter.convert(number, options) end diff --git a/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb b/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb index d85cc086d7..45ae8f1a93 100644 --- a/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb +++ b/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb @@ -3,7 +3,7 @@ module ActiveSupport class NumberToDelimitedConverter < NumberConverter #:nodoc: self.validate_float = true - DELIMITED_REGEX = /(\d)(?=(\d\d\d)+(?!\d))/ + DEFAULT_DELIMITER_REGEX = /(\d)(?=(\d\d\d)+(?!\d))/ def convert parts.join(options[:separator]) @@ -13,11 +13,16 @@ module ActiveSupport def parts left, right = number.to_s.split('.') - left.gsub!(DELIMITED_REGEX) do |digit_to_delimit| + left.gsub!(delimiter_pattern) do |digit_to_delimit| "#{digit_to_delimit}#{options[:delimiter]}" end [left, right].compact end + + def delimiter_pattern + options.fetch(:delimiter_pattern, DEFAULT_DELIMITER_REGEX) + end + end end end diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb index f8f1b9ac2c..817522677f 100644 --- a/activesupport/lib/active_support/time_with_zone.rb +++ b/activesupport/lib/active_support/time_with_zone.rb @@ -246,6 +246,7 @@ module ActiveSupport utc.future? end + # Returns +true+ if +other+ is equal to current object. def eql?(other) other.eql?(utc) end @@ -329,6 +330,11 @@ module ActiveSupport EOV end + # Returns Array of parts of Time in sequence of + # [seconds, minutes, hours, day, month, year, weekday, yearday, dst?, zone]. + # + # now = Time.zone.now # => Tue, 18 Aug 2015 02:29:27 UTC +00:00 + # now.to_a # => [27, 29, 2, 18, 8, 2015, 2, 230, false, "UTC"] def to_a [time.sec, time.min, time.hour, time.day, time.mon, time.year, time.wday, time.yday, dst?, zone] end @@ -358,11 +364,15 @@ module ActiveSupport utc.to_r end - # Return an instance of Time in the system timezone. + # Returns an instance of Time in the system timezone. def to_time utc.to_time end + # Returns an instance of DateTime with the timezone's UTC offset + # + # Time.zone.now.to_datetime # => Tue, 18 Aug 2015 02:32:20 +0000 + # Time.current.in_time_zone('Hawaii').to_datetime # => Mon, 17 Aug 2015 16:32:20 -1000 def to_datetime utc.to_datetime.new_offset(Rational(utc_offset, 86_400)) end diff --git a/activesupport/test/abstract_unit.rb b/activesupport/test/abstract_unit.rb index 65a8edbabb..c0e23e89f7 100644 --- a/activesupport/test/abstract_unit.rb +++ b/activesupport/test/abstract_unit.rb @@ -38,8 +38,6 @@ def jruby_skip(message = '') skip message if defined?(JRUBY_VERSION) end -require 'minitest/mock' - class ActiveSupport::TestCase include ActiveSupport::Testing::MethodCallAssertions end diff --git a/activesupport/test/array_inquirer_test.rb b/activesupport/test/array_inquirer_test.rb index b25e5cca86..263ab3802b 100644 --- a/activesupport/test/array_inquirer_test.rb +++ b/activesupport/test/array_inquirer_test.rb @@ -3,7 +3,7 @@ require 'active_support/core_ext/array' class ArrayInquirerTest < ActiveSupport::TestCase def setup - @array_inquirer = ActiveSupport::ArrayInquirer.new([:mobile, :tablet]) + @array_inquirer = ActiveSupport::ArrayInquirer.new([:mobile, :tablet, 'api']) end def test_individual @@ -18,6 +18,11 @@ class ArrayInquirerTest < ActiveSupport::TestCase assert_not @array_inquirer.any?(:desktop, :watch) end + def test_any_string_symbol_mismatch + assert @array_inquirer.any?('mobile') + assert @array_inquirer.any?(:api) + end + def test_any_with_block assert @array_inquirer.any? { |v| v == :mobile } assert_not @array_inquirer.any? { |v| v == :desktop } @@ -28,7 +33,7 @@ class ArrayInquirerTest < ActiveSupport::TestCase end def test_inquiry - result = [:mobile, :tablet].inquiry + result = [:mobile, :tablet, 'api'].inquiry assert_instance_of ActiveSupport::ArrayInquirer, result assert_equal @array_inquirer, result diff --git a/activesupport/test/number_helper_test.rb b/activesupport/test/number_helper_test.rb index cb5230c4eb..944bce1b41 100644 --- a/activesupport/test/number_helper_test.rb +++ b/activesupport/test/number_helper_test.rb @@ -106,6 +106,7 @@ module ActiveSupport assert_equal("123,456,789.78901", number_helper.number_to_delimited(123456789.78901)) assert_equal("0.78901", number_helper.number_to_delimited(0.78901)) assert_equal("123,456.78", number_helper.number_to_delimited("123456.78")) + assert_equal("1,23,456.78", number_helper.number_to_delimited("123456.78", delimiter_pattern: /(\d+?)(?=(\d\d)+(\d)(?!\d))/)) assert_equal("123,456.78", number_helper.number_to_delimited("123456.78".html_safe)) end end diff --git a/guides/source/4_2_release_notes.md b/guides/source/4_2_release_notes.md index 684bd286bc..8a59007420 100644 --- a/guides/source/4_2_release_notes.md +++ b/guides/source/4_2_release_notes.md @@ -227,6 +227,17 @@ restore the old behavior. If you do this, be sure to configure your firewall properly such that only trusted machines on your network can access your development server. +### Changed status option symbols for `render` + +Due to a [change in Rack](https://github.com/rack/rack/commit/be28c6a2ac152fe4adfbef71f3db9f4200df89e8), the symbols that the `render` method accepts for the `:status` option have changed: + +- 306: `:reserved` has been removed. +- 413: `:request_entity_too_large` has been renamed to `:payload_too_large`. +- 414: `:request_uri_too_long` has been renamed to `:uri_too_long`. +- 416: `:requested_range_not_satisfiable` has been renamed to `:range_not_satisfiable`. + +Keep in mind that if calling `render` with an unknown symbol, the response status will default to 500. + ### HTML Sanitizer The HTML sanitizer has been replaced with a new, more robust, implementation diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md index 09fbdc0d32..b4ca0df0de 100644 --- a/guides/source/action_controller_overview.md +++ b/guides/source/action_controller_overview.md @@ -1029,7 +1029,7 @@ There are a couple of things to notice in the above example. We need to make sure to close the response stream. Forgetting to close the stream will leave the socket open forever. We also have to set the content type to `text/event-stream` before we write to the response stream. This is because headers cannot be written -after the response has been committed (when `response.committed` returns a truthy +after the response has been committed (when `response.committed?` returns a truthy value), which occurs when you `write` or `commit` the response stream. #### Example Usage @@ -1174,7 +1174,7 @@ end WARNING: You shouldn't do `rescue_from Exception` or `rescue_from StandardError` unless you have a particular reason as it will cause serious side-effects (e.g. you won't be able to see exception details and tracebacks during development). -NOTE: Certain exceptions are only rescuable from the `ApplicationController` class, as they are raised before the controller gets initialized and the action gets executed. See Pratik Naik's [article](http://m.onkey.org/2008/7/20/rescue-from-dispatching) on the subject for more information. +NOTE: Certain exceptions are only rescuable from the `ApplicationController` class, as they are raised before the controller gets initialized and the action gets executed. Force HTTPS protocol -------------------- diff --git a/guides/source/action_view_overview.md b/guides/source/action_view_overview.md index db7eeed19a..00c41a480e 100644 --- a/guides/source/action_view_overview.md +++ b/guides/source/action_view_overview.md @@ -1443,12 +1443,12 @@ Sanitizes a block of CSS code. Strips all link tags from text leaving just the link text. ```ruby -strip_links("<a href="http://rubyonrails.org">Ruby on Rails</a>") +strip_links('<a href="http://rubyonrails.org">Ruby on Rails</a>') # => Ruby on Rails ``` ```ruby -strip_links("emails to <a href="mailto:me@email.com">me@email.com</a>.") +strip_links('emails to <a href="mailto:me@email.com">me@email.com</a>.') # => emails to me@email.com. ``` diff --git a/guides/source/active_record_migrations.md b/guides/source/active_record_migrations.md index 4e5902fb3d..c5ac70143d 100644 --- a/guides/source/active_record_migrations.md +++ b/guides/source/active_record_migrations.md @@ -522,20 +522,27 @@ majority of cases, where Active Record knows how to reverse the migration automatically. Currently, the `change` method supports only these migration definitions: -* `add_column` -* `add_index` -* `add_reference` -* `add_timestamps` -* `add_foreign_key` -* `create_table` -* `create_join_table` -* `drop_table` (must supply a block) -* `drop_join_table` (must supply a block) -* `remove_timestamps` -* `rename_column` -* `rename_index` -* `remove_reference` -* `rename_table` +* add_column +* add_foreign_key +* add_index +* add_reference +* add_timestamps +* change_column_default (must supply a :from and :to option) +* change_column_null +* create_join_table +* create_table +* disable_extension +* drop_join_table +* drop_table (must supply a block) +* enable_extension +* remove_column (must supply a type) +* remove_foreign_key (must supply a second table) +* remove_index +* remove_reference +* remove_timestamps +* rename_column +* rename_index +* rename_table `change_table` is also reversible, as long as the block does not call `change`, `change_default` or `remove`. @@ -652,7 +659,7 @@ can't be done. You can use Active Record's ability to rollback migrations using the `revert` method: ```ruby -require_relative '2012121212_example_migration' +require_relative '20121212123456_example_migration' class FixupExampleMigration < ActiveRecord::Migration def change diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md index 4b4d70d3ce..c0a4a0ba39 100644 --- a/guides/source/active_record_querying.md +++ b/guides/source/active_record_querying.md @@ -341,8 +341,6 @@ User.find_each(begin_at: 2000, batch_size: 5000) do |user| end ``` -Another example would be if you wanted multiple workers handling the same processing queue. You could have each worker handle 10000 records by setting the appropriate `:begin_at` option on each worker. - **`:end_at`** Similar to the `:begin_at` option, `:end_at` allows you to configure the last ID of the sequence whenever the highest ID is not the one you need. @@ -356,6 +354,10 @@ User.find_each(begin_at: 2000, end_at: 10000, batch_size: 5000) do |user| end ``` +Another example would be if you wanted multiple workers handling the same +processing queue. You could have each worker handle 10000 records by setting the +appropriate `:begin_at` and `:end_at` options on each worker. + #### `find_in_batches` The `find_in_batches` method is similar to `find_each`, since both retrieve batches of records. The difference is that `find_in_batches` yields _batches_ to the block as an array of models, instead of individually. The following example will yield to the supplied block an array of up to 1000 invoices at a time, with the final block containing any remaining invoices: diff --git a/guides/source/active_support_instrumentation.md b/guides/source/active_support_instrumentation.md index cd44c685ba..e5a560edd0 100644 --- a/guides/source/active_support_instrumentation.md +++ b/guides/source/active_support_instrumentation.md @@ -395,6 +395,38 @@ INFO. Cache stores may add their own keys } ``` +Active Job +-------- + +### enqueue_at.active_job + +| Key | Value | +| ------------ | -------------------------------------- | +| `:adapter` | QueueAdapter object processing the job | +| `:job` | Job object | + +### enqueue.active_job + +| Key | Value | +| ------------ | -------------------------------------- | +| `:adapter` | QueueAdapter object processing the job | +| `:job` | Job object | + +### perform_start.active_job + +| Key | Value | +| ------------ | -------------------------------------- | +| `:adapter` | QueueAdapter object processing the job | +| `:job` | Job object | + +### perform.active_job + +| Key | Value | +| ------------ | -------------------------------------- | +| `:adapter` | QueueAdapter object processing the job | +| `:job` | Job object | + + Railties -------- diff --git a/guides/source/api_app.md b/guides/source/api_app.md index 29ca872254..7f1792181e 100644 --- a/guides/source/api_app.md +++ b/guides/source/api_app.md @@ -363,11 +363,8 @@ controller modules by default: - `ActionController::Renderers::All`: Support for `render :json` and friends. - `ActionController::ConditionalGet`: Support for `stale?`. - `ActionController::ForceSSL`: Support for `force_ssl`. -- `ActionController::RackDelegation`: Support for the `request` and `response` - methods returning `ActionDispatch::Request` and `ActionDispatch::Response` - objects. - `ActionController::DataStreaming`: Support for `send_file` and `send_data`. -- `AbstractController::Callbacks`: Support for `before_filter` and friends. +- `AbstractController::Callbacks`: Support for `before_action` and friends. - `ActionController::Instrumentation`: Support for the instrumentation hooks defined by Action Controller (see [the instrumentation guide](active_support_instrumentation.html#action-controller)). diff --git a/guides/source/api_documentation_guidelines.md b/guides/source/api_documentation_guidelines.md index 46c9013087..a4feff798d 100644 --- a/guides/source/api_documentation_guidelines.md +++ b/guides/source/api_documentation_guidelines.md @@ -84,6 +84,11 @@ English Please use American English (*color*, *center*, *modularize*, etc). See [a list of American and British English spelling differences here](http://en.wikipedia.org/wiki/American_and_British_English_spelling_differences). +Comma +------- + +Please use the Oxford comma (*red, white, and blue* style). See [the detail of Oxford comma](http://en.wikipedia.org/wiki/Serial_comma). + Example Code ------------ diff --git a/guides/source/configuring.md b/guides/source/configuring.md index df9704830e..d63317433d 100644 --- a/guides/source/configuring.md +++ b/guides/source/configuring.md @@ -267,8 +267,8 @@ All these configuration options are delegated to the `I18n` library. * `config.active_record.logger` accepts a logger conforming to the interface of Log4r or the default Ruby Logger class, which is then passed on to any new database connections made. You can retrieve this logger by calling `logger` on either an Active Record model class or an Active Record model instance. Set to `nil` to disable logging. * `config.active_record.primary_key_prefix_type` lets you adjust the naming for primary key columns. By default, Rails assumes that primary key columns are named `id` (and this configuration option doesn't need to be set.) There are two other choices: -** `:table_name` would make the primary key for the Customer class `customerid` -** `:table_name_with_underscore` would make the primary key for the Customer class `customer_id` + * `:table_name` would make the primary key for the Customer class `customerid` + * `:table_name_with_underscore` would make the primary key for the Customer class `customer_id` * `config.active_record.table_name_prefix` lets you set a global string to be prepended to table names. If you set this to `northwest_`, then the Customer class will look for `northwest_customers` as its table. The default is an empty string. diff --git a/guides/source/debugging_rails_applications.md b/guides/source/debugging_rails_applications.md index c486009741..a05abb61d6 100644 --- a/guides/source/debugging_rails_applications.md +++ b/guides/source/debugging_rails_applications.md @@ -351,6 +351,7 @@ by asking the debugger for help. Type: `help` help -- prints this help. help <cmd> -- prints help on command <cmd>. help <cmd> <subcmd> -- prints help on <cmd>'s subcommand <subcmd>. +``` To see the previous ten lines you should type `list-` (or `l-`). @@ -458,12 +459,12 @@ The debugger can list, stop, resume and switch between running threads by using the `thread` command (or the abbreviated `th`). This command has a handful of options: -* `thread` shows the current thread. -* `thread list` is used to list all threads and their statuses. The plus + +* `thread`: shows the current thread. +* `thread list`: is used to list all threads and their statuses. The plus + character and the number indicates the current thread of execution. -* `thread stop _n_` stop thread _n_. -* `thread resume _n_` resumes thread _n_. -* `thread switch _n_` switches the current thread context to _n_. +* `thread stop _n_`: stop thread _n_. +* `thread resume _n_`: resumes thread _n_. +* `thread switch _n_`: switches the current thread context to _n_. This command is very helpful when you are debugging concurrent threads and need to verify that there are no race conditions in your code. @@ -619,13 +620,16 @@ Processing by ArticlesController#index as HTML (byebug) ``` -If we use `next`, we want go deep inside method calls. Instead, byebug will go -to the next line within the same context. In this case, this is the last line of -the method, so `byebug` will jump to next next line of the previous frame. +If we use `next`, we won't go deep inside method calls. Instead, `byebug` will +go to the next line within the same context. In this case, it is the last line +of the current method, so `byebug` will return to the next line of the caller +method. ``` (byebug) next -Next went up a frame because previous frame finished + +Next advances to the next line (line 6: `end`), which returns to the next line +of the caller method: [4, 13] in /PathTo/project/test_app/app/controllers/articles_controller.rb 4: # GET /articles @@ -642,8 +646,8 @@ Next went up a frame because previous frame finished (byebug) ``` -If we use `step` in the same situation, we will literally go to the next Ruby -instruction to be executed. In this case, Active Support's `week` method. +If we use `step` in the same situation, `byebug` will literally go to the next +Ruby instruction to be executed -- in this case, Active Support's `week` method. ``` (byebug) step @@ -741,12 +745,12 @@ To list all active catchpoints use `catch`. There are two ways to resume execution of an application that is stopped in the debugger: -* `continue` [line-specification] \(or `c`): resume program execution, at the +* `continue [line-specification]` \(or `c`): resume program execution, at the address where your script last stopped; any breakpoints set at that address are bypassed. The optional argument line-specification allows you to specify a line number to set a one-time breakpoint which is deleted when that breakpoint is reached. -* `finish` [frame-number] \(or `fin`): execute until the selected stack frame +* `finish [frame-number]` \(or `fin`): execute until the selected stack frame returns. If no frame number is given, the application will run until the currently selected frame returns. The currently selected frame starts out the most-recent frame or 0 if no frame positioning (e.g up, down or frame) has been diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md index ef66b75ec1..5700e71103 100644 --- a/guides/source/getting_started.md +++ b/guides/source/getting_started.md @@ -71,10 +71,9 @@ The Rails philosophy includes two major guiding principles: Creating a New Rails Project ---------------------------- - -The best way to use this guide is to follow each step as it happens, no code or -step needed to make this example application has been left out, so you can -literally follow along step by step. +The best way to read this guide is to follow it step by step. All steps are +essential to run this example application and no additional code or steps are +needed. By following along with this guide, you'll create a Rails project called `blog`, a (very) simple weblog. Before you can start building the application, diff --git a/guides/source/layouts_and_rendering.md b/guides/source/layouts_and_rendering.md index 94cd7297e2..b425eb126a 100644 --- a/guides/source/layouts_and_rendering.md +++ b/guides/source/layouts_and_rendering.md @@ -360,7 +360,6 @@ Rails understands both numeric status codes and the corresponding symbols shown | | 303 | :see_other | | | 304 | :not_modified | | | 305 | :use_proxy | -| | 306 | :reserved | | | 307 | :temporary_redirect | | | 308 | :permanent_redirect | | **Client Error** | 400 | :bad_request | @@ -376,10 +375,10 @@ Rails understands both numeric status codes and the corresponding symbols shown | | 410 | :gone | | | 411 | :length_required | | | 412 | :precondition_failed | -| | 413 | :request_entity_too_large | -| | 414 | :request_uri_too_long | +| | 413 | :payload_too_large | +| | 414 | :uri_too_long | | | 415 | :unsupported_media_type | -| | 416 | :requested_range_not_satisfiable | +| | 416 | :range_not_satisfiable | | | 417 | :expectation_failed | | | 422 | :unprocessable_entity | | | 423 | :locked | diff --git a/guides/source/plugins.md b/guides/source/plugins.md index 4e630a39f3..b94c26a1ae 100644 --- a/guides/source/plugins.md +++ b/guides/source/plugins.md @@ -443,4 +443,3 @@ $ bundle exec rake rdoc * [Developing a RubyGem using Bundler](https://github.com/radar/guides/blob/master/gem-development.md) * [Using .gemspecs as Intended](http://yehudakatz.com/2010/04/02/using-gemspecs-as-intended/) * [Gemspec Reference](http://guides.rubygems.org/specification-reference/) -* [GemPlugins: A Brief Introduction to the Future of Rails Plugins](http://www.intridea.com/blog/2008/6/11/gemplugins-a-brief-introduction-to-the-future-of-rails-plugins) diff --git a/guides/source/security.md b/guides/source/security.md index 850d111bd7..93c270064a 100644 --- a/guides/source/security.md +++ b/guides/source/security.md @@ -760,7 +760,7 @@ s = sanitize(user_input, tags: tags, attributes: %w(href title)) This allows only the given tags and does a good job, even against all kinds of tricks and malformed tags. -As a second step, _it is good practice to escape all output of the application_, especially when re-displaying user input, which hasn't been input-filtered (as in the search form example earlier on). _Use `escapeHTML()` (or its alias `h()`) method_ to replace the HTML input characters &, ", <, > by their uninterpreted representations in HTML (`&`, `"`, `<`;, and `>`). However, it can easily happen that the programmer forgets to use it, so _it is recommended to use the SafeErb gem. SafeErb reminds you to escape strings from external sources. +As a second step, _it is good practice to escape all output of the application_, especially when re-displaying user input, which hasn't been input-filtered (as in the search form example earlier on). _Use `escapeHTML()` (or its alias `h()`) method_ to replace the HTML input characters &, ", <, and > by their uninterpreted representations in HTML (`&`, `"`, `<`, and `>`). However, it can easily happen that the programmer forgets to use it, so _it is recommended to use the SafeErb gem. SafeErb reminds you to escape strings from external sources. ##### Obfuscation and Encoding Injection diff --git a/guides/source/testing.md b/guides/source/testing.md index 3a691220cf..aa3497fa13 100644 --- a/guides/source/testing.md +++ b/guides/source/testing.md @@ -50,7 +50,7 @@ By default, every Rails application has three environments: development, test, a Each environment's configuration can be modified similarly. In this case, we can modify our test environment by changing the options found in `config/environments/test.rb`. -NOTE: Your test are run under RAILS_ENV=test. +NOTE: Your tests are run under `RAILS_ENV=test`. ### Rails meets Minitest @@ -336,7 +336,8 @@ All the basic assertions such as `assert_equal` defined in `Minitest::Assertions Each of these classes include `Minitest::Assertions`, allowing us to use all of the basic assertions in our tests. -NOTE: For more information on `Minitest`, refer to [Minitest](http://docs.seattlerb.org/minitest) +NOTE: For more information on `Minitest`, refer to [its +documentation](http://docs.seattlerb.org/minitest). ### The Rails Test Runner @@ -355,7 +356,8 @@ Finished tests in 0.009262s, 107.9680 tests/s, 107.9680 assertions/s. This will run all test methods from the test case. -You can also run a particular test method from the test case by providing the `-n` or `--name` flag and the `test method name`. +You can also run a particular test method from the test case by providing the +`-n` or `--name` flag and the test's method name. ```bash $ bin/rails test test/models/article_test.rb -n test_the_truth @@ -469,7 +471,8 @@ user_<%= n %>: #### Fixtures in Action -Rails by default automatically loads all fixtures from the `test/fixtures` directory for your models and controllers test. Loading involves three steps: +Rails automatically loads all fixtures from the `test/fixtures` directory by +default. Loading involves three steps: 1. Remove any existing data from the table corresponding to the fixture 2. Load the fixture data into the table @@ -505,7 +508,8 @@ Model Testing Model tests are used to test the various models of your application. -For creating Rails model tests, we use the 'test/model' directory for your application. Rails provides a generator to create an model test skeleton for you. +Rails model tests are stored under the `test/models` directory. Rails provides +a generator to create a model test skeleton for you. ```bash $ bin/rails generate test_unit:model article title:string body:text @@ -563,7 +567,8 @@ We'll start by generating our integration test skeleton: $ bin/rails generate integration_test blog_flow ``` -It should have created a test file placeholder for us, with the output of the previous command you should see: +It should have created a test file placeholder for us. With the output of the +previous command you should see: ```bash invoke test_unit @@ -1110,10 +1115,13 @@ require 'test_helper' class UserMailerTest < ActionMailer::TestCase test "invite" do + # Create the email and store it for further assertions + email = UserMailer.create_invite('me@example.com', + 'friend@example.com', Time.now) + # Send the email, then test that it got queued assert_emails 1 do - email = UserMailer.create_invite('me@example.com', - 'friend@example.com', Time.now).deliver_now + email.deliver_now end # Test the body of the sent email contains what we expect it to diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index 21adac272d..faf6846e4b 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,3 +1,7 @@ +* `rails server` will now honour the `PORT` environment variable + + *David Cornu* + * Plugins generated using `rails plugin new` are now generated with the version number set to 0.1.0. diff --git a/railties/Rakefile b/railties/Rakefile index 4393f45790..cf130a5f14 100644 --- a/railties/Rakefile +++ b/railties/Rakefile @@ -26,7 +26,7 @@ end Rake::TestTask.new('test:regular') do |t| t.libs << 'test' << "#{File.dirname(__FILE__)}/../activesupport/lib" t.pattern = 'test/**/*_test.rb' - t.warning = true + t.warning = false t.verbose = true t.ruby_opts = ["--dev"] if defined?(JRUBY_VERSION) end diff --git a/railties/lib/rails/commands/server.rb b/railties/lib/rails/commands/server.rb index c8fb58ab05..d3ea441f8e 100644 --- a/railties/lib/rails/commands/server.rb +++ b/railties/lib/rails/commands/server.rb @@ -86,7 +86,7 @@ module Rails def default_options super.merge({ - Port: 3000, + Port: ENV.fetch('PORT', 3000).to_i, DoNotReverseLookup: true, environment: (ENV['RAILS_ENV'] || ENV['RACK_ENV'] || "development").dup, daemonize: false, diff --git a/railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb b/railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb index b1b77629d4..f315144723 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb +++ b/railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb @@ -14,9 +14,6 @@ require "rails/test_help" # to be shown. Minitest.backtrace_filter = Minitest::BacktraceFilter.new -# Load support files -Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f } - # Load fixtures from the engine if ActiveSupport::TestCase.respond_to?(:fixture_path=) ActiveSupport::TestCase.fixture_path = File.expand_path("../fixtures", __FILE__) diff --git a/railties/lib/rails/test_unit/minitest_plugin.rb b/railties/lib/rails/test_unit/minitest_plugin.rb index 5552779ef1..8e5301d1e0 100644 --- a/railties/lib/rails/test_unit/minitest_plugin.rb +++ b/railties/lib/rails/test_unit/minitest_plugin.rb @@ -16,7 +16,7 @@ module Minitest opts.separator "" opts.separator "Rails options:" - opts.on("-e", "--environment [ENV]", + opts.on("-e", "--environment ENV", "Run tests in the ENV environment") do |env| options[:environment] = env.strip end diff --git a/railties/test/application/initializers/frameworks_test.rb b/railties/test/application/initializers/frameworks_test.rb index 7bba910b9e..6e3707cc27 100644 --- a/railties/test/application/initializers/frameworks_test.rb +++ b/railties/test/application/initializers/frameworks_test.rb @@ -1,5 +1,4 @@ require "isolation/abstract_unit" -require 'set' module ApplicationTests class FrameworksTest < ActiveSupport::TestCase diff --git a/railties/test/commands/server_test.rb b/railties/test/commands/server_test.rb index 7a063aeddf..3be4a74f74 100644 --- a/railties/test/commands/server_test.rb +++ b/railties/test/commands/server_test.rb @@ -44,6 +44,13 @@ class Rails::ServerTest < ActiveSupport::TestCase end end + def test_environment_with_port + switch_env "PORT", "1234" do + server = Rails::Server.new + assert_equal 1234, server.options[:Port] + end + end + def test_caching_without_option args = [] options = Rails::Server::Options.new.parse!(args) diff --git a/railties/test/generators/named_base_test.rb b/railties/test/generators/named_base_test.rb index 1c32fc1bfd..291f5e06c3 100644 --- a/railties/test/generators/named_base_test.rb +++ b/railties/test/generators/named_base_test.rb @@ -1,6 +1,5 @@ require 'generators/generators_test_helper' require 'rails/generators/rails/scaffold_controller/scaffold_controller_generator' -require 'minitest/mock' class NamedBaseTest < Rails::Generators::TestCase include GeneratorsTestHelper diff --git a/railties/test/path_generation_test.rb b/railties/test/path_generation_test.rb index 27e64b97b7..a16adc72a6 100644 --- a/railties/test/path_generation_test.rb +++ b/railties/test/path_generation_test.rb @@ -11,26 +11,26 @@ class PathGenerationTest < ActiveSupport::TestCase super() end - class Dispatcher < ActionDispatch::Routing::RouteSet::Dispatcher - def initialize(defaults, set, block) - super(defaults) + class Request < DelegateClass(ActionDispatch::Request) + def initialize(target, url_helpers, block) + super(target) + @url_helpers = url_helpers @block = block - @set = set end - def controller_reference(controller_param) + def controller_class + url_helpers = @url_helpers block = @block - set = @set Class.new(ActionController::Base) { - include set.url_helpers + include url_helpers define_method(:process) { |name| block.call(self) } def to_a; [200, {}, []]; end } end end - def dispatcher defaults - TestSet::Dispatcher.new defaults, self, @block + def make_request(env) + Request.new super, self.url_helpers, @block end end diff --git a/tasks/release.rb b/tasks/release.rb index d8c1390eef..2c7e927679 100644 --- a/tasks/release.rb +++ b/tasks/release.rb @@ -98,7 +98,7 @@ namespace :all do task :push => FRAMEWORKS.map { |f| "#{f}:push" } + ['rails:push'] task :ensure_clean_state do - unless `git status -s | grep -v RAILS_VERSION`.strip.empty? + unless `git status -s | grep -v 'RAILS_VERSION\\|CHANGELOG'`.strip.empty? abort "[ABORTING] `git status` reports a dirty tree. Make sure all changes are committed" end |